Browse Source

Fix #11328 PSR-7 HTTP Message Integration (#14701)

Add PSR-7 'HTTP Message'
tags/3.0.0-alpha1
Paul Klimov 7 years ago committed by GitHub
parent
commit
e82b9c72f3
  1. 1
      composer.json
  2. 338
      composer.lock
  3. 1
      framework/CHANGELOG.md
  4. 11
      framework/UPGRADE.md
  5. 12
      framework/captcha/CaptchaAction.php
  6. 8
      framework/classes.php
  7. 1
      framework/composer.json
  8. 3
      framework/filters/Cors.php
  9. 8
      framework/filters/HttpCache.php
  10. 23
      framework/filters/PageCache.php
  11. 7
      framework/filters/RateLimiter.php
  12. 2
      framework/filters/VerbFilter.php
  13. 2
      framework/filters/auth/HttpBasicAuth.php
  14. 4
      framework/filters/auth/HttpBearerAuth.php
  15. 2
      framework/http/Cookie.php
  16. 2
      framework/http/CookieCollection.php
  17. 267
      framework/http/FileStream.php
  18. 2
      framework/http/HeaderCollection.php
  19. 205
      framework/http/MemoryStream.php
  20. 370
      framework/http/MessageTrait.php
  21. 241
      framework/http/ResourceStream.php
  22. 190
      framework/http/UploadedFile.php
  23. 548
      framework/http/Uri.php
  24. 11
      framework/rest/Serializer.php
  25. 26
      framework/validators/FileValidator.php
  26. 14
      framework/validators/ImageValidator.php
  27. 4
      framework/web/ErrorHandler.php
  28. 2
      framework/web/HtmlResponseFormatter.php
  29. 4
      framework/web/JsonResponseFormatter.php
  30. 2
      framework/web/MultipartFormDataParser.php
  31. 231
      framework/web/Request.php
  32. 283
      framework/web/Response.php
  33. 1
      framework/web/User.php
  34. 2
      framework/web/XmlResponseFormatter.php
  35. 5
      framework/widgets/Pjax.php
  36. 11
      tests/framework/captcha/CaptchaActionTest.php
  37. 13
      tests/framework/filters/HttpCacheTest.php
  38. 20
      tests/framework/filters/PageCacheTest.php
  39. 2
      tests/framework/filters/auth/AuthTest.php
  40. 250
      tests/framework/http/FileStreamTest.php
  41. 133
      tests/framework/http/MemoryStreamTest.php
  42. 134
      tests/framework/http/MessageTraitTest.php
  43. 243
      tests/framework/http/ResourceStreamTest.php
  44. 44
      tests/framework/http/UploadedFileTest.php
  45. 203
      tests/framework/http/UriTest.php
  46. 3
      tests/framework/rest/UrlRuleTest.php
  47. 54
      tests/framework/validators/FileValidatorTest.php
  48. 29
      tests/framework/web/ControllerTest.php
  49. 2
      tests/framework/web/FormatterTest.php
  50. 17
      tests/framework/web/RequestTest.php
  51. 40
      tests/framework/web/ResponseTest.php
  52. 10
      tests/framework/web/UrlManagerParseUrlTest.php
  53. 6
      tests/framework/web/UserTest.php
  54. 2
      tests/framework/web/stubs/VendorImage.php

1
composer.json

@ -75,6 +75,7 @@
"psr/log": "~1.0.2",
"yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0",
"psr/http-message": "~1.0.0",
"ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",

338
composer.lock generated

@ -4,21 +4,21 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "81cb8a6aa01acac345059695e795fd68",
"content-hash": "b36161ae5a97ecec89951d7fd5b2aa6a",
"hash": "85aa1827f1afe6bc8ac2213443d06fbe",
"content-hash": "9fc9598aed2de7fd7f85836a8b1f8a44",
"packages": [
{
"name": "bower-asset/inputmask",
"version": "3.3.7",
"version": "3.3.8",
"source": {
"type": "git",
"url": "https://github.com/RobinHerbots/Inputmask.git",
"reference": "9835731cb78cac749734d94a1cb5bd70da4d3b10"
"reference": "791d84990c4a98df1597e9d155be53a3725805dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/9835731cb78cac749734d94a1cb5bd70da4d3b10",
"reference": "9835731cb78cac749734d94a1cb5bd70da4d3b10",
"url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/791d84990c4a98df1597e9d155be53a3725805dd",
"reference": "791d84990c4a98df1597e9d155be53a3725805dd",
"shasum": null
},
"require": {
@ -34,30 +34,18 @@
"version": "2.2.4",
"source": {
"type": "git",
"url": "https://github.com/components/jquery.git",
"reference": "981036fcb56668433a7eb0d1e71190324b4574df"
"url": "https://github.com/jquery/jquery-dist.git",
"reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/components/jquery/zipball/981036fcb56668433a7eb0d1e71190324b4574df",
"reference": "981036fcb56668433a7eb0d1e71190324b4574df",
"shasum": ""
},
"type": "bower-asset-library",
"extra": {
"bower-asset-main": "dist/jquery.js",
"bower-asset-ignore": [
"package.json"
]
"url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72",
"reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72",
"shasum": null
},
"type": "bower-asset",
"license": [
"MIT"
],
"keywords": [
"browser",
"javascript",
"jquery",
"library"
]
},
{
@ -72,21 +60,9 @@
"type": "zip",
"url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3",
"reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3",
"shasum": ""
"shasum": null
},
"type": "bower-asset-library",
"extra": {
"bower-asset-main": "punycode.js",
"bower-asset-ignore": [
"coverage",
"tests",
".*",
"component.json",
"Gruntfile.js",
"node_modules",
"package.json"
]
}
"type": "bower-asset"
},
{
"name": "bower-asset/yii2-pjax",
@ -100,24 +76,12 @@
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978",
"reference": "60728da6ade5879e807a49ce59ef9a72039b8978",
"shasum": ""
"shasum": null
},
"require": {
"bower-asset/jquery": ">=1.8"
},
"type": "bower-asset-library",
"extra": {
"bower-asset-main": "./jquery.pjax.js",
"bower-asset-ignore": [
".travis.yml",
"Gemfile",
"Gemfile.lock",
"CONTRIBUTING.md",
"vendor/",
"script/",
"test/"
]
},
"type": "bower-asset",
"license": [
"MIT"
]
@ -230,6 +194,56 @@
"time": "2017-06-03 02:28:16"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06 14:39:51"
},
{
"name": "psr/simple-cache",
"version": "1.0.0",
"source": {
@ -540,16 +554,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v2.2.5",
"version": "v2.2.6",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "27c2cd9d4abd2178b5b585fa2c3cca656d377c69"
"reference": "c1cc52c242f17c4d52d9601159631da488fac7a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/27c2cd9d4abd2178b5b585fa2c3cca656d377c69",
"reference": "27c2cd9d4abd2178b5b585fa2c3cca656d377c69",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c1cc52c242f17c4d52d9601159631da488fac7a4",
"reference": "c1cc52c242f17c4d52d9601159631da488fac7a4",
"shasum": ""
},
"require": {
@ -620,20 +634,20 @@
}
],
"description": "A tool to automatically fix PHP code style",
"time": "2017-07-18 15:16:38"
"time": "2017-08-22 14:08:16"
},
{
"name": "gecko-packages/gecko-php-unit",
"version": "v2.1",
"version": "v2.2",
"source": {
"type": "git",
"url": "https://github.com/GeckoPackages/GeckoPHPUnit.git",
"reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e"
"reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/5b9e9622c7efd3b22655270b80c03f9e52878a6e",
"reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e",
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/ab525fac9a9ffea219687f261b02008b18ebf2d1",
"reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1",
"shasum": ""
},
"require": {
@ -642,24 +656,29 @@
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.4.3"
},
"suggest": {
"ext-dom": "When testing with xml.",
"ext-libxml": "When testing with xml.",
"phpunit/phpunit": "This is an extension for it so make sure you have it some way."
},
"type": "library",
"autoload": {
"psr-4": {
"GeckoPackages\\PHPUnit\\": "src\\PHPUnit"
"GeckoPackages\\PHPUnit\\": "src/PHPUnit"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Additional PHPUnit tests.",
"description": "Additional PHPUnit asserts and constraints.",
"homepage": "https://github.com/GeckoPackages",
"keywords": [
"extension",
"filesystem",
"phpunit"
],
"time": "2017-06-20 11:22:48"
"time": "2017-08-23 07:39:54"
},
{
"name": "ircmaxell/password-compat",
@ -951,22 +970,22 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "3.2.2",
"version": "3.2.3",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157"
"reference": "86e24012a3139b42a7b71155cfaa325389f00f1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157",
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/86e24012a3139b42a7b71155cfaa325389f00f1f",
"reference": "86e24012a3139b42a7b71155cfaa325389f00f1f",
"shasum": ""
},
"require": {
"php": ">=5.5",
"php": "^7.0",
"phpdocumentor/reflection-common": "^1.0@dev",
"phpdocumentor/type-resolver": "^0.3.0",
"phpdocumentor/type-resolver": "^0.4.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
@ -992,20 +1011,20 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2017-08-08 06:39:58"
"time": "2017-08-29 19:37:41"
},
{
"name": "phpdocumentor/type-resolver",
"version": "0.3.0",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773"
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773",
"reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
"shasum": ""
},
"require": {
@ -1039,7 +1058,7 @@
"email": "me@mikevanriel.com"
}
],
"time": "2017-06-03 08:32:36"
"time": "2017-07-14 14:27:02"
},
{
"name": "phpspec/prophecy",
@ -1307,16 +1326,16 @@
},
{
"name": "phpunit/php-token-stream",
"version": "2.0.0",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b"
"reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ecb0b2cdaa0add708fe6f329ef65ae0c5225130b",
"reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0",
"reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0",
"shasum": ""
},
"require": {
@ -1352,7 +1371,7 @@
"keywords": [
"tokenizer"
],
"time": "2017-08-03 14:17:41"
"time": "2017-08-20 05:47:52"
},
{
"name": "phpunit/phpunit",
@ -2105,20 +2124,20 @@
},
{
"name": "symfony/console",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201"
"reference": "d6596cb5022b6a0bd940eae54a1de78646a5fda6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201",
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201",
"url": "https://api.github.com/repos/symfony/console/zipball/d6596cb5022b6a0bd940eae54a1de78646a5fda6",
"reference": "d6596cb5022b6a0bd940eae54a1de78646a5fda6",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"php": "^5.5.9|>=7.0.8",
"symfony/debug": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0"
},
@ -2131,7 +2150,6 @@
"symfony/dependency-injection": "~3.3",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/filesystem": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0"
},
"suggest": {
@ -2170,24 +2188,24 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2017-07-29 21:27:59"
"time": "2017-08-27 14:52:21"
},
{
"name": "symfony/debug",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13"
"reference": "084d804fe35808eb2ef596ec83d85d9768aa6c9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
"reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13",
"url": "https://api.github.com/repos/symfony/debug/zipball/084d804fe35808eb2ef596ec83d85d9768aa6c9d",
"reference": "084d804fe35808eb2ef596ec83d85d9768aa6c9d",
"shasum": ""
},
"require": {
"php": ">=5.5.9",
"php": "^5.5.9|>=7.0.8",
"psr/log": "~1.0"
},
"conflict": {
@ -2226,24 +2244,24 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2017-07-28 15:27:31"
"time": "2017-08-27 14:52:21"
},
{
"name": "symfony/event-dispatcher",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e"
"reference": "54ca9520a00386f83bca145819ad3b619aaa2485"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e",
"reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54ca9520a00386f83bca145819ad3b619aaa2485",
"reference": "54ca9520a00386f83bca145819ad3b619aaa2485",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
"php": "^5.5.9|>=7.0.8"
},
"conflict": {
"symfony/dependency-injection": "<3.3"
@ -2289,24 +2307,24 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2017-06-09 14:53:08"
"time": "2017-07-29 21:54:42"
},
{
"name": "symfony/filesystem",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "427987eb4eed764c3b6e38d52a0f87989e010676"
"reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676",
"reference": "427987eb4eed764c3b6e38d52a0f87989e010676",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b32a0e5f928d0fa3d1dd03c78d020777e50c10cb",
"reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
@ -2338,24 +2356,24 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2017-07-11 07:17:58"
"time": "2017-07-29 21:54:42"
},
{
"name": "symfony/finder",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4"
"reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4",
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4",
"url": "https://api.github.com/repos/symfony/finder/zipball/b2260dbc80f3c4198f903215f91a1ac7fe9fe09e",
"reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
@ -2387,24 +2405,24 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2017-06-01 21:01:25"
"time": "2017-07-29 21:54:42"
},
{
"name": "symfony/options-resolver",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0"
"reference": "ee4e22978fe885b54ee5da8c7964f0a5301abfb6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0",
"reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/ee4e22978fe885b54ee5da8c7964f0a5301abfb6",
"reference": "ee4e22978fe885b54ee5da8c7964f0a5301abfb6",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
@ -2441,20 +2459,20 @@
"configuration",
"options"
],
"time": "2017-04-12 14:14:56"
"time": "2017-07-29 21:54:42"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.4.0",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "f29dca382a6485c3cbe6379f0c61230167681937"
"reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937",
"reference": "f29dca382a6485c3cbe6379f0c61230167681937",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
"reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
"shasum": ""
},
"require": {
@ -2466,7 +2484,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -2500,20 +2518,20 @@
"portable",
"shim"
],
"time": "2017-06-09 14:24:12"
"time": "2017-06-14 15:44:48"
},
{
"name": "symfony/polyfill-php54",
"version": "v1.4.0",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php54.git",
"reference": "7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789"
"reference": "b7763422a5334c914ef0298ed21b253d25913a6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789",
"reference": "7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789",
"url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/b7763422a5334c914ef0298ed21b253d25913a6e",
"reference": "b7763422a5334c914ef0298ed21b253d25913a6e",
"shasum": ""
},
"require": {
@ -2522,7 +2540,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -2558,20 +2576,20 @@
"portable",
"shim"
],
"time": "2017-06-09 08:25:21"
"time": "2017-06-14 15:44:48"
},
{
"name": "symfony/polyfill-php55",
"version": "v1.4.0",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php55.git",
"reference": "94566239a7720cde0820f15f0cc348ddb51ba51d"
"reference": "29b1381d66f16e0581aab0b9f678ccf073288f68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/94566239a7720cde0820f15f0cc348ddb51ba51d",
"reference": "94566239a7720cde0820f15f0cc348ddb51ba51d",
"url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/29b1381d66f16e0581aab0b9f678ccf073288f68",
"reference": "29b1381d66f16e0581aab0b9f678ccf073288f68",
"shasum": ""
},
"require": {
@ -2581,7 +2599,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -2614,20 +2632,20 @@
"portable",
"shim"
],
"time": "2017-06-09 08:25:21"
"time": "2017-06-14 15:44:48"
},
{
"name": "symfony/polyfill-php70",
"version": "v1.4.0",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php70.git",
"reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874"
"reference": "b6482e68974486984f59449ecea1fbbb22ff840f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/032fd647d5c11a9ceab8ee8747e13b5448e93874",
"reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874",
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/b6482e68974486984f59449ecea1fbbb22ff840f",
"reference": "b6482e68974486984f59449ecea1fbbb22ff840f",
"shasum": ""
},
"require": {
@ -2637,7 +2655,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -2673,20 +2691,20 @@
"portable",
"shim"
],
"time": "2017-06-09 14:24:12"
"time": "2017-06-14 15:44:48"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.4.0",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c"
"reference": "8abc9097f5001d310f0edba727469c988acc6ea7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/d3a71580c1e2cab33b6d705f0ec40e9015e14d5c",
"reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8abc9097f5001d310f0edba727469c988acc6ea7",
"reference": "8abc9097f5001d310f0edba727469c988acc6ea7",
"shasum": ""
},
"require": {
@ -2695,7 +2713,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
"dev-master": "1.5-dev"
}
},
"autoload": {
@ -2728,24 +2746,24 @@
"portable",
"shim"
],
"time": "2017-06-09 08:25:21"
"time": "2017-07-11 13:25:55"
},
{
"name": "symfony/process",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
"reference": "b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
"url": "https://api.github.com/repos/symfony/process/zipball/b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0",
"reference": "b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
@ -2777,24 +2795,24 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2017-07-13 13:05:09"
"time": "2017-07-29 21:54:42"
},
{
"name": "symfony/stopwatch",
"version": "v3.3.6",
"version": "v3.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "602a15299dc01556013b07167d4f5d3a60e90d15"
"reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15",
"reference": "602a15299dc01556013b07167d4f5d3a60e90d15",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/9a5610a8d6a50985a7be485c0ba745c22607beeb",
"reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
@ -2826,7 +2844,7 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com",
"time": "2017-04-12 14:14:56"
"time": "2017-07-29 21:54:42"
},
{
"name": "theseer/tokenizer",

1
framework/CHANGELOG.md

@ -5,6 +5,7 @@ Yii Framework 2 Change Log
-----------------------
- Enh #879: Caching implementation refactored according to PSR-16 'Simple Cache' specification (klimov-paul)
- Enh #11328: Added support for PSR-7 'HTTP Message' (klimov-paul)
- Enh #13799: CAPTCHA rendering logic extracted into `yii\captcha\DriverInterface`, which instance is available via `yii\captcha\CaptchaAction::$driver` field (vladis84, klimov-paul)
- Enh #9260: Mail view rendering encapsulated into `yii\mail\Template` class allowing rendering in isolation and access to `yii\mail\MessageInterface` instance via `$this->context->message` inside the view (klimov-paul)
- Enh #11058: Add `$checkAjax` parameter to method `yii\web\Controller::redirect()` which controls redirection in AJAX and PJAX requests (ivanovyordan)

11
framework/UPGRADE.md

@ -80,6 +80,17 @@ Upgrade from Yii 2.0.x
* Profiling related functionality has been extracted into a separated component under `yii\profile\ProfilerInterface`.
Profiling messages should be collection using `yii\base\Application::$profiler`. In case you wish to
continue storing profiling messages along with the log ones, you may use `yii\profile\LogTarget` profiling target.
* Classes `yii\web\Request` and `yii\web\Response` have been updated to match interfaces `Psr\Http\Message\RequestInterface`
and `Psr\Http\Message\ResponseInterface` accordingly. Make sure you use their methods and properties correctly.
In particular: method `getHeaders()` and corresponding virtual property `$headers` are no longer return `HeaderCollection`
instance, you can use `getHeaderCollection()` in order to use old headers setup syntax; `Request|Response::$version` renamed
to `Request|Response::$protocolVersion`; `Response::$statusText` renamed `Response::$reasonPhrase`;
* `yii\web\Response::$stream` is no longer available, use `yii\web\Response::withBody()` to setup stream response.
You can use `Response::$bodyRange` to setup stream content range.
* Classes `yii\web\CookieCollection`, `yii\web\HeaderCollection` and `yii\web\UploadedFile` have been moved under
namespace `yii\http\*`. Make sure to refer to those classes using correct fully qualified name.
* Public interface of `UploadedFile` class has been changed according to `Psr\Http\Message\UploadedFileInterface`.
Make sure you refer to its properties and methods with correct names.
* `yii\captcha\CaptchaAction` has been refactored. Rendering logic was extracted into `yii\captcha\DriverInterface`, which
instance is available via `yii\captcha\CaptchaAction::$driver` field. All image settings now should be passed to
the driver fields instead of action. Automatic detection of the rendering driver is no longer supported.

12
framework/captcha/CaptchaAction.php

@ -181,11 +181,11 @@ class CaptchaAction extends Action
*/
protected function setHttpHeaders()
{
Yii::$app->getResponse()->getHeaders()
->set('Pragma', 'public')
->set('Expires', '0')
->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->set('Content-Transfer-Encoding', 'binary')
->set('Content-type', $this->driver->getImageMimeType());
$response = Yii::$app->getResponse();
$response->setHeader('Pragma', 'public');
$response->setHeader('Expires', '0');
$response->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
$response->setHeader('Content-Transfer-Encoding', 'binary');
$response->setHeader('Content-type', $this->driver->getImageMimeType());
}
}

8
framework/classes.php

@ -279,15 +279,15 @@ return [
'yii\web\CompositeUrlRule' => YII2_PATH . '/web/CompositeUrlRule.php',
'yii\web\ConflictHttpException' => YII2_PATH . '/web/ConflictHttpException.php',
'yii\web\Controller' => YII2_PATH . '/web/Controller.php',
'yii\web\Cookie' => YII2_PATH . '/web/Cookie.php',
'yii\web\CookieCollection' => YII2_PATH . '/web/CookieCollection.php',
'yii\http\Cookie' => YII2_PATH . '/http/Cookie.php',
'yii\http\CookieCollection' => YII2_PATH . '/http/CookieCollection.php',
'yii\web\DbSession' => YII2_PATH . '/web/DbSession.php',
'yii\web\ErrorAction' => YII2_PATH . '/web/ErrorAction.php',
'yii\web\ErrorHandler' => YII2_PATH . '/web/ErrorHandler.php',
'yii\web\ForbiddenHttpException' => YII2_PATH . '/web/ForbiddenHttpException.php',
'yii\web\GoneHttpException' => YII2_PATH . '/web/GoneHttpException.php',
'yii\web\GroupUrlRule' => YII2_PATH . '/web/GroupUrlRule.php',
'yii\web\HeaderCollection' => YII2_PATH . '/web/HeaderCollection.php',
'yii\http\HeaderCollection' => YII2_PATH . '/http/HeaderCollection.php',
'yii\web\HtmlResponseFormatter' => YII2_PATH . '/web/HtmlResponseFormatter.php',
'yii\web\HttpException' => YII2_PATH . '/web/HttpException.php',
'yii\web\IdentityInterface' => YII2_PATH . '/web/IdentityInterface.php',
@ -314,7 +314,7 @@ return [
'yii\web\UnauthorizedHttpException' => YII2_PATH . '/web/UnauthorizedHttpException.php',
'yii\web\UnprocessableEntityHttpException' => YII2_PATH . '/web/UnprocessableEntityHttpException.php',
'yii\web\UnsupportedMediaTypeHttpException' => YII2_PATH . '/web/UnsupportedMediaTypeHttpException.php',
'yii\web\UploadedFile' => YII2_PATH . '/web/UploadedFile.php',
'yii\http\UploadedFile' => YII2_PATH . '/http/UploadedFile.php',
'yii\web\UrlManager' => YII2_PATH . '/web/UrlManager.php',
'yii\web\UrlNormalizer' => YII2_PATH . '/web/UrlNormalizer.php',
'yii\web\UrlNormalizerRedirectException' => YII2_PATH . '/web/UrlNormalizerRedirectException.php',

1
framework/composer.json

@ -70,6 +70,7 @@
"psr/log": "~1.0.2",
"yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0",
"psr/http-message": "~1.0.0",
"ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",

3
framework/filters/Cors.php

@ -212,9 +212,8 @@ class Cors extends ActionFilter
public function addCorsHeaders($response, $headers)
{
if (empty($headers) === false) {
$responseHeaders = $response->getHeaders();
foreach ($headers as $field => $value) {
$responseHeaders->set($field, $value);
$response->setHeader($field, $value);
}
}
}

8
framework/filters/HttpCache.php

@ -139,13 +139,13 @@ class HttpCache extends ActionFilter
$response = Yii::$app->getResponse();
if ($etag !== null) {
$response->getHeaders()->set('Etag', $etag);
$response->setHeader('Etag', $etag);
}
$cacheValid = $this->validateCache($lastModified, $etag);
// https://tools.ietf.org/html/rfc7232#section-4.1
if ($lastModified !== null && (!$cacheValid || ($cacheValid && $etag === null))) {
$response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
$response->setHeader('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
}
if ($cacheValid) {
$response->setStatusCode(304);
@ -192,10 +192,8 @@ class HttpCache extends ActionFilter
session_cache_limiter($this->sessionCacheLimiter);
}
$headers = Yii::$app->getResponse()->getHeaders();
if ($this->cacheControlHeader !== null) {
$headers->set('Cache-Control', $this->cacheControlHeader);
Yii::$app->getResponse()->setHeader('Cache-Control', $this->cacheControlHeader);
}
}

23
framework/filters/PageCache.php

@ -208,10 +208,15 @@ class PageCache extends ActionFilter
*/
protected function restoreResponse($response, $data)
{
foreach (['format', 'version', 'statusCode', 'statusText', 'content'] as $name) {
foreach (['format', 'protocolVersion', 'statusCode', 'reasonPhrase', 'content'] as $name) {
$response->{$name} = $data[$name];
}
foreach (['headers', 'cookies'] as $name) {
if (isset($data['headers'])) {
$response->setHeaders($data['headers']);
}
foreach (['cookies'] as $name) {
if (isset($data[$name]) && is_array($data[$name])) {
$response->{$name}->fromArray(array_merge($data[$name], $response->{$name}->toArray()));
}
@ -256,9 +261,14 @@ class PageCache extends ActionFilter
}
$data['dynamicPlaceholders'] = $this->dynamicPlaceholders;
foreach (['format', 'version', 'statusCode', 'statusText'] as $name) {
$data[$name] = $response->{$name};
}
$data = array_merge($data, [
'format' => $response->format,
'protocolVersion' => $response->getProtocolVersion(),
'statusCode' => $response->getStatusCode(),
'reasonPhrase' => $response->getReasonPhrase(),
]);
$this->insertResponseCollectionIntoData($response, 'headers', $data);
$this->insertResponseCollectionIntoData($response, 'cookies', $data);
$this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency);
@ -281,7 +291,8 @@ class PageCache extends ActionFilter
return;
}
$all = $response->{$collectionName}->toArray();
$collection = $response->{$collectionName};
$all = is_array($collection) ? $collection : $collection->toArray();
if (is_array($this->{$property})) {
$filtered = [];
foreach ($this->{$property} as $name) {

7
framework/filters/RateLimiter.php

@ -136,10 +136,9 @@ class RateLimiter extends ActionFilter
public function addRateLimitHeaders($response, $limit, $remaining, $reset)
{
if ($this->enableRateLimitHeaders) {
$response->getHeaders()
->set('X-Rate-Limit-Limit', $limit)
->set('X-Rate-Limit-Remaining', $remaining)
->set('X-Rate-Limit-Reset', $reset);
$response->setHeader('X-Rate-Limit-Limit', $limit);
$response->setHeader('X-Rate-Limit-Remaining', $remaining);
$response->setHeader('X-Rate-Limit-Reset', $reset);
}
}
}

2
framework/filters/VerbFilter.php

@ -101,7 +101,7 @@ class VerbFilter extends Behavior
if (!in_array($verb, $allowed)) {
$event->isValid = false;
// https://tools.ietf.org/html/rfc2616#section-14.7
Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
Yii::$app->getResponse()->setHeader('Allow', implode(', ', $allowed));
throw new MethodNotAllowedHttpException('Method Not Allowed. This URL can only handle the following request methods: ' . implode(', ', $allowed) . '.');
}

2
framework/filters/auth/HttpBasicAuth.php

@ -113,6 +113,6 @@ class HttpBasicAuth extends AuthMethod
*/
public function challenge($response)
{
$response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
$response->setHeader('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
}
}

4
framework/filters/auth/HttpBearerAuth.php

@ -39,7 +39,7 @@ class HttpBearerAuth extends AuthMethod
*/
public function authenticate($user, $request, $response)
{
$authHeader = $request->getHeaders()->get('Authorization');
$authHeader = $request->getHeaderLine('Authorization');
if ($authHeader !== null && preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) {
$identity = $user->loginByAccessToken($matches[1], get_class($this));
if ($identity === null) {
@ -56,6 +56,6 @@ class HttpBearerAuth extends AuthMethod
*/
public function challenge($response)
{
$response->getHeaders()->set('WWW-Authenticate', "Bearer realm=\"{$this->realm}\"");
$response->setHeader('WWW-Authenticate', "Bearer realm=\"{$this->realm}\"");
}
}

2
framework/web/Cookie.php → framework/http/Cookie.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
namespace yii\http;
/**
* Cookie represents information related with a cookie, such as [[name]], [[value]], [[domain]], etc.

2
framework/web/CookieCollection.php → framework/http/CookieCollection.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
namespace yii\http;
use ArrayIterator;
use Yii;

267
framework/http/FileStream.php

@ -0,0 +1,267 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\http;
use Psr\Http\Message\StreamInterface;
use Yii;
use yii\base\BaseObject;
use yii\base\ErrorHandler;
use yii\base\InvalidConfigException;
/**
* FileStream represents file stream.
*
* Example:
*
* ```php
* $stream = new FileSteam([
* 'filename' => '@app/files/items.txt',
* 'mode' => 'w+',
* ]);
*
* $stream->write('some content...');
* $stream->close();
* ```
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1.0
*/
class FileStream extends BaseObject implements StreamInterface
{
/**
* @var string file or stream name.
* Path alias can be used here, for example: '@app/runtime/items.csv'.
* This field can also be PHP stream name, e.g. anything which can be passed to `fopen()`, for example: 'php://input'.
*/
public $filename;
/**
* @var string file open mode.
*/
public $mode = 'r';
/**
* @var resource|null stream resource
*/
private $_resource;
/**
* @var array a resource metadata.
*/
private $_metadata;
/**
* Destructor.
* Closes the stream resource when destroyed.
*/
public function __destruct()
{
$this->close();
}
/**
* @return resource a file pointer resource.
* @throws InvalidConfigException if unable to open a resource.
*/
public function getResource()
{
if ($this->_resource === null) {
$resource = fopen(Yii::getAlias($this->filename), $this->mode);
if ($resource === false) {
throw new InvalidConfigException("Unable to open file '{$this->filename}' with mode '{$this->mode}'");
}
$this->_resource = $resource;
}
return $this->_resource;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
// __toString cannot throw exception
// use trigger_error to bypass this limitation
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
ErrorHandler::convertExceptionToError($e);
return '';
}
}
/**
* {@inheritdoc}
*/
public function close()
{
if ($this->_resource !== null) {
fclose($this->_resource);
$this->_resource = null;
$this->_metadata = null;
}
}
/**
* {@inheritdoc}
*/
public function detach()
{
if ($this->_resource === null) {
return null;
}
$result = $this->_resource;
$this->_resource = null;
$this->_metadata = null;
return $result;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
$uri = $this->getMetadata('uri');
if (!empty($uri)) {
// clear the stat cache in case stream has a URI
clearstatcache(true, $uri);
}
$stats = fstat($this->getResource());
if (isset($stats['size'])) {
return $stats['size'];
}
return null;
}
/**
* {@inheritdoc}
*/
public function tell()
{
$result = ftell($this->getResource());
if ($result === false) {
throw new \RuntimeException('Unable to determine stream position');
}
return $result;
}
/**
* {@inheritdoc}
*/
public function eof()
{
return feof($this->getResource());
}
/**
* {@inheritdoc}
*/
public function isSeekable()
{
return (bool)$this->getMetadata('seekable');
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if (fseek($this->getResource(), $offset, $whence) === -1) {
throw new \RuntimeException("Unable to seek to stream position '{$offset}' with whence '{$whence}'");
}
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->seek(0);
}
/**
* {@inheritdoc}
*/
public function isWritable()
{
$mode = $this->getMetadata('mode');
foreach (['w', 'c', 'a', 'x', 'r+'] as $key) {
if (strpos($mode, $key) !== false) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function write($string)
{
$result = fwrite($this->getResource(), $string);
if ($result === false) {
throw new \RuntimeException('Unable to write to stream');
}
return $result;
}
/**
* {@inheritdoc}
*/
public function isReadable()
{
$mode = $this->getMetadata('mode');
foreach (['r', 'w+', 'a+', 'c+', 'x+'] as $key) {
if (strpos($mode, $key) !== false) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function read($length)
{
$string = fread($this->getResource(), $length);
if ($string === false) {
throw new \RuntimeException('Unable to read from stream');
}
return $string;
}
/**
* {@inheritdoc}
*/
public function getContents()
{
$contents = stream_get_contents($this->getResource());
if ($contents === false) {
throw new \RuntimeException('Unable to read stream contents');
}
return $contents;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
if ($this->_metadata === null) {
$this->_metadata = stream_get_meta_data($this->getResource());
}
if ($key === null) {
return $this->_metadata;
}
return isset($this->_metadata[$key]) ? $this->_metadata[$key] : null;
}
}

2
framework/web/HeaderCollection.php → framework/http/HeaderCollection.php

@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
namespace yii\http;
use ArrayIterator;
use Yii;

205
framework/http/MemoryStream.php

@ -0,0 +1,205 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\http;
use Psr\Http\Message\StreamInterface;
use yii\base\BaseObject;
use yii\base\InvalidArgumentException;
/**
* MemoryStream uses internal field as a stream source. Thus data associated with this stream exists only in
* memory and will be lost once stream is closed.
*
* Example:
*
* ```php
* $stream = new MemoryStream();
*
* $stream->write('some content...');
* // ...
* $stream->rewind();
* echo $stream->getContents();
* ```
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1.0
*/
class MemoryStream extends BaseObject implements StreamInterface
{
/**
* @var string internal content.
*/
private $buffer = '';
/**
* @var int internal stream pointer.
*/
private $pointer = 0;
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->buffer;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->buffer = '';
$this->pointer = 0;
}
/**
* {@inheritdoc}
*/
public function detach()
{
$this->close();
return null;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
return strlen($this->buffer);
}
/**
* {@inheritdoc}
*/
public function tell()
{
return $this->pointer;
}
/**
* {@inheritdoc}
*/
public function eof()
{
return $this->pointer >= $this->getSize();
}
/**
* {@inheritdoc}
*/
public function isSeekable()
{
return true;
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
$this->pointer = $offset;
break;
case SEEK_CUR:
$this->pointer += $offset;
break;
case SEEK_END:
$this->pointer = $this->getSize() + $offset;
break;
default:
throw new InvalidArgumentException("Unknown seek whence: '{$whence}'.");
}
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->seek(0);
}
/**
* {@inheritdoc}
*/
public function isWritable()
{
return true;
}
/**
* {@inheritdoc}
*/
public function write($string)
{
$size = $this->getSize();
$writeSize = strlen($string);
if ($this->pointer >= $size) {
$this->buffer .= $string;
$this->pointer = $size + $writeSize;
return $writeSize;
}
$begin = substr($this->buffer, 0, $this->pointer);
$end = substr($this->buffer, $this->pointer + $writeSize);
$this->buffer = $begin . $string . $end;
$this->pointer += $writeSize;
return $writeSize;
}
/**
* {@inheritdoc}
*/
public function isReadable()
{
return true;
}
/**
* {@inheritdoc}
*/
public function read($length)
{
$data = substr($this->buffer, $this->pointer, $length);
$this->pointer += $length;
return $data;
}
/**
* {@inheritdoc}
*/
public function getContents()
{
if ($this->pointer === 0) {
return $this->buffer;
}
return substr($this->buffer, $this->pointer);
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
$metadata = [
'mode' => 'rw',
'seekable' => $this->isSeekable(),
];
if ($key === null) {
return $metadata;
}
return (isset($metadata[$key])) ? $metadata[$key] : null;
}
}

370
framework/http/MessageTrait.php

@ -0,0 +1,370 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\http;
use Psr\Http\Message\StreamInterface;
use yii\di\Instance;
/**
* MessageTrait provides set of methods to satisfy [[\Psr\Http\Message\MessageInterface]].
*
* This trait should be applied to descendant of [[\yii\base\BaseObject]] implementing [[\Psr\Http\Message\MessageInterface]].
*
* @property string $protocolVersion HTTP protocol version as a string.
* @property string[][] $headers the message's headers.
* @property StreamInterface $body the body of the message.
* @property HeaderCollection $headerCollection The header collection. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1.0
*/
trait MessageTrait
{
/**
* @var string HTTP protocol version as a string.
*/
private $_protocolVersion;
/**
* @var HeaderCollection header collection, which is used for headers storage.
*/
private $_headerCollection;
/**
* @var StreamInterface the body of the message.
*/
private $_body;
/**
* Retrieves the HTTP protocol version as a string.
* @return string HTTP protocol version.
*/
public function getProtocolVersion()
{
if ($this->_protocolVersion === null) {
$this->_protocolVersion = $this->defaultProtocolVersion();
}
return $this->_protocolVersion;
}
/**
* Specifies HTTP protocol version.
* @param string $version HTTP protocol version
*/
public function setProtocolVersion($version)
{
$this->_protocolVersion = $version;
}
/**
* Return an instance with the specified HTTP protocol version.
*
* This method retains the immutability of the message and returns an instance that has the
* new protocol version.
*
* @param string $version HTTP protocol version
* @return static
*/
public function withProtocolVersion($version)
{
if ($this->getProtocolVersion() === $version) {
return $this;
}
$newInstance = clone $this;
$newInstance->setProtocolVersion($version);
return $newInstance;
}
/**
* Returns default HTTP protocol version to be used in case it is not explicitly set.
* @return string HTTP protocol version.
*/
protected function defaultProtocolVersion()
{
if (!empty($_SERVER['SERVER_PROTOCOL'])) {
return str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']);
}
return '1.0';
}
/**
* Returns the header collection.
* The header collection contains the currently registered HTTP headers.
* @return HeaderCollection the header collection
*/
public function getHeaderCollection()
{
if ($this->_headerCollection === null) {
$headerCollection = new HeaderCollection();
$headerCollection->fromArray($this->defaultHeaders());
$this->_headerCollection = $headerCollection;
}
return $this->_headerCollection;
}
/**
* Returns default message's headers, which should be present once [[headerCollection]] is instantiated.
* @return string[][] an associative array of the message's headers.
*/
protected function defaultHeaders()
{
return [];
}
/**
* Sets up message's headers at batch, removing any previously existing ones.
* @param string[][] $headers an associative array of the message's headers.
*/
public function setHeaders($headers)
{
$headerCollection = $this->getHeaderCollection();
$headerCollection->removeAll();
$headerCollection->fromArray($headers);
}
/**
* Sets up a particular message's header, removing any its previously existing value.
* @param string $name Case-insensitive header field name.
* @param string|string[] $value Header value(s).
*/
public function setHeader($name, $value)
{
$this->getHeaderCollection()->set($name, $value);
}
/**
* Appends the given value to the specified header.
* Existing values for the specified header will be maintained. The new
* value(s) will be appended to the existing list. If the header did not
* exist previously, it will be added.
* @param string $name Case-insensitive header field name to add.
* @param string|string[] $value Header value(s).
*/
public function addHeader($name, $value)
{
$this->getHeaderCollection()->add($name, $value);
}
/**
* Retrieves all message header values.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->getHeaders() as $name => $values) {
* echo $name . ": " . implode(", ", $values);
* }
*
* // Emit headers iteratively:
* foreach ($message->getHeaders() as $name => $values) {
* foreach ($values as $value) {
* header(sprintf('%s: %s', $name, $value), false);
* }
* }
*
* While header names are not case-sensitive, getHeaders() will preserve the
* exact case in which headers were originally specified.
*
* @return string[][] Returns an associative array of the message's headers. Each
* key MUST be a header name, and each value MUST be an array of strings
* for that header.
*/
public function getHeaders()
{
return $this->getHeaderCollection()->toArray();
}
/**
* Checks if a header exists by the given case-insensitive name.
*
* @param string $name Case-insensitive header field name.
* @return bool Returns true if any header names match the given header
* name using a case-insensitive string comparison. Returns false if
* no matching header name is found in the message.
*/
public function hasHeader($name)
{
return $this->getHeaderCollection()->has($name);
}
/**
* Retrieves a message header value by the given case-insensitive name.
*
* This method returns an array of all the header values of the given
* case-insensitive header name.
*
* If the header does not appear in the message, this method will return an
* empty array.
*
* @param string $name Case-insensitive header field name.
* @return string[] An array of string values as provided for the given
* header. If the header does not appear in the message, this method MUST
* return an empty array.
*/
public function getHeader($name)
{
return $this->getHeaderCollection()->get($name, [], false);
}
/**
* Retrieves a comma-separated string of the values for a single header.
*
* This method returns all of the header values of the given
* case-insensitive header name as a string concatenated together using
* a comma.
*
* NOTE: Not all header values may be appropriately represented using
* comma concatenation. For such headers, use getHeader() instead
* and supply your own delimiter when concatenating.
*
* If the header does not appear in the message, this method MUST return
* an empty string.
*
* @param string $name Case-insensitive header field name.
* @return string A string of values as provided for the given header
* concatenated together using a comma. If the header does not appear in
* the message, this method MUST return an empty string.
*/
public function getHeaderLine($name)
{
return implode(',', $this->getHeader($name));
}
/**
* Return an instance with the provided value replacing the specified header.
* This method retains the immutability of the message and returns an instance that has the
* new and/or updated header and value.
* @param string $name Case-insensitive header field name.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withHeader($name, $value)
{
$newInstance = clone $this;
$newInstance->setHeader($name, $value);
return $newInstance;
}
/**
* Return an instance with the specified header appended with the given value.
*
* Existing values for the specified header will be maintained. The new
* value(s) will be appended to the existing list. If the header did not
* exist previously, it will be added.
*
* This method retains the immutability of the message and returns an instance that has the
* new header and/or value.
*
* @param string $name Case-insensitive header field name to add.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withAddedHeader($name, $value)
{
$newInstance = clone $this;
$newInstance->addHeader($name, $value);
return $newInstance;
}
/**
* Return an instance without the specified header.
* Header resolution performed without case-sensitivity.
* This method retains the immutability of the message and returns an instance that removes
* the named header.
* @param string $name Case-insensitive header field name to remove.
* @return static
*/
public function withoutHeader($name)
{
$newInstance = clone $this;
$newInstance->getHeaderCollection()->remove($name);
return $newInstance;
}
/**
* Gets the body of the message.
* @return StreamInterface Returns the body as a stream.
*/
public function getBody()
{
if (!$this->_body instanceof StreamInterface) {
if ($this->_body === null) {
$body = $this->defaultBody();
} elseif ($this->_body instanceof \Closure) {
$body = call_user_func($this->_body, $this);
} else {
$body = $this->_body;
}
$this->_body = Instance::ensure($body, StreamInterface::class);
}
return $this->_body;
}
/**
* Specifies message body.
* @param StreamInterface|\Closure|array $body stream instance or its DI compatible configuration.
*/
public function setBody($body)
{
$this->_body = $body;
}
/**
* Return an instance with the specified message body.
* This method retains the immutability of the message and returns an instance that has the
* new body stream.
* @param StreamInterface $body Body.
* @return static
* @throws \InvalidArgumentException When the body is not valid.
*/
public function withBody(StreamInterface $body)
{
if ($this->getBody() === $body) {
return $this;
}
$newInstance = clone $this;
$newInstance->setBody($body);
return $newInstance;
}
/**
* Returns default message body to be used in case it is not explicitly set.
* @return StreamInterface default body instance.
*/
protected function defaultBody()
{
return new MemoryStream();
}
/**
* This method is called after the object is created by cloning an existing one.
*/
public function __clone()
{
$this->cloneHttpMessageInternals();
}
/**
* Ensures any internal object-type fields related to `MessageTrait` are cloned from their origins.
* In case actual trait owner implementing method [[__clone()]], it must invoke this method within it.
*/
private function cloneHttpMessageInternals()
{
if (is_object($this->_headerCollection)) {
$this->_headerCollection = clone $this->_headerCollection;
}
if (is_object($this->_body)) {
$this->_body = clone $this->_body;
}
}
}

241
framework/http/ResourceStream.php

@ -0,0 +1,241 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\http;
use Psr\Http\Message\StreamInterface;
use yii\base\BaseObject;
use yii\base\ErrorHandler;
/**
* ResourceStream wraps existing PHP stream resource, e.g. one opened by `fopen()`.
*
* Example:
*
* ```php
* $stream = new ResourceStream([
* 'resource' => tmpfile(),
* ]);
*
* $stream->write('some content...');
* $stream->close();
* ```
*
* Usage of this class make sense in case you already have an opened PHP stream from elsewhere and wish to wrap it into `StreamInterface`.
*
* > Note: closing this stream will close the resource associated with it, so it becomes invalid for usage elsewhere.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1.0
*/
class ResourceStream extends BaseObject implements StreamInterface
{
/**
* @var resource stream resource.
*/
public $resource;
/**
* @var array a resource metadata.
*/
private $_metadata;
/**
* Destructor.
* Closes the stream resource when destroyed.
*/
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function __toString()
{
// __toString cannot throw exception
// use trigger_error to bypass this limitation
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
ErrorHandler::convertExceptionToError($e);
return '';
}
}
/**
* {@inheritdoc}
*/
public function close()
{
if ($this->resource !== null && is_resource($this->resource)) {
fclose($this->resource);
$this->_metadata = null;
}
}
/**
* {@inheritdoc}
*/
public function detach()
{
if ($this->resource === null) {
return null;
}
$result = $this->resource;
$this->resource = null;
$this->_metadata = null;
return $result;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
$uri = $this->getMetadata('uri');
if (!empty($uri)) {
// clear the stat cache in case stream has a URI
clearstatcache(true, $uri);
}
$stats = fstat($this->resource);
if (isset($stats['size'])) {
return $stats['size'];
}
return null;
}
/**
* {@inheritdoc}
*/
public function tell()
{
$result = ftell($this->resource);
if ($result === false) {
throw new \RuntimeException('Unable to determine stream position');
}
return $result;
}
/**
* {@inheritdoc}
*/
public function eof()
{
return feof($this->resource);
}
/**
* {@inheritdoc}
*/
public function isSeekable()
{
return (bool)$this->getMetadata('seekable');
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if (fseek($this->resource, $offset, $whence) === -1) {
throw new \RuntimeException("Unable to seek to stream position '{$offset}' with whence '{$whence}'");
}
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->seek(0);
}
/**
* {@inheritdoc}
*/
public function isWritable()
{
$mode = $this->getMetadata('mode');
foreach (['w', 'c', 'a', 'x', 'r+'] as $key) {
if (strpos($mode, $key) !== false) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function write($string)
{
$result = fwrite($this->resource, $string);
if ($result === false) {
throw new \RuntimeException('Unable to write to stream');
}
return $result;
}
/**
* {@inheritdoc}
*/
public function isReadable()
{
$mode = $this->getMetadata('mode');
foreach (['r', 'w+', 'a+', 'c+', 'x+'] as $key) {
if (strpos($mode, $key) !== false) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function read($length)
{
$string = fread($this->resource, $length);
if ($string === false) {
throw new \RuntimeException('Unable to read from stream');
}
return $string;
}
/**
* {@inheritdoc}
*/
public function getContents()
{
$contents = stream_get_contents($this->resource);
if ($contents === false) {
throw new \RuntimeException('Unable to read stream contents');
}
return $contents;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
if ($this->_metadata === null) {
$this->_metadata = stream_get_meta_data($this->resource);
}
if ($key === null) {
return $this->_metadata;
}
return isset($this->_metadata[$key]) ? $this->_metadata[$key] : null;
}
}

190
framework/web/UploadedFile.php → framework/http/UploadedFile.php

@ -5,9 +5,13 @@
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
namespace yii\http;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use yii\base\BaseObject;
use yii\base\InvalidArgumentException;
use yii\di\Instance;
use yii\helpers\Html;
/**
@ -15,46 +19,59 @@ use yii\helpers\Html;
*
* You can call [[getInstance()]] to retrieve the instance of an uploaded file,
* and then use [[saveAs()]] to save it on the server.
* You may also query other information about the file, including [[name]],
* [[tempName]], [[type]], [[size]] and [[error]].
* You may also query other information about the file, including [[clientFilename]],
* [[tempFilename]], [[clientMediaType]], [[size]] and [[error]].
*
* For more details and usage information on UploadedFile, see the [guide article on handling uploads](guide:input-file-upload).
*
* @property string $clientFilename the original name of the file being uploaded.
* @property int $error an error code describing the status of this file uploading.
* @property int $size the actual size of the uploaded file in bytes.
* @property string $clientMediaType the MIME-type of the uploaded file (such as "image/gif").
* Since this MIME type is not checked on the server-side, do not take this value for granted.
* Instead, use [[\yii\helpers\FileHelper::getMimeType()]] to determine the exact MIME type.
* @property string $baseName Original file base name. This property is read-only.
* @property string $extension File extension. This property is read-only.
* @property bool $hasError Whether there is an error with the uploaded file. Check [[error]] for detailed
* error code information. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class UploadedFile extends BaseObject
class UploadedFile extends BaseObject implements UploadedFileInterface
{
/**
* @var string the original name of the file being uploaded
*/
public $name;
/**
* @var string the path of the uploaded file on the server.
* Note, this is a temporary file which will be automatically deleted by PHP
* after the current request is processed.
*/
public $tempName;
public $tempFilename;
/**
* @var string the original name of the file being uploaded
*/
private $_clientFilename;
/**
* @var string the MIME-type of the uploaded file (such as "image/gif").
* Since this MIME type is not checked on the server-side, do not take this value for granted.
* Instead, use [[\yii\helpers\FileHelper::getMimeType()]] to determine the exact MIME type.
*/
public $type;
private $_clientMediaType;
/**
* @var int the actual size of the uploaded file in bytes
*/
public $size;
private $_size;
/**
* @var int an error code describing the status of this file uploading.
* @see http://www.php.net/manual/en/features.file-upload.errors.php
*/
public $error;
private $_error;
/**
* @var StreamInterface stream for this file.
* @since 2.1.0
*/
private $_stream;
private static $_files;
@ -67,7 +84,7 @@ class UploadedFile extends BaseObject
*/
public function __toString()
{
return $this->name;
return $this->clientFilename;
}
/**
@ -160,9 +177,10 @@ class UploadedFile extends BaseObject
{
if ($this->error == UPLOAD_ERR_OK) {
if ($deleteTempFile) {
return move_uploaded_file($this->tempName, $file);
} elseif (is_uploaded_file($this->tempName)) {
return copy($this->tempName, $file);
$this->moveTo($file);
return true;
} elseif (is_uploaded_file($this->tempFilename)) {
return copy($this->tempFilename, $file);
}
}
return false;
@ -174,7 +192,7 @@ class UploadedFile extends BaseObject
public function getBaseName()
{
// https://github.com/yiisoft/yii2/issues/11012
$pathInfo = pathinfo('_' . $this->name, PATHINFO_FILENAME);
$pathInfo = pathinfo('_' . $this->getClientFilename(), PATHINFO_FILENAME);
return mb_substr($pathInfo, 1, mb_strlen($pathInfo, '8bit'), '8bit');
}
@ -183,7 +201,7 @@ class UploadedFile extends BaseObject
*/
public function getExtension()
{
return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
return strtolower(pathinfo($this->getClientFilename(), PATHINFO_EXTENSION));
}
/**
@ -229,12 +247,142 @@ class UploadedFile extends BaseObject
}
} elseif ((int) $errors !== UPLOAD_ERR_NO_FILE) {
self::$_files[$key] = [
'name' => $names,
'tempName' => $tempNames,
'type' => $types,
'clientFilename' => $names,
'tempFilename' => $tempNames,
'clientMediaType' => $types,
'size' => $sizes,
'error' => $errors,
];
}
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getStream()
{
if (!$this->_stream instanceof StreamInterface) {
if ($this->_stream === null) {
if ($this->getError() !== UPLOAD_ERR_OK) {
throw new \RuntimeException('Unable to create file stream due to upload error: ' . $this->getError());
}
$stream = [
'class' => FileStream::class,
'filename' => $this->tempFilename,
'mode' => 'r',
];
} elseif ($this->_stream instanceof \Closure) {
$stream = call_user_func($this->_stream, $this);
} else {
$stream = $this->_stream;
}
$this->_stream = Instance::ensure($stream, StreamInterface::class);
}
return $this->_stream;
}
/**
* @param StreamInterface|\Closure|array $stream stream instance or its DI compatible configuration.
* @since 2.1.0
*/
public function setStream($stream)
{
$this->_stream = $stream;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function moveTo($targetPath)
{
if ($this->error !== UPLOAD_ERR_OK) {
throw new \RuntimeException('Unable to move file due to upload error: ' . $this->error);
}
if (!move_uploaded_file($this->tempFilename, $targetPath)) {
throw new \RuntimeException('Unable to move uploaded file.');
}
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getSize()
{
return $this->_size;
}
/**
* @param int $size the actual size of the uploaded file in bytes.
* @throws InvalidArgumentException on invalid size given.
* @since 2.1.0
*/
public function setSize($size)
{
if (!is_int($size)) {
throw new InvalidArgumentException('"' . get_class($this) . '::$size" must be an integer.');
}
$this->_size = $size;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getError()
{
return $this->_error;
}
/**
* @param int $error upload error code.
* @throws InvalidArgumentException on invalid error given.
* @since 2.1.0
*/
public function setError($error)
{
if (!is_int($error)) {
throw new InvalidArgumentException('"' . get_class($this) . '::$error" must be an integer.');
}
$this->_error = $error;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getClientFilename()
{
return $this->_clientFilename;
}
/**
* @param string $clientFilename the original name of the file being uploaded.
* @since 2.1.0
*/
public function setClientFilename($clientFilename)
{
$this->_clientFilename = $clientFilename;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getClientMediaType()
{
return $this->_clientMediaType;
}
/**
* @param string $clientMediaType the MIME-type of the uploaded file (such as "image/gif").
* @since 2.1.0
*/
public function setClientMediaType($clientMediaType)
{
$this->_clientMediaType = $clientMediaType;
}
}

548
framework/http/Uri.php

@ -0,0 +1,548 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\http;
use Psr\Http\Message\UriInterface;
use yii\base\BaseObject;
use yii\base\ErrorHandler;
use yii\base\InvalidArgumentException;
/**
* Uri represents a URI.
*
* Create from components example:
*
* ```php
* $uri = new Uri([
* 'scheme' => 'http',
* 'user' => 'username',
* 'password' => 'password',
* 'host' => 'example.com',
* 'port' => 9090,
* 'path' => '/content/path',
* 'query' => 'foo=some',
* 'fragment' => 'anchor',
* ]);
* ```
*
* Create from string example:
*
* ```php
* $uri = new Uri(['string' => 'http://example.com?foo=some']);
* ```
*
* Create using PSR-7 syntax:
*
* ```php
* $uri = (new Uri())
* ->withScheme('http')
* ->withUserInfo('username', 'password')
* ->withHost('example.com')
* ->withPort(9090)
* ->withPath('/content/path')
* ->withQuery('foo=some')
* ->withFragment('anchor');
* ```
*
* @property string $scheme the scheme component of the URI.
* @property string $user
* @property string $password
* @property string $host the hostname to be used.
* @property int|null $port port number.
* @property string $path the path component of the URI
* @property string|array $query the query string or array of query parameters.
* @property string $fragment URI fragment.
* @property string $authority the authority component of the URI. This property is read-only.
* @property string $userInfo the user information component of the URI. This property is read-only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.1.0
*/
class Uri extends BaseObject implements UriInterface
{
/**
* @var string URI complete string.
*/
private $_string;
/**
* @var array URI components.
*/
private $_components;
/**
* @var array scheme default ports in format: `[scheme => port]`
*/
private static $defaultPorts = [
'http' => 80,
'https' => 443,
'ftp' => 21,
'gopher' => 70,
'nntp' => 119,
'news' => 119,
'telnet' => 23,
'tn3270' => 23,
'imap' => 143,
'pop' => 110,
'ldap' => 389,
];
/**
* @return string URI string representation.
*/
public function getString()
{
if ($this->_string !== null) {
return $this->_string;
}
if ($this->_components === null) {
return '';
}
return $this->composeUri($this->_components);
}
/**
* @param string $string URI full string.
*/
public function setString($string)
{
$this->_string = $string;
$this->_components = null;
}
/**
* {@inheritdoc}
*/
public function getScheme()
{
return $this->getComponent('scheme');
}
/**
* Sets up the scheme component of the URI.
* @param string $scheme the scheme.
*/
public function setScheme($scheme)
{
$this->setComponent('scheme', $scheme);
}
/**
* {@inheritdoc}
*/
public function withScheme($scheme)
{
if ($this->getScheme() === $scheme) {
return $this;
}
$newInstance = clone $this;
$newInstance->setScheme($scheme);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getAuthority()
{
return $this->composeAuthority($this->getComponents());
}
/**
* {@inheritdoc}
*/
public function getUserInfo()
{
return $this->composeUserInfo($this->getComponents());
}
/**
* {@inheritdoc}
*/
public function getHost()
{
return $this->getComponent('host', '');
}
/**
* Specifies hostname.
* @param string $host the hostname to be used.
*/
public function setHost($host)
{
$this->setComponent('host', $host);
}
/**
* {@inheritdoc}
*/
public function withHost($host)
{
if ($this->getHost() === $host) {
return $this;
}
$newInstance = clone $this;
$newInstance->setHost($host);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getPort()
{
return $this->getComponent('port');
}
/**
* Specifies port.
* @param int|null $port The port to be used; a `null` value removes the port information.
*/
public function setPort($port)
{
if ($port !== null) {
if (!is_int($port)) {
throw new InvalidArgumentException('URI port must be an integer.');
}
}
$this->setComponent('port', $port);
}
/**
* {@inheritdoc}
*/
public function withPort($port)
{
if ($this->getPort() === $port) {
return $this;
}
$newInstance = clone $this;
$newInstance->setPort($port);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->getComponent('path', '');
}
/**
* Specifies path component of the URI
* @param string $path the path to be used.
*/
public function setPath($path)
{
$this->setComponent('path', $path);
}
/**
* {@inheritdoc}
*/
public function withPath($path)
{
if ($this->getPath() === $path) {
return $this;
}
$newInstance = clone $this;
$newInstance->setPath($path);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getQuery()
{
return $this->getComponent('query', '');
}
/**
* Specifies query string.
* @param string|array|object $query the query string or array of query parameters.
*/
public function setQuery($query)
{
if (is_array($query) || is_object($query)) {
$query = http_build_query($query);
}
$this->setComponent('query', $query);
}
/**
* {@inheritdoc}
*/
public function withQuery($query)
{
if ($this->getQuery() === $query) {
return $this;
}
$newInstance = clone $this;
$newInstance->setQuery($query);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getFragment()
{
return $this->getComponent('fragment', '');
}
/**
* Specifies URI fragment.
* @param string $fragment the fragment to be used.
*/
public function setFragment($fragment)
{
$this->setComponent('fragment', $fragment);
}
/**
* {@inheritdoc}
*/
public function withFragment($fragment)
{
if ($this->getFragment() === $fragment) {
return $this;
}
$newInstance = clone $this;
$newInstance->setFragment($fragment);
return $newInstance;
}
/**
* @return string the user name to use for authority.
*/
public function getUser()
{
return $this->getComponent('user', '');
}
/**
* @param string $user the user name to use for authority.
*/
public function setUser($user)
{
$this->setComponent('user', $user);
}
/**
* @return string password associated with [[user]].
*/
public function getPassword()
{
return $this->getComponent('pass', '');
}
/**
* @param string $password password associated with [[user]].
*/
public function setPassword($password)
{
$this->setComponent('pass', $password);
}
/**
* {@inheritdoc}
*/
public function withUserInfo($user, $password = null)
{
$userInfo = $user;
if ($password != '') {
$userInfo .= ':' . $password;
}
if ($userInfo === $this->composeUserInfo($this->getComponents())) {
return $this;
}
$newInstance = clone $this;
$newInstance->setUser($user);
$newInstance->setPassword($password);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
// __toString cannot throw exception
// use trigger_error to bypass this limitation
try {
return $this->getString();
} catch (\Exception $e) {
ErrorHandler::convertExceptionToError($e);
return '';
}
}
/**
* Sets up particular URI component.
* @param string $name URI component name.
* @param mixed $value URI component value.
*/
protected function setComponent($name, $value)
{
if ($this->_string !== null) {
$this->_components = $this->parseUri($this->_string);
}
$this->_components[$name] = $value;
$this->_string = null;
}
/**
* @param string $name URI component name.
* @param mixed $default default value, which should be returned in case component is not exist.
* @return mixed URI component value.
*/
protected function getComponent($name, $default = null)
{
$components = $this->getComponents();
if (isset($components[$name])) {
return $components[$name];
}
return $default;
}
/**
* Returns URI components for this instance as an associative array.
* @return array URI components in format: `[name => value]`
*/
protected function getComponents()
{
if ($this->_components === null) {
if ($this->_string === null) {
return [];
}
$this->_components = $this->parseUri($this->_string);
}
return $this->_components;
}
/**
* Parses a URI and returns an associative array containing any of the various components of the URI
* that are present.
* @param string $uri the URI string to parse.
* @return array URI components.
*/
protected function parseUri($uri)
{
$components = parse_url($uri);
if ($components === false) {
throw new InvalidArgumentException("URI string '{$uri}' is not a valid URI.");
}
return $components;
}
/**
* Composes URI string from given components.
* @param array $components URI components.
* @return string URI full string.
*/
protected function composeUri(array $components)
{
$uri = '';
$scheme = empty($components['scheme']) ? '' : $components['scheme'];
if ($scheme !== '') {
$uri .= $components['scheme'] . ':';
}
$authority = $this->composeAuthority($components);
if ($authority !== '' || $scheme === 'file') {
// authority separator is added even when the authority is missing/empty for the "file" scheme
// while `file:///myfile` and `file:/myfile` are equivalent according to RFC 3986, `file:///` is more common
// PHP functions and Chrome, for example, use this format
$uri .= '//' . $authority;
}
if (!empty($components['path'])) {
$uri .= $components['path'];
}
if (!empty($components['query'])) {
$uri .= '?' . $components['query'];
}
if (!empty($components['fragment'])) {
$uri .= '#' . $components['fragment'];
}
return $uri;
}
/**
* @param array $components URI components.
* @return string user info string.
*/
protected function composeUserInfo(array $components)
{
$userInfo = '';
if (!empty($components['user'])) {
$userInfo .= $components['user'];
}
if (!empty($components['pass'])) {
$userInfo .= ':' . $components['pass'];
}
return $userInfo;
}
/**
* @param array $components URI components.
* @return string authority string.
*/
protected function composeAuthority(array $components)
{
$authority = '';
$scheme = empty($components['scheme']) ? '' : $components['scheme'];
if (empty($components['host'])) {
if (in_array($scheme, ['http', 'https'], true)) {
$authority = 'localhost';
}
} else {
$authority = $components['host'];
}
if (!empty($components['port']) && !$this->isDefaultPort($scheme, $components['port'])) {
$authority .= ':' . $components['port'];
}
$userInfo = $this->composeUserInfo($components);
if ($userInfo !== '') {
$authority = $userInfo . '@' . $authority;
}
return $authority;
}
/**
* Checks whether specified port is default one for the specified scheme.
* @param string $scheme scheme.
* @param int $port port number.
* @return bool whether specified port is default for specified scheme
*/
protected function isDefaultPort($scheme, $port)
{
if (!isset(self::$defaultPorts[$scheme])) {
return false;
}
return self::$defaultPorts[$scheme] == $port;
}
}

11
framework/rest/Serializer.php

@ -237,12 +237,11 @@ class Serializer extends Component
$links[] = "<$url>; rel=$rel";
}
$this->response->getHeaders()
->set($this->totalCountHeader, $pagination->totalCount)
->set($this->pageCountHeader, $pagination->getPageCount())
->set($this->currentPageHeader, $pagination->getPage() + 1)
->set($this->perPageHeader, $pagination->pageSize)
->set('Link', implode(', ', $links));
$this->response->setHeader($this->totalCountHeader, $pagination->totalCount);
$this->response->setHeader($this->pageCountHeader, $pagination->getPageCount());
$this->response->setHeader($this->currentPageHeader, $pagination->getPage() + 1);
$this->response->setHeader($this->perPageHeader, $pagination->pageSize);
$this->response->setHeader('Link', implode(', ', $links));
}
/**

26
framework/validators/FileValidator.php

@ -12,7 +12,7 @@ use yii\helpers\FileHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
use yii\web\UploadedFile;
use yii\http\UploadedFile;
/**
* FileValidator verifies if an attribute is receiving a valid uploaded file.
@ -224,7 +224,7 @@ class FileValidator extends Validator
*/
protected function validateValue($value)
{
if (!$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE) {
if (!$value instanceof UploadedFile || $value->getError() == UPLOAD_ERR_NO_FILE) {
return [$this->uploadRequired, []];
}
@ -234,7 +234,7 @@ class FileValidator extends Validator
return [
$this->tooBig,
[
'file' => $value->name,
'file' => $value->getClientFilename(),
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
],
@ -243,35 +243,35 @@ class FileValidator extends Validator
return [
$this->tooSmall,
[
'file' => $value->name,
'file' => $value->getClientFilename(),
'limit' => $this->minSize,
'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize),
],
];
} elseif (!empty($this->extensions) && !$this->validateExtension($value)) {
return [$this->wrongExtension, ['file' => $value->name, 'extensions' => implode(', ', $this->extensions)]];
return [$this->wrongExtension, ['file' => $value->getClientFilename(), 'extensions' => implode(', ', $this->extensions)]];
} elseif (!empty($this->mimeTypes) && !$this->validateMimeType($value)) {
return [$this->wrongMimeType, ['file' => $value->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
return [$this->wrongMimeType, ['file' => $value->getClientFilename(), 'mimeTypes' => implode(', ', $this->mimeTypes)]];
}
return null;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
return [$this->tooBig, [
'file' => $value->name,
'file' => $value->getClientFilename(),
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
]];
case UPLOAD_ERR_PARTIAL:
Yii::warning('File was only partially uploaded: ' . $value->name, __METHOD__);
Yii::warning('File was only partially uploaded: ' . $value->getClientFilename(), __METHOD__);
break;
case UPLOAD_ERR_NO_TMP_DIR:
Yii::warning('Missing the temporary folder to store the uploaded file: ' . $value->name, __METHOD__);
Yii::warning('Missing the temporary folder to store the uploaded file: ' . $value->getClientFilename(), __METHOD__);
break;
case UPLOAD_ERR_CANT_WRITE:
Yii::warning('Failed to write the uploaded file to disk: ' . $value->name, __METHOD__);
Yii::warning('Failed to write the uploaded file to disk: ' . $value->getClientFilename(), __METHOD__);
break;
case UPLOAD_ERR_EXTENSION:
Yii::warning('File upload was stopped by some PHP extension: ' . $value->name, __METHOD__);
Yii::warning('File upload was stopped by some PHP extension: ' . $value->getClientFilename(), __METHOD__);
break;
default:
break;
@ -353,7 +353,7 @@ class FileValidator extends Validator
$extension = mb_strtolower($file->extension, 'UTF-8');
if ($this->checkExtensionByMimeType) {
$mimeType = FileHelper::getMimeType($file->tempName, null, false);
$mimeType = FileHelper::getMimeType($file->tempFilename, null, false);
if ($mimeType === null) {
return false;
}
@ -476,7 +476,7 @@ class FileValidator extends Validator
*/
protected function validateMimeType($file)
{
$fileMimeType = FileHelper::getMimeType($file->tempName);
$fileMimeType = FileHelper::getMimeType($file->tempFilename);
foreach ($this->mimeTypes as $mimeType) {
if ($mimeType === $fileMimeType) {

14
framework/validators/ImageValidator.php

@ -8,7 +8,7 @@
namespace yii\validators;
use Yii;
use yii\web\UploadedFile;
use yii\http\UploadedFile;
/**
* ImageValidator verifies if an attribute is receiving a valid image.
@ -130,30 +130,30 @@ class ImageValidator extends FileValidator
*/
protected function validateImage($image)
{
if (false === ($imageInfo = getimagesize($image->tempName))) {
if (false === ($imageInfo = getimagesize($image->tempFilename))) {
return [$this->notImage, ['file' => $image->name]];
}
[$width, $height] = $imageInfo;
if ($width == 0 || $height == 0) {
return [$this->notImage, ['file' => $image->name]];
return [$this->notImage, ['file' => $image->getClientFilename()]];
}
if ($this->minWidth !== null && $width < $this->minWidth) {
return [$this->underWidth, ['file' => $image->name, 'limit' => $this->minWidth]];
return [$this->underWidth, ['file' => $image->getClientFilename(), 'limit' => $this->minWidth]];
}
if ($this->minHeight !== null && $height < $this->minHeight) {
return [$this->underHeight, ['file' => $image->name, 'limit' => $this->minHeight]];
return [$this->underHeight, ['file' => $image->getClientFilename(), 'limit' => $this->minHeight]];
}
if ($this->maxWidth !== null && $width > $this->maxWidth) {
return [$this->overWidth, ['file' => $image->name, 'limit' => $this->maxWidth]];
return [$this->overWidth, ['file' => $image->getClientFilename(), 'limit' => $this->maxWidth]];
}
if ($this->maxHeight !== null && $height > $this->maxHeight) {
return [$this->overHeight, ['file' => $image->name, 'limit' => $this->maxHeight]];
return [$this->overHeight, ['file' => $image->getClientFilename(), 'limit' => $this->maxHeight]];
}
return null;

4
framework/web/ErrorHandler.php

@ -82,9 +82,9 @@ class ErrorHandler extends \yii\base\ErrorHandler
// reset parameters of response to avoid interference with partially created response data
// in case the error occurred while sending the response.
$response->isSent = false;
$response->stream = null;
$response->bodyRange = null;
$response->data = null;
$response->content = null;
$response->setBody(null);
} else {
$response = new Response();
}

2
framework/web/HtmlResponseFormatter.php

@ -34,7 +34,7 @@ class HtmlResponseFormatter extends Component implements ResponseFormatterInterf
if (stripos($this->contentType, 'charset') === false) {
$this->contentType .= '; charset=' . $response->charset;
}
$response->getHeaders()->set('Content-Type', $this->contentType);
$response->setHeader('Content-Type', $this->contentType);
if ($response->data !== null) {
$response->content = $response->data;
}

4
framework/web/JsonResponseFormatter.php

@ -80,7 +80,7 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf
*/
protected function formatJson($response)
{
$response->getHeaders()->set('Content-Type', 'application/json; charset=UTF-8');
$response->setHeader('Content-Type', 'application/json; charset=UTF-8');
if ($response->data !== null) {
$options = $this->encodeOptions;
if ($this->prettyPrint) {
@ -96,7 +96,7 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf
*/
protected function formatJsonp($response)
{
$response->getHeaders()->set('Content-Type', 'application/javascript; charset=UTF-8');
$response->setHeader('Content-Type', 'application/javascript; charset=UTF-8');
if (is_array($response->data) && isset($response->data['data'], $response->data['callback'])) {
$response->content = sprintf('%s(%s);', $response->data['callback'], Json::htmlEncode($response->data['data']));
} elseif ($response->data !== null) {

2
framework/web/MultipartFormDataParser.php

@ -42,7 +42,7 @@ use yii\helpers\StringHelper;
* Usage example:
*
* ```php
* use yii\web\UploadedFile;
* use yii\http\UploadedFile;
*
* $restRequestData = Yii::$app->request->getBodyParams();
* $uploadedFile = UploadedFile::getInstancesByName('photo');

231
framework/web/Request.php

@ -7,8 +7,17 @@
namespace yii\web;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use Yii;
use yii\base\InvalidConfigException;
use yii\di\Instance;
use yii\http\Cookie;
use yii\http\CookieCollection;
use yii\http\FileStream;
use yii\http\MessageTrait;
use yii\http\Uri;
/**
* The web Request class represents an HTTP request
@ -41,7 +50,6 @@ use yii\base\InvalidConfigException;
* @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned
* if no such header is sent. This property is read-only.
* @property array $eTags The entity tags. This property is read-only.
* @property HeaderCollection $headers The header collection. This property is read-only.
* @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
* (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
* [[getHostInfo()]] for security related notes on this property.
@ -60,7 +68,9 @@ use yii\base\InvalidConfigException;
* @property bool $isSecureConnection If the request is sent via secure channel (https). This property is
* read-only.
* @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is
* turned into upper case. This property is read-only.
* turned into upper case.
* @property UriInterface $uri the URI instance.
* @property mixed $requestTarget the message's request target.
* @property string $pathInfo Part of the request URL that is after the entry script and before the question
* mark. Note, the returned path info is already URL-decoded.
* @property int $port Port number for insecure requests.
@ -84,8 +94,10 @@ use yii\base\InvalidConfigException;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Request extends \yii\base\Request
class Request extends \yii\base\Request implements RequestInterface
{
use MessageTrait;
/**
* The name of the HTTP header for sending CSRF token.
*/
@ -170,9 +182,17 @@ class Request extends \yii\base\Request
*/
private $_cookies;
/**
* @var HeaderCollection Collection of request headers.
* @var string the HTTP method of the request.
*/
private $_method;
/**
* @var UriInterface the URI instance associated with request.
*/
private $_uri;
/**
* @var mixed the message's request target.
*/
private $_headers;
private $_requestTarget;
/**
@ -197,56 +217,160 @@ class Request extends \yii\base\Request
}
/**
* Returns the header collection.
* The header collection contains incoming HTTP headers.
* @return HeaderCollection the header collection
* Returns default message's headers, which should be present once [[headerCollection]] is instantiated.
* @return string[][] an associative array of the message's headers.
*/
public function getHeaders()
protected function defaultHeaders()
{
if ($this->_headers === null) {
$this->_headers = new HeaderCollection();
if (function_exists('getallheaders')) {
$headers = getallheaders();
} elseif (function_exists('http_get_request_headers')) {
$headers = http_get_request_headers();
} else {
foreach ($_SERVER as $name => $value) {
if (strncmp($name, 'HTTP_', 5) === 0) {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
$this->_headers->add($name, $value);
}
if (function_exists('getallheaders')) {
$headers = getallheaders();
} elseif (function_exists('http_get_request_headers')) {
$headers = http_get_request_headers();
} else {
$headers = [];
foreach ($_SERVER as $name => $value) {
if (strncmp($name, 'HTTP_', 5) === 0) {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
$headers[$name] = $value;
}
return $this->_headers;
}
foreach ($headers as $name => $value) {
$this->_headers->add($name, $value);
}
}
return $this->_headers;
foreach ($headers as $name => $value) {
$headers[strtolower($name)] = (array)$value;
}
return $headers;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getRequestTarget()
{
if ($this->_requestTarget === null) {
$this->_requestTarget = $this->getUri()->__toString();
}
return $this->_requestTarget;
}
/**
* Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
* @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
* The value returned is turned into upper case.
* Specifies the message's request target
* @param mixed $requestTarget the message's request target.
* @since 2.1.0
*/
public function setRequestTarget($requestTarget)
{
$this->_requestTarget = $requestTarget;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function withRequestTarget($requestTarget)
{
if ($this->getRequestTarget() === $requestTarget) {
return $this;
}
$newInstance = clone $this;
$newInstance->setRequestTarget($requestTarget);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getMethod()
{
if (isset($_POST[$this->methodParam])) {
return strtoupper($_POST[$this->methodParam]);
if ($this->_method === null) {
if (isset($_POST[$this->methodParam])) {
$this->_method = $_POST[$this->methodParam];
} elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$this->_method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
} elseif (isset($_SERVER['REQUEST_METHOD'])) {
$this->_method = $_SERVER['REQUEST_METHOD'];
} else {
$this->_method = 'GET';
}
}
return $this->_method;
}
/**
* Specifies request HTTP method.
* @param string $method case-sensitive HTTP method.
* @since 2.1.0
*/
public function setMethod($method)
{
$this->_method = $method;
}
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function withMethod($method)
{
if ($this->getMethod() === $method) {
return $this;
}
if (isset($_SERVER['REQUEST_METHOD'])) {
return strtoupper($_SERVER['REQUEST_METHOD']);
$newInstance = clone $this;
$newInstance->setMethod($method);
return $newInstance;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function getUri()
{
if (!$this->_uri instanceof UriInterface) {
if ($this->_uri === null) {
$uri = new Uri(['string' => $this->getAbsoluteUrl()]);
} elseif ($this->_uri instanceof \Closure) {
$uri = call_user_func($this->_uri, $this);
} else {
$uri = $this->_uri;
}
$this->_uri = Instance::ensure($uri, UriInterface::class);
}
return $this->_uri;
}
/**
* Specifies the URI instance.
* @param UriInterface|\Closure|array $uri URI instance or its DI compatible configuration.
* @since 2.1.0
*/
public function setUri($uri)
{
$this->_uri = $uri;
}
/**
* {@inheritdoc}
* @since 2.1.0
*/
public function withUri(UriInterface $uri, $preserveHost = false)
{
if ($this->getUri() === $uri) {
return $this;
}
return 'GET';
$newInstance = clone $this;
$newInstance->setUri($uri);
if (!$preserveHost) {
return $newInstance->withHeader('host', $uri->getHost());
}
return $newInstance;
}
/**
@ -344,6 +468,18 @@ class Request extends \yii\base\Request
(stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false);
}
/**
* Returns default message body to be used in case it is not explicitly set.
* @return StreamInterface default body instance.
*/
protected function defaultBody()
{
return new FileStream([
'filename' => 'php://input',
'mode' => 'r',
]);
}
private $_rawBody;
/**
@ -353,7 +489,7 @@ class Request extends \yii\base\Request
public function getRawBody()
{
if ($this->_rawBody === null) {
$this->_rawBody = file_get_contents('php://input');
$this->_rawBody = $this->getBody()->__toString();
}
return $this->_rawBody;
@ -925,7 +1061,7 @@ class Request extends \yii\base\Request
*/
public function getOrigin()
{
return $this->getHeaders()->get('origin');
return $this->getHeaderLine('origin');
}
/**
@ -1400,7 +1536,7 @@ class Request extends \yii\base\Request
*/
public function getCsrfTokenFromHeader()
{
return $this->headers->get(static::CSRF_HEADER);
return $this->getHeaderLine(static::CSRF_HEADER);
}
/**
@ -1440,6 +1576,7 @@ class Request extends \yii\base\Request
return true;
}
$trueToken = $this->getCsrfToken();
if ($clientSuppliedToken !== null) {
@ -1467,4 +1604,18 @@ class Request extends \yii\base\Request
return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
}
/**
* {@inheritdoc}
*/
public function __clone()
{
parent::__clone();
$this->cloneHttpMessageInternals();
if (is_object($this->_cookies)) {
$this->_cookies = clone $this->_cookies;
}
}
}

283
framework/web/Response.php

@ -7,14 +7,19 @@
namespace yii\web;
use Psr\Http\Message\ResponseInterface;
use Yii;
use yii\base\InvalidArgumentException;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\helpers\FileHelper;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
use yii\helpers\Url;
use yii\http\CookieCollection;
use yii\http\HeaderCollection;
use yii\http\MemoryStream;
use yii\http\MessageTrait;
use yii\http\ResourceStream;
/**
* The web Response class represents an HTTP response
@ -40,7 +45,6 @@ use yii\helpers\Url;
*
* @property CookieCollection $cookies The cookie collection. This property is read-only.
* @property string $downloadHeaders The attachment file name. This property is write-only.
* @property HeaderCollection $headers The header collection. This property is read-only.
* @property bool $isClientError Whether this response indicates a client error. This property is read-only.
* @property bool $isEmpty Whether this response is empty. This property is read-only.
* @property bool $isForbidden Whether this response indicates the current request is forbidden. This property
@ -55,13 +59,16 @@ use yii\helpers\Url;
* @property bool $isSuccessful Whether this response is successful. This property is read-only.
* @property int $statusCode The HTTP status code to send with the response.
* @property \Exception|\Error $statusCodeByException The exception object. This property is write-only.
* @property string $content body content string.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Response extends \yii\base\Response
class Response extends \yii\base\Response implements ResponseInterface
{
use MessageTrait;
/**
* @event ResponseEvent an event that is triggered at the beginning of [[send()]].
*/
@ -129,17 +136,10 @@ class Response extends \yii\base\Response
*/
public $data;
/**
* @var string the response content. When [[data]] is not null, it will be converted into [[content]]
* according to [[format]] when the response is being sent out.
* @see data
* @var array the stream range to be applied on [[send()]]. This should be an array of the begin position and the end position.
* Note that when this property is set, the [[data]] property will be ignored by [[send()]].
*/
public $content;
/**
* @var resource|array the stream to be sent. This can be a stream handle or an array of stream handle,
* the begin position and the end position. Note that when this property is set, the [[data]] and [[content]]
* properties will be ignored by [[send()]].
*/
public $stream;
public $bodyRange;
/**
* @var string the charset of the text response. If not set, it will use
* the value of [[Application::charset]].
@ -149,12 +149,7 @@ class Response extends \yii\base\Response
* @var string the HTTP status description that comes together with the status code.
* @see httpStatuses
*/
public $statusText = 'OK';
/**
* @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
* or '1.1' if that is not available.
*/
public $version;
public $reasonPhrase = 'OK';
/**
* @var bool whether the response has been sent. If this is true, calling [[send()]] will do nothing.
*/
@ -236,10 +231,6 @@ class Response extends \yii\base\Response
* @var int the HTTP status code to send with the response.
*/
private $_statusCode = 200;
/**
* @var HeaderCollection
*/
private $_headers;
/**
@ -247,13 +238,6 @@ class Response extends \yii\base\Response
*/
public function init()
{
if ($this->version === null) {
if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0') {
$this->version = '1.0';
} else {
$this->version = '1.1';
}
}
if ($this->charset === null) {
$this->charset = Yii::$app->charset;
}
@ -261,7 +245,7 @@ class Response extends \yii\base\Response
}
/**
* @return int the HTTP status code to send with the response.
* {@inheritdoc}
*/
public function getStatusCode()
{
@ -271,29 +255,51 @@ class Response extends \yii\base\Response
/**
* Sets the response status code.
* This method will set the corresponding status text if `$text` is null.
* @param int $value the status code
* @param string $text the status text. If not set, it will be set automatically based on the status code.
* @param int $code the status code
* @param string $reasonPhrase the status text. If not set, it will be set automatically based on the status code.
* @throws InvalidArgumentException if the status code is invalid.
* @return $this the response object itself
*/
public function setStatusCode($value, $text = null)
public function setStatusCode($code, $reasonPhrase = null)
{
if ($value === null) {
$value = 200;
if ($code === null) {
$code = 200;
}
$this->_statusCode = (int) $value;
$this->_statusCode = (int) $code;
if ($this->getIsInvalid()) {
throw new InvalidArgumentException("The HTTP status code is invalid: $value");
throw new InvalidArgumentException("The HTTP status code is invalid: $code");
}
if ($text === null) {
$this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
if (empty($reasonPhrase)) {
$this->reasonPhrase = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
} else {
$this->statusText = $text;
$this->reasonPhrase = $reasonPhrase;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function withStatus($code, $reasonPhrase = '')
{
if ($this->getStatusCode() === $code && $this->reasonPhrase === $reasonPhrase) {
return $this;
}
$newInstance = clone $this;
$newInstance->setStatusCode($code, $reasonPhrase);
return $newInstance;
}
/**
* {@inheritdoc}
*/
public function getReasonPhrase()
{
return $this->reasonPhrase;
}
/**
* Sets the response status code based on the exception.
* @param \Exception|\Error $e the exception object.
* @throws InvalidArgumentException if the status code is invalid.
@ -311,16 +317,23 @@ class Response extends \yii\base\Response
}
/**
* Returns the header collection.
* The header collection contains the currently registered HTTP headers.
* @return HeaderCollection the header collection
* @return string body content string.
* @since 2.1.0
*/
public function getHeaders()
public function getContent()
{
if ($this->_headers === null) {
$this->_headers = new HeaderCollection();
}
return $this->_headers;
return $this->getBody()->__toString();
}
/**
* @param string $content body content string.
* @since 2.1.0
*/
public function setContent($content)
{
$body = new MemoryStream();
$body->write($content);
$this->setBody($body);
}
/**
@ -345,14 +358,14 @@ class Response extends \yii\base\Response
*/
public function clear()
{
$this->_headers = null;
$this->_headerCollection = null;
$this->_cookies = null;
$this->_statusCode = 200;
$this->statusText = 'OK';
$this->reasonPhrase = 'OK';
$this->data = null;
$this->stream = null;
$this->content = null;
$this->bodyRange = null;
$this->isSent = false;
$this->setBody(null);
}
/**
@ -363,7 +376,7 @@ class Response extends \yii\base\Response
if (headers_sent()) {
return;
}
if ($this->_headers) {
if ($this->_headerCollection) {
$headers = $this->getHeaders();
foreach ($headers as $name => $values) {
$name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
@ -376,7 +389,8 @@ class Response extends \yii\base\Response
}
}
$statusCode = $this->getStatusCode();
header("HTTP/{$this->version} {$statusCode} {$this->statusText}");
$protocolVersion = $this->getProtocolVersion();
header("HTTP/{$protocolVersion} {$statusCode} {$this->reasonPhrase}");
$this->sendCookies();
}
@ -409,32 +423,40 @@ class Response extends \yii\base\Response
*/
protected function sendContent()
{
if ($this->stream === null) {
echo $this->content;
return;
$body = $this->getBody();
if (!$body->isReadable()) {
throw new \RuntimeException('Unable to send content: body stream is not readable.');
}
set_time_limit(0); // Reset time limit for big files
$chunkSize = 8 * 1024 * 1024; // 8MB per chunk
if (is_array($this->stream)) {
[$handle, $begin, $end] = $this->stream;
fseek($handle, $begin);
while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
if (is_array($this->bodyRange)) {
[$begin, $end] = $this->bodyRange;
if (!$body->isSeekable()) {
throw new \RuntimeException('Unable to send content in range: body stream is not seekable.');
}
$body->seek($begin);
while (!$body->eof() && ($pos = $body->tell()) <= $end) {
if ($pos + $chunkSize > $end) {
$chunkSize = $end - $pos + 1;
}
echo fread($handle, $chunkSize);
echo $body->read($chunkSize);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}
fclose($handle);
$body->close();
} else {
while (!feof($this->stream)) {
echo fread($this->stream, $chunkSize);
flush();
if ($body->isSeekable()) {
$body->seek(0);
}
while (!$body->eof()) {
echo $body->read($chunkSize);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}
fclose($this->stream);
$body->close();
return;
}
}
@ -507,31 +529,30 @@ class Response extends \yii\base\Response
*/
public function sendContentAsFile($content, $attachmentName, $options = [])
{
$headers = $this->getHeaders();
$contentLength = StringHelper::byteLength($content);
$range = $this->getHttpRange($contentLength);
if ($range === false) {
$headers->set('Content-Range', "bytes */$contentLength");
$this->setHeader('Content-Range', "bytes */$contentLength");
throw new RangeNotSatisfiableHttpException();
}
[$begin, $end] = $range;
$body = new MemoryStream();
if ($begin != 0 || $end != $contentLength - 1) {
$this->setStatusCode(206);
$headers->set('Content-Range', "bytes $begin-$end/$contentLength");
$this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1);
$this->setHeader('Content-Range', "bytes $begin-$end/$contentLength");
$body->write(StringHelper::byteSubstr($content, $begin, $end - $begin + 1));
} else {
$this->setStatusCode(200);
$this->content = $content;
$body->write($content);
}
$mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
$this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
$this->format = self::FORMAT_RAW;
$this->setBody($body);
return $this;
}
@ -558,7 +579,6 @@ class Response extends \yii\base\Response
*/
public function sendStreamAsFile($handle, $attachmentName, $options = [])
{
$headers = $this->getHeaders();
if (isset($options['fileSize'])) {
$fileSize = $options['fileSize'];
} else {
@ -568,14 +588,14 @@ class Response extends \yii\base\Response
$range = $this->getHttpRange($fileSize);
if ($range === false) {
$headers->set('Content-Range', "bytes */$fileSize");
$this->setHeader('Content-Range', "bytes */$fileSize");
throw new RangeNotSatisfiableHttpException();
}
[$begin, $end] = $range;
if ($begin != 0 || $end != $fileSize - 1) {
$this->setStatusCode(206);
$headers->set('Content-Range', "bytes $begin-$end/$fileSize");
$this->setHeader('Content-Range', "bytes $begin-$end/$fileSize");
} else {
$this->setStatusCode(200);
}
@ -584,8 +604,12 @@ class Response extends \yii\base\Response
$this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
$this->format = self::FORMAT_RAW;
$this->stream = [$handle, $begin, $end];
$this->bodyRange = [$begin, $end];
$body = new ResourceStream();
$body->resource = $handle;
$this->setBody($body);
return $this;
}
@ -600,21 +624,28 @@ class Response extends \yii\base\Response
*/
public function setDownloadHeaders($attachmentName, $mimeType = null, $inline = false, $contentLength = null)
{
$headers = $this->getHeaders();
$disposition = $inline ? 'inline' : 'attachment';
$headers->setDefault('Pragma', 'public')
->setDefault('Accept-Ranges', 'bytes')
->setDefault('Expires', '0')
->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->setDefault('Content-Disposition', $this->getDispositionHeaderValue($disposition, $attachmentName));
$headers = [
'Pragma' => 'public',
'Accept-Ranges' => 'bytes',
'Expires' => '0',
'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
'Content-Disposition' => $this->getDispositionHeaderValue($disposition, $attachmentName),
];
if ($mimeType !== null) {
$headers->setDefault('Content-Type', $mimeType);
$headers['Content-Type'] = $mimeType;
}
if ($contentLength !== null) {
$headers->setDefault('Content-Length', $contentLength);
$headers['Content-Length'] = $contentLength;
}
foreach ($headers as $name => $value) {
if (!$this->hasHeader($name)) {
$this->setHeader($name, $value);
}
}
return $this;
@ -728,10 +759,18 @@ class Response extends \yii\base\Response
}
$disposition = empty($options['inline']) ? 'attachment' : 'inline';
$this->getHeaders()
->setDefault($xHeader, $filePath)
->setDefault('Content-Type', $mimeType)
->setDefault('Content-Disposition', $this->getDispositionHeaderValue($disposition, $attachmentName));
$headers = [
$xHeader => $filePath,
'Content-Type' => $mimeType,
'Content-Disposition' => $this->getDispositionHeaderValue($disposition, $attachmentName),
];
foreach ($headers as $name => $value) {
if (!$this->hasHeader($name)) {
$this->setHeader($name, $value);
}
}
$this->format = self::FORMAT_RAW;
@ -843,20 +882,20 @@ class Response extends \yii\base\Response
if ($checkAjax) {
if (Yii::$app->getRequest()->getIsAjax()) {
if (Yii::$app->getRequest()->getHeaders()->get('X-Ie-Redirect-Compatibility') !== null && $statusCode === 302) {
if (Yii::$app->getRequest()->hasHeader('X-Ie-Redirect-Compatibility') && $statusCode === 302) {
// Ajax 302 redirect in IE does not work. Change status code to 200. See https://github.com/yiisoft/yii2/issues/9670
$statusCode = 200;
}
if (Yii::$app->getRequest()->getIsPjax()) {
$this->getHeaders()->set('X-Pjax-Url', $url);
$this->setHeader('X-Pjax-Url', $url);
} else {
$this->getHeaders()->set('X-Redirect', $url);
$this->setHeader('X-Redirect', $url);
}
} else {
$this->getHeaders()->set('Location', $url);
$this->setHeader('Location', $url);
}
} else {
$this->getHeaders()->set('Location', $url);
$this->setHeader('Location', $url);
}
$this->setStatusCode($statusCode);
@ -1022,7 +1061,7 @@ class Response extends \yii\base\Response
*/
protected function prepare()
{
if ($this->stream !== null) {
if ($this->bodyRange !== null) {
return;
}
@ -1033,26 +1072,44 @@ class Response extends \yii\base\Response
}
if ($formatter instanceof ResponseFormatterInterface) {
$formatter->format($this);
} else {
throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
}
} elseif ($this->format === self::FORMAT_RAW) {
if ($this->data !== null) {
$this->content = $this->data;
return;
}
} else {
throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
} elseif ($this->format !== self::FORMAT_RAW) {
throw new InvalidConfigException("Unsupported response format: {$this->format}");
}
if (is_array($this->content)) {
throw new InvalidArgumentException('Response content must not be an array.');
} elseif (is_object($this->content)) {
if (method_exists($this->content, '__toString')) {
$this->content = $this->content->__toString();
if ($this->data !== null) {
if (is_array($this->data)) {
throw new InvalidArgumentException('Response raw data must not be an array.');
} elseif (is_object($this->data)) {
if (method_exists($this->data, '__toString')) {
$content = $this->data->__toString();
} else {
throw new InvalidArgumentException('Response raw data must be a string or an object implementing '
. ' __toString().');
}
} else {
throw new InvalidArgumentException('Response content must be a string or an object implementing '
. ' __toString().');
$content = $this->data;
}
$body = new MemoryStream();
$body->write($content);
$this->setBody($body);
}
}
/**
* {@inheritdoc}
*/
public function __clone()
{
parent::__clone();
$this->cloneHttpMessageInternals();
if (is_object($this->_cookies)) {
$this->_cookies = clone $this->_cookies;
}
}
}

1
framework/web/User.php

@ -11,6 +11,7 @@ use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidValueException;
use yii\http\Cookie;
use yii\rbac\CheckAccessInterface;
/**

2
framework/web/XmlResponseFormatter.php

@ -68,7 +68,7 @@ class XmlResponseFormatter extends Component implements ResponseFormatterInterfa
if (stripos($this->contentType, 'charset') === false) {
$this->contentType .= '; charset=' . $charset;
}
$response->getHeaders()->set('Content-Type', $this->contentType);
$response->setHeader('Content-Type', $this->contentType);
if ($response->data !== null) {
$dom = new DOMDocument($this->version, $charset);
if (!empty($this->rootTag)) {

5
framework/widgets/Pjax.php

@ -182,9 +182,8 @@ class Pjax extends Widget
*/
protected function requiresPjax()
{
$headers = Yii::$app->getRequest()->getHeaders();
return $headers->get('X-Pjax') && explode(' ', $headers->get('X-Pjax-Container'))[0] === '#' . $this->options['id'];
$request = Yii::$app->getRequest();
return $request->hasHeader('X-Pjax') && explode(' ', $request->getHeader('X-Pjax-Container')[0])[0] === '#' . $this->options['id'];
}
/**

11
tests/framework/captcha/CaptchaActionTest.php

@ -53,12 +53,11 @@ class CaptchaActionTest extends TestCase
/* @var $response Response */
$response = Yii::$app->response;
$this->assertEquals(Response::FORMAT_RAW, $response->format);
$headerCollection = $response->getHeaders();
$this->assertEquals($driver->getImageMimeType(), $headerCollection->get('Content-type'));
$this->assertEquals('binary', $headerCollection->get('Content-Transfer-Encoding'));
$this->assertEquals('public', $headerCollection->get('Pragma'));
$this->assertEquals('0', $headerCollection->get('Expires'));
$this->assertEquals('must-revalidate, post-check=0, pre-check=0', $headerCollection->get('Cache-Control'));
$this->assertEquals([$driver->getImageMimeType()], $response->getHeader('Content-type'));
$this->assertEquals(['binary'], $response->getHeader('Content-Transfer-Encoding'));
$this->assertEquals(['public'], $response->getHeader('Pragma'));
$this->assertEquals(['0'], $response->getHeader('Expires'));
$this->assertEquals(['must-revalidate, post-check=0, pre-check=0'], $response->getHeader('Cache-Control'));
}
public function testRunRefresh()

13
tests/framework/filters/HttpCacheTest.php

@ -41,8 +41,7 @@ class HttpCacheTest extends \yiiunit\TestCase
};
$httpCache->beforeAction(null);
$response = Yii::$app->getResponse();
$this->assertFalse($response->getHeaders()->offsetExists('Pragma'));
$this->assertNotSame($response->getHeaders()->get('Pragma'), '');
$this->assertFalse($response->hasHeader('Pragma'));
}
/**
@ -88,7 +87,7 @@ class HttpCacheTest extends \yiiunit\TestCase
};
$httpCache->beforeAction(null);
$response = Yii::$app->getResponse();
$this->assertFalse($response->getHeaders()->offsetExists('ETag'));
$this->assertFalse($response->hasHeader('ETag'));
$httpCache->etagSeed = function ($action, $params) {
return '';
@ -96,9 +95,9 @@ class HttpCacheTest extends \yiiunit\TestCase
$httpCache->beforeAction(null);
$response = Yii::$app->getResponse();
$this->assertTrue($response->getHeaders()->offsetExists('ETag'));
$this->assertTrue($response->hasHeader('ETag'));
$etag = $response->getHeaders()->get('ETag');
$etag = $response->getHeaderLine('ETag');
$this->assertStringStartsWith('"', $etag);
$this->assertStringEndsWith('"', $etag);
@ -107,9 +106,9 @@ class HttpCacheTest extends \yiiunit\TestCase
$httpCache->beforeAction(null);
$response = Yii::$app->getResponse();
$this->assertTrue($response->getHeaders()->offsetExists('ETag'));
$this->assertTrue($response->hasHeader('ETag'));
$etag = $response->getHeaders()->get('ETag');
$etag = $response->getHeaderLine('ETag');
$this->assertStringStartsWith('W/"', $etag);
$this->assertStringEndsWith('"', $etag);
}

20
tests/framework/filters/PageCacheTest.php

@ -16,7 +16,7 @@ use yii\filters\PageCache;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\web\Controller;
use yii\web\Cookie;
use yii\http\Cookie;
use yii\web\View;
use yiiunit\framework\caching\CacheTestCase;
use yiiunit\TestCase;
@ -176,7 +176,7 @@ class PageCacheTest extends TestCase
if (isset($testCase['headers'])) {
foreach (array_keys($testCase['headers']) as $name) {
$value = Yii::$app->security->generateRandomString();
Yii::$app->response->headers->add($name, $value);
Yii::$app->response->addHeader($name, $value);
$headers[$name] = $value;
}
}
@ -191,9 +191,9 @@ class PageCacheTest extends TestCase
// Metadata
$metadata = [
'format' => Yii::$app->response->format,
'version' => Yii::$app->response->version,
'statusCode' => Yii::$app->response->statusCode,
'statusText' => Yii::$app->response->statusText,
'protocolVersion' => Yii::$app->response->getProtocolVersion(),
'statusCode' => Yii::$app->response->getStatusCode(),
'reasonPhrase' => Yii::$app->response->getReasonPhrase(),
];
if ($testCase['cacheable']) {
$this->assertNotEmpty($this->getInaccessibleProperty($filter->cache->handler, '_cache'), $testCase['name']);
@ -219,9 +219,9 @@ class PageCacheTest extends TestCase
$this->assertSame($dynamic, $json['dynamic'], $testCase['name']);
// Metadata
$this->assertSame($metadata['format'], Yii::$app->response->format, $testCase['name']);
$this->assertSame($metadata['version'], Yii::$app->response->version, $testCase['name']);
$this->assertSame($metadata['statusCode'], Yii::$app->response->statusCode, $testCase['name']);
$this->assertSame($metadata['statusText'], Yii::$app->response->statusText, $testCase['name']);
$this->assertSame($metadata['protocolVersion'], Yii::$app->response->getProtocolVersion(), $testCase['name']);
$this->assertSame($metadata['statusCode'], Yii::$app->response->getStatusCode(), $testCase['name']);
$this->assertSame($metadata['reasonPhrase'], Yii::$app->response->getReasonPhrase(), $testCase['name']);
// Cookies
if (isset($testCase['cookies'])) {
foreach ($testCase['cookies'] as $name => $expected) {
@ -234,9 +234,9 @@ class PageCacheTest extends TestCase
// Headers
if (isset($testCase['headers'])) {
foreach ($testCase['headers'] as $name => $expected) {
$this->assertSame($expected, Yii::$app->response->headers->has($name), $testCase['name']);
$this->assertSame($expected, Yii::$app->response->hasHeader($name), $testCase['name']);
if ($expected) {
$this->assertSame($headers[$name], Yii::$app->response->headers->get($name), $testCase['name']);
$this->assertSame($headers[$name], Yii::$app->response->getHeaderLine($name), $testCase['name']);
}
}
}

2
tests/framework/filters/auth/AuthTest.php

@ -142,7 +142,7 @@ class AuthTest extends \yiiunit\TestCase
*/
public function testHttpBearerAuth($token, $login)
{
Yii::$app->request->headers->set('Authorization', "Bearer $token");
Yii::$app->request->addHeader('Authorization', "Bearer $token");
$filter = ['class' => HttpBearerAuth::class];
$this->authOnly($token, $login, $filter, 'bearer-auth');
$this->authOptional($token, $login, $filter, 'bearer-auth');

250
tests/framework/http/FileStreamTest.php

@ -0,0 +1,250 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\http;
use Yii;
use yii\helpers\FileHelper;
use yii\http\FileStream;
use yiiunit\TestCase;
class FileStreamTest extends TestCase
{
/**
* @var string test file path.
*/
protected $testFilePath;
/**
* {@inheritdoc}
*/
protected function setUp()
{
parent::setUp();
$this->testFilePath = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . 'file-stream-test-' . getmypid();
FileHelper::createDirectory($this->testFilePath);
}
/**
* {@inheritdoc}
*/
protected function tearDown()
{
FileHelper::removeDirectory($this->testFilePath);
parent::tearDown();
}
public function testRead()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'read.txt';
file_put_contents($filename, '0123456789');
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'r';
$this->assertTrue($stream->isReadable());
$this->assertTrue($stream->isSeekable());
$this->assertFalse($stream->isWritable());
$this->assertSame('01234', $stream->read(5));
$this->assertFalse($stream->eof());
$this->assertSame('56789', $stream->read(6));
$this->assertTrue($stream->eof());
}
/**
* @depends testRead
*/
public function testSeek()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'seek.txt';
file_put_contents($filename, '0123456789');
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'r';
$stream->seek(5);
$this->assertSame('56789', $stream->read(5));
$stream->seek(0);
$this->assertSame('01234', $stream->read(5));
}
/**
* @depends testSeek
*/
public function testGetContents()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'get-content.txt';
file_put_contents($filename, '0123456789');
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'r';
$this->assertSame('0123456789', $stream->getContents());
$stream->seek(5);
$this->assertSame('56789', $stream->getContents());
}
/**
* @depends testGetContents
*/
public function testToString()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'to-string.txt';
file_put_contents($filename, '0123456789');
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'r';
$this->assertSame('0123456789', (string)$stream);
$stream->seek(5);
$this->assertSame('0123456789', (string)$stream);
}
/**
* @depends testRead
*/
public function testWrite()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'write.txt';
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'w+';
$this->assertTrue($stream->isWritable());
$stream->write('01234');
$stream->write('56789');
$stream->close();
$this->assertSame('0123456789', file_get_contents($filename));
}
/**
* @depends testRead
*/
public function testGetSize()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'get-size.txt';
file_put_contents($filename, '0123456789');
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'r';
$this->assertSame(10, $stream->getSize());
file_put_contents($filename, '');
$this->assertSame(0, $stream->getSize());
}
/**
* @depends testRead
*/
public function testGetMetadata()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'get-meta-data.txt';
file_put_contents($filename, '0123456789');
$stream = new FileStream();
$stream->filename = $filename;
$stream->mode = 'r';
$metadata = $stream->getMetadata();
$this->assertSame('r', $metadata['mode']);
$this->assertSame('plainfile', $metadata['wrapper_type']);
$this->assertSame('r', $stream->getMetadata('mode'));
}
/**
* @return array test data.
*/
public function dataProviderFileMode()
{
return [
['r', true, false],
['r+', true, true],
['w', false, true],
['w+', true, true],
['rw', true, true],
['x', false, true],
['x+', true, true],
['c', false, true],
['c+', true, true],
['a', false, true],
['a+', true, true],
['wb', false, true],
['rb', true, false],
['w+b', true, true],
['r+b', true, true],
['rt', true, false],
['w+t', true, true],
['r+t', true, true],
['x+t', true, true],
['c+t', true, true],
];
}
/**
* @depends testGetMetadata
* @dataProvider dataProviderFileMode
*
* @param string $mode
* @param bool $isReadable
* @param bool $isWritable
*/
public function testIsReadable($mode, $isReadable, $isWritable)
{
/* @var $stream FileStream|\PHPUnit_Framework_MockObject_MockObject */
$stream = $this->getMockBuilder(FileStream::class)
->setMethods(['getMetadata'])
->getMock();
$stream->expects($this->any())
->method('getMetadata')
->with('mode')
->willReturn($mode);
$this->assertSame($isReadable, $stream->isReadable());
}
/**
* @depends testGetMetadata
* @dataProvider dataProviderFileMode
*
* @param string $mode
* @param bool $isReadable
* @param bool $isWritable
*/
public function testIsWritable($mode, $isReadable, $isWritable)
{
/* @var $stream FileStream|\PHPUnit_Framework_MockObject_MockObject */
$stream = $this->getMockBuilder(FileStream::class)
->setMethods(['getMetadata'])
->getMock();
$stream->expects($this->any())
->method('getMetadata')
->with('mode')
->willReturn($mode);
$this->assertSame($isWritable, $stream->isWritable());
}
}

133
tests/framework/http/MemoryStreamTest.php

@ -0,0 +1,133 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\http;
use yii\http\MemoryStream;
use yiiunit\TestCase;
class MemoryStreamTest extends TestCase
{
public function testWrite()
{
$stream = new MemoryStream();
$this->assertTrue($stream->isWritable());
$this->assertSame(5, $stream->write('01234'));
$this->assertSame(5, $stream->write('56789'));
$this->assertSame('0123456789', (string)$stream);
}
/**
* @depends testWrite
*/
public function testRead()
{
$stream = new MemoryStream();
$stream->write('0123456789');
$this->assertTrue($stream->isReadable());
$stream->rewind();
$this->assertSame('01234', $stream->read(5));
$this->assertFalse($stream->eof());
$this->assertSame('56789', $stream->read(6));
$this->assertTrue($stream->eof());
}
/**
* @depends testRead
*/
public function testSeek()
{
$stream = new MemoryStream();
$stream->write('0123456789');
$stream->rewind();
$this->assertTrue($stream->isSeekable());
$stream->seek(5);
$this->assertSame('56789', $stream->read(5));
$stream->seek(0);
$this->assertSame('01234', $stream->read(5));
}
/**
* @depends testSeek
*/
public function testGetContents()
{
$stream = new MemoryStream();
$stream->write('0123456789');
$stream->rewind();
$this->assertSame('0123456789', $stream->getContents());
$stream->seek(5);
$this->assertSame('56789', $stream->getContents());
}
/**
* @depends testGetContents
*/
public function testToString()
{
$stream = new MemoryStream();
$stream->write('0123456789');
$stream->rewind();
$this->assertSame('0123456789', (string)$stream);
$stream->seek(5);
$this->assertSame('0123456789', (string)$stream);
}
/**
* @depends testRead
*/
public function testGetSize()
{
$stream = new MemoryStream();
$this->assertSame(0, $stream->getSize());
$stream->write('0123456789');
$this->assertSame(10, $stream->getSize());
}
/**
* @depends testRead
*/
public function testGetMetadata()
{
$stream = new MemoryStream();
$metadata = $stream->getMetadata();
$this->assertSame('rw', $metadata['mode']);
$this->assertSame('rw', $stream->getMetadata('mode'));
}
/**
* @depends testSeek
*/
public function testRewrite()
{
$stream = new MemoryStream();
$stream->write('0123456789');
$stream->seek(5);
$this->assertSame(4, $stream->write('0000'));
$this->assertSame('0123400009', (string)$stream);
}
}

134
tests/framework/http/MessageTraitTest.php

@ -0,0 +1,134 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\http;
use Psr\Http\Message\MessageInterface;
use yii\base\BaseObject;
use yii\http\FileStream;
use yii\http\MemoryStream;
use yii\http\MessageTrait;
use yiiunit\TestCase;
class MessageTraitTest extends TestCase
{
public function testSetupProtocolVersion()
{
$message = new TestMessage();
$message->setProtocolVersion('2.0');
$this->assertSame('2.0', $message->getProtocolVersion());
$newMessage = $message->withProtocolVersion('2.1');
$this->assertNotSame($newMessage, $message);
$this->assertSame('2.1', $newMessage->getProtocolVersion());
}
/**
* @depends testSetupProtocolVersion
*/
public function testDefaultProtocolVersion()
{
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.2';
$message = new TestMessage();
$this->assertSame('1.2', $message->getProtocolVersion());
unset($_SERVER['SERVER_PROTOCOL']);
$message = new TestMessage();
$this->assertSame('1.0', $message->getProtocolVersion());
}
public function testSetupBody()
{
$message = new TestMessage();
$message->setBody([
'class' => FileStream::class
]);
$this->assertTrue($message->getBody() instanceof FileStream);
$body = new MemoryStream();
$newMessage = $message->withBody($body);
$this->assertNotSame($newMessage, $message);
$this->assertSame($body, $newMessage->getBody());
}
/**
* @depends testSetupBody
*/
public function testDefaultBody()
{
$message = new TestMessage();
$this->assertTrue($message->getBody() instanceof MemoryStream);
}
public function testSetupHeaders()
{
$message = new TestMessage();
$this->assertFalse($message->hasHeader('some'));
$headerMessage = $message->withHeader('some', 'foo');
$this->assertNotSame($headerMessage, $message);
$this->assertTrue($headerMessage->hasHeader('some'));
$this->assertEquals(['some' => ['foo']], $headerMessage->getHeaders());
$headerAddedMessage = $headerMessage->withAddedHeader('some', 'another');
$this->assertNotSame($headerMessage, $headerAddedMessage);
$this->assertEquals(['some' => ['foo', 'another']], $headerAddedMessage->getHeaders());
$this->assertEquals(['foo', 'another'], $headerAddedMessage->getHeader('some'));
$this->assertEquals('foo,another', $headerAddedMessage->getHeaderLine('some'));
$overrideMessage = $headerAddedMessage->withHeader('some', 'override');
$this->assertNotSame($headerAddedMessage, $overrideMessage);
$this->assertEquals(['some' => ['override']], $overrideMessage->getHeaders());
$clearMessage = $headerMessage->withoutHeader('some');
$this->assertNotSame($headerMessage, $clearMessage);
$this->assertFalse($clearMessage->hasHeader('some'));
$this->assertEquals([], $clearMessage->getHeader('some'));
$this->assertEquals('', $clearMessage->getHeaderLine('some'));
$message->setHeaders([
'some' => ['line1', 'line2']
]);
$this->assertEquals(['some' => ['line1', 'line2']], $message->getHeaders());
$message->setHeaders([
'another' => ['one']
]);
$this->assertEquals(['another' => ['one']], $message->getHeaders());
}
/**
* @depends testSetupProtocolVersion
* @depends testSetupBody
* @depends testSetupHeaders
*/
public function testCreateFromConfig()
{
$message = new TestMessage([
'protocolVersion' => '2.1',
'headers' => [
'header' => [
'line1',
'line2',
],
],
'body' => [
'class' => FileStream::class
],
]);
$this->assertSame('2.1', $message->getProtocolVersion());
$this->assertEquals(['header' => ['line1', 'line2']], $message->getHeaders());
$this->assertTrue($message->getBody() instanceof FileStream);
}
}
class TestMessage extends BaseObject implements MessageInterface
{
use MessageTrait;
}

243
tests/framework/http/ResourceStreamTest.php

@ -0,0 +1,243 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\http;
use Yii;
use yii\helpers\FileHelper;
use yii\http\ResourceStream;
use yiiunit\TestCase;
class ResourceStreamTest extends TestCase
{
/**
* @var string test file path.
*/
protected $testFilePath;
/**
* {@inheritdoc}
*/
protected function setUp()
{
parent::setUp();
$this->testFilePath = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . 'resource-stream-test-' . getmypid();
FileHelper::createDirectory($this->testFilePath);
}
/**
* {@inheritdoc}
*/
protected function tearDown()
{
FileHelper::removeDirectory($this->testFilePath);
parent::tearDown();
}
public function testRead()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'read.txt';
file_put_contents($filename, '0123456789');
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'r');
$this->assertTrue($stream->isReadable());
$this->assertTrue($stream->isSeekable());
$this->assertFalse($stream->isWritable());
$this->assertSame('01234', $stream->read(5));
$this->assertFalse($stream->eof());
$this->assertSame('56789', $stream->read(6));
$this->assertTrue($stream->eof());
}
/**
* @depends testRead
*/
public function testSeek()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'seek.txt';
file_put_contents($filename, '0123456789');
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'r');
$stream->seek(5);
$this->assertSame('56789', $stream->read(5));
$stream->seek(0);
$this->assertSame('01234', $stream->read(5));
}
/**
* @depends testSeek
*/
public function testGetContents()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'get-content.txt';
file_put_contents($filename, '0123456789');
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'r');
$this->assertSame('0123456789', $stream->getContents());
$stream->seek(5);
$this->assertSame('56789', $stream->getContents());
}
/**
* @depends testGetContents
*/
public function testToString()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'to-string.txt';
file_put_contents($filename, '0123456789');
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'r');
$this->assertSame('0123456789', (string)$stream);
$stream->seek(5);
$this->assertSame('0123456789', (string)$stream);
}
/**
* @depends testRead
*/
public function testWrite()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'write.txt';
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'w+');
$this->assertTrue($stream->isWritable());
$stream->write('01234');
$stream->write('56789');
$stream->close();
$this->assertSame('0123456789', file_get_contents($filename));
}
/**
* @depends testRead
*/
public function testGetSize()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'get-size.txt';
file_put_contents($filename, '0123456789');
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'r');
$this->assertSame(10, $stream->getSize());
file_put_contents($filename, '');
$this->assertSame(0, $stream->getSize());
}
/**
* @depends testRead
*/
public function testGetMetadata()
{
$filename = $this->testFilePath . DIRECTORY_SEPARATOR . 'get-meta-data.txt';
file_put_contents($filename, '0123456789');
$stream = new ResourceStream();
$stream->resource = fopen($filename, 'r');
$metadata = $stream->getMetadata();
$this->assertSame('r', $metadata['mode']);
$this->assertSame('plainfile', $metadata['wrapper_type']);
$this->assertSame('r', $stream->getMetadata('mode'));
}
/**
* @return array test data.
*/
public function dataProviderFileMode()
{
return [
['r', true, false],
['r+', true, true],
['w', false, true],
['w+', true, true],
['rw', true, true],
['x', false, true],
['x+', true, true],
['c', false, true],
['c+', true, true],
['a', false, true],
['a+', true, true],
['wb', false, true],
['rb', true, false],
['w+b', true, true],
['r+b', true, true],
['rt', true, false],
['w+t', true, true],
['r+t', true, true],
['x+t', true, true],
['c+t', true, true],
];
}
/**
* @depends testGetMetadata
* @dataProvider dataProviderFileMode
*
* @param string $mode
* @param bool $isReadable
* @param bool $isWritable
*/
public function testIsReadable($mode, $isReadable, $isWritable)
{
/* @var $stream ResourceStream|\PHPUnit_Framework_MockObject_MockObject */
$stream = $this->getMockBuilder(ResourceStream::class)
->setMethods(['getMetadata'])
->getMock();
$stream->expects($this->any())
->method('getMetadata')
->with('mode')
->willReturn($mode);
$this->assertSame($isReadable, $stream->isReadable());
}
/**
* @depends testGetMetadata
* @dataProvider dataProviderFileMode
*
* @param string $mode
* @param bool $isReadable
* @param bool $isWritable
*/
public function testIsWritable($mode, $isReadable, $isWritable)
{
/* @var $stream ResourceStream|\PHPUnit_Framework_MockObject_MockObject */
$stream = $this->getMockBuilder(ResourceStream::class)
->setMethods(['getMetadata'])
->getMock();
$stream->expects($this->any())
->method('getMetadata')
->with('mode')
->willReturn($mode);
$this->assertSame($isWritable, $stream->isWritable());
}
}

44
tests/framework/web/UploadedFileTest.php → tests/framework/http/UploadedFileTest.php

@ -5,15 +5,19 @@
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\web;
namespace yiiunit\framework\http;
use yii\web\UploadedFile;
use Psr\Http\Message\StreamInterface;
use Yii;
use yii\http\FileStream;
use yii\http\MemoryStream;
use yii\http\UploadedFile;
use yiiunit\framework\web\stubs\ModelStub;
use yiiunit\framework\web\stubs\VendorImage;
use yiiunit\TestCase;
/**
* @group web
* @group http
*/
class UploadedFileTest extends TestCase
{
@ -72,4 +76,38 @@ class UploadedFileTest extends TestCase
$this->assertInstanceOf(VendorImage::class, $vendorImage);
}
}
public function testSetupStream()
{
$uploadedFile = new UploadedFile();
$stream = new MemoryStream();
$uploadedFile->setStream($stream);
$this->assertSame($stream, $uploadedFile->getStream());
$uploadedFile->setStream(['class' => MemoryStream::class]);
$this->assertNotSame($stream, $uploadedFile->getStream());
$this->assertTrue($uploadedFile->getStream() instanceof MemoryStream);
$uploadedFile->setStream(function () {
return new FileStream(['filename' => 'test.txt']);
});
$this->assertTrue($uploadedFile->getStream() instanceof FileStream);
$this->assertSame('test.txt', $uploadedFile->getStream()->filename);
}
/**
* @depends testSetupStream
*/
public function testDefaultStream()
{
$uploadedFile = new UploadedFile();
$uploadedFile->setError(UPLOAD_ERR_OK);
$uploadedFile->tempFilename = tempnam(Yii::getAlias('@yiiunit/runtime'), 'tmp-');
file_put_contents($uploadedFile->tempFilename, '0123456789');
$stream = $uploadedFile->getStream();
$this->assertTrue($stream instanceof StreamInterface);
$this->assertSame('0123456789', $stream->__toString());
}
}

203
tests/framework/http/UriTest.php

@ -0,0 +1,203 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\http;
use yii\http\Uri;
use yiiunit\TestCase;
class UriTest extends TestCase
{
public function testSetupString()
{
$uri = new Uri();
$uri->setString('http://example.com?foo=some');
$this->assertEquals('http://example.com?foo=some', $uri->getString());
}
/**
* @depends testSetupString
*/
public function testParseString()
{
$uri = new Uri();
$uri->setString('http://username:password@example.com:9090/content/path?foo=some#anchor');
$this->assertSame('http', $uri->getScheme());
$this->assertSame('username:password', $uri->getUserInfo());
$this->assertSame('example.com', $uri->getHost());
$this->assertSame(9090, $uri->getPort());
$this->assertSame('/content/path', $uri->getPath());
$this->assertSame('foo=some', $uri->getQuery());
$this->assertSame('anchor', $uri->getFragment());
}
/**
* @depends testSetupString
*/
public function testConstructFromString()
{
$uri = new Uri(['string' => 'http://example.com?foo=some']);
$this->assertSame('http://example.com?foo=some', $uri->getString());
}
public function testConstructFromComponents()
{
$uri = new Uri([
'scheme' => 'http',
'user' => 'username',
'password' => 'password',
'host' => 'example.com',
'port' => 9090,
'path' => '/content/path',
'query' => 'foo=some',
'fragment' => 'anchor',
]);
$this->assertSame('http://username:password@example.com:9090/content/path?foo=some#anchor', $uri->getString());
}
/**
* @depends testConstructFromComponents
*/
public function testToString()
{
$uri = new Uri([
'scheme' => 'http',
'host' => 'example.com',
'path' => '/content/path',
'query' => 'foo=some',
]);
$this->assertSame('http://example.com/content/path?foo=some', (string)$uri);
}
/**
* @depends testParseString
*/
public function testGetUserInfo()
{
$uri = new Uri();
$uri->setString('http://username:password@example.com/content/path?foo=some');
$this->assertSame('username:password', $uri->getUserInfo());
}
/**
* @depends testParseString
*/
public function testGetAuthority()
{
$uri = new Uri();
$uri->setString('http://username:password@example.com/content/path?foo=some');
$this->assertSame('username:password@example.com', $uri->getAuthority());
}
/**
* @depends testConstructFromComponents
*/
public function testOmitDefaultPort()
{
$uri = new Uri([
'scheme' => 'http',
'host' => 'example.com',
'port' => 80,
'path' => '/content/path',
'query' => 'foo=some',
]);
$this->assertSame('http://example.com/content/path?foo=some', $uri->getString());
}
/**
* @depends testConstructFromComponents
*/
public function testSetupQueryByArray()
{
$uri = new Uri([
'scheme' => 'http',
'host' => 'example.com',
'path' => '/content/path',
'query' => [
'param1' => 'value1',
'param2' => 'value2',
],
]);
$this->assertSame('http://example.com/content/path?param1=value1&param2=value2', $uri->getString());
}
/**
* @depends testToString
*/
public function testPsrSyntax()
{
$uri = (new Uri())
->withScheme('http')
->withUserInfo('username', 'password')
->withHost('example.com')
->withPort(9090)
->withPath('/content/path')
->withQuery('foo=some')
->withFragment('anchor');
$this->assertSame('http://username:password@example.com:9090/content/path?foo=some#anchor', $uri->getString());
}
/**
* @depends testConstructFromString
* @depends testPsrSyntax
*/
public function testModify()
{
$uri = new Uri(['string' => 'http://example.com?foo=some']);
$uri->setHost('another.com');
$uri->setPort(9090);
$this->assertSame('http://another.com:9090?foo=some', $uri->getString());
}
/**
* @depends testPsrSyntax
*/
public function testImmutability()
{
$uri = new Uri([
'scheme' => 'http',
'user' => 'username',
'password' => 'password',
'host' => 'example.com',
'port' => 9090,
'path' => '/content/path',
'query' => 'foo=some',
'fragment' => 'anchor',
]);
$this->assertSame($uri, $uri->withScheme('http'));
$this->assertNotSame($uri, $uri->withScheme('https'));
$this->assertSame($uri, $uri->withHost('example.com'));
$this->assertNotSame($uri, $uri->withHost('another.com'));
$this->assertSame($uri, $uri->withPort(9090));
$this->assertNotSame($uri, $uri->withPort(33));
$this->assertSame($uri, $uri->withPath('/content/path'));
$this->assertNotSame($uri, $uri->withPath('/another/path'));
$this->assertSame($uri, $uri->withQuery('foo=some'));
$this->assertNotSame($uri, $uri->withQuery('foo=another'));
$this->assertSame($uri, $uri->withFragment('anchor'));
$this->assertNotSame($uri, $uri->withFragment('another'));
$this->assertSame($uri, $uri->withUserInfo('username', 'password'));
$this->assertNotSame($uri, $uri->withUserInfo('username', 'another'));
}
}

3
tests/framework/rest/UrlRuleTest.php

@ -47,7 +47,8 @@ class UrlRuleTest extends TestCase
foreach ($tests as $j => $test) {
[$request->pathInfo, $route] = $test;
$params = $test[2] ?? [];
$_POST['_METHOD'] = $test[3] ?? 'GET';
$request->setMethod($test[3] ?? 'GET');
$result = $rule->parseRequest($manager, $request);
if ($route === false) {
$this->assertFalse($result, "Test#$i-$j: $name");

54
tests/framework/validators/FileValidatorTest.php

@ -10,7 +10,7 @@ namespace yiiunit\framework\validators;
use Yii;
use yii\helpers\FileHelper;
use yii\validators\FileValidator;
use yii\web\UploadedFile;
use yii\http\UploadedFile;
use yiiunit\data\validators\models\FakedValidationModel;
use yiiunit\TestCase;
@ -124,7 +124,7 @@ class FileValidatorTest extends TestCase
'attr_files' => $this->createTestFiles(
[
[
'name' => 'test_up_1.txt',
'clientFilename' => 'test_up_1.txt',
'size' => 1024,
],
[
@ -155,17 +155,17 @@ class FileValidatorTest extends TestCase
'attr_images' => $this->createTestFiles(
[
[
'name' => 'image.png',
'clientFilename' => 'image.png',
'size' => 1024,
'type' => 'image/png',
'clientMediaType' => 'image/png',
],
[
'name' => 'image.png',
'clientFilename' => 'image.png',
'size' => 1024,
'type' => 'image/png',
'clientMediaType' => 'image/png',
],
[
'name' => 'text.txt',
'clientFilename' => 'text.txt',
'size' => 1024,
],
]
@ -182,14 +182,14 @@ class FileValidatorTest extends TestCase
'attr_images' => $this->createTestFiles(
[
[
'name' => 'image.png',
'clientFilename' => 'image.png',
'size' => 1024,
'type' => 'image/png',
'clientMediaType' => 'image/png',
],
[
'name' => 'image.png',
'clientFilename' => 'image.png',
'size' => 1024,
'type' => 'image/png',
'clientMediaType' => 'image/png',
],
]
),
@ -203,7 +203,7 @@ class FileValidatorTest extends TestCase
'attr_image' => $this->createTestFiles(
[
[
'name' => 'text.txt',
'clientFilename' => 'text.txt',
'size' => 1024,
],
]
@ -235,7 +235,7 @@ class FileValidatorTest extends TestCase
$files[] = ['no instance of UploadedFile'];
continue;
}
$name = isset($param['name']) ? $param['name'] : $rndString();
$name = isset($param['clientFilename']) ? $param['clientFilename'] : $rndString();
$tempName = \Yii::getAlias('@yiiunit/runtime/validators/file/tmp/') . $name;
if (is_readable($tempName)) {
$size = filesize($tempName);
@ -245,23 +245,23 @@ class FileValidatorTest extends TestCase
$this->sizeToBytes(ini_get('upload_max_filesize'))
);
}
$type = isset($param['type']) ? $param['type'] : 'text/plain';
$type = isset($param['clientMediaType']) ? $param['clientMediaType'] : 'text/plain';
$error = isset($param['error']) ? $param['error'] : UPLOAD_ERR_OK;
if (count($params) == 1) {
$error = empty($param) ? UPLOAD_ERR_NO_FILE : $error;
return new UploadedFile([
'name' => $name,
'tempName' => $tempName,
'type' => $type,
'clientFilename' => $name,
'tempFilename' => $tempName,
'clientMediaType' => $type,
'size' => $size,
'error' => $error,
]);
}
$files[] = new UploadedFile([
'name' => $name,
'tempName' => $tempName,
'type' => $type,
'clientFilename' => $name,
'tempFilename' => $tempName,
'clientMediaType' => $type,
'size' => $size,
'error' => $error,
]);
@ -279,9 +279,9 @@ class FileValidatorTest extends TestCase
$filePath = \Yii::getAlias('@yiiunit/framework/validators/data/mimeType/') . $fileName;
return new UploadedFile([
'name' => $fileName,
'tempName' => $filePath,
'type' => FileHelper::getMimeType($filePath),
'clientFilename' => $fileName,
'tempFilename' => $filePath,
'clientMediaType' => FileHelper::getMimeType($filePath),
'size' => filesize($filePath),
'error' => UPLOAD_ERR_OK,
]);
@ -341,8 +341,8 @@ class FileValidatorTest extends TestCase
]);
$m = FakedValidationModel::createWithAttributes(
[
'attr_jpg' => $this->createTestFiles([['name' => 'one.jpeg']]),
'attr_exe' => $this->createTestFiles([['name' => 'bad.exe']]),
'attr_jpg' => $this->createTestFiles([['clientFilename' => 'one.jpeg']]),
'attr_exe' => $this->createTestFiles([['clientFilename' => 'bad.exe']]),
]
);
$val->validateAttribute($m, 'attr_jpg');
@ -357,7 +357,7 @@ class FileValidatorTest extends TestCase
$baseName = '飛兒樂團光茫';
/** @var UploadedFile $file */
$file = $this->createTestFiles([
['name' => $baseName . '.txt'],
['clientFilename' => $baseName . '.txt'],
]);
$this->assertEquals($baseName, $file->getBaseName());
}
@ -414,7 +414,7 @@ class FileValidatorTest extends TestCase
return FakedValidationModel::createWithAttributes(
[
'attr_files' => $this->createTestFiles([
['name' => 'abc.jpg', 'size' => 1024, 'type' => 'image/jpeg'],
['clientFilename' => 'abc.jpg', 'size' => 1024, 'clientMediaType' => 'image/jpeg'],
]),
'attr_files_empty' => $this->createTestFiles([[]]),
'attr_err_ini' => $this->createTestFiles([['error' => UPLOAD_ERR_INI_SIZE]]),

29
tests/framework/web/ControllerTest.php

@ -17,6 +17,11 @@ use yiiunit\TestCase;
*/
class ControllerTest extends TestCase
{
/**
* @var FakeController
*/
protected $controller;
public function testBindActionParams()
{
$aksi1 = new InlineAction('aksi1', $this->controller, 'actionAksi1');
@ -61,18 +66,18 @@ class ControllerTest extends TestCase
public function testRedirect()
{
$_SERVER['REQUEST_URI'] = 'http://test-domain.com/';
$this->assertEquals($this->controller->redirect('')->headers->get('location'), '/');
$this->assertEquals($this->controller->redirect('http://some-external-domain.com')->headers->get('location'), 'http://some-external-domain.com');
$this->assertEquals($this->controller->redirect('/')->headers->get('location'), '/');
$this->assertEquals($this->controller->redirect('/something-relative')->headers->get('location'), '/something-relative');
$this->assertEquals($this->controller->redirect(['/'])->headers->get('location'), '/index.php?r=');
$this->assertEquals($this->controller->redirect(['view'])->headers->get('location'), '/index.php?r=fake%2Fview');
$this->assertEquals($this->controller->redirect(['/controller'])->headers->get('location'), '/index.php?r=controller');
$this->assertEquals($this->controller->redirect(['/controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->controller->redirect(['//controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->controller->redirect(['//controller/index', 'id' => 3])->headers->get('location'), '/index.php?r=controller%2Findex&id=3');
$this->assertEquals($this->controller->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->headers->get('location'), '/index.php?r=controller%2Findex&id_1=3&id_2=4');
$this->assertEquals($this->controller->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->headers->get('location'), '/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29');
$this->assertEquals($this->controller->redirect('')->getHeader('location'), ['/']);
$this->assertEquals($this->controller->redirect('http://some-external-domain.com')->getHeader('location'), ['http://some-external-domain.com']);
$this->assertEquals($this->controller->redirect('/')->getHeader('location'), ['/']);
$this->assertEquals($this->controller->redirect('/something-relative')->getHeader('location'), ['/something-relative']);
$this->assertEquals($this->controller->redirect(['/'])->getHeader('location'), ['/index.php?r=']);
$this->assertEquals($this->controller->redirect(['view'])->getHeader('location'), ['/index.php?r=fake%2Fview']);
$this->assertEquals($this->controller->redirect(['/controller'])->getHeader('location'), ['/index.php?r=controller']);
$this->assertEquals($this->controller->redirect(['/controller/index'])->getHeader('location'), ['/index.php?r=controller%2Findex']);
$this->assertEquals($this->controller->redirect(['//controller/index'])->getHeader('location'), ['/index.php?r=controller%2Findex']);
$this->assertEquals($this->controller->redirect(['//controller/index', 'id' => 3])->getHeader('location'), ['/index.php?r=controller%2Findex&id=3']);
$this->assertEquals($this->controller->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->getHeader('location'), ['/index.php?r=controller%2Findex&id_1=3&id_2=4']);
$this->assertEquals($this->controller->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->getHeader('location'), ['/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29']);
}
protected function setUp()

2
tests/framework/web/FormatterTest.php

@ -40,7 +40,7 @@ abstract class FormatterTest extends \yiiunit\TestCase
{
$this->response->data = null;
$this->formatter->format($this->response);
$this->assertNull($this->response->content);
$this->assertSame('', $this->response->content);
}
/**

17
tests/framework/web/RequestTest.php

@ -102,7 +102,7 @@ class RequestTest extends TestCase
// accept any value on GET request
foreach (['GET', 'HEAD', 'OPTIONS'] as $method) {
$_POST[$request->methodParam] = $method;
$request->setMethod($method);
$this->assertTrue($request->validateCsrfToken($token));
$this->assertTrue($request->validateCsrfToken($token . 'a'));
$this->assertTrue($request->validateCsrfToken([]));
@ -113,7 +113,7 @@ class RequestTest extends TestCase
// only accept valid token on POST
foreach (['POST', 'PUT', 'DELETE'] as $method) {
$_POST[$request->methodParam] = $method;
$request->setMethod($method);
$this->assertTrue($request->validateCsrfToken($token));
$this->assertFalse($request->validateCsrfToken($token . 'a'));
$this->assertFalse($request->validateCsrfToken([]));
@ -137,13 +137,13 @@ class RequestTest extends TestCase
// accept no value on GET request
foreach (['GET', 'HEAD', 'OPTIONS'] as $method) {
$_POST[$request->methodParam] = $method;
$request->setMethod($method);
$this->assertTrue($request->validateCsrfToken());
}
// only accept valid token on POST
foreach (['POST', 'PUT', 'DELETE'] as $method) {
$_POST[$request->methodParam] = $method;
$request->setMethod($method);
$request->setBodyParams([]);
$this->assertFalse($request->validateCsrfToken());
$request->setBodyParams([$request->csrfParam => $token]);
@ -171,12 +171,11 @@ class RequestTest extends TestCase
// only accept valid token on POST
foreach (['POST', 'PUT', 'DELETE'] as $method) {
$_POST[$request->methodParam] = $method;
$request->setMethod($method);
$request->setBodyParams([]);
$request->headers->remove(Request::CSRF_HEADER);
$this->assertFalse($request->validateCsrfToken());
$request->headers->add(Request::CSRF_HEADER, $token);
$this->assertTrue($request->validateCsrfToken());
$this->assertFalse($request->withoutHeader(Request::CSRF_HEADER)->validateCsrfToken());
$this->assertTrue($request->withAddedHeader(Request::CSRF_HEADER, $token)->validateCsrfToken());
}
}

40
tests/framework/web/ResponseTest.php

@ -55,11 +55,10 @@ class ResponseTest extends \yiiunit\TestCase
$this->assertEquals($expectedContent, $content);
$this->assertEquals(206, $this->response->statusCode);
$headers = $this->response->headers;
$this->assertEquals('bytes', $headers->get('Accept-Ranges'));
$this->assertEquals('bytes ' . $expectedHeader . '/' . StringHelper::byteLength($fullContent), $headers->get('Content-Range'));
$this->assertEquals('text/plain', $headers->get('Content-Type'));
$this->assertEquals("$length", $headers->get('Content-Length'));
$this->assertEquals(['bytes'], $this->response->getHeader('Accept-Ranges'));
$this->assertEquals(['bytes ' . $expectedHeader . '/' . StringHelper::byteLength($fullContent)], $this->response->getHeader('Content-Range'));
$this->assertEquals(['text/plain'], $this->response->getHeader('Content-Type'));
$this->assertEquals(["$length"], $this->response->getHeader('Content-Length'));
}
public function wrongRanges()
@ -104,27 +103,26 @@ class ResponseTest extends \yiiunit\TestCase
static::assertEquals('test', $content);
static::assertEquals(200, $this->response->statusCode);
$headers = $this->response->headers;
static::assertEquals('application/octet-stream', $headers->get('Content-Type'));
static::assertEquals('attachment; filename="test.txt"', $headers->get('Content-Disposition'));
static::assertEquals(4, $headers->get('Content-Length'));
static::assertEquals(['application/octet-stream'], $this->response->getHeader('Content-Type'));
static::assertEquals(['attachment; filename="test.txt"'], $this->response->getHeader('Content-Disposition'));
static::assertEquals([4], $this->response->getHeader('Content-Length'));
}
public function testRedirect()
{
$_SERVER['REQUEST_URI'] = 'http://test-domain.com/';
$this->assertEquals($this->response->redirect('')->headers->get('location'), '/');
$this->assertEquals($this->response->redirect('http://some-external-domain.com')->headers->get('location'), 'http://some-external-domain.com');
$this->assertEquals($this->response->redirect('/')->headers->get('location'), '/');
$this->assertEquals($this->response->redirect('/something-relative')->headers->get('location'), '/something-relative');
$this->assertEquals($this->response->redirect(['/'])->headers->get('location'), '/index.php?r=');
$this->assertEquals($this->response->redirect(['view'])->headers->get('location'), '/index.php?r=view');
$this->assertEquals($this->response->redirect(['/controller'])->headers->get('location'), '/index.php?r=controller');
$this->assertEquals($this->response->redirect(['/controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->response->redirect(['//controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->response->redirect(['//controller/index', 'id' => 3])->headers->get('location'), '/index.php?r=controller%2Findex&id=3');
$this->assertEquals($this->response->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->headers->get('location'), '/index.php?r=controller%2Findex&id_1=3&id_2=4');
$this->assertEquals($this->response->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->headers->get('location'), '/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29');
$this->assertEquals($this->response->redirect('')->getHeader('location'), ['/']);
$this->assertEquals($this->response->redirect('http://some-external-domain.com')->getHeader('location'), ['http://some-external-domain.com']);
$this->assertEquals($this->response->redirect('/')->getHeader('location'), ['/']);
$this->assertEquals($this->response->redirect('/something-relative')->getHeader('location'), ['/something-relative']);
$this->assertEquals($this->response->redirect(['/'])->getHeader('location'), ['/index.php?r=']);
$this->assertEquals($this->response->redirect(['view'])->getHeader('location'), ['/index.php?r=view']);
$this->assertEquals($this->response->redirect(['/controller'])->getHeader('location'), ['/index.php?r=controller']);
$this->assertEquals($this->response->redirect(['/controller/index'])->getHeader('location'), ['/index.php?r=controller%2Findex']);
$this->assertEquals($this->response->redirect(['//controller/index'])->getHeader('location'), ['/index.php?r=controller%2Findex']);
$this->assertEquals($this->response->redirect(['//controller/index', 'id' => 3])->getHeader('location'), ['/index.php?r=controller%2Findex&id=3']);
$this->assertEquals($this->response->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->getHeader('location'), ['/index.php?r=controller%2Findex&id_1=3&id_2=4']);
$this->assertEquals($this->response->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->getHeader('location'), ['/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29']);
}
/**

10
tests/framework/web/UrlManagerParseUrlTest.php

@ -308,22 +308,22 @@ class UrlManagerParseUrlTest extends TestCase
],
]);
// matching pathinfo GET request
$_SERVER['REQUEST_METHOD'] = 'GET';
$request->setMethod('GET');
$request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request);
$this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result);
// matching pathinfo PUT/POST request
$_SERVER['REQUEST_METHOD'] = 'PUT';
$request->setMethod('PUT');
$request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request);
$this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result);
$_SERVER['REQUEST_METHOD'] = 'POST';
$request->setMethod('POST');
$request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request);
$this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result);
// no wrong matching
$_SERVER['REQUEST_METHOD'] = 'POST';
$request->setMethod('POST');
$request->pathInfo = 'POST/GET';
$result = $manager->parseRequest($request);
$this->assertEquals(['post/get', []], $result);
@ -339,7 +339,5 @@ class UrlManagerParseUrlTest extends TestCase
], \yii\web\Application::class);
$this->assertEquals('/app/post/delete?id=123', $manager->createUrl(['post/delete', 'id' => 123]));
$this->destroyApplication();
unset($_SERVER['REQUEST_METHOD']);
}
}

6
tests/framework/web/UserTest.php

@ -22,8 +22,8 @@ use Yii;
use yii\base\Component;
use yii\base\NotSupportedException;
use yii\rbac\PhpManager;
use yii\web\Cookie;
use yii\web\CookieCollection;
use yii\http\Cookie;
use yii\http\CookieCollection;
use yii\web\ForbiddenHttpException;
use yii\web\IdentityInterface;
use yiiunit\TestCase;
@ -159,6 +159,7 @@ class UserTest extends TestCase
]);
Yii::$app->user->setReturnUrl(null);
}
public function testLoginRequired()
{
$appConfig = [
@ -185,7 +186,6 @@ class UserTest extends TestCase
$this->assertEquals('normal', $user->getReturnUrl());
$this->assertTrue(Yii::$app->response->getIsRedirection());
$this->reset();
Yii::$app->request->setUrl('ajax');
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';

2
tests/framework/web/stubs/VendorImage.php

@ -7,7 +7,7 @@
namespace yiiunit\framework\web\stubs;
use yii\web\UploadedFile;
use yii\http\UploadedFile;
class VendorImage extends UploadedFile
{

Loading…
Cancel
Save