├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── src └── URL │ └── Normalizer.php ├── test-client.php └── tests └── URL └── NormalizerTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.phar 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Glen Scott 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Syntax based normalization of URI's 3 | 4 | This normalizes URI's based on the specification RFC 3986 5 | https://tools.ietf.org/html/rfc3986 6 | 7 | ### Example usage 8 | 9 | ```php 10 | require_once 'vendor/autoload.php'; 11 | 12 | $url = 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d'; 13 | $un = new URL\Normalizer( $url ); 14 | echo $un->normalize(); 15 | 16 | // Result: 'example://a/b/c/%7Bfoo%7D' 17 | ``` 18 | 19 | ### The normalization process preserves semantics 20 | 21 | So, for example, the following URL's are all equivalent. 22 | 23 | - `HTTP://www.Example.com/` and `http://www.example.com/` 24 | - `http://www.example.com/a%c2%b1b` and `http://www.example.com/a%C2%B1b` 25 | - `http://www.example.com/%7Eusername/` and `http://www.example.com/~username/` 26 | - `http://www.example.com` and `http://www.example.com/` 27 | - `http://www.example.com:80/bar.html` and `http://www.example.com/bar.html` 28 | - `http://www.example.com/../a/b/../c/./d.html` and `http://www.example.com/a/c/d.html` 29 | - `http://www.example.com/?array[key]=value` and `http://www.example.com/?array%5Bkey%5D=value` 30 | 31 | ### Normalizations performed 32 | 33 | 1. Converting the scheme and host to lower case 34 | 1. Capitalizing letters in escape sequences 35 | 1. Decoding percent-encoded octets of unreserved characters 36 | 1. Adding trailing `/` 37 | 1. Removing the default port 38 | 1. Removing dot-segments 39 | 40 | For more information about these normalizations, please see the following Wikipedia article: 41 | 42 | http://en.wikipedia.org/wiki/URL_normalization#Normalizations_that_Preserve_Semantics 43 | 44 | For license information, please see LICENSE file. 45 | 46 | ### Options 47 | 48 | Two options are available when normalizing URLs which are disabled by default: 49 | 50 | 1. Remove empty delimiters. Enabling this option would normalize `http://www.example.com/?` to `http://www.example.com/` Currently, only the query string delimiter (`?`) is supported by this option. 51 | 2. Sort query parameters. Enabling this option sorts the query parameters by key alphabetically. For example, `http://www.example.com/?c=3&b=2&a=1` becomes `http://www.example.com/?a=1&b=2&c=3` 52 | 53 | ### TODO 54 | 55 | Add further scheme-based normalization steps, as detailed in section 6.2.3 of the RFC. 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glenscott/url-normalizer", 3 | "description": "Syntax based normalization of URL's", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Glen Scott", 8 | "email": "glen@glenscott.co.uk" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.3.0", 13 | "ext-mbstring": "*" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^7" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "URL\\" : "src/URL" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "c71b345c9ce0012cc5fc856910059f93", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.1.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", 21 | "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1" 26 | }, 27 | "require-dev": { 28 | "athletic/athletic": "~0.1.8", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpunit/phpunit": "^6.2.3", 32 | "squizlabs/php_codesniffer": "^3.0.2" 33 | }, 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "1.2.x-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Marco Pivetta", 52 | "email": "ocramius@gmail.com", 53 | "homepage": "http://ocramius.github.com/" 54 | } 55 | ], 56 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 57 | "homepage": "https://github.com/doctrine/instantiator", 58 | "keywords": [ 59 | "constructor", 60 | "instantiate" 61 | ], 62 | "time": "2017-07-22T11:58:36+00:00" 63 | }, 64 | { 65 | "name": "myclabs/deep-copy", 66 | "version": "1.7.0", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/myclabs/DeepCopy.git", 70 | "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", 75 | "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "php": "^5.6 || ^7.0" 80 | }, 81 | "require-dev": { 82 | "doctrine/collections": "^1.0", 83 | "doctrine/common": "^2.6", 84 | "phpunit/phpunit": "^4.1" 85 | }, 86 | "type": "library", 87 | "autoload": { 88 | "psr-4": { 89 | "DeepCopy\\": "src/DeepCopy/" 90 | }, 91 | "files": [ 92 | "src/DeepCopy/deep_copy.php" 93 | ] 94 | }, 95 | "notification-url": "https://packagist.org/downloads/", 96 | "license": [ 97 | "MIT" 98 | ], 99 | "description": "Create deep copies (clones) of your objects", 100 | "keywords": [ 101 | "clone", 102 | "copy", 103 | "duplicate", 104 | "object", 105 | "object graph" 106 | ], 107 | "time": "2017-10-19T19:58:43+00:00" 108 | }, 109 | { 110 | "name": "phar-io/manifest", 111 | "version": "1.0.1", 112 | "source": { 113 | "type": "git", 114 | "url": "https://github.com/phar-io/manifest.git", 115 | "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" 116 | }, 117 | "dist": { 118 | "type": "zip", 119 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", 120 | "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", 121 | "shasum": "" 122 | }, 123 | "require": { 124 | "ext-dom": "*", 125 | "ext-phar": "*", 126 | "phar-io/version": "^1.0.1", 127 | "php": "^5.6 || ^7.0" 128 | }, 129 | "type": "library", 130 | "extra": { 131 | "branch-alias": { 132 | "dev-master": "1.0.x-dev" 133 | } 134 | }, 135 | "autoload": { 136 | "classmap": [ 137 | "src/" 138 | ] 139 | }, 140 | "notification-url": "https://packagist.org/downloads/", 141 | "license": [ 142 | "BSD-3-Clause" 143 | ], 144 | "authors": [ 145 | { 146 | "name": "Arne Blankerts", 147 | "email": "arne@blankerts.de", 148 | "role": "Developer" 149 | }, 150 | { 151 | "name": "Sebastian Heuer", 152 | "email": "sebastian@phpeople.de", 153 | "role": "Developer" 154 | }, 155 | { 156 | "name": "Sebastian Bergmann", 157 | "email": "sebastian@phpunit.de", 158 | "role": "Developer" 159 | } 160 | ], 161 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 162 | "time": "2017-03-05T18:14:27+00:00" 163 | }, 164 | { 165 | "name": "phar-io/version", 166 | "version": "1.0.1", 167 | "source": { 168 | "type": "git", 169 | "url": "https://github.com/phar-io/version.git", 170 | "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" 171 | }, 172 | "dist": { 173 | "type": "zip", 174 | "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", 175 | "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", 176 | "shasum": "" 177 | }, 178 | "require": { 179 | "php": "^5.6 || ^7.0" 180 | }, 181 | "type": "library", 182 | "autoload": { 183 | "classmap": [ 184 | "src/" 185 | ] 186 | }, 187 | "notification-url": "https://packagist.org/downloads/", 188 | "license": [ 189 | "BSD-3-Clause" 190 | ], 191 | "authors": [ 192 | { 193 | "name": "Arne Blankerts", 194 | "email": "arne@blankerts.de", 195 | "role": "Developer" 196 | }, 197 | { 198 | "name": "Sebastian Heuer", 199 | "email": "sebastian@phpeople.de", 200 | "role": "Developer" 201 | }, 202 | { 203 | "name": "Sebastian Bergmann", 204 | "email": "sebastian@phpunit.de", 205 | "role": "Developer" 206 | } 207 | ], 208 | "description": "Library for handling version information and constraints", 209 | "time": "2017-03-05T17:38:23+00:00" 210 | }, 211 | { 212 | "name": "phpdocumentor/reflection-common", 213 | "version": "1.0.1", 214 | "source": { 215 | "type": "git", 216 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 217 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" 218 | }, 219 | "dist": { 220 | "type": "zip", 221 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 222 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 223 | "shasum": "" 224 | }, 225 | "require": { 226 | "php": ">=5.5" 227 | }, 228 | "require-dev": { 229 | "phpunit/phpunit": "^4.6" 230 | }, 231 | "type": "library", 232 | "extra": { 233 | "branch-alias": { 234 | "dev-master": "1.0.x-dev" 235 | } 236 | }, 237 | "autoload": { 238 | "psr-4": { 239 | "phpDocumentor\\Reflection\\": [ 240 | "src" 241 | ] 242 | } 243 | }, 244 | "notification-url": "https://packagist.org/downloads/", 245 | "license": [ 246 | "MIT" 247 | ], 248 | "authors": [ 249 | { 250 | "name": "Jaap van Otterdijk", 251 | "email": "opensource@ijaap.nl" 252 | } 253 | ], 254 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 255 | "homepage": "http://www.phpdoc.org", 256 | "keywords": [ 257 | "FQSEN", 258 | "phpDocumentor", 259 | "phpdoc", 260 | "reflection", 261 | "static analysis" 262 | ], 263 | "time": "2017-09-11T18:02:19+00:00" 264 | }, 265 | { 266 | "name": "phpdocumentor/reflection-docblock", 267 | "version": "4.3.0", 268 | "source": { 269 | "type": "git", 270 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 271 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08" 272 | }, 273 | "dist": { 274 | "type": "zip", 275 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", 276 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08", 277 | "shasum": "" 278 | }, 279 | "require": { 280 | "php": "^7.0", 281 | "phpdocumentor/reflection-common": "^1.0.0", 282 | "phpdocumentor/type-resolver": "^0.4.0", 283 | "webmozart/assert": "^1.0" 284 | }, 285 | "require-dev": { 286 | "doctrine/instantiator": "~1.0.5", 287 | "mockery/mockery": "^1.0", 288 | "phpunit/phpunit": "^6.4" 289 | }, 290 | "type": "library", 291 | "extra": { 292 | "branch-alias": { 293 | "dev-master": "4.x-dev" 294 | } 295 | }, 296 | "autoload": { 297 | "psr-4": { 298 | "phpDocumentor\\Reflection\\": [ 299 | "src/" 300 | ] 301 | } 302 | }, 303 | "notification-url": "https://packagist.org/downloads/", 304 | "license": [ 305 | "MIT" 306 | ], 307 | "authors": [ 308 | { 309 | "name": "Mike van Riel", 310 | "email": "me@mikevanriel.com" 311 | } 312 | ], 313 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 314 | "time": "2017-11-30T07:14:17+00:00" 315 | }, 316 | { 317 | "name": "phpdocumentor/type-resolver", 318 | "version": "0.4.0", 319 | "source": { 320 | "type": "git", 321 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 322 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 323 | }, 324 | "dist": { 325 | "type": "zip", 326 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 327 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 328 | "shasum": "" 329 | }, 330 | "require": { 331 | "php": "^5.5 || ^7.0", 332 | "phpdocumentor/reflection-common": "^1.0" 333 | }, 334 | "require-dev": { 335 | "mockery/mockery": "^0.9.4", 336 | "phpunit/phpunit": "^5.2||^4.8.24" 337 | }, 338 | "type": "library", 339 | "extra": { 340 | "branch-alias": { 341 | "dev-master": "1.0.x-dev" 342 | } 343 | }, 344 | "autoload": { 345 | "psr-4": { 346 | "phpDocumentor\\Reflection\\": [ 347 | "src/" 348 | ] 349 | } 350 | }, 351 | "notification-url": "https://packagist.org/downloads/", 352 | "license": [ 353 | "MIT" 354 | ], 355 | "authors": [ 356 | { 357 | "name": "Mike van Riel", 358 | "email": "me@mikevanriel.com" 359 | } 360 | ], 361 | "time": "2017-07-14T14:27:02+00:00" 362 | }, 363 | { 364 | "name": "phpspec/prophecy", 365 | "version": "1.7.5", 366 | "source": { 367 | "type": "git", 368 | "url": "https://github.com/phpspec/prophecy.git", 369 | "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" 370 | }, 371 | "dist": { 372 | "type": "zip", 373 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", 374 | "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", 375 | "shasum": "" 376 | }, 377 | "require": { 378 | "doctrine/instantiator": "^1.0.2", 379 | "php": "^5.3|^7.0", 380 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 381 | "sebastian/comparator": "^1.1|^2.0", 382 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 383 | }, 384 | "require-dev": { 385 | "phpspec/phpspec": "^2.5|^3.2", 386 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" 387 | }, 388 | "type": "library", 389 | "extra": { 390 | "branch-alias": { 391 | "dev-master": "1.7.x-dev" 392 | } 393 | }, 394 | "autoload": { 395 | "psr-0": { 396 | "Prophecy\\": "src/" 397 | } 398 | }, 399 | "notification-url": "https://packagist.org/downloads/", 400 | "license": [ 401 | "MIT" 402 | ], 403 | "authors": [ 404 | { 405 | "name": "Konstantin Kudryashov", 406 | "email": "ever.zet@gmail.com", 407 | "homepage": "http://everzet.com" 408 | }, 409 | { 410 | "name": "Marcello Duarte", 411 | "email": "marcello.duarte@gmail.com" 412 | } 413 | ], 414 | "description": "Highly opinionated mocking framework for PHP 5.3+", 415 | "homepage": "https://github.com/phpspec/prophecy", 416 | "keywords": [ 417 | "Double", 418 | "Dummy", 419 | "fake", 420 | "mock", 421 | "spy", 422 | "stub" 423 | ], 424 | "time": "2018-02-19T10:16:54+00:00" 425 | }, 426 | { 427 | "name": "phpunit/php-code-coverage", 428 | "version": "6.0.1", 429 | "source": { 430 | "type": "git", 431 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 432 | "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba" 433 | }, 434 | "dist": { 435 | "type": "zip", 436 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f8ca4b604baf23dab89d87773c28cc07405189ba", 437 | "reference": "f8ca4b604baf23dab89d87773c28cc07405189ba", 438 | "shasum": "" 439 | }, 440 | "require": { 441 | "ext-dom": "*", 442 | "ext-xmlwriter": "*", 443 | "php": "^7.1", 444 | "phpunit/php-file-iterator": "^1.4.2", 445 | "phpunit/php-text-template": "^1.2.1", 446 | "phpunit/php-token-stream": "^3.0", 447 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 448 | "sebastian/environment": "^3.0", 449 | "sebastian/version": "^2.0.1", 450 | "theseer/tokenizer": "^1.1" 451 | }, 452 | "require-dev": { 453 | "phpunit/phpunit": "^7.0" 454 | }, 455 | "suggest": { 456 | "ext-xdebug": "^2.6.0" 457 | }, 458 | "type": "library", 459 | "extra": { 460 | "branch-alias": { 461 | "dev-master": "6.0-dev" 462 | } 463 | }, 464 | "autoload": { 465 | "classmap": [ 466 | "src/" 467 | ] 468 | }, 469 | "notification-url": "https://packagist.org/downloads/", 470 | "license": [ 471 | "BSD-3-Clause" 472 | ], 473 | "authors": [ 474 | { 475 | "name": "Sebastian Bergmann", 476 | "email": "sebastian@phpunit.de", 477 | "role": "lead" 478 | } 479 | ], 480 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 481 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 482 | "keywords": [ 483 | "coverage", 484 | "testing", 485 | "xunit" 486 | ], 487 | "time": "2018-02-02T07:01:41+00:00" 488 | }, 489 | { 490 | "name": "phpunit/php-file-iterator", 491 | "version": "1.4.5", 492 | "source": { 493 | "type": "git", 494 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 495 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" 496 | }, 497 | "dist": { 498 | "type": "zip", 499 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", 500 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", 501 | "shasum": "" 502 | }, 503 | "require": { 504 | "php": ">=5.3.3" 505 | }, 506 | "type": "library", 507 | "extra": { 508 | "branch-alias": { 509 | "dev-master": "1.4.x-dev" 510 | } 511 | }, 512 | "autoload": { 513 | "classmap": [ 514 | "src/" 515 | ] 516 | }, 517 | "notification-url": "https://packagist.org/downloads/", 518 | "license": [ 519 | "BSD-3-Clause" 520 | ], 521 | "authors": [ 522 | { 523 | "name": "Sebastian Bergmann", 524 | "email": "sb@sebastian-bergmann.de", 525 | "role": "lead" 526 | } 527 | ], 528 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 529 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 530 | "keywords": [ 531 | "filesystem", 532 | "iterator" 533 | ], 534 | "time": "2017-11-27T13:52:08+00:00" 535 | }, 536 | { 537 | "name": "phpunit/php-text-template", 538 | "version": "1.2.1", 539 | "source": { 540 | "type": "git", 541 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 542 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 543 | }, 544 | "dist": { 545 | "type": "zip", 546 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 547 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 548 | "shasum": "" 549 | }, 550 | "require": { 551 | "php": ">=5.3.3" 552 | }, 553 | "type": "library", 554 | "autoload": { 555 | "classmap": [ 556 | "src/" 557 | ] 558 | }, 559 | "notification-url": "https://packagist.org/downloads/", 560 | "license": [ 561 | "BSD-3-Clause" 562 | ], 563 | "authors": [ 564 | { 565 | "name": "Sebastian Bergmann", 566 | "email": "sebastian@phpunit.de", 567 | "role": "lead" 568 | } 569 | ], 570 | "description": "Simple template engine.", 571 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 572 | "keywords": [ 573 | "template" 574 | ], 575 | "time": "2015-06-21T13:50:34+00:00" 576 | }, 577 | { 578 | "name": "phpunit/php-timer", 579 | "version": "2.0.0", 580 | "source": { 581 | "type": "git", 582 | "url": "https://github.com/sebastianbergmann/php-timer.git", 583 | "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" 584 | }, 585 | "dist": { 586 | "type": "zip", 587 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", 588 | "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", 589 | "shasum": "" 590 | }, 591 | "require": { 592 | "php": "^7.1" 593 | }, 594 | "require-dev": { 595 | "phpunit/phpunit": "^7.0" 596 | }, 597 | "type": "library", 598 | "extra": { 599 | "branch-alias": { 600 | "dev-master": "2.0-dev" 601 | } 602 | }, 603 | "autoload": { 604 | "classmap": [ 605 | "src/" 606 | ] 607 | }, 608 | "notification-url": "https://packagist.org/downloads/", 609 | "license": [ 610 | "BSD-3-Clause" 611 | ], 612 | "authors": [ 613 | { 614 | "name": "Sebastian Bergmann", 615 | "email": "sebastian@phpunit.de", 616 | "role": "lead" 617 | } 618 | ], 619 | "description": "Utility class for timing", 620 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 621 | "keywords": [ 622 | "timer" 623 | ], 624 | "time": "2018-02-01T13:07:23+00:00" 625 | }, 626 | { 627 | "name": "phpunit/php-token-stream", 628 | "version": "3.0.0", 629 | "source": { 630 | "type": "git", 631 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 632 | "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" 633 | }, 634 | "dist": { 635 | "type": "zip", 636 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", 637 | "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", 638 | "shasum": "" 639 | }, 640 | "require": { 641 | "ext-tokenizer": "*", 642 | "php": "^7.1" 643 | }, 644 | "require-dev": { 645 | "phpunit/phpunit": "^7.0" 646 | }, 647 | "type": "library", 648 | "extra": { 649 | "branch-alias": { 650 | "dev-master": "3.0-dev" 651 | } 652 | }, 653 | "autoload": { 654 | "classmap": [ 655 | "src/" 656 | ] 657 | }, 658 | "notification-url": "https://packagist.org/downloads/", 659 | "license": [ 660 | "BSD-3-Clause" 661 | ], 662 | "authors": [ 663 | { 664 | "name": "Sebastian Bergmann", 665 | "email": "sebastian@phpunit.de" 666 | } 667 | ], 668 | "description": "Wrapper around PHP's tokenizer extension.", 669 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 670 | "keywords": [ 671 | "tokenizer" 672 | ], 673 | "time": "2018-02-01T13:16:43+00:00" 674 | }, 675 | { 676 | "name": "phpunit/phpunit", 677 | "version": "7.0.2", 678 | "source": { 679 | "type": "git", 680 | "url": "https://github.com/sebastianbergmann/phpunit.git", 681 | "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9" 682 | }, 683 | "dist": { 684 | "type": "zip", 685 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", 686 | "reference": "e2f8aa21bc54b6ba218bdd4f9e0dac1e9bc3b4e9", 687 | "shasum": "" 688 | }, 689 | "require": { 690 | "ext-dom": "*", 691 | "ext-json": "*", 692 | "ext-libxml": "*", 693 | "ext-mbstring": "*", 694 | "ext-xml": "*", 695 | "myclabs/deep-copy": "^1.6.1", 696 | "phar-io/manifest": "^1.0.1", 697 | "phar-io/version": "^1.0", 698 | "php": "^7.1", 699 | "phpspec/prophecy": "^1.7", 700 | "phpunit/php-code-coverage": "^6.0", 701 | "phpunit/php-file-iterator": "^1.4.3", 702 | "phpunit/php-text-template": "^1.2.1", 703 | "phpunit/php-timer": "^2.0", 704 | "phpunit/phpunit-mock-objects": "^6.0", 705 | "sebastian/comparator": "^2.1", 706 | "sebastian/diff": "^3.0", 707 | "sebastian/environment": "^3.1", 708 | "sebastian/exporter": "^3.1", 709 | "sebastian/global-state": "^2.0", 710 | "sebastian/object-enumerator": "^3.0.3", 711 | "sebastian/resource-operations": "^1.0", 712 | "sebastian/version": "^2.0.1" 713 | }, 714 | "require-dev": { 715 | "ext-pdo": "*" 716 | }, 717 | "suggest": { 718 | "ext-xdebug": "*", 719 | "phpunit/php-invoker": "^2.0" 720 | }, 721 | "bin": [ 722 | "phpunit" 723 | ], 724 | "type": "library", 725 | "extra": { 726 | "branch-alias": { 727 | "dev-master": "7.0-dev" 728 | } 729 | }, 730 | "autoload": { 731 | "classmap": [ 732 | "src/" 733 | ] 734 | }, 735 | "notification-url": "https://packagist.org/downloads/", 736 | "license": [ 737 | "BSD-3-Clause" 738 | ], 739 | "authors": [ 740 | { 741 | "name": "Sebastian Bergmann", 742 | "email": "sebastian@phpunit.de", 743 | "role": "lead" 744 | } 745 | ], 746 | "description": "The PHP Unit Testing framework.", 747 | "homepage": "https://phpunit.de/", 748 | "keywords": [ 749 | "phpunit", 750 | "testing", 751 | "xunit" 752 | ], 753 | "time": "2018-02-26T07:03:12+00:00" 754 | }, 755 | { 756 | "name": "phpunit/phpunit-mock-objects", 757 | "version": "6.0.1", 758 | "source": { 759 | "type": "git", 760 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 761 | "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53" 762 | }, 763 | "dist": { 764 | "type": "zip", 765 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e3249dedc2d99259ccae6affbc2684eac37c2e53", 766 | "reference": "e3249dedc2d99259ccae6affbc2684eac37c2e53", 767 | "shasum": "" 768 | }, 769 | "require": { 770 | "doctrine/instantiator": "^1.0.5", 771 | "php": "^7.1", 772 | "phpunit/php-text-template": "^1.2.1", 773 | "sebastian/exporter": "^3.1" 774 | }, 775 | "require-dev": { 776 | "phpunit/phpunit": "^7.0" 777 | }, 778 | "suggest": { 779 | "ext-soap": "*" 780 | }, 781 | "type": "library", 782 | "extra": { 783 | "branch-alias": { 784 | "dev-master": "6.0.x-dev" 785 | } 786 | }, 787 | "autoload": { 788 | "classmap": [ 789 | "src/" 790 | ] 791 | }, 792 | "notification-url": "https://packagist.org/downloads/", 793 | "license": [ 794 | "BSD-3-Clause" 795 | ], 796 | "authors": [ 797 | { 798 | "name": "Sebastian Bergmann", 799 | "email": "sebastian@phpunit.de", 800 | "role": "lead" 801 | } 802 | ], 803 | "description": "Mock Object library for PHPUnit", 804 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 805 | "keywords": [ 806 | "mock", 807 | "xunit" 808 | ], 809 | "time": "2018-02-15T05:27:38+00:00" 810 | }, 811 | { 812 | "name": "sebastian/code-unit-reverse-lookup", 813 | "version": "1.0.1", 814 | "source": { 815 | "type": "git", 816 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 817 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 818 | }, 819 | "dist": { 820 | "type": "zip", 821 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 822 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 823 | "shasum": "" 824 | }, 825 | "require": { 826 | "php": "^5.6 || ^7.0" 827 | }, 828 | "require-dev": { 829 | "phpunit/phpunit": "^5.7 || ^6.0" 830 | }, 831 | "type": "library", 832 | "extra": { 833 | "branch-alias": { 834 | "dev-master": "1.0.x-dev" 835 | } 836 | }, 837 | "autoload": { 838 | "classmap": [ 839 | "src/" 840 | ] 841 | }, 842 | "notification-url": "https://packagist.org/downloads/", 843 | "license": [ 844 | "BSD-3-Clause" 845 | ], 846 | "authors": [ 847 | { 848 | "name": "Sebastian Bergmann", 849 | "email": "sebastian@phpunit.de" 850 | } 851 | ], 852 | "description": "Looks up which function or method a line of code belongs to", 853 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 854 | "time": "2017-03-04T06:30:41+00:00" 855 | }, 856 | { 857 | "name": "sebastian/comparator", 858 | "version": "2.1.3", 859 | "source": { 860 | "type": "git", 861 | "url": "https://github.com/sebastianbergmann/comparator.git", 862 | "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" 863 | }, 864 | "dist": { 865 | "type": "zip", 866 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", 867 | "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", 868 | "shasum": "" 869 | }, 870 | "require": { 871 | "php": "^7.0", 872 | "sebastian/diff": "^2.0 || ^3.0", 873 | "sebastian/exporter": "^3.1" 874 | }, 875 | "require-dev": { 876 | "phpunit/phpunit": "^6.4" 877 | }, 878 | "type": "library", 879 | "extra": { 880 | "branch-alias": { 881 | "dev-master": "2.1.x-dev" 882 | } 883 | }, 884 | "autoload": { 885 | "classmap": [ 886 | "src/" 887 | ] 888 | }, 889 | "notification-url": "https://packagist.org/downloads/", 890 | "license": [ 891 | "BSD-3-Clause" 892 | ], 893 | "authors": [ 894 | { 895 | "name": "Jeff Welch", 896 | "email": "whatthejeff@gmail.com" 897 | }, 898 | { 899 | "name": "Volker Dusch", 900 | "email": "github@wallbash.com" 901 | }, 902 | { 903 | "name": "Bernhard Schussek", 904 | "email": "bschussek@2bepublished.at" 905 | }, 906 | { 907 | "name": "Sebastian Bergmann", 908 | "email": "sebastian@phpunit.de" 909 | } 910 | ], 911 | "description": "Provides the functionality to compare PHP values for equality", 912 | "homepage": "https://github.com/sebastianbergmann/comparator", 913 | "keywords": [ 914 | "comparator", 915 | "compare", 916 | "equality" 917 | ], 918 | "time": "2018-02-01T13:46:46+00:00" 919 | }, 920 | { 921 | "name": "sebastian/diff", 922 | "version": "3.0.0", 923 | "source": { 924 | "type": "git", 925 | "url": "https://github.com/sebastianbergmann/diff.git", 926 | "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" 927 | }, 928 | "dist": { 929 | "type": "zip", 930 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", 931 | "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", 932 | "shasum": "" 933 | }, 934 | "require": { 935 | "php": "^7.1" 936 | }, 937 | "require-dev": { 938 | "phpunit/phpunit": "^7.0", 939 | "symfony/process": "^2 || ^3.3 || ^4" 940 | }, 941 | "type": "library", 942 | "extra": { 943 | "branch-alias": { 944 | "dev-master": "3.0-dev" 945 | } 946 | }, 947 | "autoload": { 948 | "classmap": [ 949 | "src/" 950 | ] 951 | }, 952 | "notification-url": "https://packagist.org/downloads/", 953 | "license": [ 954 | "BSD-3-Clause" 955 | ], 956 | "authors": [ 957 | { 958 | "name": "Kore Nordmann", 959 | "email": "mail@kore-nordmann.de" 960 | }, 961 | { 962 | "name": "Sebastian Bergmann", 963 | "email": "sebastian@phpunit.de" 964 | } 965 | ], 966 | "description": "Diff implementation", 967 | "homepage": "https://github.com/sebastianbergmann/diff", 968 | "keywords": [ 969 | "diff", 970 | "udiff", 971 | "unidiff", 972 | "unified diff" 973 | ], 974 | "time": "2018-02-01T13:45:15+00:00" 975 | }, 976 | { 977 | "name": "sebastian/environment", 978 | "version": "3.1.0", 979 | "source": { 980 | "type": "git", 981 | "url": "https://github.com/sebastianbergmann/environment.git", 982 | "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" 983 | }, 984 | "dist": { 985 | "type": "zip", 986 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", 987 | "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", 988 | "shasum": "" 989 | }, 990 | "require": { 991 | "php": "^7.0" 992 | }, 993 | "require-dev": { 994 | "phpunit/phpunit": "^6.1" 995 | }, 996 | "type": "library", 997 | "extra": { 998 | "branch-alias": { 999 | "dev-master": "3.1.x-dev" 1000 | } 1001 | }, 1002 | "autoload": { 1003 | "classmap": [ 1004 | "src/" 1005 | ] 1006 | }, 1007 | "notification-url": "https://packagist.org/downloads/", 1008 | "license": [ 1009 | "BSD-3-Clause" 1010 | ], 1011 | "authors": [ 1012 | { 1013 | "name": "Sebastian Bergmann", 1014 | "email": "sebastian@phpunit.de" 1015 | } 1016 | ], 1017 | "description": "Provides functionality to handle HHVM/PHP environments", 1018 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1019 | "keywords": [ 1020 | "Xdebug", 1021 | "environment", 1022 | "hhvm" 1023 | ], 1024 | "time": "2017-07-01T08:51:00+00:00" 1025 | }, 1026 | { 1027 | "name": "sebastian/exporter", 1028 | "version": "3.1.0", 1029 | "source": { 1030 | "type": "git", 1031 | "url": "https://github.com/sebastianbergmann/exporter.git", 1032 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" 1033 | }, 1034 | "dist": { 1035 | "type": "zip", 1036 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", 1037 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", 1038 | "shasum": "" 1039 | }, 1040 | "require": { 1041 | "php": "^7.0", 1042 | "sebastian/recursion-context": "^3.0" 1043 | }, 1044 | "require-dev": { 1045 | "ext-mbstring": "*", 1046 | "phpunit/phpunit": "^6.0" 1047 | }, 1048 | "type": "library", 1049 | "extra": { 1050 | "branch-alias": { 1051 | "dev-master": "3.1.x-dev" 1052 | } 1053 | }, 1054 | "autoload": { 1055 | "classmap": [ 1056 | "src/" 1057 | ] 1058 | }, 1059 | "notification-url": "https://packagist.org/downloads/", 1060 | "license": [ 1061 | "BSD-3-Clause" 1062 | ], 1063 | "authors": [ 1064 | { 1065 | "name": "Jeff Welch", 1066 | "email": "whatthejeff@gmail.com" 1067 | }, 1068 | { 1069 | "name": "Volker Dusch", 1070 | "email": "github@wallbash.com" 1071 | }, 1072 | { 1073 | "name": "Bernhard Schussek", 1074 | "email": "bschussek@2bepublished.at" 1075 | }, 1076 | { 1077 | "name": "Sebastian Bergmann", 1078 | "email": "sebastian@phpunit.de" 1079 | }, 1080 | { 1081 | "name": "Adam Harvey", 1082 | "email": "aharvey@php.net" 1083 | } 1084 | ], 1085 | "description": "Provides the functionality to export PHP variables for visualization", 1086 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1087 | "keywords": [ 1088 | "export", 1089 | "exporter" 1090 | ], 1091 | "time": "2017-04-03T13:19:02+00:00" 1092 | }, 1093 | { 1094 | "name": "sebastian/global-state", 1095 | "version": "2.0.0", 1096 | "source": { 1097 | "type": "git", 1098 | "url": "https://github.com/sebastianbergmann/global-state.git", 1099 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 1100 | }, 1101 | "dist": { 1102 | "type": "zip", 1103 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1104 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1105 | "shasum": "" 1106 | }, 1107 | "require": { 1108 | "php": "^7.0" 1109 | }, 1110 | "require-dev": { 1111 | "phpunit/phpunit": "^6.0" 1112 | }, 1113 | "suggest": { 1114 | "ext-uopz": "*" 1115 | }, 1116 | "type": "library", 1117 | "extra": { 1118 | "branch-alias": { 1119 | "dev-master": "2.0-dev" 1120 | } 1121 | }, 1122 | "autoload": { 1123 | "classmap": [ 1124 | "src/" 1125 | ] 1126 | }, 1127 | "notification-url": "https://packagist.org/downloads/", 1128 | "license": [ 1129 | "BSD-3-Clause" 1130 | ], 1131 | "authors": [ 1132 | { 1133 | "name": "Sebastian Bergmann", 1134 | "email": "sebastian@phpunit.de" 1135 | } 1136 | ], 1137 | "description": "Snapshotting of global state", 1138 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1139 | "keywords": [ 1140 | "global state" 1141 | ], 1142 | "time": "2017-04-27T15:39:26+00:00" 1143 | }, 1144 | { 1145 | "name": "sebastian/object-enumerator", 1146 | "version": "3.0.3", 1147 | "source": { 1148 | "type": "git", 1149 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1150 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 1151 | }, 1152 | "dist": { 1153 | "type": "zip", 1154 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1155 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1156 | "shasum": "" 1157 | }, 1158 | "require": { 1159 | "php": "^7.0", 1160 | "sebastian/object-reflector": "^1.1.1", 1161 | "sebastian/recursion-context": "^3.0" 1162 | }, 1163 | "require-dev": { 1164 | "phpunit/phpunit": "^6.0" 1165 | }, 1166 | "type": "library", 1167 | "extra": { 1168 | "branch-alias": { 1169 | "dev-master": "3.0.x-dev" 1170 | } 1171 | }, 1172 | "autoload": { 1173 | "classmap": [ 1174 | "src/" 1175 | ] 1176 | }, 1177 | "notification-url": "https://packagist.org/downloads/", 1178 | "license": [ 1179 | "BSD-3-Clause" 1180 | ], 1181 | "authors": [ 1182 | { 1183 | "name": "Sebastian Bergmann", 1184 | "email": "sebastian@phpunit.de" 1185 | } 1186 | ], 1187 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1188 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1189 | "time": "2017-08-03T12:35:26+00:00" 1190 | }, 1191 | { 1192 | "name": "sebastian/object-reflector", 1193 | "version": "1.1.1", 1194 | "source": { 1195 | "type": "git", 1196 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1197 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 1198 | }, 1199 | "dist": { 1200 | "type": "zip", 1201 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 1202 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 1203 | "shasum": "" 1204 | }, 1205 | "require": { 1206 | "php": "^7.0" 1207 | }, 1208 | "require-dev": { 1209 | "phpunit/phpunit": "^6.0" 1210 | }, 1211 | "type": "library", 1212 | "extra": { 1213 | "branch-alias": { 1214 | "dev-master": "1.1-dev" 1215 | } 1216 | }, 1217 | "autoload": { 1218 | "classmap": [ 1219 | "src/" 1220 | ] 1221 | }, 1222 | "notification-url": "https://packagist.org/downloads/", 1223 | "license": [ 1224 | "BSD-3-Clause" 1225 | ], 1226 | "authors": [ 1227 | { 1228 | "name": "Sebastian Bergmann", 1229 | "email": "sebastian@phpunit.de" 1230 | } 1231 | ], 1232 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1233 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1234 | "time": "2017-03-29T09:07:27+00:00" 1235 | }, 1236 | { 1237 | "name": "sebastian/recursion-context", 1238 | "version": "3.0.0", 1239 | "source": { 1240 | "type": "git", 1241 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1242 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 1243 | }, 1244 | "dist": { 1245 | "type": "zip", 1246 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1247 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1248 | "shasum": "" 1249 | }, 1250 | "require": { 1251 | "php": "^7.0" 1252 | }, 1253 | "require-dev": { 1254 | "phpunit/phpunit": "^6.0" 1255 | }, 1256 | "type": "library", 1257 | "extra": { 1258 | "branch-alias": { 1259 | "dev-master": "3.0.x-dev" 1260 | } 1261 | }, 1262 | "autoload": { 1263 | "classmap": [ 1264 | "src/" 1265 | ] 1266 | }, 1267 | "notification-url": "https://packagist.org/downloads/", 1268 | "license": [ 1269 | "BSD-3-Clause" 1270 | ], 1271 | "authors": [ 1272 | { 1273 | "name": "Jeff Welch", 1274 | "email": "whatthejeff@gmail.com" 1275 | }, 1276 | { 1277 | "name": "Sebastian Bergmann", 1278 | "email": "sebastian@phpunit.de" 1279 | }, 1280 | { 1281 | "name": "Adam Harvey", 1282 | "email": "aharvey@php.net" 1283 | } 1284 | ], 1285 | "description": "Provides functionality to recursively process PHP variables", 1286 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1287 | "time": "2017-03-03T06:23:57+00:00" 1288 | }, 1289 | { 1290 | "name": "sebastian/resource-operations", 1291 | "version": "1.0.0", 1292 | "source": { 1293 | "type": "git", 1294 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1295 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" 1296 | }, 1297 | "dist": { 1298 | "type": "zip", 1299 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1300 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1301 | "shasum": "" 1302 | }, 1303 | "require": { 1304 | "php": ">=5.6.0" 1305 | }, 1306 | "type": "library", 1307 | "extra": { 1308 | "branch-alias": { 1309 | "dev-master": "1.0.x-dev" 1310 | } 1311 | }, 1312 | "autoload": { 1313 | "classmap": [ 1314 | "src/" 1315 | ] 1316 | }, 1317 | "notification-url": "https://packagist.org/downloads/", 1318 | "license": [ 1319 | "BSD-3-Clause" 1320 | ], 1321 | "authors": [ 1322 | { 1323 | "name": "Sebastian Bergmann", 1324 | "email": "sebastian@phpunit.de" 1325 | } 1326 | ], 1327 | "description": "Provides a list of PHP built-in functions that operate on resources", 1328 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1329 | "time": "2015-07-28T20:34:47+00:00" 1330 | }, 1331 | { 1332 | "name": "sebastian/version", 1333 | "version": "2.0.1", 1334 | "source": { 1335 | "type": "git", 1336 | "url": "https://github.com/sebastianbergmann/version.git", 1337 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1338 | }, 1339 | "dist": { 1340 | "type": "zip", 1341 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1342 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1343 | "shasum": "" 1344 | }, 1345 | "require": { 1346 | "php": ">=5.6" 1347 | }, 1348 | "type": "library", 1349 | "extra": { 1350 | "branch-alias": { 1351 | "dev-master": "2.0.x-dev" 1352 | } 1353 | }, 1354 | "autoload": { 1355 | "classmap": [ 1356 | "src/" 1357 | ] 1358 | }, 1359 | "notification-url": "https://packagist.org/downloads/", 1360 | "license": [ 1361 | "BSD-3-Clause" 1362 | ], 1363 | "authors": [ 1364 | { 1365 | "name": "Sebastian Bergmann", 1366 | "email": "sebastian@phpunit.de", 1367 | "role": "lead" 1368 | } 1369 | ], 1370 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1371 | "homepage": "https://github.com/sebastianbergmann/version", 1372 | "time": "2016-10-03T07:35:21+00:00" 1373 | }, 1374 | { 1375 | "name": "theseer/tokenizer", 1376 | "version": "1.1.0", 1377 | "source": { 1378 | "type": "git", 1379 | "url": "https://github.com/theseer/tokenizer.git", 1380 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" 1381 | }, 1382 | "dist": { 1383 | "type": "zip", 1384 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", 1385 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", 1386 | "shasum": "" 1387 | }, 1388 | "require": { 1389 | "ext-dom": "*", 1390 | "ext-tokenizer": "*", 1391 | "ext-xmlwriter": "*", 1392 | "php": "^7.0" 1393 | }, 1394 | "type": "library", 1395 | "autoload": { 1396 | "classmap": [ 1397 | "src/" 1398 | ] 1399 | }, 1400 | "notification-url": "https://packagist.org/downloads/", 1401 | "license": [ 1402 | "BSD-3-Clause" 1403 | ], 1404 | "authors": [ 1405 | { 1406 | "name": "Arne Blankerts", 1407 | "email": "arne@blankerts.de", 1408 | "role": "Developer" 1409 | } 1410 | ], 1411 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1412 | "time": "2017-04-07T12:08:54+00:00" 1413 | }, 1414 | { 1415 | "name": "webmozart/assert", 1416 | "version": "1.3.0", 1417 | "source": { 1418 | "type": "git", 1419 | "url": "https://github.com/webmozart/assert.git", 1420 | "reference": "0df1908962e7a3071564e857d86874dad1ef204a" 1421 | }, 1422 | "dist": { 1423 | "type": "zip", 1424 | "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", 1425 | "reference": "0df1908962e7a3071564e857d86874dad1ef204a", 1426 | "shasum": "" 1427 | }, 1428 | "require": { 1429 | "php": "^5.3.3 || ^7.0" 1430 | }, 1431 | "require-dev": { 1432 | "phpunit/phpunit": "^4.6", 1433 | "sebastian/version": "^1.0.1" 1434 | }, 1435 | "type": "library", 1436 | "extra": { 1437 | "branch-alias": { 1438 | "dev-master": "1.3-dev" 1439 | } 1440 | }, 1441 | "autoload": { 1442 | "psr-4": { 1443 | "Webmozart\\Assert\\": "src/" 1444 | } 1445 | }, 1446 | "notification-url": "https://packagist.org/downloads/", 1447 | "license": [ 1448 | "MIT" 1449 | ], 1450 | "authors": [ 1451 | { 1452 | "name": "Bernhard Schussek", 1453 | "email": "bschussek@gmail.com" 1454 | } 1455 | ], 1456 | "description": "Assertions to validate method input/output with nice error messages.", 1457 | "keywords": [ 1458 | "assert", 1459 | "check", 1460 | "validate" 1461 | ], 1462 | "time": "2018-01-29T19:49:41+00:00" 1463 | } 1464 | ], 1465 | "aliases": [], 1466 | "minimum-stability": "stable", 1467 | "stability-flags": [], 1468 | "prefer-stable": false, 1469 | "prefer-lowest": false, 1470 | "platform": { 1471 | "php": ">=5.3.0", 1472 | "ext-mbstring": "*" 1473 | }, 1474 | "platform-dev": [] 1475 | } 1476 | -------------------------------------------------------------------------------- /src/URL/Normalizer.php: -------------------------------------------------------------------------------- 1 | 13 | * require_once 'vendor/autoload.php'; 14 | * 15 | * $url = 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d'; 16 | * $un = new URL\Normalizer( $url ); 17 | * echo $un->normalize(); 18 | * 19 | * // result: "example://a/b/c/%7Bfoo%7D" 20 | * 21 | * 22 | * @author Glen Scott 23 | */ 24 | class Normalizer 25 | { 26 | private $url; 27 | private $scheme; 28 | private $host; 29 | private $port; 30 | private $user; 31 | private $pass; 32 | private $path; 33 | private $query; 34 | private $fragment; 35 | private $default_scheme_ports = array( 'http:' => 80, 'https:' => 443, ); 36 | private $components = array( 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment', ); 37 | private $remove_empty_delimiters; 38 | private $sort_query_params; 39 | 40 | /** 41 | * Does the original URL have a ? query delimiter 42 | */ 43 | private $query_delimiter; 44 | 45 | public function __construct($url = null, $remove_empty_delimiters = false, $sort_query_params = false) 46 | { 47 | if ($url) { 48 | $this->setUrl($url); 49 | } 50 | 51 | $this->remove_empty_delimiters = $remove_empty_delimiters; 52 | $this->sort_query_params = $sort_query_params; 53 | } 54 | 55 | private function getQuery($query) 56 | { 57 | $qs = array(); 58 | foreach ($query as $qk => $qv) { 59 | if (is_array($qv)) { 60 | $qs[rawurldecode($qk)] = $this->getQuery($qv); 61 | } else { 62 | $qs[rawurldecode($qk)] = rawurldecode($qv); 63 | } 64 | } 65 | return $qs; 66 | } 67 | 68 | public function getUrl() 69 | { 70 | return $this->url; 71 | } 72 | 73 | public function setUrl($url) 74 | { 75 | $this->url = $url; 76 | 77 | if (strpos($this->url, '?') !== false) { 78 | $this->query_delimiter = true; 79 | } else { 80 | $this->query_delimiter = false; 81 | } 82 | 83 | // parse URL into respective parts 84 | $url_components = $this->mbParseUrl($this->url); 85 | 86 | if (! $url_components) { 87 | // Reset URL 88 | $this->url = ''; 89 | 90 | // Flush properties 91 | foreach ($this->components as $key) { 92 | if (property_exists($this, $key)) { 93 | $this->$key = ''; 94 | } 95 | } 96 | 97 | return false; 98 | } else { 99 | // Update properties 100 | foreach ($url_components as $key => $value) { 101 | if (property_exists($this, $key)) { 102 | $this->$key = $value; 103 | } 104 | } 105 | 106 | // Flush missing components 107 | $missing_components = array_diff( 108 | array_values($this->components), 109 | array_keys($url_components) 110 | ); 111 | 112 | foreach ($missing_components as $key) { 113 | if (property_exists($this, $key)) { 114 | $this->$key = ''; 115 | } 116 | } 117 | 118 | return true; 119 | } 120 | } 121 | 122 | public function normalize() 123 | { 124 | 125 | // URI Syntax Components 126 | // scheme authority path query fragment 127 | // @link https://tools.ietf.org/html/rfc3986#section-3 128 | 129 | // Scheme 130 | // @link https://tools.ietf.org/html/rfc3986#section-3.1 131 | 132 | if ($this->scheme) { 133 | // Converting the scheme to lower case 134 | $this->scheme = strtolower($this->scheme) . ':'; 135 | } 136 | 137 | // Authority 138 | // @link https://tools.ietf.org/html/rfc3986#section-3.2 139 | 140 | $authority = ''; 141 | if ($this->host) { 142 | $authority .= '//'; 143 | 144 | // User Information 145 | // @link https://tools.ietf.org/html/rfc3986#section-3.2.1 146 | 147 | if ($this->user) { 148 | if ($this->pass) { 149 | $authority .= $this->user . ':' . $this->pass . '@'; 150 | } else { 151 | $authority .= $this->user . '@'; 152 | } 153 | } 154 | 155 | // Host 156 | // @link https://tools.ietf.org/html/rfc3986#section-3.2.2 157 | 158 | // Converting the host to lower case 159 | if (mb_detect_encoding($this->host) == 'UTF-8') { 160 | $authority .= mb_strtolower($this->host, 'UTF-8'); 161 | } else { 162 | $authority .= strtolower($this->host); 163 | } 164 | 165 | // Port 166 | // @link https://tools.ietf.org/html/rfc3986#section-3.2.3 167 | 168 | // Removing the default port 169 | if (isset($this->default_scheme_ports[$this->scheme] ) 170 | && $this->port == $this->default_scheme_ports[$this->scheme]) { 171 | $this->port = ''; 172 | } 173 | 174 | if ($this->port) { 175 | $authority .= ':' . $this->port; 176 | } 177 | } 178 | 179 | // Path 180 | // @link https://tools.ietf.org/html/rfc3986#section-3.3 181 | 182 | if ($this->path) { 183 | $this->path = $this->removeAdditionalPathPrefixSlashes($this->path); 184 | $this->path = $this->removeDotSegments($this->path); 185 | $this->path = $this->urlDecodeUnreservedChars($this->path); 186 | $this->path = $this->urlDecodeReservedSubDelimChars($this->path); 187 | } elseif ($this->url) { 188 | // Add default path only when valid URL is present 189 | // Adding trailing / 190 | $this->path = '/'; 191 | } 192 | 193 | // Query 194 | // @link https://tools.ietf.org/html/rfc3986#section-3.4 195 | 196 | if ($this->query) { 197 | $query = $this->parseStr($this->query); 198 | 199 | //encodes every parameter correctly 200 | $qs = $this->getQuery($query); 201 | 202 | $this->query = '?'; 203 | 204 | if ($this->sort_query_params) { 205 | ksort($qs); 206 | } 207 | 208 | foreach ($qs as $key => $val) { 209 | if (strlen($this->query) > 1) { 210 | $this->query .= '&'; 211 | } 212 | 213 | if (is_array($val)) { 214 | for ($i = 0; $i < count($val); $i++) { 215 | if ($i > 0) { 216 | $this->query .= '&'; 217 | } 218 | $this->query .= rawurlencode($key) . '=' . rawurlencode($val[$i]); 219 | } 220 | } else { 221 | $this->query .= rawurlencode($key) . '=' . rawurlencode($val); 222 | } 223 | } 224 | 225 | // Fix http_build_query adding equals sign to empty keys 226 | $this->query = str_replace('=&', '&', rtrim($this->query, '=')); 227 | } else { 228 | if ($this->query_delimiter && ! $this->remove_empty_delimiters) { 229 | $this->query = '?'; 230 | } 231 | } 232 | 233 | // Fragment 234 | // @link https://tools.ietf.org/html/rfc3986#section-3.5 235 | 236 | if ($this->fragment) { 237 | $this->fragment = rawurldecode($this->fragment); 238 | $this->fragment = rawurlencode($this->fragment); 239 | $this->fragment = '#' . $this->fragment; 240 | } 241 | 242 | $this->setUrl($this->scheme . $authority . $this->path . $this->query . $this->fragment); 243 | 244 | return $this->getUrl(); 245 | } 246 | 247 | /** 248 | * Path segment normalization 249 | * https://tools.ietf.org/html/rfc3986#section-5.2.4 250 | */ 251 | public function removeDotSegments($path) 252 | { 253 | $new_path = ''; 254 | 255 | while (! empty($path)) { 256 | // A 257 | $pattern_a = '!^(\.\./|\./)!x'; 258 | $pattern_b_1 = '!^(/\./)!x'; 259 | $pattern_b_2 = '!^(/\.)$!x'; 260 | $pattern_c = '!^(/\.\./|/\.\.)!x'; 261 | $pattern_d = '!^(\.|\.\.)$!x'; 262 | $pattern_e = '!(/*[^/]*)!x'; 263 | 264 | if (preg_match($pattern_a, $path)) { 265 | // remove prefix from $path 266 | $path = preg_replace($pattern_a, '', $path); 267 | } elseif (preg_match($pattern_b_1, $path, $matches) || preg_match($pattern_b_2, $path, $matches)) { 268 | $path = preg_replace("!^" . $matches[1] . "!", '/', $path); 269 | } elseif (preg_match($pattern_c, $path, $matches)) { 270 | $path = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $path); 271 | 272 | // remove the last segment and its preceding "/" (if any) from output buffer 273 | $new_path = preg_replace('!/([^/]+)$!x', '', $new_path); 274 | } elseif (preg_match($pattern_d, $path)) { 275 | $path = preg_replace($pattern_d, '', $path); 276 | } else { 277 | if (preg_match($pattern_e, $path, $matches)) { 278 | $first_path_segment = $matches[1]; 279 | 280 | $path = preg_replace('/^' . preg_quote($first_path_segment, '/') . '/', '', $path, 1); 281 | 282 | $new_path .= $first_path_segment; 283 | } 284 | } 285 | } 286 | 287 | return $new_path; 288 | } 289 | 290 | public function getScheme() 291 | { 292 | return $this->scheme; 293 | } 294 | 295 | /** 296 | * Decode unreserved characters 297 | * 298 | * @link https://tools.ietf.org/html/rfc3986#section-2.3 299 | */ 300 | public function urlDecodeUnreservedChars($string) 301 | { 302 | $string = rawurldecode($string); 303 | $string = rawurlencode($string); 304 | $string = str_replace(array( '%2F', '%3A', '%40' ), array( '/', ':', '@' ), $string); 305 | 306 | return $string; 307 | } 308 | 309 | /** 310 | * Decode reserved sub-delims 311 | * 312 | * @link https://tools.ietf.org/html/rfc3986#section-2.2 313 | */ 314 | public function urlDecodeReservedSubDelimChars($string) 315 | { 316 | return str_replace( 317 | array( '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D' ), 318 | array( '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=' ), 319 | $string 320 | ); 321 | } 322 | 323 | /** 324 | * Replacement for PHP's parse_string which does not deal with spaces or dots in key names 325 | * 326 | * @param string $string URL query string 327 | * @return array key value pairs 328 | */ 329 | private function parseStr($string) 330 | { 331 | $params = array(); 332 | 333 | $pairs = explode('&', $string); 334 | 335 | foreach ($pairs as $pair) { 336 | if (! $pair) { 337 | continue; 338 | } 339 | 340 | $var = explode('=', $pair, 2); 341 | $val = ( isset( $var[1] ) ? $var[1] : '' ); 342 | 343 | if (isset($params[$var[0]])) { 344 | if (is_array($params[$var[0]])) { 345 | $params[$var[0]][] = $val; 346 | } else { 347 | $params[$var[0]] = array($params[$var[0]], $val); 348 | } 349 | } else { 350 | $params[$var[0]] = $val; 351 | } 352 | } 353 | 354 | return $params; 355 | } 356 | 357 | private function mbParseUrl($url) 358 | { 359 | $result = false; 360 | 361 | // Build arrays of values we need to decode before parsing 362 | $entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D'); 363 | $replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]"); 364 | 365 | // Create encoded URL with special URL characters decoded so it can be parsed 366 | // All other characters will be encoded 367 | $encodedURL = str_replace($entities, $replacements, urlencode($url)); 368 | 369 | // Parse the encoded URL 370 | $encodedParts = parse_url($encodedURL); 371 | 372 | // Now, decode each value of the resulting array 373 | if ($encodedParts) { 374 | foreach ($encodedParts as $key => $value) { 375 | $result[$key] = urldecode(str_replace($replacements, $entities, $value)); 376 | } 377 | } 378 | return $result; 379 | } 380 | 381 | /* 382 | * Converts ////foo to /foo within each path segment 383 | */ 384 | private function removeAdditionalPathPrefixSlashes($path) 385 | { 386 | return preg_replace('/(\/)+/', '/', $path); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /test-client.php: -------------------------------------------------------------------------------- 1 | '; 4 | 5 | require_once 'src/URL/Normalizer.php'; 6 | 7 | $un = new URL\Normalizer(); 8 | 9 | test('eXAMPLE://a/./b/../b/%63/%7bfoo%7d', 'example://a/b/c/%7Bfoo%7D'); 10 | test('http://www.example.com', 'http://www.example.com/'); 11 | test('http://www.yahoo.com/%a1', 'http://www.yahoo.com/%A1'); 12 | 13 | test('HTTP://www.Example.com/', 'http://www.example.com/'); 14 | test('http://www.example.com/a%c2%b1b', 'http://www.example.com/a%C2%B1b'); 15 | test('http://www.example.com/%7Eusername/', 'http://www.example.com/~username/'); 16 | test('http://www.example.com:80/bar.html', 'http://www.example.com/bar.html'); 17 | 18 | test('http://www.example.com/../a/b/../c/./d.html', 'http://www.example.com/a/c/d.html'); 19 | test('../', '' ); 20 | test('./', '' ); 21 | test('/./', '/' ); 22 | test('/.', '/' ); 23 | test('/a/b/c/./../../g', '/a/g' ); 24 | test('mid/content=5/../6', 'mid/6' ); 25 | test('/foo/bar/.', '/foo/bar/' ); 26 | test('/foo/bar/./', '/foo/bar/' ); 27 | test('/foo/bar/..', '/foo/' ); 28 | test('/foo/bar/../', '/foo/' ); 29 | test('/foo/bar/../baz', '/foo/baz' ); 30 | test('/foo/bar/../..', '/'); 31 | test('/foo/bar/../../' , '/'); 32 | test('/foo/bar/../../baz' , '/baz'); 33 | //test('/foo/bar/../../../baz' , '/../baz'); 34 | test('a/./b/../b/', 'a/b/' ); 35 | test('.', '' ); 36 | test('..', '' ); 37 | 38 | test('%63', 'c'); 39 | test('%63/%7b', 'c/%7B'); 40 | 41 | test('http://example.com', 'http://example.com/'); 42 | test('http://example.com/', 'http://example.com/'); 43 | test('http://example.com:/', 'http://example.com/'); 44 | test('http://example.com:80/', 'http://example.com/'); 45 | 46 | test('https://example.com', 'https://example.com/'); 47 | test('https://example.com/', 'https://example.com/'); 48 | test('https://example.com:/', 'https://example.com/'); 49 | test('https://example.com:443/', 'https://example.com/'); 50 | 51 | test('http://fancysite.nl/links/doit.pl?id=2029', 'http://fancysite.nl/links/doit.pl?id=2029'); 52 | test('http://example.com/index.html#fragment', 'http://example.com/index.html#fragment'); 53 | test('http://example.com:81/index.html', 'http://example.com:81/index.html'); 54 | test('HtTp://User:Pass@www.ExAmPle.com:80/Blah', 'http://User:Pass@www.example.com/Blah'); 55 | test('/test:2/', ''); 56 | test('mailto:mail@example.com', 'mailto:mail@example.com'); 57 | test('http://user@example.com/', 'http://user@example.com/'); 58 | test('http://example.com/path/?query#fragment', 'http://example.com/path/?query#fragment'); 59 | test('http://example.com/path/?q1&q2&q3&q4', 'http://example.com/path/?q1&q2&q3&q4'); 60 | test('http://example.com:400/', 'http://example.com:400/'); 61 | test('http://example.com/', 'http://example.com/'); 62 | 63 | test('http://example.com/path/?query=space value', 'http://example.com/path/?query=space%20value'); 64 | 65 | test('http://www.example.com/?array[key]=value', 'http://www.example.com/?array%5Bkey%5D=value'); 66 | 67 | /** 68 | * Test URL Normalization 69 | * 70 | * @param string $input URL to normalize 71 | * @param string $expected Anticipated result of normalization 72 | * @return void 73 | * @author emojka 74 | **/ 75 | function test($input, $expected) { 76 | global $un; 77 | $un->setUrl($input); 78 | $result = $un->normalize(); 79 | if ($result === $expected) { 80 | printf("✔ %s → %s\n", $input, $result); 81 | } else { 82 | printf("%s ✘ %s → %s\n", $expected, $input, $result); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/URL/NormalizerTest.php: -------------------------------------------------------------------------------- 1 | fixture = new Normalizer(); 15 | } 16 | 17 | public function testClassCanBeInstantiated() { 18 | $this->assertTrue( is_object( $this->fixture ) ); 19 | } 20 | 21 | public function testObjectIsOfCorrectType() { 22 | $this->assertTrue( get_class( $this->fixture ) == 'URL\Normalizer' ); 23 | } 24 | 25 | public function testObjectHasGetUrlMethod() { 26 | $this->assertTrue( method_exists( $this->fixture, 'getUrl' ) ); 27 | } 28 | 29 | public function testSetUrlFromConstructor() { 30 | $this->fixture = new Normalizer( 'http://www.example.com/' ); 31 | $this->assertTrue( $this->fixture->getUrl() == 'http://www.example.com/' ); 32 | } 33 | 34 | public function testSetUrl() { 35 | $this->fixture->setUrl( $this->test_url ); 36 | $this->assertTrue( $this->fixture->getUrl() == $this->test_url ); 37 | } 38 | 39 | public function testObjectHasGetSchemeMethod() { 40 | $this->assertTrue( method_exists( $this->fixture, 'getScheme' ) ); 41 | } 42 | 43 | public function testSchemeExtractedFromUrl() { 44 | $this->fixture->setUrl( $this->test_url ); 45 | $this->assertTrue( $this->fixture->getScheme() == 'http' ); 46 | } 47 | 48 | /** 49 | * @dataProvider provider 50 | */ 51 | public function testUrlsAreNormalised( $url, $normalised_url ) { 52 | $this->fixture->setUrl( $url ); 53 | 54 | $this->assertEquals( $normalised_url, $this->fixture->normalize() ); 55 | } 56 | 57 | /** 58 | * @dataProvider provider 59 | */ 60 | public function testUrlsAreNormalisedAgain( $url, $normalised_url ) { 61 | $this->fixture->setUrl( $url ); 62 | 63 | // normalize once 64 | $this->fixture->normalize(); 65 | 66 | // then normalize again 67 | $this->assertEquals( $normalised_url, $this->fixture->normalize() ); 68 | } 69 | 70 | public function provider() { 71 | // tests from http://en.wikipedia.org/wiki/URL_normalization#Normalizations_that_Preserve_Semantics 72 | return array( 73 | array( 'HTTP://www.Example.com/', 'http://www.example.com/' ), 74 | array( 'http://www.example.com/a%c2%b1b', 'http://www.example.com/a%C2%B1b' ), 75 | array( 'http://www.example.com/%7Eusername/', 'http://www.example.com/~username/' ), 76 | array( 'http://www.example.com', 'http://www.example.com/' ), 77 | array( 'http://www.example.com:80/bar.html', 'http://www.example.com/bar.html' ), 78 | array( 'http://www.example.com/../a/b/../c/./d.html', 'http://www.example.com/a/c/d.html' ), 79 | array( 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d', 'example://a/b/c/%7Bfoo%7D' ), 80 | ); 81 | } 82 | 83 | public function testCaseIsNormalization() { 84 | $this->fixture->setUrl( 'http://www.yahoo.com/%a1' ); 85 | $this->assertEquals( 'http://www.yahoo.com/%A1', $this->fixture->normalize() ); 86 | } 87 | 88 | /** 89 | * @dataProvider dotSegmentProvider 90 | * 91 | * https://tools.ietf.org/html/rfc3986#section-5.2.4 92 | */ 93 | public function testRemoveDotSegments( $path, $normalised_path ) { 94 | $this->assertEquals( $normalised_path, $this->fixture->removeDotSegments( $path ) ); 95 | } 96 | 97 | public function dotSegmentProvider() { 98 | return array( 99 | array( '../', '' ), 100 | array( './', '' ), 101 | array( '/./', '/' ), 102 | array( '/.', '/' ), 103 | array( '/a/b/c/./../../g', '/a/g' ), 104 | array( 'mid/content=5/../6', 'mid/6' ), 105 | array( '/foo/bar/.', '/foo/bar/' ), 106 | array( '/foo/bar/./', '/foo/bar/' ), 107 | array( '/foo/bar/..', '/foo/' ), 108 | array( '/foo/bar/../', '/foo/' ), 109 | array( '/foo/bar/../baz', '/foo/baz' ), 110 | array('/foo/bar/../..', '/'), 111 | array('/foo/bar/../../' , '/'), 112 | array('/foo/bar/../../baz' , '/baz'), 113 | //array('/foo/bar/../../../baz' , '/../baz'), 114 | array( 'a/./b/../b/', 'a/b/' ), 115 | array( '.', '' ), 116 | array( '..', '' ), 117 | ); 118 | } 119 | 120 | public function testDecodingUnreservedUrlChars() { 121 | $this->assertEquals( 'c', $this->fixture->urlDecodeUnreservedChars( '%63' ) ); 122 | $this->assertEquals( 'c/%7B', $this->fixture->urlDecodeUnreservedChars( '%63/%7b' ) ); 123 | $this->assertEquals( 'eXAMPLE://a/./b/../b/c/%7Bfoo%7D', $this->fixture->urlDecodeUnreservedChars( 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d' ) ); 124 | } 125 | 126 | /** 127 | * @dataProvider schemeData 128 | * 129 | * https://tools.ietf.org/html/rfc3986#section-6.2.3 130 | */ 131 | public function testSchemeBasedNormalization( $url ) { 132 | $expected_uri = 'http://example.com/'; 133 | 134 | $this->fixture->setUrl( $url ); 135 | $this->assertEquals( $expected_uri, $this->fixture->normalize() ); 136 | 137 | } 138 | 139 | public function schemeData() { 140 | return array( array( 'http://example.com' ), 141 | array( 'http://example.com/' ), 142 | array( 'http://example.com:/' ), 143 | array( 'http://example.com:80/' ), ); 144 | } 145 | 146 | /** 147 | * @dataProvider schemeDataSSL 148 | * 149 | * https://tools.ietf.org/html/rfc3986#section-6.2.3 150 | */ 151 | public function testSchemeBasedNormalizationSSL( $url ) { 152 | $expected_uri = 'https://example.com/'; 153 | 154 | $this->fixture->setUrl( $url ); 155 | $this->assertEquals( $expected_uri, $this->fixture->normalize() ); 156 | 157 | } 158 | 159 | public function schemeDataSSL() { 160 | return array( array( 'https://example.com' ), 161 | array( 'https://example.com/' ), 162 | array( 'https://example.com:/' ), 163 | array( 'https://example.com:443/' ), ); 164 | } 165 | 166 | public function testQueryParametersArePreserved() { 167 | $url = 'http://fancysite.nl/links/doit.pl?id=2029'; 168 | 169 | $this->fixture->setUrl( $url ); 170 | $this->assertEquals( $url, $this->fixture->normalize() ); 171 | } 172 | 173 | public function testFragmentIdentifiersArePreserved() { 174 | $url = 'http://example.com/index.html#fragment'; 175 | 176 | $this->fixture->setUrl( $url ); 177 | $this->assertEquals( $url, $this->fixture->normalize() ); 178 | } 179 | 180 | public function testPortNumbersArePreserved() { 181 | $url = 'http://example.com:81/index.html'; 182 | 183 | $this->fixture->setUrl( $url ); 184 | $this->assertEquals( $url, $this->fixture->normalize() ); 185 | } 186 | 187 | public function testCaseSensitiveElementsArePreserved() { 188 | $url = 'HtTp://User:Pass@www.ExAmPle.com:80/Blah'; 189 | 190 | $this->fixture->setUrl( $url ); 191 | $this->assertEquals( 'http://User:Pass@www.example.com/Blah', $this->fixture->normalize() ); 192 | } 193 | 194 | public function testSetUrlReturnsFalseWithUnparseableUrl() { 195 | $this->assertFalse( $this->fixture->setUrl( '/test:2/' ) ); 196 | } 197 | 198 | public function testTrailingSlashIsAdded() { 199 | $url = 'http://example.com'; 200 | 201 | $this->fixture->setUrl( $url ); 202 | $this->assertEquals( 'http://example.com/', $this->fixture->normalize() ); 203 | } 204 | 205 | public function testDoubleSlashNotAddedToSchemeIfNoHost() { 206 | $uri = 'mailto:mail@example.com'; 207 | 208 | $this->fixture->setUrl( $uri ); 209 | $this->assertEquals( 'mailto:mail@example.com', $this->fixture->normalize() ); 210 | } 211 | 212 | public function testColonNotAddedToUsernameWhenNoPassword() { 213 | $uri = 'http://user@example.com/'; 214 | 215 | $this->fixture->setUrl( $uri ); 216 | $this->assertEquals( 'http://user@example.com/', $this->fixture->normalize() ); 217 | } 218 | 219 | public function testPortAndFragmentDoNotPersistBetweenCalls() { 220 | $this->fixture->setUrl( 'http://example.com/path/?query#fragment' ); 221 | $this->fixture->normalize(); 222 | 223 | $uri = 'http://example.com:400/'; 224 | $this->fixture->setUrl( $uri ); 225 | $this->assertEquals( $uri, $this->fixture->normalize() ); 226 | 227 | $uri = 'http://example.com/'; 228 | $this->fixture->setUrl( $uri ); 229 | $this->assertEquals( $uri, $this->fixture->normalize() ); 230 | } 231 | 232 | public function testEbayImageUrl() { 233 | $this->fixture->setUrl( 'http://i.ebayimg.com/t/O05520-Adidas-OM-Olympique-Marseille-Jacket-Hooded-UK-S-/00/s/NDAwWDQwMA==/$(KGrHqF,!lMF!iFJh4nmBQflyg7GSw~~60_12.JPG' ); 234 | $this->assertEquals( 'http://i.ebayimg.com/t/O05520-Adidas-OM-Olympique-Marseille-Jacket-Hooded-UK-S-/00/s/NDAwWDQwMA==/$(KGrHqF,!lMF!iFJh4nmBQflyg7GSw~~60_12.JPG', 235 | $this->fixture->normalize() ); 236 | 237 | } 238 | 239 | public function testReservedCharactersInPathSegmentAreNotEncoded() { 240 | $this->fixture->setUrl( "http://www.example.com/!$&'()*+,;=/" ); 241 | $this->assertEquals( "http://www.example.com/!$&'()*+,;=/", $this->fixture->normalize() ); 242 | } 243 | 244 | public function testQueryWithArray() { 245 | $this->fixture->setUrl('http://www.example.com/?array[key]=value'); 246 | $this->assertEquals('http://www.example.com/?array%5Bkey%5D=value', $this->fixture->normalize() ); 247 | } 248 | 249 | public function testSpacesInQueryStringAreCorrectlyEncoded() { 250 | $this->fixture->setUrl( 'http://www.example.com/?a space' ); 251 | $this->assertEquals( 'http://www.example.com/?a%20space', $this->fixture->normalize() ); 252 | } 253 | 254 | public function testQueryValuesThatContainEqualsSignsArePreserved() { 255 | $this->fixture->setUrl( 'http://www.example.com/?key=v1=v2' ); 256 | $this->assertEquals( 'http://www.example.com/?key=v1%3Dv2', $this->fixture->normalize() ); 257 | } 258 | 259 | public function testUtf8HostNames() { 260 | $this->fixture->setUrl('http://www.Яндекс.РФ'); 261 | $this->assertEquals( 'http://www.яндекс.рф/', $this->fixture->normalize() ); 262 | 263 | $this->fixture->setUrl('http://dev.ŽiŪrKėNaS.lt'); 264 | $this->assertEquals( 'http://dev.žiūrkėnas.lt/', $this->fixture->normalize() ); 265 | 266 | } 267 | 268 | public function testTrimMultipleSlashes() { 269 | $this->fixture->setUrl('http://www.яндекс.рф/////'); 270 | $this->assertEquals( 'http://www.яндекс.рф/', $this->fixture->normalize() ); 271 | 272 | $this->fixture->setUrl('http://www.яндекс.рф/////index.php'); 273 | $this->assertEquals( 'http://www.яндекс.рф/index.php', $this->fixture->normalize() ); 274 | 275 | $this->fixture->setUrl('http://www.яндекс.рф///index////subdir////'); 276 | $this->assertEquals( 'http://www.яндекс.рф/index/subdir/', $this->fixture->normalize() ); 277 | 278 | $this->fixture->setUrl('http://www.яндекс.рф/index/../../subdir'); 279 | $this->assertEquals( 'http://www.яндекс.рф/subdir', $this->fixture->normalize() ); 280 | 281 | } 282 | 283 | /** 284 | * @dataProvider unnamedKeys 285 | */ 286 | public function testUnnamedKeysInQueryStringArePreserved($url, $expected) { 287 | $this->fixture->setUrl($url); 288 | $this->assertEquals($expected, $this->fixture->normalize()); 289 | } 290 | 291 | public function unnamedKeys() { 292 | return array( 293 | array('http://www.example.com/?foo[]=bar&foo[]=baz', 'http://www.example.com/?foo%5B%5D=bar&foo%5B%5D=baz'), 294 | ); 295 | } 296 | 297 | public function testDelimitersArePreservedIfAssociatedComponentIsEmpty() 298 | { 299 | $this->fixture->setUrl('http://www.example.com/?'); 300 | $this->assertEquals( 'http://www.example.com/?', $this->fixture->normalize() ); 301 | } 302 | 303 | public function testEmptyDelimitersAreRemovedOption() 304 | { 305 | $this->fixture = new Normalizer('http://www.example.com/?', true); 306 | $this->assertEquals( 'http://www.example.com/', $this->fixture->normalize() ); 307 | } 308 | 309 | public function testEmptyParametersAreNotPreserved() 310 | { 311 | $this->fixture->setUrl('http://www.example.com/?a&'); 312 | $this->assertEquals( 'http://www.example.com/?a', $this->fixture->normalize() ); 313 | } 314 | 315 | public function testAlphabeticalSortingOfQueryParameters() 316 | { 317 | $this->fixture = new Normalizer('http://www.example.com/?c=3&b=2&a=1', false, true); 318 | $this->assertEquals( 'http://www.example.com/?a=1&b=2&c=3', $this->fixture->normalize() ); 319 | } 320 | } 321 | --------------------------------------------------------------------------------