18d9c2b68SGreg Roach<?php 28d9c2b68SGreg Roach 38d9c2b68SGreg Roach/** 48d9c2b68SGreg Roach * webtrees: online genealogy 58d9c2b68SGreg Roach * Copyright (C) 2021 webtrees development team 68d9c2b68SGreg Roach * This program is free software: you can redistribute it and/or modify 78d9c2b68SGreg Roach * it under the terms of the GNU General Public License as published by 88d9c2b68SGreg Roach * the Free Software Foundation, either version 3 of the License, or 98d9c2b68SGreg Roach * (at your option) any later version. 108d9c2b68SGreg Roach * This program is distributed in the hope that it will be useful, 118d9c2b68SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 128d9c2b68SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 138d9c2b68SGreg Roach * GNU General Public License for more details. 148d9c2b68SGreg Roach * You should have received a copy of the GNU General Public License 158d9c2b68SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 168d9c2b68SGreg Roach */ 178d9c2b68SGreg Roach 188d9c2b68SGreg Roachdeclare(strict_types=1); 198d9c2b68SGreg Roach 208d9c2b68SGreg Roachnamespace Fisharebest\Webtrees; 218d9c2b68SGreg Roach 228d9c2b68SGreg Roachuse Closure; 238d9c2b68SGreg Roachuse LogicException; 248d9c2b68SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 258d9c2b68SGreg Roach 268d9c2b68SGreg Roachuse function array_reduce; 278d9c2b68SGreg Roachuse function ctype_digit; 288d9c2b68SGreg Roachuse function is_array; 298d9c2b68SGreg Roachuse function is_int; 308d9c2b68SGreg Roachuse function is_string; 318d9c2b68SGreg Roachuse function parse_url; 328d9c2b68SGreg Roachuse function str_starts_with; 338d9c2b68SGreg Roach 348d9c2b68SGreg Roach/** 358d9c2b68SGreg Roach * Validate a parameter from an HTTP request 368d9c2b68SGreg Roach */ 378d9c2b68SGreg Roachclass Validator 388d9c2b68SGreg Roach{ 398d9c2b68SGreg Roach /** @var array<string|array> */ 408d9c2b68SGreg Roach private array $parameters; 418d9c2b68SGreg Roach 428d9c2b68SGreg Roach /** @var array<Closure> */ 438d9c2b68SGreg Roach private array $rules = []; 448d9c2b68SGreg Roach 458d9c2b68SGreg Roach /** 468d9c2b68SGreg Roach * @param array<string|array> $parameters 478d9c2b68SGreg Roach */ 488d9c2b68SGreg Roach public function __construct(array $parameters) 498d9c2b68SGreg Roach { 508d9c2b68SGreg Roach $this->parameters = $parameters; 518d9c2b68SGreg Roach } 528d9c2b68SGreg Roach 538d9c2b68SGreg Roach /** 548d9c2b68SGreg Roach * @param ServerRequestInterface $request 558d9c2b68SGreg Roach * 568d9c2b68SGreg Roach * @return self 578d9c2b68SGreg Roach */ 588d9c2b68SGreg Roach public static function parsedBody(ServerRequestInterface $request): self 598d9c2b68SGreg Roach { 608d9c2b68SGreg Roach return new self((array) $request->getParsedBody()); 618d9c2b68SGreg Roach } 628d9c2b68SGreg Roach 638d9c2b68SGreg Roach /** 648d9c2b68SGreg Roach * @param ServerRequestInterface $request 658d9c2b68SGreg Roach * 668d9c2b68SGreg Roach * @return self 678d9c2b68SGreg Roach */ 688d9c2b68SGreg Roach public static function queryParams(ServerRequestInterface $request): self 698d9c2b68SGreg Roach { 708d9c2b68SGreg Roach return new self($request->getQueryParams()); 718d9c2b68SGreg Roach } 728d9c2b68SGreg Roach 738d9c2b68SGreg Roach /** 748d9c2b68SGreg Roach * @param int $minimum 758d9c2b68SGreg Roach * @param int $maximum 768d9c2b68SGreg Roach * 778d9c2b68SGreg Roach * @return self 788d9c2b68SGreg Roach */ 798d9c2b68SGreg Roach public function isBetween(int $minimum, int $maximum): self 808d9c2b68SGreg Roach { 818d9c2b68SGreg Roach $this->rules[] = static function ($value) use ($minimum, $maximum): ?int { 828d9c2b68SGreg Roach if (is_int($value)) { 838d9c2b68SGreg Roach if ($value < $minimum || $value > $maximum) { 848d9c2b68SGreg Roach return null; 858d9c2b68SGreg Roach } 868d9c2b68SGreg Roach 878d9c2b68SGreg Roach return $value; 888d9c2b68SGreg Roach } 898d9c2b68SGreg Roach 908d9c2b68SGreg Roach throw new LogicException('Validator::isBetween() can only be used for integers'); 918d9c2b68SGreg Roach }; 928d9c2b68SGreg Roach 938d9c2b68SGreg Roach return $this; 948d9c2b68SGreg Roach } 958d9c2b68SGreg Roach 968d9c2b68SGreg Roach public function localUrl(string $base_url): self 978d9c2b68SGreg Roach { 988d9c2b68SGreg Roach $this->rules[] = static function ($value) use ($base_url): ?string { 998d9c2b68SGreg Roach if (is_string($value)) { 1008d9c2b68SGreg Roach $value_info = parse_url($value); 1018d9c2b68SGreg Roach $base_url_info = parse_url($base_url); 1028d9c2b68SGreg Roach 1038d9c2b68SGreg Roach if (!is_array($base_url_info)) { 1048d9c2b68SGreg Roach throw new LogicException(__METHOD__ . ' needs a valid URL'); 1058d9c2b68SGreg Roach } 1068d9c2b68SGreg Roach 1078d9c2b68SGreg Roach if (is_array($value_info)) { 1088d9c2b68SGreg Roach $scheme_ok = ($value_info['scheme'] ?? 'http') === ($base_url_info['scheme'] ?? 'http'); 1098d9c2b68SGreg Roach $host_ok = ($value_info['host'] ?? '') === ($base_url_info['host'] ?? ''); 1108d9c2b68SGreg Roach $port_ok = ($value_info['port'] ?? '') === ($base_url_info['port'] ?? ''); 1118d9c2b68SGreg Roach $user_ok = ($value_info['user'] ?? '') === ($base_url_info['user'] ?? ''); 1128d9c2b68SGreg Roach $path_ok = str_starts_with($value_info['path'] ?? '/', $base_url_info['path'] ?? '/'); 1138d9c2b68SGreg Roach 1148d9c2b68SGreg Roach if ($scheme_ok && $host_ok && $port_ok && $user_ok && $path_ok) { 1158d9c2b68SGreg Roach return $value; 1168d9c2b68SGreg Roach } 1178d9c2b68SGreg Roach } 1188d9c2b68SGreg Roach 1198d9c2b68SGreg Roach return null; 1208d9c2b68SGreg Roach } 1218d9c2b68SGreg Roach 1228d9c2b68SGreg Roach throw new LogicException(__METHOD__ . ' can only be used for strings'); 1238d9c2b68SGreg Roach }; 1248d9c2b68SGreg Roach 1258d9c2b68SGreg Roach return $this; 1268d9c2b68SGreg Roach } 1278d9c2b68SGreg Roach 1288d9c2b68SGreg Roach /** 1298d9c2b68SGreg Roach * @param string $parameter 1308d9c2b68SGreg Roach * 1318d9c2b68SGreg Roach * @return array<string> 1328d9c2b68SGreg Roach */ 1338d9c2b68SGreg Roach public function array(string $parameter): array 1348d9c2b68SGreg Roach { 1358d9c2b68SGreg Roach $value = $this->parameters[$parameter] ?? null; 1368d9c2b68SGreg Roach 1378d9c2b68SGreg Roach if (!is_array($value)) { 1388d9c2b68SGreg Roach $value = []; 1398d9c2b68SGreg Roach } 1408d9c2b68SGreg Roach 141*fcbce9d8SGreg Roach return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value); 1428d9c2b68SGreg Roach } 1438d9c2b68SGreg Roach 1448d9c2b68SGreg Roach /** 1458d9c2b68SGreg Roach * @param string $parameter 1468d9c2b68SGreg Roach * 1478d9c2b68SGreg Roach * @return int|null 1488d9c2b68SGreg Roach */ 1498d9c2b68SGreg Roach public function integer(string $parameter): ?int 1508d9c2b68SGreg Roach { 1518d9c2b68SGreg Roach $value = $this->parameters[$parameter] ?? null; 1528d9c2b68SGreg Roach 1538d9c2b68SGreg Roach if (is_string($value) && ctype_digit($value)) { 1548d9c2b68SGreg Roach $value = (int) $value; 1558d9c2b68SGreg Roach } else { 1568d9c2b68SGreg Roach $value = null; 1578d9c2b68SGreg Roach } 1588d9c2b68SGreg Roach 159*fcbce9d8SGreg Roach return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value); 1608d9c2b68SGreg Roach } 1618d9c2b68SGreg Roach 1628d9c2b68SGreg Roach /** 1638d9c2b68SGreg Roach * @param string $parameter 1648d9c2b68SGreg Roach * 1658d9c2b68SGreg Roach * @return string|null 1668d9c2b68SGreg Roach */ 1678d9c2b68SGreg Roach public function string(string $parameter): ?string 1688d9c2b68SGreg Roach { 1698d9c2b68SGreg Roach $value = $this->parameters[$parameter] ?? null; 1708d9c2b68SGreg Roach 1718d9c2b68SGreg Roach if (!is_string($value)) { 1728d9c2b68SGreg Roach $value = null; 1738d9c2b68SGreg Roach } 1748d9c2b68SGreg Roach 1758d9c2b68SGreg Roach return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value); 1768d9c2b68SGreg Roach } 1778d9c2b68SGreg Roach} 178