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. 207
      framework/web/Request.php
  32. 277
      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", "psr/log": "~1.0.2",
"yiisoft/yii2-composer": "~2.0.4", "yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0", "psr/simple-cache": "~1.0.0",
"psr/http-message": "~1.0.0",
"ezyang/htmlpurifier": "~4.6", "ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0", "cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", "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", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "81cb8a6aa01acac345059695e795fd68", "hash": "85aa1827f1afe6bc8ac2213443d06fbe",
"content-hash": "b36161ae5a97ecec89951d7fd5b2aa6a", "content-hash": "9fc9598aed2de7fd7f85836a8b1f8a44",
"packages": [ "packages": [
{ {
"name": "bower-asset/inputmask", "name": "bower-asset/inputmask",
"version": "3.3.7", "version": "3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/RobinHerbots/Inputmask.git", "url": "https://github.com/RobinHerbots/Inputmask.git",
"reference": "9835731cb78cac749734d94a1cb5bd70da4d3b10" "reference": "791d84990c4a98df1597e9d155be53a3725805dd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/9835731cb78cac749734d94a1cb5bd70da4d3b10", "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/791d84990c4a98df1597e9d155be53a3725805dd",
"reference": "9835731cb78cac749734d94a1cb5bd70da4d3b10", "reference": "791d84990c4a98df1597e9d155be53a3725805dd",
"shasum": null "shasum": null
}, },
"require": { "require": {
@ -34,30 +34,18 @@
"version": "2.2.4", "version": "2.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/components/jquery.git", "url": "https://github.com/jquery/jquery-dist.git",
"reference": "981036fcb56668433a7eb0d1e71190324b4574df" "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/components/jquery/zipball/981036fcb56668433a7eb0d1e71190324b4574df", "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72",
"reference": "981036fcb56668433a7eb0d1e71190324b4574df", "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72",
"shasum": "" "shasum": null
},
"type": "bower-asset-library",
"extra": {
"bower-asset-main": "dist/jquery.js",
"bower-asset-ignore": [
"package.json"
]
}, },
"type": "bower-asset",
"license": [ "license": [
"MIT" "MIT"
],
"keywords": [
"browser",
"javascript",
"jquery",
"library"
] ]
}, },
{ {
@ -72,21 +60,9 @@
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3",
"reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3",
"shasum": "" "shasum": null
}, },
"type": "bower-asset-library", "type": "bower-asset"
"extra": {
"bower-asset-main": "punycode.js",
"bower-asset-ignore": [
"coverage",
"tests",
".*",
"component.json",
"Gruntfile.js",
"node_modules",
"package.json"
]
}
}, },
{ {
"name": "bower-asset/yii2-pjax", "name": "bower-asset/yii2-pjax",
@ -100,24 +76,12 @@
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978",
"reference": "60728da6ade5879e807a49ce59ef9a72039b8978", "reference": "60728da6ade5879e807a49ce59ef9a72039b8978",
"shasum": "" "shasum": null
}, },
"require": { "require": {
"bower-asset/jquery": ">=1.8" "bower-asset/jquery": ">=1.8"
}, },
"type": "bower-asset-library", "type": "bower-asset",
"extra": {
"bower-asset-main": "./jquery.pjax.js",
"bower-asset-ignore": [
".travis.yml",
"Gemfile",
"Gemfile.lock",
"CONTRIBUTING.md",
"vendor/",
"script/",
"test/"
]
},
"license": [ "license": [
"MIT" "MIT"
] ]
@ -230,6 +194,56 @@
"time": "2017-06-03 02:28:16" "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", "name": "psr/simple-cache",
"version": "1.0.0", "version": "1.0.0",
"source": { "source": {
@ -540,16 +554,16 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v2.2.5", "version": "v2.2.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "27c2cd9d4abd2178b5b585fa2c3cca656d377c69" "reference": "c1cc52c242f17c4d52d9601159631da488fac7a4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/27c2cd9d4abd2178b5b585fa2c3cca656d377c69", "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c1cc52c242f17c4d52d9601159631da488fac7a4",
"reference": "27c2cd9d4abd2178b5b585fa2c3cca656d377c69", "reference": "c1cc52c242f17c4d52d9601159631da488fac7a4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -620,20 +634,20 @@
} }
], ],
"description": "A tool to automatically fix PHP code style", "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", "name": "gecko-packages/gecko-php-unit",
"version": "v2.1", "version": "v2.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git",
"reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e" "reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/5b9e9622c7efd3b22655270b80c03f9e52878a6e", "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/ab525fac9a9ffea219687f261b02008b18ebf2d1",
"reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e", "reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -642,24 +656,29 @@
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.4.3" "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", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"GeckoPackages\\PHPUnit\\": "src\\PHPUnit" "GeckoPackages\\PHPUnit\\": "src/PHPUnit"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"description": "Additional PHPUnit tests.", "description": "Additional PHPUnit asserts and constraints.",
"homepage": "https://github.com/GeckoPackages", "homepage": "https://github.com/GeckoPackages",
"keywords": [ "keywords": [
"extension", "extension",
"filesystem", "filesystem",
"phpunit" "phpunit"
], ],
"time": "2017-06-20 11:22:48" "time": "2017-08-23 07:39:54"
}, },
{ {
"name": "ircmaxell/password-compat", "name": "ircmaxell/password-compat",
@ -951,22 +970,22 @@
}, },
{ {
"name": "phpdocumentor/reflection-docblock", "name": "phpdocumentor/reflection-docblock",
"version": "3.2.2", "version": "3.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157" "reference": "86e24012a3139b42a7b71155cfaa325389f00f1f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/4aada1f93c72c35e22fb1383b47fee43b8f1d157", "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/86e24012a3139b42a7b71155cfaa325389f00f1f",
"reference": "4aada1f93c72c35e22fb1383b47fee43b8f1d157", "reference": "86e24012a3139b42a7b71155cfaa325389f00f1f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5", "php": "^7.0",
"phpdocumentor/reflection-common": "^1.0@dev", "phpdocumentor/reflection-common": "^1.0@dev",
"phpdocumentor/type-resolver": "^0.3.0", "phpdocumentor/type-resolver": "^0.4.0",
"webmozart/assert": "^1.0" "webmozart/assert": "^1.0"
}, },
"require-dev": { "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.", "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", "name": "phpdocumentor/type-resolver",
"version": "0.3.0", "version": "0.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git", "url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773" "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fb3933512008d8162b3cdf9e18dba9309b7c3773", "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
"reference": "fb3933512008d8162b3cdf9e18dba9309b7c3773", "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1039,7 +1058,7 @@
"email": "me@mikevanriel.com" "email": "me@mikevanriel.com"
} }
], ],
"time": "2017-06-03 08:32:36" "time": "2017-07-14 14:27:02"
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
@ -1307,16 +1326,16 @@
}, },
{ {
"name": "phpunit/php-token-stream", "name": "phpunit/php-token-stream",
"version": "2.0.0", "version": "2.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git", "url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b" "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ecb0b2cdaa0add708fe6f329ef65ae0c5225130b", "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0",
"reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b", "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1352,7 +1371,7 @@
"keywords": [ "keywords": [
"tokenizer" "tokenizer"
], ],
"time": "2017-08-03 14:17:41" "time": "2017-08-20 05:47:52"
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
@ -2105,20 +2124,20 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201" "reference": "d6596cb5022b6a0bd940eae54a1de78646a5fda6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/b0878233cb5c4391347e5495089c7af11b8e6201", "url": "https://api.github.com/repos/symfony/console/zipball/d6596cb5022b6a0bd940eae54a1de78646a5fda6",
"reference": "b0878233cb5c4391347e5495089c7af11b8e6201", "reference": "d6596cb5022b6a0bd940eae54a1de78646a5fda6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9", "php": "^5.5.9|>=7.0.8",
"symfony/debug": "~2.8|~3.0", "symfony/debug": "~2.8|~3.0",
"symfony/polyfill-mbstring": "~1.0" "symfony/polyfill-mbstring": "~1.0"
}, },
@ -2131,7 +2150,6 @@
"symfony/dependency-injection": "~3.3", "symfony/dependency-injection": "~3.3",
"symfony/event-dispatcher": "~2.8|~3.0", "symfony/event-dispatcher": "~2.8|~3.0",
"symfony/filesystem": "~2.8|~3.0", "symfony/filesystem": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0" "symfony/process": "~2.8|~3.0"
}, },
"suggest": { "suggest": {
@ -2170,24 +2188,24 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-07-29 21:27:59" "time": "2017-08-27 14:52:21"
}, },
{ {
"name": "symfony/debug", "name": "symfony/debug",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/debug.git", "url": "https://github.com/symfony/debug.git",
"reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13" "reference": "084d804fe35808eb2ef596ec83d85d9768aa6c9d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/7c13ae8ce1e2adbbd574fc39de7be498e1284e13", "url": "https://api.github.com/repos/symfony/debug/zipball/084d804fe35808eb2ef596ec83d85d9768aa6c9d",
"reference": "7c13ae8ce1e2adbbd574fc39de7be498e1284e13", "reference": "084d804fe35808eb2ef596ec83d85d9768aa6c9d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9", "php": "^5.5.9|>=7.0.8",
"psr/log": "~1.0" "psr/log": "~1.0"
}, },
"conflict": { "conflict": {
@ -2226,24 +2244,24 @@
], ],
"description": "Symfony Debug Component", "description": "Symfony Debug Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-07-28 15:27:31" "time": "2017-08-27 14:52:21"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" "reference": "54ca9520a00386f83bca145819ad3b619aaa2485"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54ca9520a00386f83bca145819ad3b619aaa2485",
"reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", "reference": "54ca9520a00386f83bca145819ad3b619aaa2485",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": "^5.5.9|>=7.0.8"
}, },
"conflict": { "conflict": {
"symfony/dependency-injection": "<3.3" "symfony/dependency-injection": "<3.3"
@ -2289,24 +2307,24 @@
], ],
"description": "Symfony EventDispatcher Component", "description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-06-09 14:53:08" "time": "2017-07-29 21:54:42"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "427987eb4eed764c3b6e38d52a0f87989e010676" "reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", "url": "https://api.github.com/repos/symfony/filesystem/zipball/b32a0e5f928d0fa3d1dd03c78d020777e50c10cb",
"reference": "427987eb4eed764c3b6e38d52a0f87989e010676", "reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": "^5.5.9|>=7.0.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2338,24 +2356,24 @@
], ],
"description": "Symfony Filesystem Component", "description": "Symfony Filesystem Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-07-11 07:17:58" "time": "2017-07-29 21:54:42"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" "reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", "url": "https://api.github.com/repos/symfony/finder/zipball/b2260dbc80f3c4198f903215f91a1ac7fe9fe09e",
"reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", "reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": "^5.5.9|>=7.0.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2387,24 +2405,24 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-06-01 21:01:25" "time": "2017-07-29 21:54:42"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/options-resolver.git", "url": "https://github.com/symfony/options-resolver.git",
"reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" "reference": "ee4e22978fe885b54ee5da8c7964f0a5301abfb6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ee4e22978fe885b54ee5da8c7964f0a5301abfb6",
"reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", "reference": "ee4e22978fe885b54ee5da8c7964f0a5301abfb6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": "^5.5.9|>=7.0.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2441,20 +2459,20 @@
"configuration", "configuration",
"options" "options"
], ],
"time": "2017-04-12 14:14:56" "time": "2017-07-29 21:54:42"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.4.0", "version": "v1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "f29dca382a6485c3cbe6379f0c61230167681937" "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
"reference": "f29dca382a6485c3cbe6379f0c61230167681937", "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2466,7 +2484,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.4-dev" "dev-master": "1.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -2500,20 +2518,20 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2017-06-09 14:24:12" "time": "2017-06-14 15:44:48"
}, },
{ {
"name": "symfony/polyfill-php54", "name": "symfony/polyfill-php54",
"version": "v1.4.0", "version": "v1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php54.git", "url": "https://github.com/symfony/polyfill-php54.git",
"reference": "7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789" "reference": "b7763422a5334c914ef0298ed21b253d25913a6e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789", "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/b7763422a5334c914ef0298ed21b253d25913a6e",
"reference": "7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789", "reference": "b7763422a5334c914ef0298ed21b253d25913a6e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2522,7 +2540,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.4-dev" "dev-master": "1.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -2558,20 +2576,20 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2017-06-09 08:25:21" "time": "2017-06-14 15:44:48"
}, },
{ {
"name": "symfony/polyfill-php55", "name": "symfony/polyfill-php55",
"version": "v1.4.0", "version": "v1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php55.git", "url": "https://github.com/symfony/polyfill-php55.git",
"reference": "94566239a7720cde0820f15f0cc348ddb51ba51d" "reference": "29b1381d66f16e0581aab0b9f678ccf073288f68"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/94566239a7720cde0820f15f0cc348ddb51ba51d", "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/29b1381d66f16e0581aab0b9f678ccf073288f68",
"reference": "94566239a7720cde0820f15f0cc348ddb51ba51d", "reference": "29b1381d66f16e0581aab0b9f678ccf073288f68",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2581,7 +2599,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.4-dev" "dev-master": "1.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -2614,20 +2632,20 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2017-06-09 08:25:21" "time": "2017-06-14 15:44:48"
}, },
{ {
"name": "symfony/polyfill-php70", "name": "symfony/polyfill-php70",
"version": "v1.4.0", "version": "v1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php70.git", "url": "https://github.com/symfony/polyfill-php70.git",
"reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874" "reference": "b6482e68974486984f59449ecea1fbbb22ff840f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/032fd647d5c11a9ceab8ee8747e13b5448e93874", "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/b6482e68974486984f59449ecea1fbbb22ff840f",
"reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874", "reference": "b6482e68974486984f59449ecea1fbbb22ff840f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2637,7 +2655,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.4-dev" "dev-master": "1.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -2673,20 +2691,20 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2017-06-09 14:24:12" "time": "2017-06-14 15:44:48"
}, },
{ {
"name": "symfony/polyfill-php72", "name": "symfony/polyfill-php72",
"version": "v1.4.0", "version": "v1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php72.git", "url": "https://github.com/symfony/polyfill-php72.git",
"reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c" "reference": "8abc9097f5001d310f0edba727469c988acc6ea7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8abc9097f5001d310f0edba727469c988acc6ea7",
"reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", "reference": "8abc9097f5001d310f0edba727469c988acc6ea7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2695,7 +2713,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.4-dev" "dev-master": "1.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -2728,24 +2746,24 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2017-06-09 08:25:21" "time": "2017-07-11 13:25:55"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" "reference": "b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", "url": "https://api.github.com/repos/symfony/process/zipball/b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0",
"reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", "reference": "b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": "^5.5.9|>=7.0.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2777,24 +2795,24 @@
], ],
"description": "Symfony Process Component", "description": "Symfony Process Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-07-13 13:05:09" "time": "2017-07-29 21:54:42"
}, },
{ {
"name": "symfony/stopwatch", "name": "symfony/stopwatch",
"version": "v3.3.6", "version": "v3.3.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/stopwatch.git", "url": "https://github.com/symfony/stopwatch.git",
"reference": "602a15299dc01556013b07167d4f5d3a60e90d15" "reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", "url": "https://api.github.com/repos/symfony/stopwatch/zipball/9a5610a8d6a50985a7be485c0ba745c22607beeb",
"reference": "602a15299dc01556013b07167d4f5d3a60e90d15", "reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.5.9" "php": "^5.5.9|>=7.0.8"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2826,7 +2844,7 @@
], ],
"description": "Symfony Stopwatch Component", "description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2017-04-12 14:14:56" "time": "2017-07-29 21:54:42"
}, },
{ {
"name": "theseer/tokenizer", "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 #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 #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 #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) - 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 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 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. 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 * `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 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. 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() protected function setHttpHeaders()
{ {
Yii::$app->getResponse()->getHeaders() $response = Yii::$app->getResponse();
->set('Pragma', 'public') $response->setHeader('Pragma', 'public');
->set('Expires', '0') $response->setHeader('Expires', '0');
->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') $response->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
->set('Content-Transfer-Encoding', 'binary') $response->setHeader('Content-Transfer-Encoding', 'binary');
->set('Content-type', $this->driver->getImageMimeType()); $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\CompositeUrlRule' => YII2_PATH . '/web/CompositeUrlRule.php',
'yii\web\ConflictHttpException' => YII2_PATH . '/web/ConflictHttpException.php', 'yii\web\ConflictHttpException' => YII2_PATH . '/web/ConflictHttpException.php',
'yii\web\Controller' => YII2_PATH . '/web/Controller.php', 'yii\web\Controller' => YII2_PATH . '/web/Controller.php',
'yii\web\Cookie' => YII2_PATH . '/web/Cookie.php', 'yii\http\Cookie' => YII2_PATH . '/http/Cookie.php',
'yii\web\CookieCollection' => YII2_PATH . '/web/CookieCollection.php', 'yii\http\CookieCollection' => YII2_PATH . '/http/CookieCollection.php',
'yii\web\DbSession' => YII2_PATH . '/web/DbSession.php', 'yii\web\DbSession' => YII2_PATH . '/web/DbSession.php',
'yii\web\ErrorAction' => YII2_PATH . '/web/ErrorAction.php', 'yii\web\ErrorAction' => YII2_PATH . '/web/ErrorAction.php',
'yii\web\ErrorHandler' => YII2_PATH . '/web/ErrorHandler.php', 'yii\web\ErrorHandler' => YII2_PATH . '/web/ErrorHandler.php',
'yii\web\ForbiddenHttpException' => YII2_PATH . '/web/ForbiddenHttpException.php', 'yii\web\ForbiddenHttpException' => YII2_PATH . '/web/ForbiddenHttpException.php',
'yii\web\GoneHttpException' => YII2_PATH . '/web/GoneHttpException.php', 'yii\web\GoneHttpException' => YII2_PATH . '/web/GoneHttpException.php',
'yii\web\GroupUrlRule' => YII2_PATH . '/web/GroupUrlRule.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\HtmlResponseFormatter' => YII2_PATH . '/web/HtmlResponseFormatter.php',
'yii\web\HttpException' => YII2_PATH . '/web/HttpException.php', 'yii\web\HttpException' => YII2_PATH . '/web/HttpException.php',
'yii\web\IdentityInterface' => YII2_PATH . '/web/IdentityInterface.php', 'yii\web\IdentityInterface' => YII2_PATH . '/web/IdentityInterface.php',
@ -314,7 +314,7 @@ return [
'yii\web\UnauthorizedHttpException' => YII2_PATH . '/web/UnauthorizedHttpException.php', 'yii\web\UnauthorizedHttpException' => YII2_PATH . '/web/UnauthorizedHttpException.php',
'yii\web\UnprocessableEntityHttpException' => YII2_PATH . '/web/UnprocessableEntityHttpException.php', 'yii\web\UnprocessableEntityHttpException' => YII2_PATH . '/web/UnprocessableEntityHttpException.php',
'yii\web\UnsupportedMediaTypeHttpException' => YII2_PATH . '/web/UnsupportedMediaTypeHttpException.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\UrlManager' => YII2_PATH . '/web/UrlManager.php',
'yii\web\UrlNormalizer' => YII2_PATH . '/web/UrlNormalizer.php', 'yii\web\UrlNormalizer' => YII2_PATH . '/web/UrlNormalizer.php',
'yii\web\UrlNormalizerRedirectException' => YII2_PATH . '/web/UrlNormalizerRedirectException.php', 'yii\web\UrlNormalizerRedirectException' => YII2_PATH . '/web/UrlNormalizerRedirectException.php',

1
framework/composer.json

@ -70,6 +70,7 @@
"psr/log": "~1.0.2", "psr/log": "~1.0.2",
"yiisoft/yii2-composer": "~2.0.4", "yiisoft/yii2-composer": "~2.0.4",
"psr/simple-cache": "~1.0.0", "psr/simple-cache": "~1.0.0",
"psr/http-message": "~1.0.0",
"ezyang/htmlpurifier": "~4.6", "ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0", "cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", "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) public function addCorsHeaders($response, $headers)
{ {
if (empty($headers) === false) { if (empty($headers) === false) {
$responseHeaders = $response->getHeaders();
foreach ($headers as $field => $value) { 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(); $response = Yii::$app->getResponse();
if ($etag !== null) { if ($etag !== null) {
$response->getHeaders()->set('Etag', $etag); $response->setHeader('Etag', $etag);
} }
$cacheValid = $this->validateCache($lastModified, $etag); $cacheValid = $this->validateCache($lastModified, $etag);
// https://tools.ietf.org/html/rfc7232#section-4.1 // https://tools.ietf.org/html/rfc7232#section-4.1
if ($lastModified !== null && (!$cacheValid || ($cacheValid && $etag === null))) { 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) { if ($cacheValid) {
$response->setStatusCode(304); $response->setStatusCode(304);
@ -192,10 +192,8 @@ class HttpCache extends ActionFilter
session_cache_limiter($this->sessionCacheLimiter); session_cache_limiter($this->sessionCacheLimiter);
} }
$headers = Yii::$app->getResponse()->getHeaders();
if ($this->cacheControlHeader !== null) { 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) 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]; $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])) { if (isset($data[$name]) && is_array($data[$name])) {
$response->{$name}->fromArray(array_merge($data[$name], $response->{$name}->toArray())); $response->{$name}->fromArray(array_merge($data[$name], $response->{$name}->toArray()));
} }
@ -256,9 +261,14 @@ class PageCache extends ActionFilter
} }
$data['dynamicPlaceholders'] = $this->dynamicPlaceholders; $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, 'headers', $data);
$this->insertResponseCollectionIntoData($response, 'cookies', $data); $this->insertResponseCollectionIntoData($response, 'cookies', $data);
$this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency); $this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency);
@ -281,7 +291,8 @@ class PageCache extends ActionFilter
return; return;
} }
$all = $response->{$collectionName}->toArray(); $collection = $response->{$collectionName};
$all = is_array($collection) ? $collection : $collection->toArray();
if (is_array($this->{$property})) { if (is_array($this->{$property})) {
$filtered = []; $filtered = [];
foreach ($this->{$property} as $name) { 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) public function addRateLimitHeaders($response, $limit, $remaining, $reset)
{ {
if ($this->enableRateLimitHeaders) { if ($this->enableRateLimitHeaders) {
$response->getHeaders() $response->setHeader('X-Rate-Limit-Limit', $limit);
->set('X-Rate-Limit-Limit', $limit) $response->setHeader('X-Rate-Limit-Remaining', $remaining);
->set('X-Rate-Limit-Remaining', $remaining) $response->setHeader('X-Rate-Limit-Reset', $reset);
->set('X-Rate-Limit-Reset', $reset);
} }
} }
} }

2
framework/filters/VerbFilter.php

@ -101,7 +101,7 @@ class VerbFilter extends Behavior
if (!in_array($verb, $allowed)) { if (!in_array($verb, $allowed)) {
$event->isValid = false; $event->isValid = false;
// https://tools.ietf.org/html/rfc2616#section-14.7 // 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) . '.'); 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) 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) public function authenticate($user, $request, $response)
{ {
$authHeader = $request->getHeaders()->get('Authorization'); $authHeader = $request->getHeaderLine('Authorization');
if ($authHeader !== null && preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) { if ($authHeader !== null && preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) {
$identity = $user->loginByAccessToken($matches[1], get_class($this)); $identity = $user->loginByAccessToken($matches[1], get_class($this));
if ($identity === null) { if ($identity === null) {
@ -56,6 +56,6 @@ class HttpBearerAuth extends AuthMethod
*/ */
public function challenge($response) 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/ * @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. * 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/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\web; namespace yii\http;
use ArrayIterator; use ArrayIterator;
use Yii; 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/ * @license http://www.yiiframework.com/license/
*/ */
namespace yii\web; namespace yii\http;
use ArrayIterator; use ArrayIterator;
use Yii; 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/ * @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\BaseObject;
use yii\base\InvalidArgumentException;
use yii\di\Instance;
use yii\helpers\Html; use yii\helpers\Html;
/** /**
@ -15,46 +19,59 @@ use yii\helpers\Html;
* *
* You can call [[getInstance()]] to retrieve the instance of an uploaded file, * You can call [[getInstance()]] to retrieve the instance of an uploaded file,
* and then use [[saveAs()]] to save it on the server. * and then use [[saveAs()]] to save it on the server.
* You may also query other information about the file, including [[name]], * You may also query other information about the file, including [[clientFilename]],
* [[tempName]], [[type]], [[size]] and [[error]]. * [[tempFilename]], [[clientMediaType]], [[size]] and [[error]].
* *
* For more details and usage information on UploadedFile, see the [guide article on handling uploads](guide:input-file-upload). * 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 $baseName Original file base name. This property is read-only.
* @property string $extension File extension. 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 * @property bool $hasError Whether there is an error with the uploaded file. Check [[error]] for detailed
* error code information. This property is read-only. * error code information. This property is read-only.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0 * @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. * @var string the path of the uploaded file on the server.
* Note, this is a temporary file which will be automatically deleted by PHP * Note, this is a temporary file which will be automatically deleted by PHP
* after the current request is processed. * 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"). * @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. * 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. * 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 * @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. * @var int an error code describing the status of this file uploading.
* @see http://www.php.net/manual/en/features.file-upload.errors.php * @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; private static $_files;
@ -67,7 +84,7 @@ class UploadedFile extends BaseObject
*/ */
public function __toString() public function __toString()
{ {
return $this->name; return $this->clientFilename;
} }
/** /**
@ -160,9 +177,10 @@ class UploadedFile extends BaseObject
{ {
if ($this->error == UPLOAD_ERR_OK) { if ($this->error == UPLOAD_ERR_OK) {
if ($deleteTempFile) { if ($deleteTempFile) {
return move_uploaded_file($this->tempName, $file); $this->moveTo($file);
} elseif (is_uploaded_file($this->tempName)) { return true;
return copy($this->tempName, $file); } elseif (is_uploaded_file($this->tempFilename)) {
return copy($this->tempFilename, $file);
} }
} }
return false; return false;
@ -174,7 +192,7 @@ class UploadedFile extends BaseObject
public function getBaseName() public function getBaseName()
{ {
// https://github.com/yiisoft/yii2/issues/11012 // 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'); return mb_substr($pathInfo, 1, mb_strlen($pathInfo, '8bit'), '8bit');
} }
@ -183,7 +201,7 @@ class UploadedFile extends BaseObject
*/ */
public function getExtension() 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) { } elseif ((int) $errors !== UPLOAD_ERR_NO_FILE) {
self::$_files[$key] = [ self::$_files[$key] = [
'name' => $names, 'clientFilename' => $names,
'tempName' => $tempNames, 'tempFilename' => $tempNames,
'type' => $types, 'clientMediaType' => $types,
'size' => $sizes, 'size' => $sizes,
'error' => $errors, '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"; $links[] = "<$url>; rel=$rel";
} }
$this->response->getHeaders() $this->response->setHeader($this->totalCountHeader, $pagination->totalCount);
->set($this->totalCountHeader, $pagination->totalCount) $this->response->setHeader($this->pageCountHeader, $pagination->getPageCount());
->set($this->pageCountHeader, $pagination->getPageCount()) $this->response->setHeader($this->currentPageHeader, $pagination->getPage() + 1);
->set($this->currentPageHeader, $pagination->getPage() + 1) $this->response->setHeader($this->perPageHeader, $pagination->pageSize);
->set($this->perPageHeader, $pagination->pageSize) $this->response->setHeader('Link', implode(', ', $links));
->set('Link', implode(', ', $links));
} }
/** /**

26
framework/validators/FileValidator.php

@ -12,7 +12,7 @@ use yii\helpers\FileHelper;
use yii\helpers\Html; use yii\helpers\Html;
use yii\helpers\Json; use yii\helpers\Json;
use yii\web\JsExpression; use yii\web\JsExpression;
use yii\web\UploadedFile; use yii\http\UploadedFile;
/** /**
* FileValidator verifies if an attribute is receiving a valid uploaded file. * FileValidator verifies if an attribute is receiving a valid uploaded file.
@ -224,7 +224,7 @@ class FileValidator extends Validator
*/ */
protected function validateValue($value) 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, []]; return [$this->uploadRequired, []];
} }
@ -234,7 +234,7 @@ class FileValidator extends Validator
return [ return [
$this->tooBig, $this->tooBig,
[ [
'file' => $value->name, 'file' => $value->getClientFilename(),
'limit' => $this->getSizeLimit(), 'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
], ],
@ -243,35 +243,35 @@ class FileValidator extends Validator
return [ return [
$this->tooSmall, $this->tooSmall,
[ [
'file' => $value->name, 'file' => $value->getClientFilename(),
'limit' => $this->minSize, 'limit' => $this->minSize,
'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize), 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize),
], ],
]; ];
} elseif (!empty($this->extensions) && !$this->validateExtension($value)) { } 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)) { } 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; return null;
case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE: case UPLOAD_ERR_FORM_SIZE:
return [$this->tooBig, [ return [$this->tooBig, [
'file' => $value->name, 'file' => $value->getClientFilename(),
'limit' => $this->getSizeLimit(), 'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
]]; ]];
case UPLOAD_ERR_PARTIAL: 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; break;
case UPLOAD_ERR_NO_TMP_DIR: 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; break;
case UPLOAD_ERR_CANT_WRITE: 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; break;
case UPLOAD_ERR_EXTENSION: 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; break;
default: default:
break; break;
@ -353,7 +353,7 @@ class FileValidator extends Validator
$extension = mb_strtolower($file->extension, 'UTF-8'); $extension = mb_strtolower($file->extension, 'UTF-8');
if ($this->checkExtensionByMimeType) { if ($this->checkExtensionByMimeType) {
$mimeType = FileHelper::getMimeType($file->tempName, null, false); $mimeType = FileHelper::getMimeType($file->tempFilename, null, false);
if ($mimeType === null) { if ($mimeType === null) {
return false; return false;
} }
@ -476,7 +476,7 @@ class FileValidator extends Validator
*/ */
protected function validateMimeType($file) protected function validateMimeType($file)
{ {
$fileMimeType = FileHelper::getMimeType($file->tempName); $fileMimeType = FileHelper::getMimeType($file->tempFilename);
foreach ($this->mimeTypes as $mimeType) { foreach ($this->mimeTypes as $mimeType) {
if ($mimeType === $fileMimeType) { if ($mimeType === $fileMimeType) {

14
framework/validators/ImageValidator.php

@ -8,7 +8,7 @@
namespace yii\validators; namespace yii\validators;
use Yii; use Yii;
use yii\web\UploadedFile; use yii\http\UploadedFile;
/** /**
* ImageValidator verifies if an attribute is receiving a valid image. * ImageValidator verifies if an attribute is receiving a valid image.
@ -130,30 +130,30 @@ class ImageValidator extends FileValidator
*/ */
protected function validateImage($image) protected function validateImage($image)
{ {
if (false === ($imageInfo = getimagesize($image->tempName))) { if (false === ($imageInfo = getimagesize($image->tempFilename))) {
return [$this->notImage, ['file' => $image->name]]; return [$this->notImage, ['file' => $image->name]];
} }
[$width, $height] = $imageInfo; [$width, $height] = $imageInfo;
if ($width == 0 || $height == 0) { if ($width == 0 || $height == 0) {
return [$this->notImage, ['file' => $image->name]]; return [$this->notImage, ['file' => $image->getClientFilename()]];
} }
if ($this->minWidth !== null && $width < $this->minWidth) { 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) { 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) { 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) { 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; 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 // reset parameters of response to avoid interference with partially created response data
// in case the error occurred while sending the response. // in case the error occurred while sending the response.
$response->isSent = false; $response->isSent = false;
$response->stream = null; $response->bodyRange = null;
$response->data = null; $response->data = null;
$response->content = null; $response->setBody(null);
} else { } else {
$response = new Response(); $response = new Response();
} }

2
framework/web/HtmlResponseFormatter.php

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

4
framework/web/JsonResponseFormatter.php

@ -80,7 +80,7 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf
*/ */
protected function formatJson($response) 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) { if ($response->data !== null) {
$options = $this->encodeOptions; $options = $this->encodeOptions;
if ($this->prettyPrint) { if ($this->prettyPrint) {
@ -96,7 +96,7 @@ class JsonResponseFormatter extends Component implements ResponseFormatterInterf
*/ */
protected function formatJsonp($response) 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'])) { 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'])); $response->content = sprintf('%s(%s);', $response->data['callback'], Json::htmlEncode($response->data['data']));
} elseif ($response->data !== null) { } elseif ($response->data !== null) {

2
framework/web/MultipartFormDataParser.php

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

207
framework/web/Request.php

@ -7,8 +7,17 @@
namespace yii\web; namespace yii\web;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use Yii; use Yii;
use yii\base\InvalidConfigException; 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 * 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 * @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. * if no such header is sent. This property is read-only.
* @property array $eTags The entity tags. 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 * @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 * (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. * [[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 * @property bool $isSecureConnection If the request is sent via secure channel (https). This property is
* read-only. * read-only.
* @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is * @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 * @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. * mark. Note, the returned path info is already URL-decoded.
* @property int $port Port number for insecure requests. * @property int $port Port number for insecure requests.
@ -84,8 +94,10 @@ use yii\base\InvalidConfigException;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @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. * The name of the HTTP header for sending CSRF token.
*/ */
@ -170,9 +182,17 @@ class Request extends \yii\base\Request
*/ */
private $_cookies; 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. * Returns default message's headers, which should be present once [[headerCollection]] is instantiated.
* The header collection contains incoming HTTP headers. * @return string[][] an associative array of the message's headers.
* @return HeaderCollection the header collection
*/ */
public function getHeaders() protected function defaultHeaders()
{ {
if ($this->_headers === null) {
$this->_headers = new HeaderCollection();
if (function_exists('getallheaders')) { if (function_exists('getallheaders')) {
$headers = getallheaders(); $headers = getallheaders();
} elseif (function_exists('http_get_request_headers')) { } elseif (function_exists('http_get_request_headers')) {
$headers = http_get_request_headers(); $headers = http_get_request_headers();
} else { } else {
$headers = [];
foreach ($_SERVER as $name => $value) { foreach ($_SERVER as $name => $value) {
if (strncmp($name, 'HTTP_', 5) === 0) { if (strncmp($name, 'HTTP_', 5) === 0) {
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
$this->_headers->add($name, $value); $headers[$name] = $value;
} }
} }
return $this->_headers;
} }
foreach ($headers as $name => $value) { foreach ($headers as $name => $value) {
$this->_headers->add($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;
}
/**
* 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;
} }
return $this->_headers; $newInstance = clone $this;
$newInstance->setRequestTarget($requestTarget);
return $newInstance;
} }
/** /**
* Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE). * {@inheritdoc}
* @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
* The value returned is turned into upper case.
*/ */
public function getMethod() public function getMethod()
{ {
if ($this->_method === null) {
if (isset($_POST[$this->methodParam])) { if (isset($_POST[$this->methodParam])) {
return strtoupper($_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;
} }
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { /**
return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); * 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['REQUEST_METHOD'])) { /**
return strtoupper($_SERVER['REQUEST_METHOD']); * {@inheritdoc}
* @since 2.1.0
*/
public function withMethod($method)
{
if ($this->getMethod() === $method) {
return $this;
} }
return 'GET'; $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;
}
$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); (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; private $_rawBody;
/** /**
@ -353,7 +489,7 @@ class Request extends \yii\base\Request
public function getRawBody() public function getRawBody()
{ {
if ($this->_rawBody === null) { if ($this->_rawBody === null) {
$this->_rawBody = file_get_contents('php://input'); $this->_rawBody = $this->getBody()->__toString();
} }
return $this->_rawBody; return $this->_rawBody;
@ -925,7 +1061,7 @@ class Request extends \yii\base\Request
*/ */
public function getOrigin() 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() 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; return true;
} }
$trueToken = $this->getCsrfToken(); $trueToken = $this->getCsrfToken();
if ($clientSuppliedToken !== null) { if ($clientSuppliedToken !== null) {
@ -1467,4 +1604,18 @@ class Request extends \yii\base\Request
return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken); 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;
}
}
} }

277
framework/web/Response.php

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

5
framework/widgets/Pjax.php

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

11
tests/framework/captcha/CaptchaActionTest.php

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

13
tests/framework/filters/HttpCacheTest.php

@ -41,8 +41,7 @@ class HttpCacheTest extends \yiiunit\TestCase
}; };
$httpCache->beforeAction(null); $httpCache->beforeAction(null);
$response = Yii::$app->getResponse(); $response = Yii::$app->getResponse();
$this->assertFalse($response->getHeaders()->offsetExists('Pragma')); $this->assertFalse($response->hasHeader('Pragma'));
$this->assertNotSame($response->getHeaders()->get('Pragma'), '');
} }
/** /**
@ -88,7 +87,7 @@ class HttpCacheTest extends \yiiunit\TestCase
}; };
$httpCache->beforeAction(null); $httpCache->beforeAction(null);
$response = Yii::$app->getResponse(); $response = Yii::$app->getResponse();
$this->assertFalse($response->getHeaders()->offsetExists('ETag')); $this->assertFalse($response->hasHeader('ETag'));
$httpCache->etagSeed = function ($action, $params) { $httpCache->etagSeed = function ($action, $params) {
return ''; return '';
@ -96,9 +95,9 @@ class HttpCacheTest extends \yiiunit\TestCase
$httpCache->beforeAction(null); $httpCache->beforeAction(null);
$response = Yii::$app->getResponse(); $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->assertStringStartsWith('"', $etag);
$this->assertStringEndsWith('"', $etag); $this->assertStringEndsWith('"', $etag);
@ -107,9 +106,9 @@ class HttpCacheTest extends \yiiunit\TestCase
$httpCache->beforeAction(null); $httpCache->beforeAction(null);
$response = Yii::$app->getResponse(); $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->assertStringStartsWith('W/"', $etag);
$this->assertStringEndsWith('"', $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\ArrayHelper;
use yii\helpers\Json; use yii\helpers\Json;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Cookie; use yii\http\Cookie;
use yii\web\View; use yii\web\View;
use yiiunit\framework\caching\CacheTestCase; use yiiunit\framework\caching\CacheTestCase;
use yiiunit\TestCase; use yiiunit\TestCase;
@ -176,7 +176,7 @@ class PageCacheTest extends TestCase
if (isset($testCase['headers'])) { if (isset($testCase['headers'])) {
foreach (array_keys($testCase['headers']) as $name) { foreach (array_keys($testCase['headers']) as $name) {
$value = Yii::$app->security->generateRandomString(); $value = Yii::$app->security->generateRandomString();
Yii::$app->response->headers->add($name, $value); Yii::$app->response->addHeader($name, $value);
$headers[$name] = $value; $headers[$name] = $value;
} }
} }
@ -191,9 +191,9 @@ class PageCacheTest extends TestCase
// Metadata // Metadata
$metadata = [ $metadata = [
'format' => Yii::$app->response->format, 'format' => Yii::$app->response->format,
'version' => Yii::$app->response->version, 'protocolVersion' => Yii::$app->response->getProtocolVersion(),
'statusCode' => Yii::$app->response->statusCode, 'statusCode' => Yii::$app->response->getStatusCode(),
'statusText' => Yii::$app->response->statusText, 'reasonPhrase' => Yii::$app->response->getReasonPhrase(),
]; ];
if ($testCase['cacheable']) { if ($testCase['cacheable']) {
$this->assertNotEmpty($this->getInaccessibleProperty($filter->cache->handler, '_cache'), $testCase['name']); $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']); $this->assertSame($dynamic, $json['dynamic'], $testCase['name']);
// Metadata // Metadata
$this->assertSame($metadata['format'], Yii::$app->response->format, $testCase['name']); $this->assertSame($metadata['format'], Yii::$app->response->format, $testCase['name']);
$this->assertSame($metadata['version'], Yii::$app->response->version, $testCase['name']); $this->assertSame($metadata['protocolVersion'], Yii::$app->response->getProtocolVersion(), $testCase['name']);
$this->assertSame($metadata['statusCode'], Yii::$app->response->statusCode, $testCase['name']); $this->assertSame($metadata['statusCode'], Yii::$app->response->getStatusCode(), $testCase['name']);
$this->assertSame($metadata['statusText'], Yii::$app->response->statusText, $testCase['name']); $this->assertSame($metadata['reasonPhrase'], Yii::$app->response->getReasonPhrase(), $testCase['name']);
// Cookies // Cookies
if (isset($testCase['cookies'])) { if (isset($testCase['cookies'])) {
foreach ($testCase['cookies'] as $name => $expected) { foreach ($testCase['cookies'] as $name => $expected) {
@ -234,9 +234,9 @@ class PageCacheTest extends TestCase
// Headers // Headers
if (isset($testCase['headers'])) { if (isset($testCase['headers'])) {
foreach ($testCase['headers'] as $name => $expected) { 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) { 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) public function testHttpBearerAuth($token, $login)
{ {
Yii::$app->request->headers->set('Authorization', "Bearer $token"); Yii::$app->request->addHeader('Authorization', "Bearer $token");
$filter = ['class' => HttpBearerAuth::class]; $filter = ['class' => HttpBearerAuth::class];
$this->authOnly($token, $login, $filter, 'bearer-auth'); $this->authOnly($token, $login, $filter, 'bearer-auth');
$this->authOptional($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/ * @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\ModelStub;
use yiiunit\framework\web\stubs\VendorImage; use yiiunit\framework\web\stubs\VendorImage;
use yiiunit\TestCase; use yiiunit\TestCase;
/** /**
* @group web * @group http
*/ */
class UploadedFileTest extends TestCase class UploadedFileTest extends TestCase
{ {
@ -72,4 +76,38 @@ class UploadedFileTest extends TestCase
$this->assertInstanceOf(VendorImage::class, $vendorImage); $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) { foreach ($tests as $j => $test) {
[$request->pathInfo, $route] = $test; [$request->pathInfo, $route] = $test;
$params = $test[2] ?? []; $params = $test[2] ?? [];
$_POST['_METHOD'] = $test[3] ?? 'GET'; $request->setMethod($test[3] ?? 'GET');
$result = $rule->parseRequest($manager, $request); $result = $rule->parseRequest($manager, $request);
if ($route === false) { if ($route === false) {
$this->assertFalse($result, "Test#$i-$j: $name"); $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;
use yii\helpers\FileHelper; use yii\helpers\FileHelper;
use yii\validators\FileValidator; use yii\validators\FileValidator;
use yii\web\UploadedFile; use yii\http\UploadedFile;
use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\data\validators\models\FakedValidationModel;
use yiiunit\TestCase; use yiiunit\TestCase;
@ -124,7 +124,7 @@ class FileValidatorTest extends TestCase
'attr_files' => $this->createTestFiles( 'attr_files' => $this->createTestFiles(
[ [
[ [
'name' => 'test_up_1.txt', 'clientFilename' => 'test_up_1.txt',
'size' => 1024, 'size' => 1024,
], ],
[ [
@ -155,17 +155,17 @@ class FileValidatorTest extends TestCase
'attr_images' => $this->createTestFiles( 'attr_images' => $this->createTestFiles(
[ [
[ [
'name' => 'image.png', 'clientFilename' => 'image.png',
'size' => 1024, 'size' => 1024,
'type' => 'image/png', 'clientMediaType' => 'image/png',
], ],
[ [
'name' => 'image.png', 'clientFilename' => 'image.png',
'size' => 1024, 'size' => 1024,
'type' => 'image/png', 'clientMediaType' => 'image/png',
], ],
[ [
'name' => 'text.txt', 'clientFilename' => 'text.txt',
'size' => 1024, 'size' => 1024,
], ],
] ]
@ -182,14 +182,14 @@ class FileValidatorTest extends TestCase
'attr_images' => $this->createTestFiles( 'attr_images' => $this->createTestFiles(
[ [
[ [
'name' => 'image.png', 'clientFilename' => 'image.png',
'size' => 1024, 'size' => 1024,
'type' => 'image/png', 'clientMediaType' => 'image/png',
], ],
[ [
'name' => 'image.png', 'clientFilename' => 'image.png',
'size' => 1024, 'size' => 1024,
'type' => 'image/png', 'clientMediaType' => 'image/png',
], ],
] ]
), ),
@ -203,7 +203,7 @@ class FileValidatorTest extends TestCase
'attr_image' => $this->createTestFiles( 'attr_image' => $this->createTestFiles(
[ [
[ [
'name' => 'text.txt', 'clientFilename' => 'text.txt',
'size' => 1024, 'size' => 1024,
], ],
] ]
@ -235,7 +235,7 @@ class FileValidatorTest extends TestCase
$files[] = ['no instance of UploadedFile']; $files[] = ['no instance of UploadedFile'];
continue; continue;
} }
$name = isset($param['name']) ? $param['name'] : $rndString(); $name = isset($param['clientFilename']) ? $param['clientFilename'] : $rndString();
$tempName = \Yii::getAlias('@yiiunit/runtime/validators/file/tmp/') . $name; $tempName = \Yii::getAlias('@yiiunit/runtime/validators/file/tmp/') . $name;
if (is_readable($tempName)) { if (is_readable($tempName)) {
$size = filesize($tempName); $size = filesize($tempName);
@ -245,23 +245,23 @@ class FileValidatorTest extends TestCase
$this->sizeToBytes(ini_get('upload_max_filesize')) $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; $error = isset($param['error']) ? $param['error'] : UPLOAD_ERR_OK;
if (count($params) == 1) { if (count($params) == 1) {
$error = empty($param) ? UPLOAD_ERR_NO_FILE : $error; $error = empty($param) ? UPLOAD_ERR_NO_FILE : $error;
return new UploadedFile([ return new UploadedFile([
'name' => $name, 'clientFilename' => $name,
'tempName' => $tempName, 'tempFilename' => $tempName,
'type' => $type, 'clientMediaType' => $type,
'size' => $size, 'size' => $size,
'error' => $error, 'error' => $error,
]); ]);
} }
$files[] = new UploadedFile([ $files[] = new UploadedFile([
'name' => $name, 'clientFilename' => $name,
'tempName' => $tempName, 'tempFilename' => $tempName,
'type' => $type, 'clientMediaType' => $type,
'size' => $size, 'size' => $size,
'error' => $error, 'error' => $error,
]); ]);
@ -279,9 +279,9 @@ class FileValidatorTest extends TestCase
$filePath = \Yii::getAlias('@yiiunit/framework/validators/data/mimeType/') . $fileName; $filePath = \Yii::getAlias('@yiiunit/framework/validators/data/mimeType/') . $fileName;
return new UploadedFile([ return new UploadedFile([
'name' => $fileName, 'clientFilename' => $fileName,
'tempName' => $filePath, 'tempFilename' => $filePath,
'type' => FileHelper::getMimeType($filePath), 'clientMediaType' => FileHelper::getMimeType($filePath),
'size' => filesize($filePath), 'size' => filesize($filePath),
'error' => UPLOAD_ERR_OK, 'error' => UPLOAD_ERR_OK,
]); ]);
@ -341,8 +341,8 @@ class FileValidatorTest extends TestCase
]); ]);
$m = FakedValidationModel::createWithAttributes( $m = FakedValidationModel::createWithAttributes(
[ [
'attr_jpg' => $this->createTestFiles([['name' => 'one.jpeg']]), 'attr_jpg' => $this->createTestFiles([['clientFilename' => 'one.jpeg']]),
'attr_exe' => $this->createTestFiles([['name' => 'bad.exe']]), 'attr_exe' => $this->createTestFiles([['clientFilename' => 'bad.exe']]),
] ]
); );
$val->validateAttribute($m, 'attr_jpg'); $val->validateAttribute($m, 'attr_jpg');
@ -357,7 +357,7 @@ class FileValidatorTest extends TestCase
$baseName = '飛兒樂團光茫'; $baseName = '飛兒樂團光茫';
/** @var UploadedFile $file */ /** @var UploadedFile $file */
$file = $this->createTestFiles([ $file = $this->createTestFiles([
['name' => $baseName . '.txt'], ['clientFilename' => $baseName . '.txt'],
]); ]);
$this->assertEquals($baseName, $file->getBaseName()); $this->assertEquals($baseName, $file->getBaseName());
} }
@ -414,7 +414,7 @@ class FileValidatorTest extends TestCase
return FakedValidationModel::createWithAttributes( return FakedValidationModel::createWithAttributes(
[ [
'attr_files' => $this->createTestFiles([ '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_files_empty' => $this->createTestFiles([[]]),
'attr_err_ini' => $this->createTestFiles([['error' => UPLOAD_ERR_INI_SIZE]]), '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 class ControllerTest extends TestCase
{ {
/**
* @var FakeController
*/
protected $controller;
public function testBindActionParams() public function testBindActionParams()
{ {
$aksi1 = new InlineAction('aksi1', $this->controller, 'actionAksi1'); $aksi1 = new InlineAction('aksi1', $this->controller, 'actionAksi1');
@ -61,18 +66,18 @@ class ControllerTest extends TestCase
public function testRedirect() public function testRedirect()
{ {
$_SERVER['REQUEST_URI'] = 'http://test-domain.com/'; $_SERVER['REQUEST_URI'] = 'http://test-domain.com/';
$this->assertEquals($this->controller->redirect('')->headers->get('location'), '/'); $this->assertEquals($this->controller->redirect('')->getHeader('location'), ['/']);
$this->assertEquals($this->controller->redirect('http://some-external-domain.com')->headers->get('location'), 'http://some-external-domain.com'); $this->assertEquals($this->controller->redirect('http://some-external-domain.com')->getHeader('location'), ['http://some-external-domain.com']);
$this->assertEquals($this->controller->redirect('/')->headers->get('location'), '/'); $this->assertEquals($this->controller->redirect('/')->getHeader('location'), ['/']);
$this->assertEquals($this->controller->redirect('/something-relative')->headers->get('location'), '/something-relative'); $this->assertEquals($this->controller->redirect('/something-relative')->getHeader('location'), ['/something-relative']);
$this->assertEquals($this->controller->redirect(['/'])->headers->get('location'), '/index.php?r='); $this->assertEquals($this->controller->redirect(['/'])->getHeader('location'), ['/index.php?r=']);
$this->assertEquals($this->controller->redirect(['view'])->headers->get('location'), '/index.php?r=fake%2Fview'); $this->assertEquals($this->controller->redirect(['view'])->getHeader('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'])->getHeader('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'])->getHeader('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'])->getHeader('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' => 3])->getHeader('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', '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' => 'äöüß!"§$%&/()'])->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(['//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() protected function setUp()

2
tests/framework/web/FormatterTest.php

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

40
tests/framework/web/ResponseTest.php

@ -55,11 +55,10 @@ class ResponseTest extends \yiiunit\TestCase
$this->assertEquals($expectedContent, $content); $this->assertEquals($expectedContent, $content);
$this->assertEquals(206, $this->response->statusCode); $this->assertEquals(206, $this->response->statusCode);
$headers = $this->response->headers; $this->assertEquals(['bytes'], $this->response->getHeader('Accept-Ranges'));
$this->assertEquals('bytes', $headers->get('Accept-Ranges')); $this->assertEquals(['bytes ' . $expectedHeader . '/' . StringHelper::byteLength($fullContent)], $this->response->getHeader('Content-Range'));
$this->assertEquals('bytes ' . $expectedHeader . '/' . StringHelper::byteLength($fullContent), $headers->get('Content-Range')); $this->assertEquals(['text/plain'], $this->response->getHeader('Content-Type'));
$this->assertEquals('text/plain', $headers->get('Content-Type')); $this->assertEquals(["$length"], $this->response->getHeader('Content-Length'));
$this->assertEquals("$length", $headers->get('Content-Length'));
} }
public function wrongRanges() public function wrongRanges()
@ -104,27 +103,26 @@ class ResponseTest extends \yiiunit\TestCase
static::assertEquals('test', $content); static::assertEquals('test', $content);
static::assertEquals(200, $this->response->statusCode); static::assertEquals(200, $this->response->statusCode);
$headers = $this->response->headers; static::assertEquals(['application/octet-stream'], $this->response->getHeader('Content-Type'));
static::assertEquals('application/octet-stream', $headers->get('Content-Type')); static::assertEquals(['attachment; filename="test.txt"'], $this->response->getHeader('Content-Disposition'));
static::assertEquals('attachment; filename="test.txt"', $headers->get('Content-Disposition')); static::assertEquals([4], $this->response->getHeader('Content-Length'));
static::assertEquals(4, $headers->get('Content-Length'));
} }
public function testRedirect() public function testRedirect()
{ {
$_SERVER['REQUEST_URI'] = 'http://test-domain.com/'; $_SERVER['REQUEST_URI'] = 'http://test-domain.com/';
$this->assertEquals($this->response->redirect('')->headers->get('location'), '/'); $this->assertEquals($this->response->redirect('')->getHeader('location'), ['/']);
$this->assertEquals($this->response->redirect('http://some-external-domain.com')->headers->get('location'), 'http://some-external-domain.com'); $this->assertEquals($this->response->redirect('http://some-external-domain.com')->getHeader('location'), ['http://some-external-domain.com']);
$this->assertEquals($this->response->redirect('/')->headers->get('location'), '/'); $this->assertEquals($this->response->redirect('/')->getHeader('location'), ['/']);
$this->assertEquals($this->response->redirect('/something-relative')->headers->get('location'), '/something-relative'); $this->assertEquals($this->response->redirect('/something-relative')->getHeader('location'), ['/something-relative']);
$this->assertEquals($this->response->redirect(['/'])->headers->get('location'), '/index.php?r='); $this->assertEquals($this->response->redirect(['/'])->getHeader('location'), ['/index.php?r=']);
$this->assertEquals($this->response->redirect(['view'])->headers->get('location'), '/index.php?r=view'); $this->assertEquals($this->response->redirect(['view'])->getHeader('location'), ['/index.php?r=view']);
$this->assertEquals($this->response->redirect(['/controller'])->headers->get('location'), '/index.php?r=controller'); $this->assertEquals($this->response->redirect(['/controller'])->getHeader('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'])->getHeader('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'])->getHeader('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' => 3])->getHeader('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', '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' => 'äöüß!"§$%&/()'])->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(['//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 // matching pathinfo GET request
$_SERVER['REQUEST_METHOD'] = 'GET'; $request->setMethod('GET');
$request->pathInfo = 'post/123/this+is+sample'; $request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request); $result = $manager->parseRequest($request);
$this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result);
// matching pathinfo PUT/POST request // matching pathinfo PUT/POST request
$_SERVER['REQUEST_METHOD'] = 'PUT'; $request->setMethod('PUT');
$request->pathInfo = 'post/123/this+is+sample'; $request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request); $result = $manager->parseRequest($request);
$this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); $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'; $request->pathInfo = 'post/123/this+is+sample';
$result = $manager->parseRequest($request); $result = $manager->parseRequest($request);
$this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); $this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result);
// no wrong matching // no wrong matching
$_SERVER['REQUEST_METHOD'] = 'POST'; $request->setMethod('POST');
$request->pathInfo = 'POST/GET'; $request->pathInfo = 'POST/GET';
$result = $manager->parseRequest($request); $result = $manager->parseRequest($request);
$this->assertEquals(['post/get', []], $result); $this->assertEquals(['post/get', []], $result);
@ -339,7 +339,5 @@ class UrlManagerParseUrlTest extends TestCase
], \yii\web\Application::class); ], \yii\web\Application::class);
$this->assertEquals('/app/post/delete?id=123', $manager->createUrl(['post/delete', 'id' => 123])); $this->assertEquals('/app/post/delete?id=123', $manager->createUrl(['post/delete', 'id' => 123]));
$this->destroyApplication(); $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\Component;
use yii\base\NotSupportedException; use yii\base\NotSupportedException;
use yii\rbac\PhpManager; use yii\rbac\PhpManager;
use yii\web\Cookie; use yii\http\Cookie;
use yii\web\CookieCollection; use yii\http\CookieCollection;
use yii\web\ForbiddenHttpException; use yii\web\ForbiddenHttpException;
use yii\web\IdentityInterface; use yii\web\IdentityInterface;
use yiiunit\TestCase; use yiiunit\TestCase;
@ -159,6 +159,7 @@ class UserTest extends TestCase
]); ]);
Yii::$app->user->setReturnUrl(null); Yii::$app->user->setReturnUrl(null);
} }
public function testLoginRequired() public function testLoginRequired()
{ {
$appConfig = [ $appConfig = [
@ -185,7 +186,6 @@ class UserTest extends TestCase
$this->assertEquals('normal', $user->getReturnUrl()); $this->assertEquals('normal', $user->getReturnUrl());
$this->assertTrue(Yii::$app->response->getIsRedirection()); $this->assertTrue(Yii::$app->response->getIsRedirection());
$this->reset(); $this->reset();
Yii::$app->request->setUrl('ajax'); Yii::$app->request->setUrl('ajax');
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';

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

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

Loading…
Cancel
Save