1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees; 21 22use Aura\Router\Route; 23use Fisharebest\Webtrees\Contracts\UserInterface; 24use Fisharebest\Webtrees\Http\Exceptions\HttpBadRequestException; 25use PHPUnit\Framework\Attributes\CoversClass; 26use Psr\Http\Message\ServerRequestInterface; 27 28#[CoversClass(Validator::class)] 29class ValidatorTest extends TestCase 30{ 31 public function testAttributes(): void 32 { 33 $request = $this->createMock(ServerRequestInterface::class); 34 $request 35 ->method('getAttributes') 36 ->willReturn(['param' => 'test']); 37 38 self::assertSame('test', Validator::attributes($request)->string('param')); 39 } 40 41 public function testParsedBody(): void 42 { 43 $request = $this->createMock(ServerRequestInterface::class); 44 $request 45 ->method('getParsedBody') 46 ->willReturn(['param' => 'test']); 47 48 self::assertSame('test', Validator::parsedBody($request)->string('param')); 49 } 50 51 public function testQueryParams(): void 52 { 53 $request = $this->createMock(ServerRequestInterface::class); 54 $request 55 ->method('getQueryParams') 56 ->willReturn(['param' => 'test']); 57 58 self::assertSame('test', Validator::queryParams($request)->string('param')); 59 } 60 61 public function testServerParams(): void 62 { 63 $request = $this->createMock(ServerRequestInterface::class); 64 $request 65 ->method('getServerParams') 66 ->willReturn(['param' => 'test']); 67 68 self::assertSame('test', Validator::serverParams($request)->string('param')); 69 } 70 71 public function testNonUTF8QueryParameterName(): void 72 { 73 $request = $this->createMock(ServerRequestInterface::class); 74 $request 75 ->method('getQueryParams') 76 ->willReturn(["\xFF" => 'test']); 77 78 $this->expectException(HttpBadRequestException::class); 79 80 Validator::queryParams($request); 81 } 82 83 public function testNonUTF8QueryParameterValue(): void 84 { 85 $request = $this->createMock(ServerRequestInterface::class); 86 $request 87 ->method('getQueryParams') 88 ->willReturn(['test' => "\xFF"]); 89 90 $this->expectException(HttpBadRequestException::class); 91 92 Validator::queryParams($request); 93 } 94 95 public function testRequiredArrayParameter(): void 96 { 97 $request = $this->createMock(ServerRequestInterface::class); 98 $request 99 ->method('getQueryParams') 100 ->willReturn(['param' => ['test'], 'invalid' => 'not_array']); 101 102 self::assertSame(['test'], Validator::queryParams($request)->array('param')); 103 104 $this->expectException(HttpBadRequestException::class); 105 106 Validator::queryParams($request)->array('invalid'); 107 } 108 109 public function testRequiredBooleanParameter(): void 110 { 111 $request = $this->createMock(ServerRequestInterface::class); 112 $request 113 ->method('getQueryParams') 114 ->willReturn([ 115 'a' => '1', 116 'b' => 'on', 117 'c' => true, 118 'd' => '0', 119 'e' => '', 120 'f' => false, 121 ]); 122 123 self::assertSame(true, Validator::queryParams($request)->boolean('a')); 124 self::assertSame(true, Validator::queryParams($request)->boolean('b')); 125 self::assertSame(true, Validator::queryParams($request)->boolean('c')); 126 self::assertSame(false, Validator::queryParams($request)->boolean('d')); 127 self::assertSame(false, Validator::queryParams($request)->boolean('e')); 128 self::assertSame(false, Validator::queryParams($request)->boolean('f')); 129 self::assertSame(false, Validator::queryParams($request)->boolean('g', false)); 130 131 $this->expectException(HttpBadRequestException::class); 132 133 Validator::queryParams($request)->boolean('h'); 134 } 135 136 public function testRequiredIntegerParameter(): void 137 { 138 $request = $this->createMock(ServerRequestInterface::class); 139 $request 140 ->method('getQueryParams') 141 ->willReturn([ 142 'int_type_positive' => 42, 143 'int_type_negative' => -42, 144 'string_type_positive' => '42', 145 'string_type_negative' => '-42', 146 'invalid' => 'not_int', 147 ]); 148 149 self::assertSame(42, Validator::queryParams($request)->integer('int_type_positive')); 150 self::assertSame(-42, Validator::queryParams($request)->integer('int_type_negative')); 151 self::assertSame(42, Validator::queryParams($request)->integer('string_type_positive')); 152 self::assertSame(-42, Validator::queryParams($request)->integer('string_type_negative')); 153 154 $this->expectException(HttpBadRequestException::class); 155 156 Validator::queryParams($request)->integer('invalid'); 157 } 158 159 public function testRequiredRouteParameter(): void 160 { 161 $route = $this->createMock(Route::class); 162 163 $request = $this->createMock(ServerRequestInterface::class); 164 $request 165 ->method('getQueryParams') 166 ->willReturn([ 167 'valid-route' => $route, 168 'not-route' => '', 169 ]); 170 171 self::assertSame($route, Validator::queryParams($request)->route('valid-route')); 172 173 $this->expectException(HttpBadRequestException::class); 174 175 Validator::queryParams($request)->route('not-route'); 176 } 177 178 public function testRequiredStringParameter(): void 179 { 180 $request = $this->createMock(ServerRequestInterface::class); 181 $request 182 ->method('getQueryParams') 183 ->willReturn(['param' => 'test', 'invalid' => ['not_string']]); 184 185 self::assertSame('test', Validator::queryParams($request)->string('param')); 186 187 $this->expectException(HttpBadRequestException::class); 188 189 Validator::queryParams($request)->string('invalid'); 190 } 191 192 public function testRequiredTreeParameter(): void 193 { 194 $tree = $this->createMock(Tree::class); 195 196 $request = $this->createMock(ServerRequestInterface::class); 197 $request 198 ->method('getQueryParams') 199 ->willReturn([ 200 'valid-tree' => $tree, 201 'not-tree' => '', 202 ]); 203 204 self::assertSame($tree, Validator::queryParams($request)->tree('valid-tree')); 205 206 $this->expectException(HttpBadRequestException::class); 207 208 Validator::queryParams($request)->tree('no-tree'); 209 } 210 211 public function testOptionalTreeParameter(): void 212 { 213 $tree = $this->createMock(Tree::class); 214 215 $request = $this->createMock(ServerRequestInterface::class); 216 $request 217 ->method('getQueryParams') 218 ->willReturn([ 219 'valid-tree' => $tree, 220 'not-tree' => '', 221 ]); 222 223 self::assertSame($tree, Validator::queryParams($request)->treeOptional('valid-tree')); 224 self::assertSame(null, Validator::queryParams($request)->treeOptional('missing-tree')); 225 226 $this->expectException(HttpBadRequestException::class); 227 228 Validator::queryParams($request)->treeOptional('not-tree'); 229 } 230 231 public function testRequiredUserParameter(): void 232 { 233 $user = $this->createMock(UserInterface::class); 234 235 $request = $this->createMock(ServerRequestInterface::class); 236 $request 237 ->method('getQueryParams') 238 ->willReturn([ 239 'valid-user' => $user, 240 'not-user' => '', 241 ]); 242 243 self::assertSame($user, Validator::queryParams($request)->user('valid-user')); 244 245 $this->expectException(HttpBadRequestException::class); 246 247 Validator::queryParams($request)->user('not-user'); 248 } 249 250 public function testIsBetweenParameter(): void 251 { 252 $request = $this->createMock(ServerRequestInterface::class); 253 $request 254 ->method('getQueryParams') 255 ->willReturn(['param' => '42', 'invalid' => '10', 'wrongtype' => 'not_integer']); 256 257 self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('param')); 258 self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('invalid', 42)); 259 self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('wrongtype', 42)); 260 } 261 262 public function testIsInArray(): void 263 { 264 $request = $this->createMock(ServerRequestInterface::class); 265 $request 266 ->method('getQueryParams') 267 ->willReturn(['param' => 'foo']); 268 269 self::assertSame('foo', Validator::queryParams($request)->isInArray(['foo', 'bar'])->string('param')); 270 271 $this->expectException(HttpBadRequestException::class); 272 273 Validator::queryParams($request)->isInArray(['baz'])->string('param'); 274 } 275 276 public function testIsInArrayKeys(): void 277 { 278 $request = $this->createMock(ServerRequestInterface::class); 279 $request 280 ->method('getQueryParams') 281 ->willReturn(['param' => 'foo']); 282 283 self::assertSame('foo', Validator::queryParams($request)->isInArrayKeys(['foo' => 1, 'bar' => 2])->string('param')); 284 285 $this->expectException(HttpBadRequestException::class); 286 287 Validator::queryParams($request)->isInArrayKeys(['baz' => 3])->string('param'); 288 } 289 290 public function testIsNotEmpty(): void 291 { 292 $request = $this->createMock(ServerRequestInterface::class); 293 $request 294 ->method('getQueryParams') 295 ->willReturn(['empty' => '', 'not-empty' => 'foo']); 296 297 self::assertSame('foo', Validator::queryParams($request)->isNotEmpty()->string('not-empty')); 298 299 $this->expectException(HttpBadRequestException::class); 300 301 Validator::queryParams($request)->isNotEmpty()->string('empty'); 302 } 303 304 public function testIsTagParameter(): void 305 { 306 $request = $this->createMock(ServerRequestInterface::class); 307 $request 308 ->method('getQueryParams') 309 ->willReturn(['valid' => 'BIRT', 'invalid' => '@X1@']); 310 311 self::assertSame('BIRT', Validator::queryParams($request)->isTag()->string('valid')); 312 313 $this->expectException(HttpBadRequestException::class); 314 315 Validator::queryParams($request)->isTag()->string('invalid'); 316 } 317 318 public function testIsXrefParameter(): void 319 { 320 $request = $this->createMock(ServerRequestInterface::class); 321 $request 322 ->method('getQueryParams') 323 ->willReturn(['valid' => 'X1', 'invalid' => '@X1@', 'valid-array' => ['X1'], 'invalid-array' => ['@X1@']]); 324 325 self::assertSame('X1', Validator::queryParams($request)->isXref()->string('valid')); 326 self::assertSame(['X1'], Validator::queryParams($request)->isXref()->array('valid-array')); 327 self::assertSame([], Validator::queryParams($request)->isXref()->array('invalid-array')); 328 329 $this->expectException(HttpBadRequestException::class); 330 331 Validator::queryParams($request)->isXref()->string('invalid'); 332 } 333 334 public function testIsLocalUrlParameter(): void 335 { 336 $request = $this->createMock(ServerRequestInterface::class); 337 $request 338 ->method('getAttribute') 339 ->with('base_url')->willReturn('http://example.local/wt'); 340 $request 341 ->method('getQueryParams') 342 ->willReturn(['param' => 'http://example.local/wt/page', 'noscheme' => '//example.local/wt/page']); 343 344 self::assertSame('http://example.local/wt/page', Validator::queryParams($request)->isLocalUrl()->string('param')); 345 self::assertSame('//example.local/wt/page', Validator::queryParams($request)->isLocalUrl()->string('noscheme')); 346 } 347 348 public function testIsLocalUrlParameterWrongScheme(): void 349 { 350 $request = $this->createMock(ServerRequestInterface::class); 351 $request 352 ->method('getAttribute') 353 ->with('base_url') 354 ->willReturn('http://example.local/wt'); 355 $request 356 ->method('getQueryParams') 357 ->willReturn(['https' => 'https://example.local/wt/page']); 358 359 $this->expectException(HttpBadRequestException::class); 360 361 Validator::queryParams($request)->isLocalUrl()->string('https'); 362 } 363 364 public function testIsLocalUrlParameterWrongDomain(): void 365 { 366 $request = $this->createMock(ServerRequestInterface::class); 367 $request 368 ->method('getAttribute') 369 ->with('base_url') 370 ->willReturn('http://example.local/wt'); 371 $request 372 ->method('getQueryParams') 373 ->willReturn(['invalid' => 'http://example.com/wt/page']); 374 375 $this->expectException(HttpBadRequestException::class); 376 377 Validator::queryParams($request)->isLocalUrl()->string('invalid'); 378 } 379 380 public function testIsLocalUrlParameterWrongType(): void 381 { 382 $request = $this->createMock(ServerRequestInterface::class); 383 $request 384 ->method('getQueryParams') 385 ->willReturn(['wrongtype' => ['42']]); 386 387 $this->expectException(HttpBadRequestException::class); 388 389 Validator::queryParams($request)->isLocalUrl()->isLocalUrl()->string('wrongtype'); 390 } 391} 392