From 734f9f220a79bff62fac631c8f971b6e1954f99f Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Fri, 31 Aug 2012 14:44:59 -0500 Subject: [PATCH] [zendframework/zf2#2284][ZF2-507] Updated README - Notice about Date header --- .coveralls.yml | 3 + .gitattributes | 6 + .gitignore | 14 + .php_cs | 43 + .travis.yml | 35 + CONTRIBUTING.md | 229 +++ LICENSE.txt | 27 + README.md | 9 + composer.json | 38 + phpunit.xml.dist | 56 + phpunit.xml.travis | 56 + src/AbstractMessage.php | 108 ++ src/Client.php | 1293 +++++++++++++++++ src/Client/Adapter/AdapterInterface.php | 65 + src/Client/Adapter/Curl.php | 502 +++++++ .../Adapter/Exception/ExceptionInterface.php | 21 + .../Exception/InitializationException.php | 19 + .../Exception/InvalidArgumentException.php | 22 + .../Adapter/Exception/OutOfRangeException.php | 22 + .../Adapter/Exception/RuntimeException.php | 22 + .../Adapter/Exception/TimeoutException.php | 21 + src/Client/Adapter/Proxy.php | 267 ++++ src/Client/Adapter/Socket.php | 614 ++++++++ src/Client/Adapter/StreamInterface.php | 33 + src/Client/Adapter/Test.php | 217 +++ src/Client/Cookies.php | 503 +++++++ src/Client/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidArgumentException.php | 22 + src/Client/Exception/OutOfRangeException.php | 17 + src/Client/Exception/RuntimeException.php | 22 + src/ClientStatic.php | 109 ++ src/Cookies.php | 344 +++++ src/Exception/ExceptionInterface.php | 15 + src/Exception/InvalidArgumentException.php | 16 + src/Exception/OutOfRangeException.php | 15 + src/Exception/RuntimeException.php | 16 + src/Header/AbstractAccept.php | 446 ++++++ src/Header/AbstractDate.php | 234 +++ src/Header/AbstractLocation.php | 150 ++ src/Header/Accept.php | 119 ++ .../FieldValuePart/AbstractFieldValuePart.php | 90 ++ .../FieldValuePart/AcceptFieldValuePart.php | 48 + .../FieldValuePart/CharsetFieldValuePart.php | 33 + .../FieldValuePart/EncodingFieldValuePart.php | 33 + .../FieldValuePart/LanguageFieldValuePart.php | 39 + src/Header/AcceptCharset.php | 81 ++ src/Header/AcceptEncoding.php | 83 ++ src/Header/AcceptLanguage.php | 114 ++ src/Header/AcceptRanges.php | 67 + src/Header/Age.php | 105 ++ src/Header/Allow.php | 184 +++ src/Header/AuthenticationInfo.php | 52 + src/Header/Authorization.php | 52 + src/Header/CacheControl.php | 239 +++ src/Header/Connection.php | 125 ++ src/Header/ContentDisposition.php | 52 + src/Header/ContentEncoding.php | 52 + src/Header/ContentLanguage.php | 52 + src/Header/ContentLength.php | 52 + src/Header/ContentLocation.php | 32 + src/Header/ContentMD5.php | 52 + src/Header/ContentRange.php | 52 + src/Header/ContentType.php | 52 + src/Header/Cookie.php | 119 ++ src/Header/Date.php | 32 + src/Header/Etag.php | 52 + src/Header/Exception/ExceptionInterface.php | 16 + .../Exception/InvalidArgumentException.php | 18 + src/Header/Exception/RuntimeException.php | 18 + src/Header/Expect.php | 52 + src/Header/Expires.php | 32 + src/Header/From.php | 52 + src/Header/GenericHeader.php | 139 ++ src/Header/GenericMultiHeader.php | 43 + src/Header/HeaderInterface.php | 19 + src/Header/Host.php | 52 + src/Header/IfMatch.php | 52 + src/Header/IfModifiedSince.php | 32 + src/Header/IfNoneMatch.php | 52 + src/Header/IfRange.php | 52 + src/Header/IfUnmodifiedSince.php | 32 + src/Header/KeepAlive.php | 52 + src/Header/LastModified.php | 32 + src/Header/Location.php | 34 + src/Header/MaxForwards.php | 52 + src/Header/MultipleHeaderInterface.php | 16 + src/Header/Pragma.php | 52 + src/Header/ProxyAuthenticate.php | 65 + src/Header/ProxyAuthorization.php | 52 + src/Header/Range.php | 52 + src/Header/Referer.php | 49 + src/Header/Refresh.php | 52 + src/Header/RetryAfter.php | 112 ++ src/Header/Server.php | 52 + src/Header/SetCookie.php | 520 +++++++ src/Header/TE.php | 52 + src/Header/Trailer.php | 52 + src/Header/TransferEncoding.php | 52 + src/Header/Upgrade.php | 52 + src/Header/UserAgent.php | 52 + src/Header/Vary.php | 52 + src/Header/Via.php | 52 + src/Header/WWWAuthenticate.php | 65 + src/Header/Warning.php | 52 + src/HeaderLoader.php | 81 ++ src/Headers.php | 461 ++++++ src/PhpEnvironment/Request.php | 559 +++++++ src/PhpEnvironment/Response.php | 104 ++ src/Request.php | 500 +++++++ src/Response.php | 518 +++++++ src/Response/Stream.php | 277 ++++ test/Client/CommonHttpTests.php | 1049 +++++++++++++ test/Client/CurlTest.php | 326 +++++ test/Client/ProxyAdapterTest.php | 113 ++ test/Client/SocketKeepaliveTest.php | 43 + test/Client/SocketPersistentTest.php | 44 + test/Client/SocketTest.php | 270 ++++ test/Client/StaticClientTest.php | 122 ++ test/Client/StaticTest.php | 613 ++++++++ test/Client/TestAdapterTest.php | 219 +++ test/Client/UseCaseTest.php | 110 ++ .../_files/ZF2098-multibytepostdata.txt | 21 + .../Client/_files/ZF4238-zerolineresponse.txt | 3 + .../_files/ZF7038-multipartarrayrequest.txt | 30 + .../_files/ZF9404-doubleContentLength.php | 8 + test/Client/_files/staticFile.jpg | Bin 0 -> 2570 bytes test/Client/_files/testCookies.php | 11 + .../_files/testExceptionOnReadTimeout.php | 16 + test/Client/_files/testGetData.php | 11 + test/Client/_files/testHeaders.php | 0 test/Client/_files/testHttpAuth.php | 22 + .../testMultibyteChunkedResponseZF6218.php | 24 + test/Client/_files/testPatchData.php | 16 + test/Client/_files/testPostData.php | 11 + test/Client/_files/testRawPostData.php | 11 + test/Client/_files/testRedirections.php | 39 + .../_files/testRelativeRedirections.php | 25 + test/Client/_files/testResetParameters.php | 11 + test/Client/_files/testSimpleRequests.php | 1 + test/Client/_files/testStreamRequest.php | 11 + test/Client/_files/testUploads.php | 21 + test/ClientTest.php | 82 ++ test/Header/AcceptCharsetTest.php | 82 ++ test/Header/AcceptEncodingTest.php | 82 ++ test/Header/AcceptLanguageTest.php | 96 ++ test/Header/AcceptRangesTest.php | 50 + test/Header/AcceptTest.php | 372 +++++ test/Header/AgeTest.php | 53 + test/Header/AllowTest.php | 82 ++ test/Header/AuthenticationInfoTest.php | 52 + test/Header/AuthorizationTest.php | 52 + test/Header/CacheControlTest.php | 103 ++ test/Header/ConnectionTest.php | 59 + test/Header/ContentDispositionTest.php | 52 + test/Header/ContentEncodingTest.php | 52 + test/Header/ContentLanguageTest.php | 52 + test/Header/ContentLengthTest.php | 52 + test/Header/ContentLocationTest.php | 65 + test/Header/ContentMD5Test.php | 52 + test/Header/ContentRangeTest.php | 52 + test/Header/ContentTypeTest.php | 52 + test/Header/CookieTest.php | 644 ++++++++ test/Header/DateTest.php | 101 ++ test/Header/EtagTest.php | 52 + test/Header/ExpectTest.php | 52 + test/Header/ExpiresTest.php | 50 + test/Header/FromTest.php | 52 + test/Header/HostTest.php | 52 + test/Header/IfMatchTest.php | 52 + test/Header/IfModifiedSinceTest.php | 51 + test/Header/IfNoneMatchTest.php | 52 + test/Header/IfRangeTest.php | 52 + test/Header/IfUnmodifiedSinceTest.php | 51 + test/Header/KeepAliveTest.php | 52 + test/Header/LastModifiedTest.php | 51 + test/Header/LocationTest.php | 126 ++ test/Header/MaxForwardsTest.php | 52 + test/Header/PragmaTest.php | 52 + test/Header/ProxyAuthenticateTest.php | 52 + test/Header/ProxyAuthorizationTest.php | 52 + test/Header/RangeTest.php | 52 + test/Header/RefererTest.php | 71 + test/Header/RefreshTest.php | 52 + test/Header/RetryAfterTest.php | 59 + test/Header/ServerTest.php | 52 + test/Header/SetCookieTest.php | 329 +++++ test/Header/TETest.php | 52 + test/Header/TrailerTest.php | 52 + test/Header/TransferEncodingTest.php | 52 + test/Header/UpgradeTest.php | 52 + test/Header/UserAgentTest.php | 52 + test/Header/VaryTest.php | 52 + test/Header/ViaTest.php | 52 + test/Header/WWWAuthenticateTest.php | 52 + test/Header/WarningTest.php | 52 + test/HeadersTest.php | 273 ++++ test/PhpEnvironment/RequestTest.php | 682 +++++++++ test/RequestTest.php | 254 ++++ test/Response/ResponseStreamTest.php | 112 ++ test/ResponseTest.php | 344 +++++ test/_files/body | 158 ++ test/_files/response_302 | Bin 0 -> 592 bytes test/_files/response_302_iis | Bin 0 -> 591 bytes test/_files/response_403_nomessage | 18 + test/_files/response_404 | 17 + test/_files/response_500 | 24 + test/_files/response_chunked | 191 +++ test/_files/response_chunked_case | 191 +++ test/_files/response_crlf | 174 +++ test/_files/response_deflate | Bin 0 -> 4064 bytes test/_files/response_deflate_iis | Bin 0 -> 8361 bytes test/_files/response_gzip | Bin 0 -> 4073 bytes test/_files/response_leadingws | 15 + test/_files/response_lfonly | 174 +++ test/_files/response_multibyte_body | 34 + test/_files/response_multiline_header | 19 + test/_files/response_unknown | 14 + test/_files/response_with_cookies | 13 + test/_files/response_with_single_cookie | 11 + test/bootstrap.php | 34 + 220 files changed, 22897 insertions(+) create mode 100644 .coveralls.yml create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .php_cs create mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 phpunit.xml.travis create mode 100644 src/AbstractMessage.php create mode 100644 src/Client.php create mode 100644 src/Client/Adapter/AdapterInterface.php create mode 100644 src/Client/Adapter/Curl.php create mode 100644 src/Client/Adapter/Exception/ExceptionInterface.php create mode 100644 src/Client/Adapter/Exception/InitializationException.php create mode 100644 src/Client/Adapter/Exception/InvalidArgumentException.php create mode 100644 src/Client/Adapter/Exception/OutOfRangeException.php create mode 100644 src/Client/Adapter/Exception/RuntimeException.php create mode 100644 src/Client/Adapter/Exception/TimeoutException.php create mode 100644 src/Client/Adapter/Proxy.php create mode 100644 src/Client/Adapter/Socket.php create mode 100644 src/Client/Adapter/StreamInterface.php create mode 100644 src/Client/Adapter/Test.php create mode 100644 src/Client/Cookies.php create mode 100644 src/Client/Exception/ExceptionInterface.php create mode 100644 src/Client/Exception/InvalidArgumentException.php create mode 100644 src/Client/Exception/OutOfRangeException.php create mode 100644 src/Client/Exception/RuntimeException.php create mode 100644 src/ClientStatic.php create mode 100644 src/Cookies.php create mode 100644 src/Exception/ExceptionInterface.php create mode 100644 src/Exception/InvalidArgumentException.php create mode 100644 src/Exception/OutOfRangeException.php create mode 100644 src/Exception/RuntimeException.php create mode 100644 src/Header/AbstractAccept.php create mode 100644 src/Header/AbstractDate.php create mode 100644 src/Header/AbstractLocation.php create mode 100644 src/Header/Accept.php create mode 100644 src/Header/Accept/FieldValuePart/AbstractFieldValuePart.php create mode 100644 src/Header/Accept/FieldValuePart/AcceptFieldValuePart.php create mode 100644 src/Header/Accept/FieldValuePart/CharsetFieldValuePart.php create mode 100644 src/Header/Accept/FieldValuePart/EncodingFieldValuePart.php create mode 100644 src/Header/Accept/FieldValuePart/LanguageFieldValuePart.php create mode 100644 src/Header/AcceptCharset.php create mode 100644 src/Header/AcceptEncoding.php create mode 100644 src/Header/AcceptLanguage.php create mode 100644 src/Header/AcceptRanges.php create mode 100644 src/Header/Age.php create mode 100644 src/Header/Allow.php create mode 100644 src/Header/AuthenticationInfo.php create mode 100644 src/Header/Authorization.php create mode 100644 src/Header/CacheControl.php create mode 100644 src/Header/Connection.php create mode 100644 src/Header/ContentDisposition.php create mode 100644 src/Header/ContentEncoding.php create mode 100644 src/Header/ContentLanguage.php create mode 100644 src/Header/ContentLength.php create mode 100644 src/Header/ContentLocation.php create mode 100644 src/Header/ContentMD5.php create mode 100644 src/Header/ContentRange.php create mode 100644 src/Header/ContentType.php create mode 100644 src/Header/Cookie.php create mode 100644 src/Header/Date.php create mode 100644 src/Header/Etag.php create mode 100644 src/Header/Exception/ExceptionInterface.php create mode 100644 src/Header/Exception/InvalidArgumentException.php create mode 100644 src/Header/Exception/RuntimeException.php create mode 100644 src/Header/Expect.php create mode 100644 src/Header/Expires.php create mode 100644 src/Header/From.php create mode 100644 src/Header/GenericHeader.php create mode 100644 src/Header/GenericMultiHeader.php create mode 100644 src/Header/HeaderInterface.php create mode 100644 src/Header/Host.php create mode 100644 src/Header/IfMatch.php create mode 100644 src/Header/IfModifiedSince.php create mode 100644 src/Header/IfNoneMatch.php create mode 100644 src/Header/IfRange.php create mode 100644 src/Header/IfUnmodifiedSince.php create mode 100644 src/Header/KeepAlive.php create mode 100644 src/Header/LastModified.php create mode 100644 src/Header/Location.php create mode 100644 src/Header/MaxForwards.php create mode 100644 src/Header/MultipleHeaderInterface.php create mode 100644 src/Header/Pragma.php create mode 100644 src/Header/ProxyAuthenticate.php create mode 100644 src/Header/ProxyAuthorization.php create mode 100644 src/Header/Range.php create mode 100644 src/Header/Referer.php create mode 100644 src/Header/Refresh.php create mode 100644 src/Header/RetryAfter.php create mode 100644 src/Header/Server.php create mode 100644 src/Header/SetCookie.php create mode 100644 src/Header/TE.php create mode 100644 src/Header/Trailer.php create mode 100644 src/Header/TransferEncoding.php create mode 100644 src/Header/Upgrade.php create mode 100644 src/Header/UserAgent.php create mode 100644 src/Header/Vary.php create mode 100644 src/Header/Via.php create mode 100644 src/Header/WWWAuthenticate.php create mode 100644 src/Header/Warning.php create mode 100644 src/HeaderLoader.php create mode 100644 src/Headers.php create mode 100644 src/PhpEnvironment/Request.php create mode 100644 src/PhpEnvironment/Response.php create mode 100644 src/Request.php create mode 100644 src/Response.php create mode 100644 src/Response/Stream.php create mode 100644 test/Client/CommonHttpTests.php create mode 100644 test/Client/CurlTest.php create mode 100644 test/Client/ProxyAdapterTest.php create mode 100644 test/Client/SocketKeepaliveTest.php create mode 100644 test/Client/SocketPersistentTest.php create mode 100644 test/Client/SocketTest.php create mode 100644 test/Client/StaticClientTest.php create mode 100644 test/Client/StaticTest.php create mode 100644 test/Client/TestAdapterTest.php create mode 100644 test/Client/UseCaseTest.php create mode 100644 test/Client/_files/ZF2098-multibytepostdata.txt create mode 100644 test/Client/_files/ZF4238-zerolineresponse.txt create mode 100644 test/Client/_files/ZF7038-multipartarrayrequest.txt create mode 100644 test/Client/_files/ZF9404-doubleContentLength.php create mode 100644 test/Client/_files/staticFile.jpg create mode 100644 test/Client/_files/testCookies.php create mode 100644 test/Client/_files/testExceptionOnReadTimeout.php create mode 100644 test/Client/_files/testGetData.php create mode 100644 test/Client/_files/testHeaders.php create mode 100644 test/Client/_files/testHttpAuth.php create mode 100644 test/Client/_files/testMultibyteChunkedResponseZF6218.php create mode 100644 test/Client/_files/testPatchData.php create mode 100644 test/Client/_files/testPostData.php create mode 100644 test/Client/_files/testRawPostData.php create mode 100644 test/Client/_files/testRedirections.php create mode 100644 test/Client/_files/testRelativeRedirections.php create mode 100644 test/Client/_files/testResetParameters.php create mode 100644 test/Client/_files/testSimpleRequests.php create mode 100644 test/Client/_files/testStreamRequest.php create mode 100644 test/Client/_files/testUploads.php create mode 100644 test/ClientTest.php create mode 100644 test/Header/AcceptCharsetTest.php create mode 100644 test/Header/AcceptEncodingTest.php create mode 100644 test/Header/AcceptLanguageTest.php create mode 100644 test/Header/AcceptRangesTest.php create mode 100644 test/Header/AcceptTest.php create mode 100644 test/Header/AgeTest.php create mode 100644 test/Header/AllowTest.php create mode 100644 test/Header/AuthenticationInfoTest.php create mode 100644 test/Header/AuthorizationTest.php create mode 100644 test/Header/CacheControlTest.php create mode 100644 test/Header/ConnectionTest.php create mode 100644 test/Header/ContentDispositionTest.php create mode 100644 test/Header/ContentEncodingTest.php create mode 100644 test/Header/ContentLanguageTest.php create mode 100644 test/Header/ContentLengthTest.php create mode 100644 test/Header/ContentLocationTest.php create mode 100644 test/Header/ContentMD5Test.php create mode 100644 test/Header/ContentRangeTest.php create mode 100644 test/Header/ContentTypeTest.php create mode 100644 test/Header/CookieTest.php create mode 100644 test/Header/DateTest.php create mode 100644 test/Header/EtagTest.php create mode 100644 test/Header/ExpectTest.php create mode 100644 test/Header/ExpiresTest.php create mode 100644 test/Header/FromTest.php create mode 100644 test/Header/HostTest.php create mode 100644 test/Header/IfMatchTest.php create mode 100644 test/Header/IfModifiedSinceTest.php create mode 100644 test/Header/IfNoneMatchTest.php create mode 100644 test/Header/IfRangeTest.php create mode 100644 test/Header/IfUnmodifiedSinceTest.php create mode 100644 test/Header/KeepAliveTest.php create mode 100644 test/Header/LastModifiedTest.php create mode 100644 test/Header/LocationTest.php create mode 100644 test/Header/MaxForwardsTest.php create mode 100644 test/Header/PragmaTest.php create mode 100644 test/Header/ProxyAuthenticateTest.php create mode 100644 test/Header/ProxyAuthorizationTest.php create mode 100644 test/Header/RangeTest.php create mode 100644 test/Header/RefererTest.php create mode 100644 test/Header/RefreshTest.php create mode 100644 test/Header/RetryAfterTest.php create mode 100644 test/Header/ServerTest.php create mode 100644 test/Header/SetCookieTest.php create mode 100644 test/Header/TETest.php create mode 100644 test/Header/TrailerTest.php create mode 100644 test/Header/TransferEncodingTest.php create mode 100644 test/Header/UpgradeTest.php create mode 100644 test/Header/UserAgentTest.php create mode 100644 test/Header/VaryTest.php create mode 100644 test/Header/ViaTest.php create mode 100644 test/Header/WWWAuthenticateTest.php create mode 100644 test/Header/WarningTest.php create mode 100644 test/HeadersTest.php create mode 100644 test/PhpEnvironment/RequestTest.php create mode 100644 test/RequestTest.php create mode 100644 test/Response/ResponseStreamTest.php create mode 100644 test/ResponseTest.php create mode 100644 test/_files/body create mode 100644 test/_files/response_302 create mode 100644 test/_files/response_302_iis create mode 100644 test/_files/response_403_nomessage create mode 100644 test/_files/response_404 create mode 100644 test/_files/response_500 create mode 100644 test/_files/response_chunked create mode 100644 test/_files/response_chunked_case create mode 100644 test/_files/response_crlf create mode 100644 test/_files/response_deflate create mode 100644 test/_files/response_deflate_iis create mode 100644 test/_files/response_gzip create mode 100644 test/_files/response_leadingws create mode 100644 test/_files/response_lfonly create mode 100644 test/_files/response_multibyte_body create mode 100644 test/_files/response_multiline_header create mode 100644 test/_files/response_unknown create mode 100644 test/_files/response_with_cookies create mode 100644 test/_files/response_with_single_cookie create mode 100644 test/bootstrap.php diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000000..53bda829c8 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +coverage_clover: clover.xml +json_path: coveralls-upload.json +src_dir: src diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..85dc9a8c82 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/test export-ignore +/vendor export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +.php_cs export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..4cac0a2180 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.buildpath +.DS_Store +.idea +.project +.settings/ +.*.sw* +.*.un~ +nbproject +tmp/ + +clover.xml +coveralls-upload.json +phpunit.xml +vendor diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000000..bf4b799f3e --- /dev/null +++ b/.php_cs @@ -0,0 +1,43 @@ +notPath('TestAsset') + ->notPath('_files') + ->filter(function (SplFileInfo $file) { + if (strstr($file->getPath(), 'compatibility')) { + return false; + } + }); +$config = Symfony\CS\Config\Config::create(); +$config->level(null); +$config->fixers( + array( + 'braces', + 'duplicate_semicolon', + 'elseif', + 'empty_return', + 'encoding', + 'eof_ending', + 'function_call_space', + 'function_declaration', + 'indentation', + 'join_function', + 'line_after_namespace', + 'linefeed', + 'lowercase_keywords', + 'parenthesis', + 'multiple_use', + 'method_argument_space', + 'object_operator', + 'php_closing_tag', + 'psr0', + 'remove_lines_between_uses', + 'short_tag', + 'standardize_not_equal', + 'trailing_spaces', + 'unused_use', + 'visibility', + 'whitespacy_lines', + ) +); +$config->finder($finder); +return $config; diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..fe909ecb11 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +sudo: false + +language: php + +matrix: + fast_finish: true + include: + - php: 5.5 + - php: 5.6 + env: + - EXECUTE_TEST_COVERALLS=true + - EXECUTE_CS_CHECK=true + - php: 7 + - php: hhvm + allow_failures: + - php: 7 + - php: hhvm + +notifications: + irc: "irc.freenode.org#zftalk.dev" + email: false + +before_install: + - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi + +install: + - composer install --no-interaction --prefer-source + +script: + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit -c phpunit.xml.travis --coverage-clover clover.xml ; fi + - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then ./vendor/bin/phpunit -c phpunit.xml.travis ; fi + - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run --config-file=.php_cs ; fi + +after_script: + - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/coveralls ; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..6fd509f25f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,229 @@ +# CONTRIBUTING + +## RESOURCES + +If you wish to contribute to Zend Framework, please be sure to +read/subscribe to the following resources: + + - [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards) + - [Contributor's Guide](http://framework.zend.com/participate/contributor-guide) + - ZF Contributor's mailing list: + Archives: http://zend-framework-community.634137.n4.nabble.com/ZF-Contributor-f680267.html + Subscribe: zf-contributors-subscribe@lists.zend.com + - ZF Contributor's IRC channel: + #zftalk.dev on Freenode.net + +If you are working on new features or refactoring [create a proposal](https://github.com/zendframework/zend-http/issues/new). + +## Reporting Potential Security Issues + +If you have encountered a potential security vulnerability, please **DO NOT** report it on the public +issue tracker: send it to us at [zf-security@zend.com](mailto:zf-security@zend.com) instead. +We will work with you to verify the vulnerability and patch it as soon as possible. + +When reporting issues, please provide the following information: + +- Component(s) affected +- A description indicating how to reproduce the issue +- A summary of the security vulnerability and impact + +We request that you contact us via the email address above and give the project +contributors a chance to resolve the vulnerability and issue a new release prior +to any public exposure; this helps protect users and provides them with a chance +to upgrade and/or update in order to protect their applications. + +For sensitive email communications, please use [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc). + +## RUNNING TESTS + +> ### Note: testing versions prior to 2.4 +> +> This component originates with Zend Framework 2. During the lifetime of ZF2, +> testing infrastructure migrated from PHPUnit 3 to PHPUnit 4. In most cases, no +> changes were necessary. However, due to the migration, tests may not run on +> versions < 2.4. As such, you may need to change the PHPUnit dependency if +> attempting a fix on such a version. + +To run tests: + +- Clone the repository: + + ```console + $ git clone git@github.com:zendframework/zend-http.git + $ cd + ``` + +- Install dependencies via composer: + + ```console + $ curl -sS https://getcomposer.org/installer | php -- + $ ./composer.phar install + ``` + + If you don't have `curl` installed, you can also download `composer.phar` from https://getcomposer.org/ + +- Run the tests via `phpunit` and the provided PHPUnit config, like in this example: + + ```console + $ ./vendor/bin/phpunit + ``` + +You can turn on conditional tests with the phpunit.xml file. +To do so: + + - Copy `phpunit.xml.dist` file to `phpunit.xml` + - Edit `phpunit.xml` to enable any specific functionality you + want to test, as well as to provide test values to utilize. + +## Running Coding Standards Checks + +This component uses [php-cs-fixer](http://cs.sensiolabs.org/) for coding +standards checks, and provides configuration for our selected checks. +`php-cs-fixer` is installed by default via Composer. + +To run checks only: + +```console +$ ./vendor/bin/php-cs-fixer fix . -v --diff --dry-run --config-file=.php_cs +``` + +To have `php-cs-fixer` attempt to fix problems for you, omit the `--dry-run` +flag: + +```console +$ ./vendor/bin/php-cs-fixer fix . -v --diff --config-file=.php_cs +``` + +If you allow php-cs-fixer to fix CS issues, please re-run the tests to ensure +they pass, and make sure you add and commit the changes after verification. + +## Recommended Workflow for Contributions + +Your first step is to establish a public repository from which we can +pull your work into the master repository. We recommend using +[GitHub](https://github.com), as that is where the component is already hosted. + +1. Setup a [GitHub account](http://github.com/), if you haven't yet +2. Fork the repository (http://github.com/zendframework/zend-http) +3. Clone the canonical repository locally and enter it. + + ```console + $ git clone git://github.com:zendframework/zend-http.git + $ cd zend-http + ``` + +4. Add a remote to your fork; substitute your GitHub username in the command + below. + + ```console + $ git remote add {username} git@github.com:{username}/zend-http.git + $ git fetch {username} + ``` + +### Keeping Up-to-Date + +Periodically, you should update your fork or personal repository to +match the canonical ZF repository. Assuming you have setup your local repository +per the instructions above, you can do the following: + + +```console +$ git checkout master +$ git fetch origin +$ git rebase origin/master +# OPTIONALLY, to keep your remote up-to-date - +$ git push {username} master:master +``` + +If you're tracking other branches -- for example, the "develop" branch, where +new feature development occurs -- you'll want to do the same operations for that +branch; simply substitute "develop" for "master". + +### Working on a patch + +We recommend you do each new feature or bugfix in a new branch. This simplifies +the task of code review as well as the task of merging your changes into the +canonical repository. + +A typical workflow will then consist of the following: + +1. Create a new local branch based off either your master or develop branch. +2. Switch to your new local branch. (This step can be combined with the + previous step with the use of `git checkout -b`.) +3. Do some work, commit, repeat as necessary. +4. Push the local branch to your remote repository. +5. Send a pull request. + +The mechanics of this process are actually quite trivial. Below, we will +create a branch for fixing an issue in the tracker. + +```console +$ git checkout -b hotfix/9295 +Switched to a new branch 'hotfix/9295' +``` + +... do some work ... + + +```console +$ git commit +``` + +... write your log message ... + + +```console +$ git push {username} hotfix/9295:hotfix/9295 +Counting objects: 38, done. +Delta compression using up to 2 threads. +Compression objects: 100% (18/18), done. +Writing objects: 100% (20/20), 8.19KiB, done. +Total 20 (delta 12), reused 0 (delta 0) +To ssh://git@github.com/{username}/zend-http.git + b5583aa..4f51698 HEAD -> master +``` + +To send a pull request, you have two options. + +If using GitHub, you can do the pull request from there. Navigate to +your repository, select the branch you just created, and then select the +"Pull Request" button in the upper right. Select the user/organization +"zendframework" as the recipient. + +If using your own repository - or even if using GitHub - you can use `git +format-patch` to create a patchset for us to apply; in fact, this is +**recommended** for security-related patches. If you use `format-patch`, please +send the patches as attachments to: + +- zf-devteam@zend.com for patches without security implications +- zf-security@zend.com for security patches + +#### What branch to issue the pull request against? + +Which branch should you issue a pull request against? + +- For fixes against the stable release, issue the pull request against the + "master" branch. +- For new features, or fixes that introduce new elements to the public API (such + as new public methods or properties), issue the pull request against the + "develop" branch. + +### Branch Cleanup + +As you might imagine, if you are a frequent contributor, you'll start to +get a ton of branches both locally and on your remote. + +Once you know that your changes have been accepted to the master +repository, we suggest doing some cleanup of these branches. + +- Local branch cleanup + + ```console + $ git branch -d + ``` + +- Remote branch removal + + ```console + $ git push {username} : + ``` diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000..6eab5aa14f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2005-2015, Zend Technologies USA, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Zend Technologies USA, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..0566ee6586 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# zend-http + +`Zend\Http` is a primary foundational component of Zend Framework. Since much of +what PHP does is web-based, specifically HTTP, it makes sense to have a performant, +extensible, concise and consistent API to do all things HTTP. + + +- File issues at https://github.com/zendframework/zend-http/issues +- Documentation is at http://framework.zend.com/manual/current/en/index.html#zend-http diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000..04fd7e46d0 --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "zendframework/zend-http", + "description": "provides an easy interface for preforming Hyper-Text Transfer Protocol (HTTP) requests", + "license": "BSD-3-Clause", + "keywords": [ + "zf2", + "http" + ], + "homepage": "https://github.com/zendframework/zend-http", + "autoload": { + "psr-4": { + "Zend\\Http": "src/" + } + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-loader": "self.version", + "zendframework/zend-stdlib": "self.version", + "zendframework/zend-uri": "self.version", + "zendframework/zend-validator": "self.version" + }, + "extra": { + "branch-alias": { + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" + } + }, + "autoload-dev": { + "psr-4": { + "ZendTest\\Http\\": "test/" + } + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "satooshi/php-coveralls": "dev-master", + "phpunit/PHPUnit": "~4.0" + } +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000000..b5a3958e56 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,56 @@ + + + + + ./test/ + + + + + + disable + + + + + + ./src + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml.travis b/phpunit.xml.travis new file mode 100644 index 0000000000..b5a3958e56 --- /dev/null +++ b/phpunit.xml.travis @@ -0,0 +1,56 @@ + + + + + ./test/ + + + + + + disable + + + + + + ./src + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AbstractMessage.php b/src/AbstractMessage.php new file mode 100644 index 0000000000..ddfb47993a --- /dev/null +++ b/src/AbstractMessage.php @@ -0,0 +1,108 @@ +version = $version; + return $this; + } + + /** + * Return the HTTP version for this request + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Provide an alternate Parameter Container implementation for headers in this object, + * (this is NOT the primary API for value setting, for that see getHeaders()) + * + * @see getHeaders() + * @param Headers $headers + * @return AbstractMessage + */ + public function setHeaders(Headers $headers) + { + $this->headers = $headers; + return $this; + } + + /** + * Return the header container responsible for headers + * + * @return Headers + */ + public function getHeaders() + { + if ($this->headers === null || is_string($this->headers)) { + // this is only here for fromString lazy loading + $this->headers = (is_string($this->headers)) ? Headers::fromString($this->headers) : new Headers(); + } + + return $this->headers; + } + + /** + * Allow PHP casting of this object + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } +} \ No newline at end of file diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000000..1881aa089d --- /dev/null +++ b/src/Client.php @@ -0,0 +1,1293 @@ + 5, + 'strictredirects' => false, + 'useragent' => 'Zend\Http\Client', + 'timeout' => 10, + 'adapter' => 'Zend\Http\Client\Adapter\Socket', + 'httpversion' => Request::VERSION_11, + 'storeresponse' => true, + 'keepalive' => false, + 'outputstream' => false, + 'encodecookies' => true, + 'rfc3986strict' => false + ); + + /** + * Fileinfo magic database resource + * + * This variable is populated the first time _detectFileMimeType is called + * and is then reused on every call to this method + * + * @var resource + */ + protected static $fileInfoDb = null; + + /** + * Constructor + * + * @param string $uri + * @param array|Traversable $options + */ + public function __construct($uri = null, $options = null) + { + if ($uri !== null) { + $this->setUri($uri); + } + if ($options !== null) { + $this->setOptions($options); + } + } + + /** + * Set configuration parameters for this HTTP client + * + * @param array|Traversable $options + * @return Client + * @throws Client\Exception\InvalidArgumentException + */ + public function setOptions($options = array()) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (!is_array($options)) { + throw new Client\Exception\InvalidArgumentException('Config parameter is not valid'); + } + + /** Config Key Normalization */ + foreach ($options as $k => $v) { + $this->config[str_replace(array('-', '_', ' ', '.'), '', strtolower($k))] = $v; // replace w/ normalized + } + + // Pass configuration options to the adapter if it exists + if ($this->adapter instanceof Client\Adapter\AdapterInterface) { + $this->adapter->setOptions($options); + } + + return $this; + } + + /** + * Load the connection adapter + * + * While this method is not called more than one for a client, it is + * separated from ->request() to preserve logic and readability + * + * @param Client\Adapter\AdapterInterface|string $adapter + * @return Client + * @throws Client\Exception\InvalidArgumentException + */ + public function setAdapter($adapter) + { + if (is_string($adapter)) { + if (!class_exists($adapter)) { + throw new Client\Exception\InvalidArgumentException('Unable to locate adapter class "' . $adapter . '"'); + } + $adapter = new $adapter; + } + + if (! $adapter instanceof Client\Adapter\AdapterInterface) { + throw new Client\Exception\InvalidArgumentException('Passed adapter is not a HTTP connection adapter'); + } + + $this->adapter = $adapter; + $config = $this->config; + unset($config['adapter']); + $this->adapter->setOptions($config); + return $this; + } + + /** + * Load the connection adapter + * + * @return Client\Adapter\AdapterInterface $adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Set request + * + * @param Request $request + * @return Client + */ + public function setRequest(Request $request) + { + $this->request = $request; + return $this; + } + + /** + * Get Request + * + * @return Request + */ + public function getRequest() + { + if (empty($this->request)) { + $this->request = new Request(); + } + return $this->request; + } + + /** + * Set response + * + * @param Response $response + * @return Client + */ + public function setResponse(Response $response) + { + $this->response = $response; + return $this; + } + + /** + * Get Response + * + * @return Response + */ + public function getResponse() + { + if (empty($this->response)) { + $this->response = new Response(); + } + return $this->response; + } + + + /** + * Get the last request (as a string) + * + * @return string + */ + public function getLastRawRequest() + { + return $this->lastRawRequest; + } + + /** + * Get the last response (as a string) + * + * @return string + */ + public function getLastRawResponse() + { + return $this->lastRawResponse; + } + + /** + * Get the redirections count + * + * @return integer + */ + public function getRedirectionsCount() + { + return $this->redirectCounter; + } + + /** + * Set Uri (to the request) + * + * @param string|Http $uri + * @return Client + */ + public function setUri($uri) + { + if (!empty($uri)) { + $this->getRequest()->setUri($uri); + + // Set auth if username and password has been specified in the uri + if ($this->getUri()->getUser() && $this->getUri()->getPassword()) { + $this->setAuth($this->getUri()->getUser(), $this->getUri()->getPassword()); + } + + // We have no ports, set the defaults + if (! $this->getUri()->getPort()) { + $this->getUri()->setPort(($this->getUri()->getScheme() == 'https' ? 443 : 80)); + } + } + return $this; + } + + /** + * Get uri (from the request) + * + * @return Zend\Uri\Http + */ + public function getUri() + { + return $this->getRequest()->getUri(); + } + + /** + * Set the HTTP method (to the request) + * + * @param string $method + * @return Client + */ + public function setMethod($method) + { + $method = $this->getRequest()->setMethod($method)->getMethod(); + + if (($method == Request::METHOD_POST || $method == Request::METHOD_PUT || + $method == Request::METHOD_DELETE || $method == Request::METHOD_PATCH) + && empty($this->encType)) { + $this->setEncType(self::ENC_URLENCODED); + } + + return $this; + } + + /** + * Get the HTTP method + * + * @return string + */ + public function getMethod() + { + return $this->getRequest()->getMethod(); + } + + /** + * Set the encoding type and the boundary (if any) + * + * @param string $encType + * @param string $boundary + * @return Client + */ + public function setEncType($encType, $boundary = null) + { + if (!empty($encType)) { + if (!empty($boundary)) { + $this->encType = $encType . "; boundary={$boundary}"; + } else { + $this->encType = $encType; + } + } + return $this; + } + + /** + * Get the encoding type + * + * @return string + */ + public function getEncType() + { + return $this->encType; + } + + /** + * Set raw body (for advanced use cases) + * + * @param string $body + * @return Client + */ + public function setRawBody($body) + { + $this->getRequest()->setContent($body); + return $this; + } + + /** + * Set the POST parameters + * + * @param array $post + * @return Client + */ + public function setParameterPost(array $post) + { + $this->getRequest()->getPost()->fromArray($post); + return $this; + } + + /** + * Set the GET parameters + * + * @param array $query + * @return Client + */ + public function setParameterGet(array $query) + { + $this->getRequest()->getQuery()->fromArray($query); + return $this; + } + + /** + * Return the current cookies + * + * @return array + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Get the cookie Id (name+domain+path) + * + * @param Header\SetCookie|Header\Cookie $cookie + * @return string|boolean + */ + protected function getCookieId($cookie) + { + if (($cookie instanceof Header\SetCookie) || ($cookie instanceof Header\Cookie)) { + return $cookie->getName() . $cookie->getDomain() . $cookie->getPath(); + } + return false; + } + + /** + * Add a cookie + * + * @param array|ArrayIterator|Header\SetCookie|string $cookie + * @param string $value + * @param string $version + * @param string $maxAge + * @param string $domain + * @param string $expire + * @param string $path + * @param boolean $secure + * @param boolean $httponly + * @return Client + */ + public function addCookie($cookie, $value = null, $expire = null, $path = null, $domain = null, $secure = false, $httponly = true, $maxAge = null, $version = null) + { + if (is_array($cookie) || $cookie instanceof ArrayIterator) { + foreach ($cookie as $setCookie) { + if ($setCookie instanceof Header\SetCookie) { + $this->cookies[$this->getCookieId($setCookie)] = $setCookie; + } else { + throw new Exception\InvalidArgumentException('The cookie parameter is not a valid Set-Cookie type'); + } + } + } elseif ($cookie instanceof Header\SetCookie) { + $this->cookies[$this->getCookieId($cookie)] = $cookie; + } elseif (is_string($cookie) && $value !== null) { + $setCookie = new Header\SetCookie($cookie, $value, $expire, $path, $domain, $secure, $httponly, $maxAge, $version); + $this->cookies[$this->getCookieId($setCookie)] = $setCookie; + } else { + throw new Exception\InvalidArgumentException('Invalid parameter type passed as Cookie'); + } + return $this; + } + + /** + * Set an array of cookies + * + * @param array $cookies + * @return Client + */ + public function setCookies($cookies) + { + if (is_array($cookies)) { + $this->clearCookies(); + foreach ($cookies as $name => $value) { + $this->addCookie($name, $value); + } + } else { + throw new Exception\InvalidArgumentException('Invalid cookies passed as parameter, it must be an array'); + } + return $this; + } + + /** + * Clear all the cookies + */ + public function clearCookies() + { + $this->cookies = array(); + } + + /** + * Set the headers (for the request) + * + * @param Headers|array $headers + * @return Client + */ + public function setHeaders($headers) + { + if (is_array($headers)) { + $newHeaders = new Headers(); + $newHeaders->addHeaders($headers); + $this->getRequest()->setHeaders($newHeaders); + } elseif ($headers instanceof Headers) { + $this->getRequest()->setHeaders($headers); + } else { + throw new Exception\InvalidArgumentException('Invalid parameter headers passed'); + } + return $this; + } + + /** + * Check if exists the header type specified + * + * @param string $name + * @return boolean + */ + public function hasHeader($name) + { + $headers = $this->getRequest()->getHeaders(); + + if ($headers instanceof Headers) { + return $headers->has($name); + } + + return false; + } + + /** + * Get the header value of the request + * + * @param string $name + * @return string|boolean + */ + public function getHeader($name) + { + $headers = $this->getRequest()->getHeaders(); + + if ($headers instanceof Headers) { + if ($headers->get($name)) { + return $headers->get($name)->getFieldValue(); + } + } + return false; + } + + /** + * Set streaming for received data + * + * @param string|boolean $streamfile Stream file, true for temp file, false/null for no streaming + * @return \Zend\Http\Client + */ + public function setStream($streamfile = true) + { + $this->setOptions(array("outputstream" => $streamfile)); + return $this; + } + + /** + * Get status of streaming for received data + * @return boolean|string + */ + public function getStream() + { + return $this->config['outputstream']; + } + + /** + * Create temporary stream + * + * @return resource + */ + protected function openTempStream() + { + $this->streamName = $this->config['outputstream']; + + if (!is_string($this->streamName)) { + // If name is not given, create temp name + $this->streamName = tempnam( + isset($this->config['streamtmpdir']) ? $this->config['streamtmpdir'] : sys_get_temp_dir(), + 'Zend\Http\Client' + ); + } + + ErrorHandler::start(); + $fp = fopen($this->streamName, "w+b"); + $error = ErrorHandler::stop(); + if (false === $fp) { + if ($this->adapter instanceof Client\Adapter\AdapterInterface) { + $this->adapter->close(); + } + throw new Exception\RuntimeException("Could not open temp file {$this->streamName}", 0, $error); + } + + return $fp; + } + + /** + * Create a HTTP authentication "Authorization:" header according to the + * specified user, password and authentication method. + * + * @param string $user + * @param string $password + * @param string $type + * @return Client + */ + public function setAuth($user, $password, $type = self::AUTH_BASIC) + { + if (!defined('self::AUTH_' . strtoupper($type))) { + throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'"); + } + if (empty($user) || empty($password)) { + throw new Exception\InvalidArgumentException("The username and the password cannot be empty"); + } + + $this->auth = array ( + 'user' => $user, + 'password' => $password, + 'type' => $type + + ); + + return $this; + } + + /** + * Calculate the response value according to the HTTP authentication type + * + * @see http://www.faqs.org/rfcs/rfc2617.html + * @param string $user + * @param string $password + * @param string $type + * @param array $digest + * @return string|boolean + */ + protected function calcAuthDigest($user, $password, $type = self::AUTH_BASIC, $digest = array(), $entityBody = null) + { + if (!defined('self::AUTH_' . strtoupper($type))) { + throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'"); + } + $response = false; + switch (strtolower($type)) { + case self::AUTH_BASIC : + // In basic authentication, the user name cannot contain ":" + if (strpos($user, ':') !== false) { + throw new Exception\InvalidArgumentException("The user name cannot contain ':' in Basic HTTP authentication"); + } + $response = base64_encode($user . ':' . $password); + break; + case self::AUTH_DIGEST : + if (empty($digest)) { + throw new Exception\InvalidArgumentException("The digest cannot be empty"); + } + foreach ($digest as $key => $value) { + if (!defined('self::DIGEST_' . strtoupper($key))) { + throw new Exception\InvalidArgumentException("Invalid or not supported digest authentication parameter: '$key'"); + } + } + $ha1 = md5($user . ':' . $digest['realm'] . ':' . $password); + if (empty($digest['qop']) || strtolower($digest['qop']) == 'auth') { + $ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath()); + } elseif (strtolower($digest['qop']) == 'auth-int') { + if (empty($entityBody)) { + throw new Exception\InvalidArgumentException("I cannot use the auth-int digest authentication without the entity body"); + } + $ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath() . ':' . md5($entityBody)); + } + if (empty($digest['qop'])) { + $response = md5($ha1 . ':' . $digest['nonce'] . ':' . $ha2); + } else { + $response = md5($ha1 . ':' . $digest['nonce'] . ':' . $digest['nc'] + . ':' . $digest['cnonce'] . ':' . $digest['qoc'] . ':' . $ha2); + } + break; + } + return $response; + } + + /** + * Reset all the HTTP parameters (auth,cookies,request, response, etc) + * + * @param bool $clearCookies Also clear all valid cookies? (defaults to false) + * @return Client + */ + public function resetParameters($clearCookies = false) + { + $uri = $this->getUri(); + + $this->auth = null; + $this->streamName = null; + $this->encType = null; + $this->request = null; + $this->response = null; + + $this->setUri($uri); + + if ($clearCookies) { + $this->clearCookies(); + } + + return $this; + } + + /** + * Dispatch + * + * @param Stdlib\RequestInterface $request + * @param Stdlib\ResponseInterface $response + * @return Stdlib\ResponseInterface + */ + public function dispatch(Stdlib\RequestInterface $request, Stdlib\ResponseInterface $response = null) + { + $response = $this->send($request); + return $response; + } + + /** + * Send HTTP request + * + * @param Request $request + * @return Response + * @throws Exception\RuntimeException + */ + public function send(Request $request = null) + { + if ($request !== null) { + $this->setRequest($request); + } + + $this->redirectCounter = 0; + $response = null; + + // Make sure the adapter is loaded + if ($this->adapter == null) { + $this->setAdapter($this->config['adapter']); + } + + // Send the first request. If redirected, continue. + do { + // uri + $uri = $this->getUri(); + + // query + $query = $this->getRequest()->getQuery(); + + if (!empty($query)) { + $queryArray = $query->toArray(); + + if (!empty($queryArray)) { + $newUri = $uri->toString(); + $queryString = http_build_query($query); + + if ($this->config['rfc3986strict']) { + $queryString = str_replace('+', '%20', $queryString); + } + + if (strpos($newUri, '?') !== false) { + $newUri .= '&' . $queryString; + } else { + $newUri .= '?' . $queryString; + } + + $uri = new Http($newUri); + } + } + // If we have no ports, set the defaults + if (!$uri->getPort()) { + $uri->setPort($uri->getScheme() == 'https' ? 443 : 80); + } + + // method + $method = $this->getRequest()->getMethod(); + + // body + $body = $this->prepareBody(); + + // headers + $headers = $this->prepareHeaders($body, $uri); + + $secure = $uri->getScheme() == 'https'; + + // cookies + $cookie = $this->prepareCookies($uri->getHost(), $uri->getPath(), $secure); + if ($cookie->getFieldValue()) { + $headers['Cookie'] = $cookie->getFieldValue(); + } + + // check that adapter supports streaming before using it + if (is_resource($body) && !($this->adapter instanceof Client\Adapter\StreamInterface)) { + throw new Client\Exception\RuntimeException('Adapter does not support streaming'); + } + + // calling protected method to allow extending classes + // to wrap the interaction with the adapter + $response = $this->doRequest($uri, $method, $secure, $headers, $body); + + if (! $response) { + throw new Exception\RuntimeException('Unable to read response, or response is empty'); + } + + if ($this->config['storeresponse']) { + $this->lastRawResponse = $response; + } else { + $this->lastRawResponse = null; + } + + if ($this->config['outputstream']) { + $stream = $this->getStream(); + if (!is_resource($stream) && is_string($stream)) { + $stream = fopen($stream, 'r'); + } + if (!is_resource($stream)) { + $stream = $this->getUri()->toString(); + $stream = fopen($stream, 'r'); + } + $streamMetaData = stream_get_meta_data($stream); + if ($streamMetaData['seekable']) { + rewind($stream); + } + // cleanup the adapter + $this->adapter->setOutputStream(null); + $response = Response\Stream::fromStream($response, $stream); + $response->setStreamName($this->streamName); + if (!is_string($this->config['outputstream'])) { + // we used temp name, will need to clean up + $response->setCleanup(true); + } + } else { + $response = Response::fromString($response); + } + + // Get the cookies from response (if any) + $setCookie = $response->getCookie(); + if (!empty($setCookie)) { + $this->addCookie($setCookie); + } + + // If we got redirected, look for the Location header + if ($response->isRedirect() && ($response->getHeaders()->has('Location'))) { + + // Avoid problems with buggy servers that add whitespace at the + // end of some headers + $location = trim($response->getHeaders()->get('Location')->getFieldValue()); + + // Check whether we send the exact same request again, or drop the parameters + // and send a GET request + if ($response->getStatusCode() == 303 || + ((! $this->config['strictredirects']) && ($response->getStatusCode() == 302 || + $response->getStatusCode() == 301))) { + + $this->resetParameters(); + $this->setMethod(Request::METHOD_GET); + } + + // If we got a well formed absolute URI + if (($scheme = substr($location, 0, 6)) && + ($scheme == 'http:/' || $scheme == 'https:')) { + $this->setUri($location); + } else { + + // Split into path and query and set the query + if (strpos($location, '?') !== false) { + list($location, $query) = explode('?', $location, 2); + } else { + $query = ''; + } + $this->getUri()->setQuery($query); + + // Else, if we got just an absolute path, set it + if (strpos($location, '/') === 0) { + $this->getUri()->setPath($location); + // Else, assume we have a relative path + } else { + // Get the current path directory, removing any trailing slashes + $path = $this->getUri()->getPath(); + $path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); + $this->getUri()->setPath($path . '/' . $location); + } + } + ++$this->redirectCounter; + + } else { + // If we didn't get any location, stop redirecting + break; + } + + } while ($this->redirectCounter < $this->config['maxredirects']); + + $this->response = $response; + return $response; + } + + /** + * Set a file to upload (using a POST request) + * + * Can be used in two ways: + * + * 1. $data is null (default): $filename is treated as the name if a local file which + * will be read and sent. Will try to guess the content type using mime_content_type(). + * 2. $data is set - $filename is sent as the file name, but $data is sent as the file + * contents and no file is read from the file system. In this case, you need to + * manually set the Content-Type ($ctype) or it will default to + * application/octet-stream. + * + * @param string $filename Name of file to upload, or name to save as + * @param string $formname Name of form element to send as + * @param string $data Data to send (if null, $filename is read and sent) + * @param string $ctype Content type to use (if $data is set and $ctype is + * null, will be application/octet-stream) + * @return Client + * @throws Exception\RuntimeException + */ + public function setFileUpload($filename, $formname, $data = null, $ctype = null) + { + if ($data === null) { + ErrorHandler::start(); + $data = file_get_contents($filename); + $error = ErrorHandler::stop(); + if ($data === false) { + throw new Exception\RuntimeException("Unable to read file '{$filename}' for upload", 0, $error); + } + if (!$ctype) { + $ctype = $this->detectFileMimeType($filename); + } + } + + $this->getRequest()->getFiles()->set($filename, array( + 'formname' => $formname, + 'filename' => basename($filename), + 'ctype' => $ctype, + 'data' => $data + )); + + return $this; + } + + /** + * Remove a file to upload + * + * @param string $filename + * @return boolean + */ + public function removeFileUpload($filename) + { + $file = $this->getRequest()->getFiles()->get($filename); + if (!empty($file)) { + $this->getRequest()->getFiles()->set($filename, null); + return true; + } + return false; + } + + /** + * Prepare Cookies + * + * @param string $uri + * @param string $domain + * @param boolean $secure + * @return Header\Cookie|boolean + */ + protected function prepareCookies($domain, $path, $secure) + { + $validCookies = array(); + + if (!empty($this->cookies)) { + foreach ($this->cookies as $id => $cookie) { + if ($cookie->isExpired()) { + unset($this->cookies[$id]); + continue; + } + + if ($cookie->isValidForRequest($domain, $path, $secure)) { + // OAM hack some domains try to set the cookie multiple times + $validCookies[$cookie->getName()] = $cookie; + } + } + } + + $cookies = Header\Cookie::fromSetCookieArray($validCookies); + $cookies->setEncodeValue($this->config['encodecookies']); + + return $cookies; + } + + /** + * Prepare the request headers + * + * @return array + */ + protected function prepareHeaders($body, $uri) + { + $headers = array(); + + // Set the host header + if ($this->config['httpversion'] == Request::VERSION_11) { + $host = $uri->getHost(); + // If the port is not default, add it + if (!(($uri->getScheme() == 'http' && $uri->getPort() == 80) || + ($uri->getScheme() == 'https' && $uri->getPort() == 443))) { + $host .= ':' . $uri->getPort(); + } + + $headers['Host'] = $host; + } + + // Set the connection header + if (!$this->getRequest()->getHeaders()->has('Connection')) { + if (!$this->config['keepalive']) { + $headers['Connection'] = 'close'; + } + } + + // Set the Accept-encoding header if not set - depending on whether + // zlib is available or not. + if (! isset($this->headers['accept-encoding'])) { + if (function_exists('gzinflate')) { + $headers['Accept-encoding'] = 'gzip, deflate'; + } else { + $headers['Accept-encoding'] = 'identity'; + } + } + + + // Set the user agent header + if (!$this->getRequest()->getHeaders()->has('User-Agent') && isset($this->config['useragent'])) { + $headers['User-Agent'] = $this->config['useragent']; + } + + // Set HTTP authentication if needed + if (!empty($this->auth)) { + switch ($this->auth['type']) { + case self::AUTH_BASIC : + $auth = $this->calcAuthDigest($this->auth['user'], $this->auth['password'], $this->auth['type']); + if ($auth !== false) { + $headers['Authorization'] = 'Basic ' . $auth; + } + break; + case self::AUTH_DIGEST : + throw new Exception\RuntimeException("The digest authentication is not implemented yet"); + } + } + + // Content-type + $encType = $this->getEncType(); + if (!empty($encType)) { + $headers['Content-Type'] = $encType; + } + + if (!empty($body)) { + if (is_resource($body)) { + $fstat = fstat($body); + $headers['Content-Length'] = $fstat['size']; + } else { + $headers['Content-Length'] = strlen($body); + } + } + + // Merge the headers of the request (if any) + $requestHeaders = $this->getRequest()->getHeaders()->toArray(); + foreach ($requestHeaders as $key => $value) { + $headers[$key] = $value; + } + return $headers; + } + + + /** + * Prepare the request body (for PATCH, POST and PUT requests) + * + * @return string + * @throws \Zend\Http\Client\Exception\RuntimeException + */ + protected function prepareBody() + { + // According to RFC2616, a TRACE request should not have a body. + if ($this->getRequest()->isTrace()) { + return ''; + } + + $rawBody = $this->getRequest()->getContent(); + if (!empty($rawBody)) { + return $rawBody; + } + + $body = ''; + $totalFiles = 0; + + if (!$this->getRequest()->getHeaders()->has('Content-Type')) { + $totalFiles = count($this->getRequest()->getFiles()->toArray()); + // If we have files to upload, force encType to multipart/form-data + if ($totalFiles > 0) { + $this->setEncType(self::ENC_FORMDATA); + } + } else { + $this->setEncType($this->getHeader('Content-Type')); + } + + // If we have POST parameters or files, encode and add them to the body + if (count($this->getRequest()->getPost()->toArray()) > 0 || $totalFiles > 0) { + if (stripos($this->getEncType(), self::ENC_FORMDATA) === 0) { + $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); + $this->setEncType(self::ENC_FORMDATA, $boundary); + + // Get POST parameters and encode them + $params = self::flattenParametersArray($this->getRequest()->getPost()->toArray()); + foreach ($params as $pp) { + $body .= $this->encodeFormData($boundary, $pp[0], $pp[1]); + } + + // Encode files + foreach ($this->getRequest()->getFiles()->toArray() as $key => $file) { + $fhead = array('Content-Type' => $file['ctype']); + $body .= $this->encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); + } + $body .= "--{$boundary}--\r\n"; + } elseif (stripos($this->getEncType(), self::ENC_URLENCODED) === 0) { + // Encode body as application/x-www-form-urlencoded + $body = http_build_query($this->getRequest()->getPost()->toArray()); + } else { + throw new Client\Exception\RuntimeException("Cannot handle content type '{$this->encType}' automatically"); + } + } + + return $body; + } + + + /** + * Attempt to detect the MIME type of a file using available extensions + * + * This method will try to detect the MIME type of a file. If the fileinfo + * extension is available, it will be used. If not, the mime_magic + * extension which is deprecated but is still available in many PHP setups + * will be tried. + * + * If neither extension is available, the default application/octet-stream + * MIME type will be returned + * + * @param string $file File path + * @return string MIME type + */ + protected function detectFileMimeType($file) + { + $type = null; + + // First try with fileinfo functions + if (function_exists('finfo_open')) { + if (self::$fileInfoDb === null) { + ErrorHandler::start(); + self::$fileInfoDb = finfo_open(FILEINFO_MIME); + ErrorHandler::stop(); + } + + if (self::$fileInfoDb) { + $type = finfo_file(self::$fileInfoDb, $file); + } + + } elseif (function_exists('mime_content_type')) { + $type = mime_content_type($file); + } + + // Fallback to the default application/octet-stream + if (! $type) { + $type = 'application/octet-stream'; + } + + return $type; + } + + /** + * Encode data to a multipart/form-data part suitable for a POST request. + * + * @param string $boundary + * @param string $name + * @param mixed $value + * @param string $filename + * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary") + * @return string + */ + public function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) + { + $ret = "--{$boundary}\r\n" . + 'Content-Disposition: form-data; name="' . $name . '"'; + + if ($filename) { + $ret .= '; filename="' . $filename . '"'; + } + $ret .= "\r\n"; + + foreach ($headers as $hname => $hvalue) { + $ret .= "{$hname}: {$hvalue}\r\n"; + } + $ret .= "\r\n"; + $ret .= "{$value}\r\n"; + + return $ret; + } + + /** + * Convert an array of parameters into a flat array of (key, value) pairs + * + * Will flatten a potentially multi-dimentional array of parameters (such + * as POST parameters) into a flat array of (key, value) paris. In case + * of multi-dimentional arrays, square brackets ([]) will be added to the + * key to indicate an array. + * + * @since 1.9 + * + * @param array $parray + * @param string $prefix + * @return array + */ + protected function flattenParametersArray($parray, $prefix = null) + { + if (!is_array($parray)) { + return $parray; + } + + $parameters = array(); + + foreach ($parray as $name => $value) { + // Calculate array key + if ($prefix) { + if (is_int($name)) { + $key = $prefix . '[]'; + } else { + $key = $prefix . "[$name]"; + } + } else { + $key = $name; + } + + if (is_array($value)) { + $parameters = array_merge($parameters, $this->flattenParametersArray($value, $key)); + + } else { + $parameters[] = array($key, $value); + } + } + + return $parameters; + } + + /** + * Separating this from send method allows subclasses to wrap + * the interaction with the adapter + * + * @param Http $uri + * @param string $method + * @param boolean $secure + * @param array $headers + * @param string $body + * @return string the raw response + * @throws Exception\RuntimeException + */ + protected function doRequest(Http $uri, $method, $secure = false, $headers = array(), $body = '') + { + // Open the connection, send the request and read the response + $this->adapter->connect($uri->getHost(), $uri->getPort(), $secure); + + if ($this->config['outputstream']) { + if ($this->adapter instanceof Client\Adapter\StreamInterface) { + $stream = $this->openTempStream(); + $this->adapter->setOutputStream($stream); + } else { + throw new Exception\RuntimeException('Adapter does not support streaming'); + } + } + + // HTTP connection + $this->lastRawRequest = $this->adapter->write($method, + $uri, $this->config['httpversion'], $headers, $body); + + return $this->adapter->read(); + } +} diff --git a/src/Client/Adapter/AdapterInterface.php b/src/Client/Adapter/AdapterInterface.php new file mode 100644 index 0000000000..1623c19f0b --- /dev/null +++ b/src/Client/Adapter/AdapterInterface.php @@ -0,0 +1,65 @@ +invalidOverwritableCurlOptions = array( + CURLOPT_HTTPGET, + CURLOPT_POST, + CURLOPT_UPLOAD, + CURLOPT_CUSTOMREQUEST, + CURLOPT_HEADER, + CURLOPT_RETURNTRANSFER, + CURLOPT_HTTPHEADER, + CURLOPT_POSTFIELDS, + CURLOPT_INFILE, + CURLOPT_INFILESIZE, + CURLOPT_PORT, + CURLOPT_MAXREDIRS, + CURLOPT_CONNECTTIMEOUT, + CURL_HTTP_VERSION_1_1, + CURL_HTTP_VERSION_1_0, + ); + } + + /** + * Set the configuration array for the adapter + * + * @param array|Traversable $options + * @return Curl + * @throws AdapterException\InvalidArgumentException + */ + public function setOptions($options = array()) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (!is_array($options)) { + throw new AdapterException\InvalidArgumentException( + 'Array or Traversable object expected, got ' . gettype($options) + ); + } + + /** Config Key Normalization */ + foreach ($options as $k => $v) { + unset($options[$k]); // unset original value + $options[str_replace(array('-', '_', ' ', '.'), '', strtolower($k))] = $v; // replace w/ normalized + } + + if (isset($options['proxyuser']) && isset($options['proxypass'])) { + $this->setCurlOption(CURLOPT_PROXYUSERPWD, $options['proxyuser'].":".$options['proxypass']); + unset($options['proxyuser'], $options['proxypass']); + } + + foreach ($options as $k => $v) { + $option = strtolower($k); + switch ($option) { + case 'proxyhost': + $this->setCurlOption(CURLOPT_PROXY, $v); + break; + case 'proxyport': + $this->setCurlOption(CURLOPT_PROXYPORT, $v); + break; + default: + $this->config[$option] = $v; + break; + } + } + + return $this; + } + + /** + * Retrieve the array of all configuration options + * + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Direct setter for cURL adapter related options. + * + * @param string|int $option + * @param mixed $value + * @return Curl + */ + public function setCurlOption($option, $value) + { + if (!isset($this->config['curloptions'])) { + $this->config['curloptions'] = array(); + } + $this->config['curloptions'][$option] = $value; + return $this; + } + + /** + * Initialize curl + * + * @param string $host + * @param int $port + * @param boolean $secure + * @return void + * @throws AdapterException\RuntimeException if unable to connect + */ + public function connect($host, $port = 80, $secure = false) + { + // If we're already connected, disconnect first + if ($this->curl) { + $this->close(); + } + + // If we are connected to a different server or port, disconnect first + if ($this->curl + && is_array($this->connectedTo) + && ($this->connectedTo[0] != $host + || $this->connectedTo[1] != $port) + ) { + $this->close(); + } + + // Do the actual connection + $this->curl = curl_init(); + if ($port != 80) { + curl_setopt($this->curl, CURLOPT_PORT, intval($port)); + } + + // Set timeout + curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $this->config['timeout']); + + // Set Max redirects + curl_setopt($this->curl, CURLOPT_MAXREDIRS, $this->config['maxredirects']); + + if (!$this->curl) { + $this->close(); + + throw new AdapterException\RuntimeException('Unable to Connect to ' . $host . ':' . $port); + } + + if ($secure !== false) { + // Behave the same like Zend\Http\Adapter\Socket on SSL options. + if (isset($this->config['sslcert'])) { + curl_setopt($this->curl, CURLOPT_SSLCERT, $this->config['sslcert']); + } + if (isset($this->config['sslpassphrase'])) { + curl_setopt($this->curl, CURLOPT_SSLCERTPASSWD, $this->config['sslpassphrase']); + } + } + + // Update connected_to + $this->connectedTo = array($host, $port); + } + + /** + * Send request to the remote server + * + * @param string $method + * @param \Zend\Uri\Uri $uri + * @param float $httpVersion + * @param array $headers + * @param string $body + * @return string $request + * @throws AdapterException\RuntimeException If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option + */ + public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '') + { + // Make sure we're properly connected + if (!$this->curl) { + throw new AdapterException\RuntimeException("Trying to write but we are not connected"); + } + + if ($this->connectedTo[0] != $uri->getHost() || $this->connectedTo[1] != $uri->getPort()) { + throw new AdapterException\RuntimeException("Trying to write but we are connected to the wrong host"); + } + + // set URL + curl_setopt($this->curl, CURLOPT_URL, $uri->__toString()); + + // ensure correct curl call + $curlValue = true; + switch ($method) { + case 'GET' : + $curlMethod = CURLOPT_HTTPGET; + break; + + case 'POST' : + $curlMethod = CURLOPT_POST; + break; + + case 'PUT' : + // There are two different types of PUT request, either a Raw Data string has been set + // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used. + if (is_resource($body)) { + $this->config['curloptions'][CURLOPT_INFILE] = $body; + } + if (isset($this->config['curloptions'][CURLOPT_INFILE])) { + // Now we will probably already have Content-Length set, so that we have to delete it + // from $headers at this point: + foreach ($headers AS $k => $header) { + if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) { + if (is_resource($body)) { + $this->config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1]; + } + unset($headers[$k]); + } + } + + if (!isset($this->config['curloptions'][CURLOPT_INFILESIZE])) { + throw new AdapterException\RuntimeException("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE."); + } + + if (is_resource($body)) { + $body = ''; + } + + $curlMethod = CURLOPT_UPLOAD; + } else { + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "PUT"; + } + break; + + case 'PATCH' : + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "PATCH"; + break; + + case 'DELETE' : + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "DELETE"; + break; + + case 'OPTIONS' : + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "OPTIONS"; + break; + + case 'TRACE' : + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "TRACE"; + break; + + case 'HEAD' : + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "HEAD"; + break; + + default: + // For now, through an exception for unsupported request methods + throw new AdapterException\InvalidArgumentException("Method '$method' currently not supported"); + } + + if (is_resource($body) && $curlMethod != CURLOPT_UPLOAD) { + throw new AdapterException\RuntimeException("Streaming requests are allowed only with PUT"); + } + + // get http version to use + $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0; + + // mark as HTTP request and set HTTP method + curl_setopt($this->curl, $curlHttp, true); + curl_setopt($this->curl, $curlMethod, $curlValue); + + if ($this->outputStream) { + // headers will be read into the response + curl_setopt($this->curl, CURLOPT_HEADER, false); + curl_setopt($this->curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader")); + // and data will be written into the file + curl_setopt($this->curl, CURLOPT_FILE, $this->outputStream); + } else { + // ensure headers are also returned + curl_setopt($this->curl, CURLOPT_HEADER, true); + + // ensure actual response is returned + curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); + } + + // Treating basic auth headers in a special way + if (array_key_exists('Authorization', $headers) && 'Basic' == substr($headers['Authorization'], 0, 5)) { + curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($this->curl, CURLOPT_USERPWD, base64_decode(substr($headers['Authorization'], 6))); + unset($headers['Authorization']); + } + + // set additional headers + if (!isset($headers['Accept'])) { + $headers['Accept'] = ''; + } + $curlHeaders = array(); + foreach ($headers as $key => $value) { + $curlHeaders[] = $key . ': ' . $value; + } + curl_setopt($this->curl, CURLOPT_HTTPHEADER, $curlHeaders); + + /** + * Make sure POSTFIELDS is set after $curlMethod is set: + * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161 + */ + if ($method == 'POST') { + curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body); + } elseif ($curlMethod == CURLOPT_UPLOAD) { + // this covers a PUT by file-handle: + // Make the setting of this options explicit (rather than setting it through the loop following a bit lower) + // to group common functionality together. + curl_setopt($this->curl, CURLOPT_INFILE, $this->config['curloptions'][CURLOPT_INFILE]); + curl_setopt($this->curl, CURLOPT_INFILESIZE, $this->config['curloptions'][CURLOPT_INFILESIZE]); + unset($this->config['curloptions'][CURLOPT_INFILE]); + unset($this->config['curloptions'][CURLOPT_INFILESIZE]); + } elseif ($method == 'PUT') { + // This is a PUT by a setRawData string, not by file-handle + curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body); + } elseif ($method == 'PATCH') { + curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body); + } + + // set additional curl options + if (isset($this->config['curloptions'])) { + foreach ((array)$this->config['curloptions'] as $k => $v) { + if (!in_array($k, $this->invalidOverwritableCurlOptions)) { + if (curl_setopt($this->curl, $k, $v) == false) { + throw new AdapterException\RuntimeException(sprintf("Unknown or erroreous cURL option '%s' set", $k)); + } + } + } + } + + // send the request + $response = curl_exec($this->curl); + + // if we used streaming, headers are already there + if (!is_resource($this->outputStream)) { + $this->response = $response; + } + + $request = curl_getinfo($this->curl, CURLINFO_HEADER_OUT); + $request .= $body; + + if (empty($this->response)) { + throw new AdapterException\RuntimeException("Error in cURL request: " . curl_error($this->curl)); + } + + // cURL automatically decodes chunked-messages, this means we have to disallow the Zend\Http\Response to do it again + if (stripos($this->response, "Transfer-Encoding: chunked\r\n")) { + $this->response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->response); + } + + // Eliminate multiple HTTP responses. + do { + $parts = preg_split('|(?:\r?\n){2}|m', $this->response, 2); + $again = false; + + if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) { + $this->response = $parts[1]; + $again = true; + } + } while ($again); + + // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string: + if (stripos($this->response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) { + $this->response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->response); + } + + return $request; + } + + /** + * Return read response from server + * + * @return string + */ + public function read() + { + return $this->response; + } + + /** + * Close the connection to the server + * + */ + public function close() + { + if (is_resource($this->curl)) { + curl_close($this->curl); + } + $this->curl = null; + $this->connectedTo = array(null, null); + } + + /** + * Get cUrl Handle + * + * @return resource + */ + public function getHandle() + { + return $this->curl; + } + + /** + * Set output stream for the response + * + * @param resource $stream + * @return Curl + */ + public function setOutputStream($stream) + { + $this->outputStream = $stream; + return $this; + } + + /** + * Header reader function for CURL + * + * @param resource $curl + * @param string $header + * @return int + */ + public function readHeader($curl, $header) + { + $this->response .= $header; + return strlen($header); + } +} diff --git a/src/Client/Adapter/Exception/ExceptionInterface.php b/src/Client/Adapter/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..6569bdd701 --- /dev/null +++ b/src/Client/Adapter/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + 'ssl', + 'sslcert' => null, + 'sslpassphrase' => null, + 'sslverifypeer' => true, + 'sslcapath' => null, + 'sslallowselfsigned' => false, + 'sslusecontext' => false, + 'proxy_host' => '', + 'proxy_port' => 8080, + 'proxy_user' => '', + 'proxy_pass' => '', + 'proxy_auth' => Client::AUTH_BASIC, + 'persistent' => false + ); + + /** + * Whether HTTPS CONNECT was already negotiated with the proxy or not + * + * @var boolean + */ + protected $negotiated = false; + + /** + * Connect to the remote server + * + * Will try to connect to the proxy server. If no proxy was set, will + * fall back to the target server (behave like regular Socket adapter) + * + * @param string $host + * @param int $port + * @param boolean $secure + */ + public function connect($host, $port = 80, $secure = false) + { + // If no proxy is set, fall back to Socket adapter + if (! $this->config['proxy_host']) { + return parent::connect($host, $port, $secure); + } + + /* Url might require stream context even if proxy connection doesn't */ + if ($secure) { + $this->config['sslusecontext'] = true; + } + + // Connect (a non-secure connection) to the proxy server + return parent::connect( + $this->config['proxy_host'], + $this->config['proxy_port'], + false + ); + } + + /** + * Send request to the proxy server + * + * @param string $method + * @param \Zend\Uri\Uri $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + // If no proxy is set, fall back to default Socket adapter + if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body); + + // Make sure we're properly connected + if (! $this->socket) { + throw new AdapterException\RuntimeException("Trying to write but we are not connected"); + } + + $host = $this->config['proxy_host']; + $port = $this->config['proxy_port']; + + if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) { + throw new AdapterException\RuntimeException("Trying to write but we are connected to the wrong proxy server"); + } + + // Add Proxy-Authorization header + if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) { + $headers['proxy-authorization'] = Client::encodeAuthHeader( + $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth'] + ); + } + + // if we are proxying HTTPS, preform CONNECT handshake with the proxy + if ($uri->getScheme() == 'https' && (! $this->negotiated)) { + $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers); + $this->negotiated = true; + } + + // Save request method for later + $this->method = $method; + + // Build request headers + if ($this->negotiated) { + $path = $uri->getPath(); + if ($uri->getQuery()) { + $path .= '?' . $uri->getQuery(); + } + $request = "$method $path HTTP/$http_ver\r\n"; + } else { + $request = "$method $uri HTTP/$http_ver\r\n"; + } + + // Add all headers to the request string + foreach ($headers as $k => $v) { + if (is_string($k)) $v = "$k: $v"; + $request .= "$v\r\n"; + } + + if (is_resource($body)) { + $request .= "\r\n"; + } else { + // Add the request body + $request .= "\r\n" . $body; + } + + // Send the request + ErrorHandler::start(); + $test = fwrite($this->socket, $request); + $error = ErrorHandler::stop(); + if (!$test) { + throw new AdapterException\RuntimeException("Error writing request to proxy server", 0, $error); + } + + if (is_resource($body)) { + if (stream_copy_to_stream($body, $this->socket) == 0) { + throw new AdapterException\RuntimeException('Error writing request to server'); + } + } + + return $request; + } + + /** + * Preform handshaking with HTTPS proxy using CONNECT method + * + * @param string $host + * @param integer $port + * @param string $http_ver + * @param array $headers + */ + protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array()) + { + $request = "CONNECT $host:$port HTTP/$http_ver\r\n" . + "Host: " . $this->config['proxy_host'] . "\r\n"; + + // Add the user-agent header + if (isset($this->config['useragent'])) { + $request .= "User-agent: " . $this->config['useragent'] . "\r\n"; + } + + // If the proxy-authorization header is set, send it to proxy but remove + // it from headers sent to target host + if (isset($headers['proxy-authorization'])) { + $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n"; + unset($headers['proxy-authorization']); + } + + $request .= "\r\n"; + + // Send the request + ErrorHandler::start(); + $test = fwrite($this->socket, $request); + $error = ErrorHandler::stop(); + if (!$test) { + throw new AdapterException\RuntimeException("Error writing request to proxy server", 0, $error); + } + + // Read response headers only + $response = ''; + $gotStatus = false; + ErrorHandler::start(); + while ($line = fgets($this->socket)) { + $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); + if ($gotStatus) { + $response .= $line; + if (!rtrim($line)) break; + } + } + ErrorHandler::stop(); + + // Check that the response from the proxy is 200 + if (Response::extractCode($response) != 200) { + throw new AdapterException\RuntimeException("Unable to connect to HTTPS proxy. Server response: " . $response); + } + + // If all is good, switch socket to secure mode. We have to fall back + // through the different modes + $modes = array( + STREAM_CRYPTO_METHOD_TLS_CLIENT, + STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + STREAM_CRYPTO_METHOD_SSLv2_CLIENT + ); + + $success = false; + foreach ($modes as $mode) { + $success = stream_socket_enable_crypto($this->socket, true, $mode); + if ($success) break; + } + + if (! $success) { + throw new AdapterException\RuntimeException("Unable to connect to" . + " HTTPS server through proxy: could not negotiate secure connection."); + } + } + + /** + * Close the connection to the server + * + */ + public function close() + { + parent::close(); + $this->negotiated = false; + } + + /** + * Destructor: make sure the socket is disconnected + * + */ + public function __destruct() + { + if ($this->socket) $this->close(); + } +} diff --git a/src/Client/Adapter/Socket.php b/src/Client/Adapter/Socket.php new file mode 100644 index 0000000000..0f407520eb --- /dev/null +++ b/src/Client/Adapter/Socket.php @@ -0,0 +1,614 @@ + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + 'sslv2' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT, + 'sslv3' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + 'tls' => STREAM_CRYPTO_METHOD_TLS_CLIENT + ); + + /** + * The socket for server connection + * + * @var resource|null + */ + protected $socket = null; + + /** + * What host/port are we connected to? + * + * @var array + */ + protected $connected_to = array(null, null); + + /** + * Stream for storing output + * + * @var resource + */ + protected $out_stream = null; + + /** + * Parameters array + * + * @var array + */ + protected $config = array( + 'persistent' => false, + 'ssltransport' => 'ssl', + 'sslcert' => null, + 'sslpassphrase' => null, + 'sslverifypeer' => true, + 'sslcapath' => null, + 'sslallowselfsigned' => false, + 'sslusecontext' => false + ); + + /** + * Request method - will be set by write() and might be used by read() + * + * @var string + */ + protected $method = null; + + /** + * Stream context + * + * @var resource + */ + protected $context = null; + + /** + * Adapter constructor, currently empty. Config is set using setOptions() + * + */ + public function __construct() + { + } + + /** + * Set the configuration array for the adapter + * + * @param array|Traversable $options + */ + public function setOptions($options = array()) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (!is_array($options)) { + throw new AdapterException\InvalidArgumentException( + 'Array or Zend_Config object expected, got ' . gettype($options) + ); + } + + foreach ($options as $k => $v) { + $this->config[strtolower($k)] = $v; + } + } + + /** + * Retrieve the array of all configuration options + * + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Set the stream context for the TCP connection to the server + * + * Can accept either a pre-existing stream context resource, or an array + * of stream options, similar to the options array passed to the + * stream_context_create() PHP function. In such case a new stream context + * will be created using the passed options. + * + * @since Zend Framework 1.9 + * + * @param mixed $context Stream context or array of context options + * @return Socket + */ + public function setStreamContext($context) + { + if (is_resource($context) && get_resource_type($context) == 'stream-context') { + $this->context = $context; + + } elseif (is_array($context)) { + $this->context = stream_context_create($context); + + } else { + // Invalid parameter + throw new AdapterException\InvalidArgumentException( + "Expecting either a stream context resource or array, got " . gettype($context) + ); + } + + return $this; + } + + /** + * Get the stream context for the TCP connection to the server. + * + * If no stream context is set, will create a default one. + * + * @return resource + */ + public function getStreamContext() + { + if (! $this->context) { + $this->context = stream_context_create(); + } + + return $this->context; + } + + /** + * Connect to the remote server + * + * @param string $host + * @param int $port + * @param boolean $secure + * @throws AdapterException\RuntimeException + */ + public function connect($host, $port = 80, $secure = false) + { + // If we are connected to the wrong host, disconnect first + if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { + if (is_resource($this->socket)) { + $this->close(); + } + } + + // Now, if we are not connected, connect + if (!is_resource($this->socket) || ! $this->config['keepalive']) { + $context = $this->getStreamContext(); + + if ($secure || $this->config['sslusecontext']) { + if ($this->config['sslverifypeer'] !== null) { + if (!stream_context_set_option($context, 'ssl', 'verify_peer', $this->config['sslverifypeer'])) { + throw new AdapterException\RuntimeException('Unable to set sslverifypeer option'); + } + } + + if ($this->config['sslcapath']) { + if (!stream_context_set_option($context, 'ssl', 'capath', $this->config['sslcapath'])) { + throw new AdapterException\RuntimeException('Unable to set sslcapath option'); + } + } + + if ($this->config['sslallowselfsigned'] !== null) { + if (!stream_context_set_option($context, 'ssl', 'allow_self_signed', $this->config['sslallowselfsigned'])) { + throw new AdapterException\RuntimeException('Unable to set sslallowselfsigned option'); + } + } + + if ($this->config['sslcert'] !== null) { + if (!stream_context_set_option($context, 'ssl', 'local_cert', $this->config['sslcert'])) { + throw new AdapterException\RuntimeException('Unable to set sslcert option'); + } + } + + if ($this->config['sslpassphrase'] !== null) { + if (!stream_context_set_option($context, 'ssl', 'passphrase', $this->config['sslpassphrase'])) { + throw new AdapterException\RuntimeException('Unable to set sslpassphrase option'); + } + } + } + + $flags = STREAM_CLIENT_CONNECT; + if ($this->config['persistent']) { + $flags |= STREAM_CLIENT_PERSISTENT; + } + + ErrorHandler::start(); + $this->socket = stream_socket_client( + $host . ':' . $port, + $errno, + $errstr, + (int) $this->config['timeout'], + $flags, + $context + ); + $error = ErrorHandler::stop(); + + if (!$this->socket) { + $this->close(); + throw new AdapterException\RuntimeException( + sprintf( + 'Unable to connect to %s:%d%s', + $host, + $port, + ($error ? '. Error #' . $error->getCode() . ': ' . $error->getMessage() : '') + ), + 0, + $error + ); + } + + // Set the stream timeout + if (!stream_set_timeout($this->socket, (int) $this->config['timeout'])) { + throw new AdapterException\RuntimeException('Unable to set the connection timeout'); + } + + if ($secure || $this->config['sslusecontext']) { + if ($this->config['ssltransport'] && isset(self::$sslCryptoTypes[$this->config['ssltransport']])) { + $sslCryptoMethod = self::$sslCryptoTypes[$this->config['ssltransport']]; + } else { + $sslCryptoMethod = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + } + + ErrorHandler::start(); + $test = stream_socket_enable_crypto($this->socket, true, $sslCryptoMethod); + $error = ErrorHandler::stop(); + if (!$test || $error) { + // Error handling is kind of difficult when it comes to SSL + $errorString = ''; + while (($sslError = openssl_error_string()) != false) { + $errorString .= "; SSL error: $sslError"; + } + $this->close(); + + if ((! $errorString) && $this->config['sslverifypeer']) { + // There's good chance our error is due to sslcapath not being properly set + if (! ($this->config['sslcapath'] && is_dir($this->config['sslcapath']))) { + $errorString = 'make sure the "sslcapath" option points to a valid SSL certificate directory'; + } + } + + if ($errorString) { + $errorString = ": $errorString"; + } + + throw new AdapterException\RuntimeException(sprintf( + 'Unable to enable crypto on TCP connection %s%s', + $host, + $errorString + ), 0, $error); + } + + $host = $this->config['ssltransport'] . "://" . $host; + } else { + $host = 'tcp://' . $host; + } + + // Update connected_to + $this->connected_to = array($host, $port); + } + } + + + /** + * Send request to the remote server + * + * @param string $method + * @param \Zend\Uri\Uri $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + // Make sure we're properly connected + if (! $this->socket) { + throw new AdapterException\RuntimeException('Trying to write but we are not connected'); + } + + $host = $uri->getHost(); + $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host; + if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) { + throw new AdapterException\RuntimeException('Trying to write but we are connected to the wrong host'); + } + + // Save request method for later + $this->method = $method; + + // Build request headers + $path = $uri->getPath(); + if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); + $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; + foreach ($headers as $k => $v) { + if (is_string($k)) $v = ucfirst($k) . ": $v"; + $request .= "$v\r\n"; + } + + if (is_resource($body)) { + $request .= "\r\n"; + } else { + // Add the request body + $request .= "\r\n" . $body; + } + + // Send the request + ErrorHandler::start(); + $test = fwrite($this->socket, $request); + $error = ErrorHandler::stop(); + if (!$test) { + throw new AdapterException\RuntimeException('Error writing request to server', 0, $error); + } + + if (is_resource($body)) { + if (stream_copy_to_stream($body, $this->socket) == 0) { + throw new AdapterException\RuntimeException('Error writing request to server'); + } + } + + return $request; + } + + /** + * Read response from server + * + * @return string + */ + public function read() + { + // First, read headers only + $response = ''; + $gotStatus = false; + + while (($line = fgets($this->socket)) !== false) { + $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); + if ($gotStatus) { + $response .= $line; + if (rtrim($line) === '') break; + } + } + + $this->_checkSocketReadTimeout(); + + $responseObj= Response::fromString($response); + + $statusCode = $responseObj->getStatusCode(); + + // Handle 100 and 101 responses internally by restarting the read again + if ($statusCode == 100 || $statusCode == 101) return $this->read(); + + // Check headers to see what kind of connection / transfer encoding we have + $headers = $responseObj->getHeaders(); + + /** + * Responses to HEAD requests and 204 or 304 responses are not expected + * to have a body - stop reading here + */ + if ($statusCode == 304 || $statusCode == 204 || + $this->method == \Zend\Http\Request::METHOD_HEAD) { + + // Close the connection if requested to do so by the server + $connection = $headers->get('connection'); + if ($connection && $connection->getFieldValue() == 'close') { + $this->close(); + } + return $response; + } + + // If we got a 'transfer-encoding: chunked' header + $transfer_encoding = $headers->get('transfer-encoding'); + $content_length = $headers->get('content-length'); + if ($transfer_encoding !== false) { + + if (strtolower($transfer_encoding->getFieldValue()) == 'chunked') { + + do { + $line = fgets($this->socket); + $this->_checkSocketReadTimeout(); + + $chunk = $line; + + // Figure out the next chunk size + $chunksize = trim($line); + if (! ctype_xdigit($chunksize)) { + $this->close(); + throw new AdapterException\RuntimeException('Invalid chunk size "' . + $chunksize . '" unable to read chunked body'); + } + + // Convert the hexadecimal value to plain integer + $chunksize = hexdec($chunksize); + + // Read next chunk + $read_to = ftell($this->socket) + $chunksize; + + do { + $current_pos = ftell($this->socket); + if ($current_pos >= $read_to) break; + + if ($this->out_stream) { + if (stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { + $this->_checkSocketReadTimeout(); + break; + } + } else { + $line = fread($this->socket, $read_to - $current_pos); + if ($line === false || strlen($line) === 0) { + $this->_checkSocketReadTimeout(); + break; + } + $chunk .= $line; + } + } while (! feof($this->socket)); + + ErrorHandler::start(); + $chunk .= fgets($this->socket); + ErrorHandler::stop(); + $this->_checkSocketReadTimeout(); + + if (!$this->out_stream) { + $response .= $chunk; + } + } while ($chunksize > 0); + } else { + $this->close(); + throw new AdapterException\RuntimeException('Cannot handle "' . + $transfer_encoding->getFieldValue() . '" transfer encoding'); + } + + // We automatically decode chunked-messages when writing to a stream + // this means we have to disallow the Zend_Http_Response to do it again + if ($this->out_stream) { + $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response); + } + // Else, if we got the content-length header, read this number of bytes + } elseif ($content_length !== false) { + + // If we got more than one Content-Length header (see ZF-9404) use + // the last value sent + if (is_array($content_length)) { + $content_length = $content_length[count($content_length) - 1]; + } + $contentLength = $content_length->getFieldValue(); + + $current_pos = ftell($this->socket); + $chunk = ''; + + for ($read_to = $current_pos + $contentLength; + $read_to > $current_pos; + $current_pos = ftell($this->socket)) { + + if ($this->out_stream) { + if (stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { + $this->_checkSocketReadTimeout(); + break; + } + } else { + $chunk = fread($this->socket, $read_to - $current_pos); + if ($chunk === false || strlen($chunk) === 0) { + $this->_checkSocketReadTimeout(); + break; + } + + $response .= $chunk; + } + + // Break if the connection ended prematurely + if (feof($this->socket)) break; + } + + // Fallback: just read the response until EOF + } else { + + do { + if ($this->out_stream) { + if (stream_copy_to_stream($this->socket, $this->out_stream) == 0) { + $this->_checkSocketReadTimeout(); + break; + } + } else { + $buff = fread($this->socket, 8192); + if ($buff === false || strlen($buff) === 0) { + $this->_checkSocketReadTimeout(); + break; + } else { + $response .= $buff; + } + } + + } while (feof($this->socket) === false); + + $this->close(); + } + + // Close the connection if requested to do so by the server + $connection = $headers->get('connection'); + if ($connection && $connection->getFieldValue() == 'close') { + $this->close(); + } + + return $response; + } + + /** + * Close the connection to the server + * + */ + public function close() + { + if (is_resource($this->socket)) { + ErrorHandler::start(); + fclose($this->socket); + ErrorHandler::stop(); + } + $this->socket = null; + $this->connected_to = array(null, null); + } + + /** + * Check if the socket has timed out - if so close connection and throw + * an exception + * + * @throws AdapterException\TimeoutException with READ_TIMEOUT code + */ + protected function _checkSocketReadTimeout() + { + if ($this->socket) { + $info = stream_get_meta_data($this->socket); + $timedout = $info['timed_out']; + if ($timedout) { + $this->close(); + throw new AdapterException\TimeoutException( + "Read timed out after {$this->config['timeout']} seconds", + AdapterException\TimeoutException::READ_TIMEOUT + ); + } + } + } + + /** + * Set output stream for the response + * + * @param resource $stream + * @return \Zend\Http\Client\Adapter\Socket + */ + public function setOutputStream($stream) + { + $this->out_stream = $stream; + return $this; + } + + /** + * Destructor: make sure the socket is disconnected + * + * If we are in persistent TCP mode, will not close the connection + * + */ + public function __destruct() + { + if (! $this->config['persistent']) { + if ($this->socket) $this->close(); + } + } +} diff --git a/src/Client/Adapter/StreamInterface.php b/src/Client/Adapter/StreamInterface.php new file mode 100644 index 0000000000..e674e64863 --- /dev/null +++ b/src/Client/Adapter/StreamInterface.php @@ -0,0 +1,33 @@ +nextRequestWillFail = (bool) $flag; + + return $this; + } + + /** + * Set the configuration array for the adapter + * + * @param array|Traversable $options + */ + public function setOptions($options = array()) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (! is_array($options)) { + throw new Exception\InvalidArgumentException( + 'Array or Traversable object expected, got ' . gettype($options) + ); + } + + foreach ($options as $k => $v) { + $this->config[strtolower($k)] = $v; + } + } + + + /** + * Connect to the remote server + * + * @param string $host + * @param int $port + * @param boolean $secure + * @param int $timeout + * @throws Exception\RuntimeException + */ + public function connect($host, $port = 80, $secure = false) + { + if ($this->nextRequestWillFail) { + $this->nextRequestWillFail = false; + throw new Exception\RuntimeException('Request failed'); + } + } + + /** + * Send request to the remote server + * + * @param string $method + * @param \Zend\Uri\Uri $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + $host = $uri->getHost(); + $host = (strtolower($uri->getScheme()) == 'https' ? 'sslv2://' . $host : $host); + + // Build request headers + $path = $uri->getPath(); + if (empty($path)) { + $path = '/'; + } + if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); + $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; + foreach ($headers as $k => $v) { + if (is_string($k)) $v = ucfirst($k) . ": $v"; + $request .= "$v\r\n"; + } + + // Add the request body + $request .= "\r\n" . $body; + + // Do nothing - just return the request as string + + return $request; + } + + /** + * Return the response set in $this->setResponse() + * + * @return string + */ + public function read() + { + if ($this->responseIndex >= count($this->responses)) { + $this->responseIndex = 0; + } + return $this->responses[$this->responseIndex++]; + } + + /** + * Close the connection (dummy) + * + */ + public function close() + { } + + /** + * Set the HTTP response(s) to be returned by this adapter + * + * @param \Zend\Http\Response|array|string $response + */ + public function setResponse($response) + { + if ($response instanceof Response) { + $response = $response->toString(); + } + + $this->responses = (array) $response; + $this->responseIndex = 0; + } + + /** + * Add another response to the response buffer. + * + * @param string \Zend\Http\Response|$response + */ + public function addResponse($response) + { + if ($response instanceof Response) { + $response = $response->toString(); + } + + $this->responses[] = $response; + } + + /** + * Sets the position of the response buffer. Selects which + * response will be returned on the next call to read(). + * + * @param integer $index + */ + public function setResponseIndex($index) + { + if ($index < 0 || $index >= count($this->responses)) { + throw new Exception\OutOfRangeException( + 'Index out of range of response buffer size'); + } + $this->responseIndex = $index; + } +} diff --git a/src/Client/Cookies.php b/src/Client/Cookies.php new file mode 100644 index 0000000000..cc1cb1dfe5 --- /dev/null +++ b/src/Client/Cookies.php @@ -0,0 +1,503 @@ +getDomain(); + $path = $cookie->getPath(); + if (!isset($this->cookies[$domain])) { + $this->cookies[$domain] = array(); + } + if (!isset($this->cookies[$domain][$path])) { + $this->cookies[$domain][$path] = array(); + } + $this->cookies[$domain][$path][$cookie->getName()] = $cookie; + $this->rawCookies[] = $cookie; + } else { + throw new Exception\InvalidArgumentException('Supplient argument is not a valid cookie string or object'); + } + } + + /** + * Parse an HTTP response, adding all the cookies set in that response + * + * @param Response $response + * @param Uri\Uri|string $ref_uri Requested URI + */ + public function addCookiesFromResponse(Response $response, $ref_uri) + { + $cookie_hdrs = $response->getHeaders()->get('Set-Cookie'); + + if (is_array($cookie_hdrs)) { + foreach ($cookie_hdrs as $cookie) { + $this->addCookie($cookie, $ref_uri); + } + } elseif (is_string($cookie_hdrs)) { + $this->addCookie($cookie_hdrs, $ref_uri); + } + } + + /** + * Get all cookies in the cookie jar as an array + * + * @param int $ret_as Whether to return cookies as objects of \Zend\Http\Header\Cookie or as strings + * @return array|string + */ + public function getAllCookies($ret_as = self::COOKIE_OBJECT) + { + $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as); + return $cookies; + } + + /** + * Return an array of all cookies matching a specific request according to the request URI, + * whether session cookies should be sent or not, and the time to consider as "now" when + * checking cookie expiry time. + * + * @param string|Uri\Uri $uri URI to check against (secure, domain, path) + * @param boolean $matchSessionCookies Whether to send session cookies + * @param int $ret_as Whether to return cookies as objects of \Zend\Http\Header\Cookie or as strings + * @param int $now Override the current time when checking for expiry time + * @return array|string + */ + public function getMatchingCookies($uri, $matchSessionCookies = true, + $ret_as = self::COOKIE_OBJECT, $now = null) + { + if (is_string($uri)) { + $uri = Uri\UriFactory::factory($uri, 'http'); + } elseif (!$uri instanceof Uri\Uri) { + throw new Exception\InvalidArgumentException("Invalid URI string or object passed"); + } + + $host = $uri->getHost(); + if (empty($host)) { + throw new Exception\InvalidArgumentException('Invalid URI specified; does not contain a host'); + } + + // First, reduce the array of cookies to only those matching domain and path + $cookies = $this->_matchDomain($host); + $cookies = $this->_matchPath($cookies, $uri->getPath()); + $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT); + + // Next, run Cookie->match on all cookies to check secure, time and session matching + $ret = array(); + foreach ($cookies as $cookie) + if ($cookie->match($uri, $matchSessionCookies, $now)) + $ret[] = $cookie; + + // Now, use self::_flattenCookiesArray again - only to convert to the return format ;) + $ret = $this->_flattenCookiesArray($ret, $ret_as); + + return $ret; + } + + /** + * Get a specific cookie according to a URI and name + * + * @param Uri\Uri|string $uri The uri (domain and path) to match + * @param string $cookie_name The cookie's name + * @param int $ret_as Whether to return cookies as objects of \Zend\Http\Header\Cookie or as strings + * @return Cookie|string + */ + public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT) + { + if (is_string($uri)) { + $uri = Uri\UriFactory::factory($uri, 'http'); + } elseif (!$uri instanceof Uri\Uri) { + throw new Exception\InvalidArgumentException('Invalid URI specified'); + } + + $host = $uri->getHost(); + if (empty($host)) { + throw new Exception\InvalidArgumentException('Invalid URI specified; host missing'); + } + + // Get correct cookie path + $path = $uri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + if (! $path) $path = '/'; + + if (isset($this->cookies[$uri->getHost()][$path][$cookie_name])) { + $cookie = $this->cookies[$uri->getHost()][$path][$cookie_name]; + + switch ($ret_as) { + case self::COOKIE_OBJECT: + return $cookie; + break; + + case self::COOKIE_STRING_ARRAY: + case self::COOKIE_STRING_CONCAT: + return $cookie->__toString(); + break; + + default: + throw new Exception\InvalidArgumentException("Invalid value passed for \$ret_as: {$ret_as}"); + break; + } + } else { + return false; + } + } + + /** + * Helper function to recursively flatten an array. Should be used when exporting the + * cookies array (or parts of it) + * + * @param \Zend\Http\Header\Cookie|array $ptr + * @param int $ret_as What value to return + * @return array|string + */ + protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT) + { + if (is_array($ptr)) { + $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? '' : array()); + foreach ($ptr as $item) { + if ($ret_as == self::COOKIE_STRING_CONCAT) { + $ret .= $this->_flattenCookiesArray($item, $ret_as); + } else { + $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as)); + } + } + return $ret; + } elseif ($ptr instanceof Cookie) { + switch ($ret_as) { + case self::COOKIE_STRING_ARRAY: + return array($ptr->__toString()); + break; + + case self::COOKIE_STRING_CONCAT: + return $ptr->__toString(); + break; + + case self::COOKIE_OBJECT: + default: + return array($ptr); + break; + } + } + + return null; + } + + /** + * Return a subset of the cookies array matching a specific domain + * + * @param string $domain + * @return array + */ + protected function _matchDomain($domain) + { + $ret = array(); + + foreach (array_keys($this->cookies) as $cdom) { + if (Cookie::matchCookieDomain($cdom, $domain)) { + $ret[$cdom] = $this->cookies[$cdom]; + } + } + + return $ret; + } + + /** + * Return a subset of a domain-matching cookies that also match a specified path + * + * @param array $dom_array + * @param string $path + * @return array + */ + protected function _matchPath($domains, $path) + { + $ret = array(); + + foreach ($domains as $dom => $paths_array) { + foreach (array_keys($paths_array) as $cpath) { + if (Cookie::matchCookiePath($cpath, $path)) { + if (! isset($ret[$dom])) { + $ret[$dom] = array(); + } + + $ret[$dom][$cpath] = $paths_array[$cpath]; + } + } + } + + return $ret; + } + + /** + * Create a new Cookies object and automatically load into it all the + * cookies set in an Http_Response object. If $uri is set, it will be + * considered as the requested URI for setting default domain and path + * of the cookie. + * + * @param Response $response HTTP Response object + * @param Uri\Uri|string $uri The requested URI + * @return Cookies + * @todo Add the $uri functionality. + */ + public static function fromResponse(Response $response, $ref_uri) + { + $jar = new self(); + $jar->addCookiesFromResponse($response, $ref_uri); + return $jar; + } + + /** + * Required by Countable interface + * + * @return int + */ + public function count() + { + return count($this->rawCookies); + } + + /** + * Required by IteratorAggregate interface + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->rawCookies); + } + + /** + * Tells if the array of cookies is empty + * + * @return bool + */ + public function isEmpty() + { + return count($this) == 0; + } + + /** + * Empties the cookieJar of any cookie + * + * @return Cookies + */ + public function reset() + { + $this->cookies = $this->rawCookies = array(); + return $this; + } + + /** + * (PHP 5 >= 5.1.0)
+ * Whether a offset exists + * @link http://php.net/manual/en/arrayaccess.offsetexists.php + * @param mixed $offset

+ * An offset to check for. + *

+ * @return boolean Returns true on success or false on failure. + *

+ *

+ * The return value will be casted to boolean if non-boolean was returned. + */ + public function offsetExists($offset) + { + // TODO: Implement offsetExists() method. + } + + /** + * (PHP 5 >= 5.1.0)
+ * Offset to retrieve + * @link http://php.net/manual/en/arrayaccess.offsetget.php + * @param mixed $offset

+ * The offset to retrieve. + *

+ * @return mixed Can return all value types. + */ + public function offsetGet($offset) + { + // TODO: Implement offsetGet() method. + } + + /** + * (PHP 5 >= 5.1.0)
+ * Offset to set + * @link http://php.net/manual/en/arrayaccess.offsetset.php + * @param mixed $offset

+ * The offset to assign the value to. + *

+ * @param mixed $value

+ * The value to set. + *

+ * @return void + */ + public function offsetSet($offset, $value) + { + // TODO: Implement offsetSet() method. + } + + /** + * (PHP 5 >= 5.1.0)
+ * Offset to unset + * @link http://php.net/manual/en/arrayaccess.offsetunset.php + * @param mixed $offset

+ * The offset to unset. + *

+ * @return void + */ + public function offsetUnset($offset) + { + // TODO: Implement offsetUnset() method. + } + + /** + * (PHP 5 >= 5.1.0)
+ * String representation of object + * @link http://php.net/manual/en/serializable.serialize.php + * @return string the string representation of the object or &null; + */ + public function serialize() + { + // TODO: Implement serialize() method. + } + + /** + * (PHP 5 >= 5.1.0)
+ * Constructs the object + * @link http://php.net/manual/en/serializable.unserialize.php + * @param string $serialized

+ * The string representation of the object. + *

+ * @return mixed the original value unserialized. + */ + public function unserialize($serialized) + { + // TODO: Implement unserialize() method. + } + + public function fromArray(array $values) + { + // TODO: Implement fromArray() method. + } + + public function fromString($string) + { + // TODO: Implement fromString() method. + } + + public function toArray() + { + // TODO: Implement toArray() method. + } + + public function toString() + { + // TODO: Implement toString() method. + } + + public function get($name, $default = null) + { + // TODO: Implement get() method. + } + + public function set($name, $value) + { + // TODO: Implement set() method. + } +} diff --git a/src/Client/Exception/ExceptionInterface.php b/src/Client/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..d4602bc394 --- /dev/null +++ b/src/Client/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ +setUri($url); + $request->setMethod(Request::METHOD_GET); + + if (!empty($query) && is_array($query)) { + $request->getQuery()->fromArray($query); + } + + if (!empty($headers) && is_array($headers)) { + $request->getHeaders()->addHeaders($headers); + } + + if (!empty($body)) { + $request->setBody($body); + } + + return self::getStaticClient()->send($request); + } + /** + * HTTP POST METHOD (static) + * + * @param string $url + * @param array $params + * @param array $headers + * @return Response|boolean + */ + public static function post($url, $params, $headers=array(), $body=null) + { + if (empty($url)) { + return false; + } + + $request= new Request(); + $request->setUri($url); + $request->setMethod(Request::METHOD_POST); + + if (!empty($params) && is_array($params)) { + $request->getPost()->fromArray($params); + } else { + throw new Exception\InvalidArgumentException('The array of post parameters is empty'); + } + + if (!isset($headers['Content-Type'])) { + $headers['Content-Type']= Client::ENC_URLENCODED; + } + + if (!empty($headers) && is_array($headers)) { + $request->getHeaders()->addHeaders($headers); + } + + if (!empty($body)) { + $request->setContent($body); + } + + return self::getStaticClient()->send($request); + } +} diff --git a/src/Cookies.php b/src/Cookies.php new file mode 100644 index 0000000000..5af04e5a67 --- /dev/null +++ b/src/Cookies.php @@ -0,0 +1,344 @@ +headers = $headers; + parent::__construct(); + } + + /** + * Add a cookie to the class. Cookie should be passed either as a Zend\Http\Header\Cookie object + * or as a string - in which case an object is created from the string. + * + * @param Cookie|string $cookie + * @param Uri\Uri|string $ref_uri Optional reference URI (for domain, path, secure) + */ + public function addCookie(Cookie $cookie, $ref_uri = null) + { + if (is_string($cookie)) { + $cookie = Cookie::fromString($cookie, $ref_uri); + } + + if ($cookie instanceof Cookie) { + $domain = $cookie->getDomain(); + $path = $cookie->getPath(); + if (!isset($this->cookies[$domain])) { + $this->cookies[$domain] = array(); + } + if (!isset($this->cookies[$domain][$path])) { + $this->cookies[$domain][$path] = array(); + } + $this->cookies[$domain][$path][$cookie->getName()] = $cookie; + $this->rawCookies[] = $cookie; + } else { + throw new Exception\InvalidArgumentException('Supplient argument is not a valid cookie string or object'); + } + } + + /** + * Parse an HTTP response, adding all the cookies set in that response + * + * @param Response $response + * @param Uri\Uri|string $ref_uri Requested URI + */ + public function addCookiesFromResponse(Response $response, $ref_uri) + { + $cookie_hdrs = $response->getHeaders()->get('Set-Cookie'); + + if (is_array($cookie_hdrs)) { + foreach ($cookie_hdrs as $cookie) { + $this->addCookie($cookie, $ref_uri); + } + } elseif (is_string($cookie_hdrs)) { + $this->addCookie($cookie_hdrs, $ref_uri); + } + } + + /** + * Get all cookies in the cookie jar as an array + * + * @param int $ret_as Whether to return cookies as objects of \Zend\Http\Header\Cookie or as strings + * @return array|string + */ + public function getAllCookies($ret_as = self::COOKIE_OBJECT) + { + $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as); + return $cookies; + } + + /** + * Return an array of all cookies matching a specific request according to the request URI, + * whether session cookies should be sent or not, and the time to consider as "now" when + * checking cookie expiry time. + * + * @param string|Uri\Uri $uri URI to check against (secure, domain, path) + * @param boolean $matchSessionCookies Whether to send session cookies + * @param int $ret_as Whether to return cookies as objects of \Zend\Http\Header\Cookie or as strings + * @param int $now Override the current time when checking for expiry time + * @return array|string + */ + public function getMatchingCookies($uri, $matchSessionCookies = true, + $ret_as = self::COOKIE_OBJECT, $now = null) + { + if (is_string($uri)) { + $uri = Uri\UriFactory::factory($uri, 'http'); + } elseif (!$uri instanceof Uri\Uri) { + throw new Exception\InvalidArgumentException("Invalid URI string or object passed"); + } + + $host = $uri->getHost(); + if (empty($host)) { + throw new Exception\InvalidArgumentException('Invalid URI specified; does not contain a host'); + } + + // First, reduce the array of cookies to only those matching domain and path + $cookies = $this->_matchDomain($host); + $cookies = $this->_matchPath($cookies, $uri->getPath()); + $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT); + + // Next, run Cookie->match on all cookies to check secure, time and session matching + $ret = array(); + foreach ($cookies as $cookie) + if ($cookie->match($uri, $matchSessionCookies, $now)) + $ret[] = $cookie; + + // Now, use self::_flattenCookiesArray again - only to convert to the return format ;) + $ret = $this->_flattenCookiesArray($ret, $ret_as); + + return $ret; + } + + /** + * Get a specific cookie according to a URI and name + * + * @param Uri\Uri|string $uri The uri (domain and path) to match + * @param string $cookie_name The cookie's name + * @param int $ret_as Whether to return cookies as objects of \Zend\Http\Header\Cookie or as strings + * @return Cookie|string + */ + public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT) + { + if (is_string($uri)) { + $uri = Uri\UriFactory::factory($uri, 'http'); + } elseif (!$uri instanceof Uri\Uri) { + throw new Exception\InvalidArgumentException('Invalid URI specified'); + } + + $host = $uri->getHost(); + if (empty($host)) { + throw new Exception\InvalidArgumentException('Invalid URI specified; host missing'); + } + + // Get correct cookie path + $path = $uri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + if (! $path) $path = '/'; + + if (isset($this->cookies[$uri->getHost()][$path][$cookie_name])) { + $cookie = $this->cookies[$uri->getHost()][$path][$cookie_name]; + + switch ($ret_as) { + case self::COOKIE_OBJECT: + return $cookie; + break; + + case self::COOKIE_STRING_ARRAY: + case self::COOKIE_STRING_CONCAT: + return $cookie->__toString(); + break; + + default: + throw new Exception\InvalidArgumentException("Invalid value passed for \$ret_as: {$ret_as}"); + break; + } + } else { + return false; + } + } + + /** + * Helper function to recursively flatten an array. Should be used when exporting the + * cookies array (or parts of it) + * + * @param \Zend\Http\Header\Cookie|array $ptr + * @param int $ret_as What value to return + * @return array|string + */ + protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT) + { + if (is_array($ptr)) { + $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? '' : array()); + foreach ($ptr as $item) { + if ($ret_as == self::COOKIE_STRING_CONCAT) { + $ret .= $this->_flattenCookiesArray($item, $ret_as); + } else { + $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as)); + } + } + return $ret; + } elseif ($ptr instanceof Cookie) { + switch ($ret_as) { + case self::COOKIE_STRING_ARRAY: + return array($ptr->__toString()); + break; + + case self::COOKIE_STRING_CONCAT: + return $ptr->__toString(); + break; + + case self::COOKIE_OBJECT: + default: + return array($ptr); + break; + } + } + + return null; + } + + /** + * Return a subset of the cookies array matching a specific domain + * + * @param string $domain + * @return array + */ + protected function _matchDomain($domain) + { + $ret = array(); + + foreach (array_keys($this->cookies) as $cdom) { + if (Cookie::matchCookieDomain($cdom, $domain)) { + $ret[$cdom] = $this->cookies[$cdom]; + } + } + + return $ret; + } + + /** + * Return a subset of a domain-matching cookies that also match a specified path + * + * @param array $dom_array + * @param string $path + * @return array + */ + protected function _matchPath($domains, $path) + { + $ret = array(); + + foreach ($domains as $dom => $paths_array) { + foreach (array_keys($paths_array) as $cpath) { + if (Cookie::matchCookiePath($cpath, $path)) { + if (! isset($ret[$dom])) { + $ret[$dom] = array(); + } + + $ret[$dom][$cpath] = $paths_array[$cpath]; + } + } + } + + return $ret; + } + + /** + * Create a new Cookies object and automatically load into it all the + * cookies set in an Http_Response object. If $uri is set, it will be + * considered as the requested URI for setting default domain and path + * of the cookie. + * + * @param Response $response HTTP Response object + * @param Uri\Uri|string $uri The requested URI + * @return Cookies + * @todo Add the $uri functionality. + */ + public static function fromResponse(Response $response, $ref_uri) + { + $jar = new self(); + $jar->addCookiesFromResponse($response, $ref_uri); + return $jar; + } + + /** + * Tells if the array of cookies is empty + * + * @return bool + */ + public function isEmpty() + { + return count($this) == 0; + } + + /** + * Empties the cookieJar of any cookie + * + * @return Cookies + */ + public function reset() + { + $this->cookies = $this->rawCookies = array(); + return $this; + } + +} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..e46fbc4ab8 --- /dev/null +++ b/src/Exception/ExceptionInterface.php @@ -0,0 +1,15 @@ +getFieldName(); + $pos = strlen($fieldName) + 2; + if (strtolower(substr($headerLine, 0, $pos)) == strtolower($fieldName) . ': ') { + $headerLine = substr($headerLine, $pos); + } + + foreach ($this->getFieldValuePartsFromHeaderLine($headerLine) as $value) { + $this->addFieldValuePartToQueue($value); + } + } + + /** + * Factory method: parse Accept header string + * + * @param string $headerLine + * @return Accept + */ + public static function fromString($headerLine) + { + $obj = new static(); + $obj->parseHeaderLine($headerLine); + return $obj; + } + + /** + * Parse the Field Value Parts represented by a header line + * + * @param string $headerLine + * @throws Exception\InvalidArgumentException If header is invalid + * @return array + */ + public function getFieldValuePartsFromHeaderLine($headerLine) + { + // process multiple accept values, they may be between quotes + if (!preg_match_all('/(?:[^,"]|"(?:[^\\\"]|\\\.)*")+/', $headerLine, $values) + || !isset($values[0]) + ) { + throw new Exception\InvalidArgumentException( + 'Invalid header line for ' . $this->getFieldName() . ' header string' + ); + } + + $out = array(); + foreach ($values[0] as $value) { + $value = trim($value); + + $out[] = $this->parseFieldValuePart($value); + } + + return $out; + } + + /** + * Parse the accept params belonging to a media range + * + * @param string $fieldValuePart + * @return StdClass + */ + protected function parseFieldValuePart($fieldValuePart) + { + $raw = $subtypeWhole = $type = $fieldValuePart; + if ($pos = strpos($fieldValuePart, ';')) { + $type = substr($fieldValuePart, 0, $pos); + } + + $params = $this->getParametersFromFieldValuePart($fieldValuePart); + + if ($pos = strpos($fieldValuePart, ';')) { + $fieldValuePart = trim(substr($fieldValuePart, 0, $pos)); + } + + $format = '*'; + $subtype = '*'; + + return (object) array( + 'typeString' => trim($fieldValuePart), + 'type' => $type, + 'subtype' => $subtype, + 'subtypeRaw' => $subtypeWhole, + 'format' => $format, + 'priority' => isset($params['q']) ? $params['q'] : 1, + 'params' => $params, + 'raw' => trim($raw) + ); + } + + /** + * Parse the keys contained in the header line + * + * @param string $fieldValuePart + * @return array + */ + protected function getParametersFromFieldValuePart($fieldValuePart) + { + $params = array(); + if ((($pos = strpos($fieldValuePart, ';')) !== false)) { + preg_match_all('/(?:[^;"]|"(?:[^\\\"]|\\\.)*")+/', $fieldValuePart, $paramsStrings); + + if (isset($paramsStrings[0])) { + array_shift($paramsStrings[0]); + $paramsStrings = $paramsStrings[0]; + } + + foreach ($paramsStrings as $param) { + $explode = explode('=', $param, 2); + + $value = trim($explode[1]); + if ($value[0] == '"' && substr($value, -1) == '"') { + $value = substr(substr($value, 1), 0, -1); + } + + $params[trim($explode[0])] = stripslashes($value); + } + } + + return $params; + } + + + /** + * Get field value + * + * @return string + */ + public function getFieldValue($values = null) + { + if (!$values) { + return $this->getFieldValue($this->fieldValueParts); + } + + $strings = array(); + foreach ($values as $value) { + $params = $value->params; + array_walk($params, array($this, 'assembleAcceptParam')); + $strings[] = implode(';', array($value->typeString) + $params); + } + + return implode(', ', $strings); + } + + + /** + * Assemble and escape the field value parameters based on RFC 2616 section 2.1 + * + * @todo someone should review this thoroughly + * @param string value + * @param string $key + * @return void + */ + protected function assembleAcceptParam(&$value, $key) + { + $separators = array('(', ')', '<', '>', '@', ',', ';', ':', + '/', '[', ']', '?', '=', '{', '}', ' ', "\t"); + + $escaped = preg_replace_callback('/[[:cntrl:]"\\\\]/', // escape cntrl, ", \ + function($v) { return '\\' . $v[0]; }, + $value + ); + + if ($escaped == $value && !array_intersect(str_split($value), $separators)) { + $value = $key . '=' . $value; + } else { + $value = $key . '="' . $escaped . '"'; + } + + return $value; + } + + /** + * Add a type, with the given priority + * + * @param string $type + * @param int|float $priority + * @param array (optional) $params + * @return Accept + */ + protected function addType($type, $priority = 1, array $params = array()) + { + if (!preg_match($this->regexAddType, $type)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a valid type; received "%s"', + __METHOD__, + (string) $type + )); + } + + if (!is_int($priority) && !is_float($priority) && !is_numeric($priority) + || $priority > 1 || $priority < 0 + ) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a numeric priority; received %s', + __METHOD__, + (string) $priority + )); + } + + if ($priority != 1) { + $params = array('q' => sprintf('%01.1f', $priority)) + $params; + } + + $assembledString = $this->getFieldValue( + array((object) array('typeString' => $type, 'params' => $params)) + ); + + $value = $this->parseFieldValuePart($assembledString); + $this->addFieldValuePartToQueue($value); + return $this; + } + + + /** + * Does the header have the requested type? + * + * @param string $type + * @return bool + */ + protected function hasType($matchAgainst) + { + return (bool) $this->match($matchAgainst); + } + + /** + * Match a media string against this header + * + * @param array|string $matchAgainst + * @return array|boolean The matched value or false + */ + public function match($matchAgainst) + { + if (is_string($matchAgainst)) { + $matchAgainst = $this->getFieldValuePartsFromHeaderLine($matchAgainst); + } + + foreach ($this->getPrioritized() as $left) { + foreach ($matchAgainst as $right) { + if ($right->type == '*' || $left->type == '*') { + if ($res = $this->matchAcceptParams($left, $right)) { + return $res; + } + } + + if ($left->type == $right->type) { + if ((($left->subtype == $right->subtype || + ($right->subtype == '*' || $left->subtype == '*')) && + ($left->format == $right->format || + $right->format == '*' || $left->format == '*'))) + { + if ($res = $this->matchAcceptParams($left, $right)) { + return $res; + } + } + } + + + } + } + + return false; + } + + /** + * Return a match where all parameters in argument #1 match those in argument #2 + * + * @param array $match1 + * @param array $match2 + * @return boolean|array + */ + protected function matchAcceptParams($match1, $match2) + { + foreach ($match2->params as $key => $value) { + if (isset($match1->params[$key])) { + if (strpos($value, '-')) { + $values = explode('-', $value, 2); + if ($values[0] > $match1->params[$key] || + $values[1] < $match1->params[$key]) + { + return false; + } + } elseif (strpos($value, '|')) { + $options = explode('|', $value); + $good = false; + foreach ($options as $option) { + if ($option == $match1->params[$key]) { + $good = true; + break; + } + } + + if (!$good) { + return false; + } + } elseif ($match1->params[$key] != $value) { + return false; + } + } + + } + + return $match1; + } + + + /** + * Add a key/value combination to the internal queue + * + * @param unknown_type $value + * @return number + */ + protected function addFieldValuePartToQueue($value) + { + $this->fieldValueParts[] = $value; + $this->sorted = false; + } + + /** + * Sort the internal Field Value Parts + * + * @See rfc2616 sect 14.1 + * Media ranges can be overridden by more specific media ranges or + * specific media types. If more than one media range applies to a given + * type, the most specific reference has precedence. For example, + * + * Accept: text/*, text/html, text/html;level=1, * /* + * + * have the following precedence: + * + * 1) text/html;level=1 + * 2) text/html + * 3) text/* + * 4) * /* + * + * @return number + */ + protected function sortFieldValueParts() + { + $sort = function($a, $b) { // If A has higher prio than B, return -1. + if ($a->priority > $b->priority) { + return -1; + } elseif ($a->priority < $b->priority) { + return 1; + } + + // Asterisks + $values = array('type', 'subtype','format'); + foreach ($values as $value) { + if ($a->$value == '*' && $b->$value != '*') { + return 1; + } elseif ($b->$value == '*' && $a->$value != '*') { + return -1; + } + } + + if ($a->type == 'application' && $b->type != 'application') { + return -1; + } elseif ($b->type == 'application' && $a->type != 'application') { + return 1; + } + + //@todo count number of dots in case of type==application in subtype + + // So far they're still the same. Longest stringlength may be more specific + if (strlen($a->raw) == strlen($b->raw)) return 0; + return (strlen($a->raw) > strlen($b->raw)) ? -1 : 1; + }; + + usort($this->fieldValueParts, $sort); + $this->sorted = true; + } + + /** + * @return array with all the keys, values and parameters this header represents: + */ + public function getPrioritized() + { + if (!$this->sorted) { + $this->sortFieldValueParts(); + } + + return $this->fieldValueParts; + } + +} diff --git a/src/Header/AbstractDate.php b/src/Header/AbstractDate.php new file mode 100644 index 0000000000..43aa259b7f --- /dev/null +++ b/src/Header/AbstractDate.php @@ -0,0 +1,234 @@ + 'D, d M Y H:i:s \G\M\T', + self::DATE_RFC1036 => 'D, d M y H:i:s \G\M\T', + self::DATE_ANSIC => 'D M j H:i:s Y', + ); + + /** + * Create date-based header from string + * + * @param string $headerLine + * @return AbstractDate + * @throws Exception\InvalidArgumentException + */ + public static function fromString($headerLine) + { + $dateHeader = new static(); + + list($name, $date) = explode(': ', $headerLine, 2); + + // check to ensure proper header type for this factory + if (strtolower($name) !== strtolower($dateHeader->getFieldName())) { + throw new Exception\InvalidArgumentException( + 'Invalid header line for "' . $dateHeader->getFieldName() . '" header string' + ); + } + + $dateHeader->setDate($date); + + return $dateHeader; + } + + /** + * Set date output format + * + * @param int $format + * @throws Exception\InvalidArgumentException + */ + public static function setDateFormat($format) + { + if (!isset(static::$dateFormats[$format])) { + throw new Exception\InvalidArgumentException( + "No constant defined for provided date format: {$format}" + ); + } + + static::$dateFormat = static::$dateFormats[$format]; + } + + /** + * Return current date output format + * + * @return string + */ + public static function getDateFormat() + { + return static::$dateFormat; + } + + /** + * Set the date for this header, this can be a string or an instance of \DateTime + * + * @param string|DateTime $date + * @return AbstractDate + * @throws Exception\InvalidArgumentException + */ + public function setDate($date) + { + if (is_string($date)) { + try { + $date = new DateTime($date, new DateTimeZone('GMT')); + } catch (\Exception $e) { + throw new Exception\InvalidArgumentException( + sprintf('Invalid date passed as string (%s)', (string) $date), + $e->getCode(), + $e + ); + } + } elseif (!($date instanceof DateTime)) { + throw new Exception\InvalidArgumentException('Date must be an instance of \DateTime or a string'); + } + + $date->setTimezone(new DateTimeZone('GMT')); + $this->date = $date; + + return $this; + } + + /** + * Return date for this header + * + * @return string + */ + public function getDate() + { + return $this->date()->format(static::$dateFormat); + } + + /** + * Return date for this header as an instance of \DateTime + * + * @return DateTime + */ + public function date() + { + if ($this->date === null) { + $this->date = new DateTime(null, new DateTimeZone('GMT')); + } + return $this->date; + } + + /** + * Compare provided date to date for this header + * Returns < 0 if date in header is less than $date; > 0 if it's greater, and 0 if they are equal. + * @see \strcmp() + * + * @param string|DateTime $date + * @return int + * @throws Exception\InvalidArgumentException + */ + public function compareTo($date) + { + if (is_string($date)) { + try { + $date = new DateTime($date, new DateTimeZone('GMT')); + } catch (\Exception $e) { + throw new Exception\InvalidArgumentException( + sprintf('Invalid Date passed as string (%s)', (string) $date), + $e->getCode(), + $e + ); + } + } elseif (!($date instanceof DateTime)) { + throw new Exception\InvalidArgumentException('Date must be an instance of \DateTime or a string'); + } + + $dateTimestamp = $date->getTimestamp(); + $thisTimestamp = $this->date()->getTimestamp(); + + return ($thisTimestamp === $dateTimestamp) ? 0 : (($thisTimestamp > $dateTimestamp) ? 1 : -1); + } + + /** + * Get header value as formatted date + * + * @return string + */ + public function getFieldValue() + { + return $this->getDate(); + } + + /** + * Return header line + * + * @return string + */ + public function toString() + { + return $this->getFieldName() . ': ' . $this->getDate(); + } + + /** + * Allow casting to string + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } +} diff --git a/src/Header/AbstractLocation.php b/src/Header/AbstractLocation.php new file mode 100644 index 0000000000..f80743a0b8 --- /dev/null +++ b/src/Header/AbstractLocation.php @@ -0,0 +1,150 @@ +getFieldName())) { + throw new Exception\InvalidArgumentException( + 'Invalid header line for "' . $locationHeader->getFieldName() . '" header string' + ); + } + + $locationHeader->setUri(trim($uri)); + + return $locationHeader; + } + + /** + * Set the URI/URL for this header, this can be a string or an instance of Zend\Uri\Http + * + * @param string|UriInterface $uri + * @return AbstractLocation + * @throws Exception\InvalidArgumentException + */ + public function setUri($uri) + { + if (is_string($uri)) { + try { + $uri = UriFactory::factory($uri); + } catch (UriException\InvalidUriPartException $e) { + throw new Exception\InvalidArgumentException( + sprintf('Invalid URI passed as string (%s)', (string) $uri), + $e->getCode(), + $e + ); + } + } elseif (!($uri instanceof UriInterface)) { + throw new Exception\InvalidArgumentException('URI must be an instance of Zend\Uri\Http or a string'); + } + $this->uri = $uri; + + return $this; + } + + /** + * Return the URI for this header + * + * @return string + */ + public function getUri() + { + if ($this->uri instanceof UriInterface) { + return $this->uri->toString(); + } + return $this->uri; + } + + /** + * Return the URI for this header as an instance of Zend\Uri\Http + * + * @return UriInterface + */ + public function uri() + { + if ($this->uri === null || is_string($this->uri)) { + $this->uri = UriFactory::factory($this->uri); + } + return $this->uri; + } + + /** + * Get header value as URI string + * + * @return string + */ + public function getFieldValue() + { + return $this->getUri(); + } + + /** + * Output header line + * + * @return string + */ + public function toString() + { + return $this->getFieldName() . ': ' . $this->getUri(); + } + + /** + * Allow casting to string + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } +} diff --git a/src/Header/Accept.php b/src/Header/Accept.php new file mode 100644 index 0000000000..e11b62e334 --- /dev/null +++ b/src/Header/Accept.php @@ -0,0 +1,119 @@ +getFieldValue(); + } + + /** + * Add a media type, with the given priority + * + * @param string $type + * @param int|float $priority + * @param int $level + * @return Accept + */ + public function addMediaType($type, $priority = 1, array $params = array()) + { + return $this->addType($type, $priority, $params); + } + + /** + * Does the header have the requested media type? + * + * @param string $type + * @return bool + */ + public function hasMediaType($type) + { + return $this->hasType($type); + } + + /** + * Parse the keys contained in the header line + * + * @param string mediaType + * @return \Zend\Http\Header\Accept\FieldValuePart\CharsetFieldValuePart + * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() + */ + protected function parseFieldValuePart($fieldValuePart) + { + $raw = $fieldValuePart; + if ($pos = strpos($fieldValuePart, '/')) { + $type = trim(substr($fieldValuePart, 0, $pos)); + } else { + $type = trim(substr($fieldValuePart, 0)); + } + + $params = $this->getParametersFromFieldValuePart($fieldValuePart); + + if ($pos = strpos($fieldValuePart, ';')) { + $fieldValuePart = trim(substr($fieldValuePart, 0, $pos)); + } + + if ($pos = strpos($fieldValuePart, '/')) { + $subtypeWhole = $format = $subtype = trim(substr($fieldValuePart, strpos($fieldValuePart, '/')+1)); + } else { + $subtypeWhole = ''; + $format = '*'; + $subtype = '*'; + } + + $pos = strpos($subtype, '+'); + if (false !== $pos) { + $format = trim(substr($subtype, $pos+1)); + $subtype = trim(substr($subtype, 0, $pos)); + } + + $aggregated = array( + 'typeString' => trim($fieldValuePart), + 'type' => $type, + 'subtype' => $subtype, + 'subtypeRaw' => $subtypeWhole, + 'format' => $format, + 'priority' => isset($params['q']) ? $params['q'] : 1, + 'params' => $params, + 'raw' => trim($raw) + ); + + return new FieldValuePart\AcceptFieldValuePart((object) $aggregated); + } +} diff --git a/src/Header/Accept/FieldValuePart/AbstractFieldValuePart.php b/src/Header/Accept/FieldValuePart/AbstractFieldValuePart.php new file mode 100644 index 0000000000..706a6b7155 --- /dev/null +++ b/src/Header/Accept/FieldValuePart/AbstractFieldValuePart.php @@ -0,0 +1,90 @@ +internalValues = $internalValues; + } + + /** + * + * @return object + */ + protected function getInternalValues() + { + return $this->internalValues; + } + + /** + * @return string $typeString + */ + public function getTypeString() + { + return $this->getInternalValues()->typeString; + } + + /** + * @return float $priority + */ + public function getPriority() + { + return (float) $this->getInternalValues()->priority; + } + + /** + * @return StdClass $params + */ + public function getParams() + { + return (object) $this->getInternalValues()->params; + } + + /** + * @return raw $raw + */ + public function getRaw() + { + return $this->getInternalValues()->raw; + } + + /** + * + * @param mixed + * @return mixed + */ + public function __get($key) + { + return $this->getInternalValues()->$key; + } + +} diff --git a/src/Header/Accept/FieldValuePart/AcceptFieldValuePart.php b/src/Header/Accept/FieldValuePart/AcceptFieldValuePart.php new file mode 100644 index 0000000000..dc61526f38 --- /dev/null +++ b/src/Header/Accept/FieldValuePart/AcceptFieldValuePart.php @@ -0,0 +1,48 @@ +getInternalValues()->subtype; + } + + /** + * @return string + */ + public function getSubtypeRaw() + { + return $this->getInternalValues()->subtypeRaw; + } + + /** + * @return string + */ + public function getFormat() + { + return $this->getInternalValues()->format; + } + +} diff --git a/src/Header/Accept/FieldValuePart/CharsetFieldValuePart.php b/src/Header/Accept/FieldValuePart/CharsetFieldValuePart.php new file mode 100644 index 0000000000..b6a848153a --- /dev/null +++ b/src/Header/Accept/FieldValuePart/CharsetFieldValuePart.php @@ -0,0 +1,33 @@ +getInternalValues()->type; + } + +} diff --git a/src/Header/Accept/FieldValuePart/EncodingFieldValuePart.php b/src/Header/Accept/FieldValuePart/EncodingFieldValuePart.php new file mode 100644 index 0000000000..73a24e6fdc --- /dev/null +++ b/src/Header/Accept/FieldValuePart/EncodingFieldValuePart.php @@ -0,0 +1,33 @@ +type; + } + +} diff --git a/src/Header/Accept/FieldValuePart/LanguageFieldValuePart.php b/src/Header/Accept/FieldValuePart/LanguageFieldValuePart.php new file mode 100644 index 0000000000..5271486962 --- /dev/null +++ b/src/Header/Accept/FieldValuePart/LanguageFieldValuePart.php @@ -0,0 +1,39 @@ +getInternalValues()->typeString; + } + + public function getPrimaryTag() + { + return $this->getInternalValues()->type; + } + + public function getSubTag() + { + return $this->getInternalValues()->subtype; + } + +} diff --git a/src/Header/AcceptCharset.php b/src/Header/AcceptCharset.php new file mode 100644 index 0000000000..20a6ccedbe --- /dev/null +++ b/src/Header/AcceptCharset.php @@ -0,0 +1,81 @@ +getFieldValue(); + } + + /** + * Add a charset, with the given priority + * + * @param string $type + * @param int|float $priority + * @return Accept + */ + public function addCharset($type, $priority = 1) + { + return $this->addType($type, $priority); + } + + /** + * Does the header have the requested charset? + * + * @param string $type + * @return bool + */ + public function hasCharset($type) + { + return $this->hasType($type); + } + + /** + * Parse the keys contained in the header line + * + * @param string mediaType + * @return \Zend\Http\Header\Accept\FieldValuePart\CharsetFieldValuePart + * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() + */ + protected function parseFieldValuePart($fieldValuePart) + { + $internalValues = parent::parseFieldValuePart($fieldValuePart); + + return new FieldValuePart\CharsetFieldValuePart($internalValues); + } +} diff --git a/src/Header/AcceptEncoding.php b/src/Header/AcceptEncoding.php new file mode 100644 index 0000000000..eb79680e5f --- /dev/null +++ b/src/Header/AcceptEncoding.php @@ -0,0 +1,83 @@ +getFieldValue(); + } + + /** + * Add an encoding, with the given priority + * + * @param string $type + * @param int|float $priority + * @return Accept + */ + public function addEncoding($type, $priority = 1) + { + return $this->addType($type, $priority); + } + + /** + * Does the header have the requested encoding? + * + * @param string $type + * @return bool + */ + public function hasEncoding($type) + { + return $this->hasType($type); + } + + /** + * Parse the keys contained in the header line + * + * @param string mediaType + * @return \Zend\Http\Header\Accept\FieldValuePart\EncodingFieldValuePart + * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() + */ + protected function parseFieldValuePart($fieldValuePart) + { + $internalValues = parent::parseFieldValuePart($fieldValuePart); + + return new FieldValuePart\EncodingFieldValuePart($internalValues); + } +} + diff --git a/src/Header/AcceptLanguage.php b/src/Header/AcceptLanguage.php new file mode 100644 index 0000000000..8e91a66d6a --- /dev/null +++ b/src/Header/AcceptLanguage.php @@ -0,0 +1,114 @@ +getFieldValue(); + } + + /** + * Add a language, with the given priority + * + * @param string $type + * @param int|float $priority + * @return Accept + */ + public function addLanguage($type, $priority = 1) + { + return $this->addType($type, $priority); + } + + /** + * Does the header have the requested language? + * + * @param string $type + * @return bool + */ + public function hasLanguage($type) + { + return $this->hasType($type); + } + + /** + * Parse the keys contained in the header line + * + * @param string mediaType + * @return \Zend\Http\Header\Accept\FieldValuePart\LanguageFieldValuePart + * @see \Zend\Http\Header\AbstractAccept::parseFieldValuePart() + */ + protected function parseFieldValuePart($fieldValuePart) + { + $raw = $fieldValuePart; + if ($pos = strpos($fieldValuePart, '-')) { + $type = trim(substr($fieldValuePart, 0, $pos)); + } else { + $type = trim(substr($fieldValuePart, 0)); + } + + $params = $this->getParametersFromFieldValuePart($fieldValuePart); + + if ($pos = strpos($fieldValuePart, ';')) { + $fieldValuePart = $type = trim(substr($fieldValuePart, 0, $pos)); + } + + if ($pos = strpos($fieldValuePart, '-')) { + $subtypeWhole = $format = $subtype = trim(substr($fieldValuePart, strpos($fieldValuePart, '-')+1)); + } else { + $subtypeWhole = ''; + $format = '*'; + $subtype = '*'; + } + + $aggregated = array( + 'typeString' => trim($fieldValuePart), + 'type' => $type, + 'subtype' => $subtype, + 'subtypeRaw' => $subtypeWhole, + 'format' => $format, + 'priority' => isset($params['q']) ? $params['q'] : 1, + 'params' => $params, + 'raw' => trim($raw) + ); + + return new FieldValuePart\LanguageFieldValuePart((object) $aggregated); + } + +} diff --git a/src/Header/AcceptRanges.php b/src/Header/AcceptRanges.php new file mode 100644 index 0000000000..b002392dd9 --- /dev/null +++ b/src/Header/AcceptRanges.php @@ -0,0 +1,67 @@ +rangeUnit = trim($value); + + return $header; + } + + public function getFieldName() + { + return 'Accept-Ranges'; + } + + public function getFieldValue() + { + return $this->getRangeUnit(); + } + + public function setRangeUnit($rangeUnit) + { + $this->rangeUnit = $rangeUnit; + return $this; + } + + public function getRangeUnit() + { + return $this->rangeUnit; + } + + public function toString() + { + return 'Accept-Ranges: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Age.php b/src/Header/Age.php new file mode 100644 index 0000000000..f1cc3b26a1 --- /dev/null +++ b/src/Header/Age.php @@ -0,0 +1,105 @@ +deltaSeconds = (int) $value; + + return $header; + } + + /** + * Get header name + * + * @return string + */ + public function getFieldName() + { + return 'Age'; + } + + /** + * Get header value (number of seconds) + * + * @return int + */ + public function getFieldValue() + { + return $this->getDeltaSeconds(); + } + + /** + * Set number of seconds + * + * @param int $delta + * @return RetryAfter + */ + public function setDeltaSeconds($delta) + { + $this->deltaSeconds = (int) $delta; + return $this; + } + + /** + * Get number of seconds + * + * @return int + */ + public function getDeltaSeconds() + { + return $this->deltaSeconds; + } + + /** + * Return header line + * In case of overflow RFC states to set value of 2147483648 (2^31) + * + * @return string + */ + public function toString() + { + return 'Age: ' . (($this->deltaSeconds >= PHP_INT_MAX) ? '2147483648' : $this->deltaSeconds); + } +} diff --git a/src/Header/Allow.php b/src/Header/Allow.php new file mode 100644 index 0000000000..142339f6eb --- /dev/null +++ b/src/Header/Allow.php @@ -0,0 +1,184 @@ + false, + Request::METHOD_GET => true, + Request::METHOD_HEAD => false, + Request::METHOD_POST => true, + Request::METHOD_PUT => false, + Request::METHOD_DELETE => false, + Request::METHOD_TRACE => false, + Request::METHOD_CONNECT => false, + Request::METHOD_PATCH => false, + ); + + /** + * Create Allow header from header line + * + * @param string $headerLine + * @return Allow + * @throws Exception\InvalidArgumentException + */ + public static function fromString($headerLine) + { + $header = new static(); + + list($name, $value) = explode(': ', $headerLine, 2); + + // check to ensure proper header type for this factory + if (strtolower($name) !== 'allow') { + throw new Exception\InvalidArgumentException('Invalid header line for Allow string: "' . $name . '"'); + } + + // reset list of methods + $header->methods = array_fill_keys(array_keys($header->methods), false); + + // allow methods from header line + foreach (explode(',', $value) as $method) { + $method = trim(strtoupper($method)); + $header->methods[$method] = true; + } + + return $header; + } + + /** + * Get header name + * + * @return string + */ + public function getFieldName() + { + return 'Allow'; + } + + /** + * Get comma-separated list of allowed methods + * + * @return string + */ + public function getFieldValue() + { + return implode(', ', array_keys($this->methods, true, true)); + } + + /** + * Get list of all defined methods + * + * @return array + */ + public function getAllMethods() + { + return $this->methods; + } + + /** + * Get list of allowed methods + * + * @return array + */ + public function getAllowedMethods() + { + return array_keys($this->methods, true, true); + } + + /** + * Allow methods or list of methods + * + * @param array|string $allowedMethods + * @return Allow + */ + public function allowMethods($allowedMethods) + { + foreach ((array) $allowedMethods as $method) { + $method = trim(strtoupper($method)); + $this->methods[$method] = true; + } + + return $this; + } + + /** + * Disallow methods or list of methods + * + * @param array|string $disallowedMethods + * @return Allow + */ + public function disallowMethods($disallowedMethods) + { + foreach ((array) $disallowedMethods as $method) { + $method = trim(strtoupper($method)); + $this->methods[$method] = false; + } + + return $this; + } + + /** + * Convenience alias for @see disallowMethods() + * + * @param array|string $disallowedMethods + * @return Allow + */ + public function denyMethods($disallowedMethods) + { + return $this->disallowMethods($disallowedMethods); + } + + /** + * Check whether method is allowed + * + * @param string $method + * @return boolean + */ + public function isAllowedMethod($method) + { + $method = trim(strtoupper($method)); + + // disallow unknown method + if (! isset($this->methods[$method])) { + $this->methods[$method] = false; + } + + return $this->methods[$method]; + } + + /** + * Return header as string + * + * @return string + */ + public function toString() + { + return 'Allow: ' . $this->getFieldValue(); + } +} diff --git a/src/Header/AuthenticationInfo.php b/src/Header/AuthenticationInfo.php new file mode 100644 index 0000000000..e02cf6d0a7 --- /dev/null +++ b/src/Header/AuthenticationInfo.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Authentication-Info'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Authentication-Info: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Authorization.php b/src/Header/Authorization.php new file mode 100644 index 0000000000..fe37aee662 --- /dev/null +++ b/src/Header/Authorization.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Authorization'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Authorization: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/CacheControl.php b/src/Header/CacheControl.php new file mode 100644 index 0000000000..24e5a275ca --- /dev/null +++ b/src/Header/CacheControl.php @@ -0,0 +1,239 @@ +directives = self::parseValue($value); + + return $header; + } + + /** + * Required from HeaderDescription interface + * + * @return string + */ + public function getFieldName() + { + return 'Cache-Control'; + } + + /** + * Checks if the internal directives array is empty + * + * @return boolean + */ + public function isEmpty() + { + return empty($this->directives); + } + + /** + * Add a directive + * For directives like 'max-age=60', $value = '60' + * For directives like 'private', use the default $value = true + * + * @param string $key + * @param string|boolean $value + * @return CacheControl - provides the fluent interface + */ + public function addDirective($key, $value = true) + { + $this->directives[$key] = $value; + return $this; + } + + /** + * Check the internal directives array for a directive + * + * @param string $key + * @return boolean + */ + public function hasDirective($key) + { + return array_key_exists($key, $this->directives); + } + + /** + * Fetch the value of a directive from the internal directive array + * + * @param string $key + * @return string|null + */ + public function getDirective($key) + { + return array_key_exists($key, $this->directives) ? $this->directives[$key] : null; + } + + /** + * Remove a directive + * + * @param string $key + * @return CacheControl - provides the fluent interface + */ + public function removeDirective($key) + { + unset($this->directives[$key]); + return $this; + } + + /** + * Assembles the directives into a comma-delimited string + * + * @return string + */ + public function getFieldValue() + { + $parts = array(); + ksort($this->directives); + foreach ($this->directives as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"'.$value.'"'; + } + $parts[] = "$key=$value"; + } + } + return implode(', ', $parts); + } + + /** + * Returns a string representation of the HTTP Cache-Control header + * + * @return string + */ + public function toString() + { + return 'Cache-Control: ' . $this->getFieldValue(); + } + + /** + * Internal function for parsing the value part of a + * HTTP Cache-Control header + * + * @param string $value + * @throws Exception\InvalidArgumentException + * @return array + */ + protected static function parseValue($value) + { + $value = trim($value); + + $directives = array(); + + // handle empty string early so we don't need a separate start state + if ($value == '') { + return $directives; + } + + $lastMatch = null; + + state_directive: + switch (self::match(array('[a-zA-Z][a-zA-Z_-]*'), $value, $lastMatch)) { + case 0: + $directive = $lastMatch; + goto state_value; + break; + + default: + throw new Exception\InvalidArgumentException('expected DIRECTIVE'); + break; + } + + state_value: + switch (self::match(array('="[^"]*"', '=[^",\s;]*'), $value, $lastMatch)) { + case 0: + $directives[$directive] = substr($lastMatch, 2, -1); + goto state_separator; + break; + + case 1: + $directives[$directive] = rtrim(substr($lastMatch, 1)); + goto state_separator; + break; + + default: + $directives[$directive] = true; + goto state_separator; + break; + } + + state_separator: + switch (self::match(array('\s*,\s*', '$'), $value, $lastMatch)) { + case 0: + goto state_directive; + break; + + case 1: + return $directives; + break; + + default: + throw new Exception\InvalidArgumentException('expected SEPARATOR or END'); + break; + + } + } + + /** + * Internal function used by parseValue to match tokens + * + * @param array $tokens + * @param string $string + * @param string $lastMatch + * @return int + */ + protected static function match($tokens, &$string, &$lastMatch) + { + foreach ($tokens as $i => $token) { + if (preg_match('/^'.$token.'/', $string, $matches)) { + $lastMatch = $matches[0]; + $string = substr($string, strlen($matches[0])); + return $i; + } + } + return -1; + } +} diff --git a/src/Header/Connection.php b/src/Header/Connection.php new file mode 100644 index 0000000000..13a0b32ade --- /dev/null +++ b/src/Header/Connection.php @@ -0,0 +1,125 @@ +setValue(trim($value)); + + return $header; + } + + + /** + * Set Connection header to define persistent connection + * + * @param boolean $flag + * @return Connection + */ + public function setPersistent($flag) + { + if ((bool)$flag === true) { + $this->value = self::CONNECTION_KEEP_ALIVE; + } else { + $this->value = self::CONNECTION_CLOSE; + } + return $this; + } + + /** + * Get whether this connection is persistent + * + * @return bool + */ + public function isPersistent() + { + return ($this->value === self::CONNECTION_KEEP_ALIVE); + } + + /** + * Set arbitrary header value + * RFC allows any token as value, 'close' and 'keep-alive' are commonly used + * + * @param string $value + * @return Connection + */ + public function setValue($value) + { + $this->value = strtolower($value); + return $this; + } + + + /** + * Connection header name + * + * @return string + */ + public function getFieldName() + { + return 'Connection'; + } + + /** + * Connection header value + * + * @return string + */ + public function getFieldValue() + { + return $this->value; + } + + /** + * Return header line + * + * @return string + */ + public function toString() + { + return 'Connection: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentDisposition.php b/src/Header/ContentDisposition.php new file mode 100644 index 0000000000..fd5b3def79 --- /dev/null +++ b/src/Header/ContentDisposition.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-Disposition'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-Disposition: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentEncoding.php b/src/Header/ContentEncoding.php new file mode 100644 index 0000000000..97ce6f3352 --- /dev/null +++ b/src/Header/ContentEncoding.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-Encoding'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-Encoding: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentLanguage.php b/src/Header/ContentLanguage.php new file mode 100644 index 0000000000..07df21c773 --- /dev/null +++ b/src/Header/ContentLanguage.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-Language'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-Language: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentLength.php b/src/Header/ContentLength.php new file mode 100644 index 0000000000..ebe413790a --- /dev/null +++ b/src/Header/ContentLength.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-Length'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-Length: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentLocation.php b/src/Header/ContentLocation.php new file mode 100644 index 0000000000..02688589de --- /dev/null +++ b/src/Header/ContentLocation.php @@ -0,0 +1,32 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-MD5'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-MD5: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentRange.php b/src/Header/ContentRange.php new file mode 100644 index 0000000000..ffe7aae54c --- /dev/null +++ b/src/Header/ContentRange.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-Range'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-Range: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ContentType.php b/src/Header/ContentType.php new file mode 100644 index 0000000000..02a22563ed --- /dev/null +++ b/src/Header/ContentType.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Content-Type'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Content-Type: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Cookie.php b/src/Header/Cookie.php new file mode 100644 index 0000000000..4125adb8aa --- /dev/null +++ b/src/Header/Cookie.php @@ -0,0 +1,119 @@ +getName(), $nvPairs)) { + throw new Exception\InvalidArgumentException('Two cookies with the same name were provided to ' . __CLASS__ . '::' . __METHOD__); + } + + $nvPairs[$setCookie->getName()] = $setCookie->getValue(); + } + return new static($nvPairs); + } + + public static function fromString($headerLine) + { + $header = new static(); + + list($name, $value) = explode(': ', $headerLine, 2); + + // check to ensure proper header type for this factory + if (strtolower($name) !== 'cookie') { + throw new Exception\InvalidArgumentException('Invalid header line for Server string: "' . $name . '"'); + } + + $nvPairs = preg_split('#;\s*#', $value); + + $arrayInfo = array(); + foreach ($nvPairs as $nvPair) { + $parts = explode('=', $nvPair, 2); + if (count($parts) != 2) { + throw new Exception\RuntimeException('Malformed Cookie header found'); + } + list($name, $value) = $parts; + $arrayInfo[$name] = urldecode($value); + } + + $header->exchangeArray($arrayInfo); + + return $header; + } + + public function __construct(array $array = array()) + { + parent::__construct($array, ArrayObject::ARRAY_AS_PROPS); + } + + public function setEncodeValue($encodeValue) + { + $this->encodeValue = (bool) $encodeValue; + return $this; + } + + public function getEncodeValue() + { + return $this->encodeValue; + } + + public function getFieldName() + { + return 'Cookie'; + } + + public function getFieldValue() + { + $nvPairs = array(); + + foreach ($this as $name => $value) { + $nvPairs[] = $name . '=' . (($this->encodeValue) ? urlencode($value) : $value); + } + + return implode('; ', $nvPairs); + } + + public function toString() + { + return 'Cookie: ' . $this->getFieldValue(); + } + + /** + * Get the cookie as a string, suitable for sending as a "Cookie" header in an + * HTTP request + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + +} diff --git a/src/Header/Date.php b/src/Header/Date.php new file mode 100644 index 0000000000..0d091fc190 --- /dev/null +++ b/src/Header/Date.php @@ -0,0 +1,32 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Etag'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Etag: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Exception/ExceptionInterface.php b/src/Header/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..34c0a15201 --- /dev/null +++ b/src/Header/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Expect'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Expect: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Expires.php b/src/Header/Expires.php new file mode 100644 index 0000000000..80e7016dfa --- /dev/null +++ b/src/Header/Expires.php @@ -0,0 +1,32 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'From'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'From: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/GenericHeader.php b/src/Header/GenericHeader.php new file mode 100644 index 0000000000..82007ecb0b --- /dev/null +++ b/src/Header/GenericHeader.php @@ -0,0 +1,139 @@ +setFieldName($fieldName); + } + + if ($fieldValue) { + $this->setFieldValue($fieldValue); + } + } + + /** + * Set header field name + * + * @param string $fieldName + * @return GenericHeader + * @throws Exception\InvalidArgumentException( + */ + public function setFieldName($fieldName) + { + if (!is_string($fieldName) || empty($fieldName)) { + throw new Exception\InvalidArgumentException('Header name must be a string'); + } + + // Pre-filter to normalize valid characters, change underscore to dash + $fieldName = str_replace(' ', '-', ucwords(str_replace(array('_', '-'), ' ', $fieldName))); + + // Validate what we have + if (!preg_match('/^[a-z][a-z0-9-]*$/i', $fieldName)) { + throw new Exception\InvalidArgumentException( + 'Header name must start with a letter, and consist of only letters, numbers, and dashes' + ); + } + + $this->fieldName = $fieldName; + return $this; + } + + /** + * Retrieve header field name + * + * @return string + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Set header field value + * + * @param string $fieldValue + * @return GenericHeader + */ + public function setFieldValue($fieldValue) + { + $fieldValue = (string) $fieldValue; + + if (empty($fieldValue) || preg_match('/^\s+$/', $fieldValue)) { + $fieldValue = ''; + } + + $this->fieldValue = $fieldValue; + return $this; + } + + /** + * Retrieve header field value + * + * @return string + */ + public function getFieldValue() + { + return $this->fieldValue; + } + + /** + * Cast to string as a well formed HTTP header line + * + * Returns in form of "NAME: VALUE\r\n" + * + * @return string + */ + public function toString() + { + return $this->getFieldName() . ': ' . $this->getFieldValue(); + } +} diff --git a/src/Header/GenericMultiHeader.php b/src/Header/GenericMultiHeader.php new file mode 100644 index 0000000000..844279bc7d --- /dev/null +++ b/src/Header/GenericMultiHeader.php @@ -0,0 +1,43 @@ +getFieldName(); + $values = array($this->getFieldValue()); + foreach ($headers as $header) { + if (!$header instanceof static) { + throw new Exception\InvalidArgumentException('This method toStringMultipleHeaders was expecting an array of headers of the same type'); + } + $values[] = $header->getFieldValue(); + } + return $name. ': ' . implode(',', $values) . "\r\n"; + } +} diff --git a/src/Header/HeaderInterface.php b/src/Header/HeaderInterface.php new file mode 100644 index 0000000000..f8282c2c41 --- /dev/null +++ b/src/Header/HeaderInterface.php @@ -0,0 +1,19 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Host'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Host: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/IfMatch.php b/src/Header/IfMatch.php new file mode 100644 index 0000000000..1683961f66 --- /dev/null +++ b/src/Header/IfMatch.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'If-Match'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'If-Match: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/IfModifiedSince.php b/src/Header/IfModifiedSince.php new file mode 100644 index 0000000000..9eabf0e0a8 --- /dev/null +++ b/src/Header/IfModifiedSince.php @@ -0,0 +1,32 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'If-None-Match'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'If-None-Match: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/IfRange.php b/src/Header/IfRange.php new file mode 100644 index 0000000000..d7d7ed4bad --- /dev/null +++ b/src/Header/IfRange.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'If-Range'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'If-Range: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/IfUnmodifiedSince.php b/src/Header/IfUnmodifiedSince.php new file mode 100644 index 0000000000..2828c201ff --- /dev/null +++ b/src/Header/IfUnmodifiedSince.php @@ -0,0 +1,32 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Keep-Alive'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Keep-Alive: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/LastModified.php b/src/Header/LastModified.php new file mode 100644 index 0000000000..0dad953d08 --- /dev/null +++ b/src/Header/LastModified.php @@ -0,0 +1,32 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Max-Forwards'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Max-Forwards: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/MultipleHeaderInterface.php b/src/Header/MultipleHeaderInterface.php new file mode 100644 index 0000000000..3675fecac8 --- /dev/null +++ b/src/Header/MultipleHeaderInterface.php @@ -0,0 +1,16 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Pragma'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Pragma: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/ProxyAuthenticate.php b/src/Header/ProxyAuthenticate.php new file mode 100644 index 0000000000..56edcb536b --- /dev/null +++ b/src/Header/ProxyAuthenticate.php @@ -0,0 +1,65 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Proxy-Authenticate'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Proxy-Authenticate: ' . $this->getFieldValue(); + } + + public function toStringMultipleHeaders(array $headers) + { + $strings = array($this->toString()); + foreach ($headers as $header) { + if (!$header instanceof ProxyAuthenticate) { + throw new Exception\RuntimeException( + 'The ProxyAuthenticate multiple header implementation can only accept an array of ProxyAuthenticate headers' + ); + } + $strings[] = $header->toString(); + } + return implode("\r\n", $strings); + } +} diff --git a/src/Header/ProxyAuthorization.php b/src/Header/ProxyAuthorization.php new file mode 100644 index 0000000000..bab412621e --- /dev/null +++ b/src/Header/ProxyAuthorization.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Proxy-Authorization'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Proxy-Authorization: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Range.php b/src/Header/Range.php new file mode 100644 index 0000000000..6b116c7112 --- /dev/null +++ b/src/Header/Range.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Range'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Range: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Referer.php b/src/Header/Referer.php new file mode 100644 index 0000000000..07baec4d13 --- /dev/null +++ b/src/Header/Referer.php @@ -0,0 +1,49 @@ +uri->setFragment(null); + + return $this; + } + + /** + * Return header name + * + * @return string + */ + public function getFieldName() + { + return 'Referer'; + } +} diff --git a/src/Header/Refresh.php b/src/Header/Refresh.php new file mode 100644 index 0000000000..bceb98ac72 --- /dev/null +++ b/src/Header/Refresh.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Refresh'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Refresh: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/RetryAfter.php b/src/Header/RetryAfter.php new file mode 100644 index 0000000000..261d587b74 --- /dev/null +++ b/src/Header/RetryAfter.php @@ -0,0 +1,112 @@ +getFieldName())) { + throw new Exception\InvalidArgumentException( + 'Invalid header line for "' . $dateHeader->getFieldName() . '" header string' + ); + } + + if (is_numeric($date)) { + $dateHeader->setDeltaSeconds($date); + } else { + $dateHeader->setDate($date); + } + + return $dateHeader; + } + + /** + * Set number of seconds + * + * @param int $delta + * @return RetryAfter + */ + public function setDeltaSeconds($delta) + { + $this->deltaSeconds = (int) $delta; + return $this; + } + + /** + * Get number of seconds + * + * @return int + */ + public function getDeltaSeconds() + { + return $this->deltaSeconds; + } + + /** + * Get header name + * + * @return string + */ + public function getFieldName() + { + return 'Retry-After'; + } + + /** + * Returns date if it's set, or number of seconds + * + * @return int|string + */ + public function getFieldValue() + { + return ($this->date === null) ? $this->deltaSeconds : $this->getDate(); + } + + /** + * Return header line + * + * @return string + */ + public function toString() + { + return 'Retry-After: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Server.php b/src/Header/Server.php new file mode 100644 index 0000000000..86af2af6d9 --- /dev/null +++ b/src/Header/Server.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Server'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Server: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/SetCookie.php b/src/Header/SetCookie.php new file mode 100644 index 0000000000..4c55022b92 --- /dev/null +++ b/src/Header/SetCookie.php @@ -0,0 +1,520 @@ +getName() === NULL) { + $header->setName($headerKey); + $header->setValue($headerValue); + continue; + } + + // Process the remaining elements + switch (str_replace(array('-', '_'), '', strtolower($headerKey))) { + case 'expires' : $header->setExpires($headerValue); break; + case 'domain' : $header->setDomain($headerValue); break; + case 'path' : $header->setPath($headerValue); break; + case 'secure' : $header->setSecure(true); break; + case 'httponly': $header->setHttponly(true); break; + case 'version' : $header->setVersion((int) $headerValue); break; + case 'maxage' : $header->setMaxAge((int) $headerValue); break; + default: + // Intentionally omitted + } + } + + return $header; + }; + } + + list($name, $value) = explode(': ', $headerLine, 2); + + // check to ensure proper header type for this factory + if (strtolower($name) !== 'set-cookie') { + throw new Exception\InvalidArgumentException('Invalid header line for Set-Cookie string: "' . $name . '"'); + } + + $multipleHeaders = preg_split('#(?type = 'Cookie'; + + if ($name) { + $this->setName($name); + } + + if ($value) { + $this->setValue($value); // in parent + } + + if ($version!==null) { + $this->setVersion($version); + } + + if ($maxAge!==null) { + $this->setMaxAge($maxAge); + } + + if ($domain) { + $this->setDomain($domain); + } + + if ($expires) { + $this->setExpires($expires); + } + + if ($path) { + $this->setPath($path); + } + + if ($secure) { + $this->setSecure($secure); + } + + if ($httponly) { + $this->setHttpOnly($httponly); + } + } + + /** + * @return string 'Set-Cookie' + */ + public function getFieldName() + { + return 'Set-Cookie'; + } + + /** + * @throws Exception\RuntimeException + * @return string + */ + public function getFieldValue() + { + if ($this->getName() == '') { + throw new Exception\RuntimeException('A cookie name is required to generate a field value for this cookie'); + } + + $value = $this->getValue(); + if (strpos($value, '"')!==false) { + $value = '"'.urlencode(str_replace('"', '', $value)).'"'; + } else { + $value = urlencode($value); + } + $fieldValue = $this->getName() . '=' . $value; + + $version = $this->getVersion(); + if ($version!==null) { + $fieldValue .= '; Version=' . $version; + } + + $maxAge = $this->getMaxAge(); + if ($maxAge!==null) { + $fieldValue .= '; Max-Age=' . $maxAge; + } + + $expires = $this->getExpires(); + if ($expires) { + $fieldValue .= '; Expires=' . $expires; + } + + $domain = $this->getDomain(); + if ($domain) { + $fieldValue .= '; Domain=' . $domain; + } + + $path = $this->getPath(); + if ($path) { + $fieldValue .= '; Path=' . $path; + } + + if ($this->isSecure()) { + $fieldValue .= '; Secure'; + } + + if ($this->isHttponly()) { + $fieldValue .= '; HttpOnly'; + } + + return $fieldValue; + } + + /** + * @param string $name + * @return SetCookie + */ + public function setName($name) + { + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new Exception\InvalidArgumentException("Cookie name cannot contain these characters: =,; \\t\\r\\n\\013\\014 ({$name})"); + } + + $this->name = $name; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $value + */ + public function setValue($value) + { + $this->value = $value; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set version + * + * @param integer $version + */ + public function setVersion($version) + { + if (!is_int($version)) { + throw new Exception\InvalidArgumentException('Invalid Version number specified'); + } + $this->version = $version; + } + + /** + * Get version + * + * @return integer + */ + public function getVersion() + { + return $this->version; + } + + /** + * Set Max-Age + * + * @param integer $maxAge + */ + public function setMaxAge($maxAge) + { + if (!is_int($maxAge) || ($maxAge<0)) { + throw new Exception\InvalidArgumentException('Invalid Max-Age number specified'); + } + $this->maxAge = $maxAge; + } + + /** + * Get Max-Age + * + * @return integer + */ + public function getMaxAge() + { + return $this->maxAge; + } + + /** + * @param int $expires + * @return SetCookie + */ + public function setExpires($expires) + { + if (!empty($expires)) { + if (is_string($expires)) { + $expires = strtotime($expires); + } elseif (!is_int($expires)) { + throw new Exception\InvalidArgumentException('Invalid expires time specified'); + } + $this->expires = (int) $expires; + } + return $this; + } + + /** + * @return int + */ + public function getExpires($inSeconds = false) + { + if ($this->expires == null) { + return; + } + if ($inSeconds) { + return $this->expires; + } + return gmdate('D, d-M-Y H:i:s', $this->expires) . ' GMT'; + } + + /** + * @param string $domain + */ + public function setDomain($domain) + { + $this->domain = $domain; + } + + /** + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * @param string $path + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @param boolean $secure + */ + public function setSecure($secure) + { + $this->secure = $secure; + } + + /** + * @return boolean + */ + public function isSecure() + { + return $this->secure; + } + + /** + * @param boolean $httponly + */ + public function setHttponly($httponly) + { + $this->httponly = $httponly; + } + + /** + * @return boolean + */ + public function isHttponly() + { + return $this->httponly; + } + + /** + * Check whether the cookie has expired + * + * Always returns false if the cookie is a session cookie (has no expiry time) + * + * @param int $now Timestamp to consider as "now" + * @return boolean + */ + public function isExpired($now = null) + { + if ($now === null) { + $now = time(); + } + + if (is_int($this->expires) && $this->expires < $now) { + return true; + } else { + return false; + } + } + + /** + * Check whether the cookie is a session cookie (has no expiry time set) + * + * @return boolean + */ + public function isSessionCookie() + { + return ($this->expires === null); + } + + public function isValidForRequest($requestDomain, $path, $isSecure = false) + { + if ($this->getDomain() && (strrpos($requestDomain, $this->getDomain()) !== false)) { + return false; + } + + if ($this->getPath() && (strpos($path, $this->getPath()) !== 0)) { + return false; + } + + if ($this->secure && $this->isSecure()!==$isSecure) { + return false; + } + + return true; + + } + + public function toString() + { + return 'Set-Cookie: ' . $this->getFieldValue(); + } + + public function toStringMultipleHeaders(array $headers) + { + $headerLine = $this->toString(); + /* @var $header SetCookie */ + foreach ($headers as $header) { + if (!$header instanceof SetCookie) { + throw new Exception\RuntimeException( + 'The SetCookie multiple header implementation can only accept an array of SetCookie headers' + ); + } + $headerLine .= ', ' . $header->getFieldValue(); + } + return $headerLine; + } + + +} diff --git a/src/Header/TE.php b/src/Header/TE.php new file mode 100644 index 0000000000..15b97fafa6 --- /dev/null +++ b/src/Header/TE.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'TE'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'TE: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Trailer.php b/src/Header/Trailer.php new file mode 100644 index 0000000000..849dc2e3bc --- /dev/null +++ b/src/Header/Trailer.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Trailer'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Trailer: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/TransferEncoding.php b/src/Header/TransferEncoding.php new file mode 100644 index 0000000000..b083134de7 --- /dev/null +++ b/src/Header/TransferEncoding.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Transfer-Encoding'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Transfer-Encoding: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Upgrade.php b/src/Header/Upgrade.php new file mode 100644 index 0000000000..485eb07b5a --- /dev/null +++ b/src/Header/Upgrade.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Upgrade'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Upgrade: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/UserAgent.php b/src/Header/UserAgent.php new file mode 100644 index 0000000000..a3bc3411a5 --- /dev/null +++ b/src/Header/UserAgent.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'User-Agent'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'User-Agent: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Vary.php b/src/Header/Vary.php new file mode 100644 index 0000000000..1e3e8fa60b --- /dev/null +++ b/src/Header/Vary.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Vary'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Vary: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/Via.php b/src/Header/Via.php new file mode 100644 index 0000000000..0c2e66a5aa --- /dev/null +++ b/src/Header/Via.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Via'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Via: ' . $this->getFieldValue(); + } + +} diff --git a/src/Header/WWWAuthenticate.php b/src/Header/WWWAuthenticate.php new file mode 100644 index 0000000000..6f9db887e7 --- /dev/null +++ b/src/Header/WWWAuthenticate.php @@ -0,0 +1,65 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'WWW-Authenticate'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'WWW-Authenticate: ' . $this->getFieldValue(); + } + + public function toStringMultipleHeaders(array $headers) + { + $strings = array($this->toString()); + foreach ($headers as $header) { + if (!$header instanceof WWWAuthenticate) { + throw new Exception\RuntimeException( + 'The WWWAuthenticate multiple header implementation can only accept an array of WWWAuthenticate headers' + ); + } + $strings[] = $header->toString(); + } + return implode("\r\n", $strings); + } +} diff --git a/src/Header/Warning.php b/src/Header/Warning.php new file mode 100644 index 0000000000..09bb035a95 --- /dev/null +++ b/src/Header/Warning.php @@ -0,0 +1,52 @@ +value = $value; + + return $header; + } + + public function getFieldName() + { + return 'Warning'; + } + + public function getFieldValue() + { + return $this->value; + } + + public function toString() + { + return 'Warning: ' . $this->getFieldValue(); + } + +} diff --git a/src/HeaderLoader.php b/src/HeaderLoader.php new file mode 100644 index 0000000000..31640450c0 --- /dev/null +++ b/src/HeaderLoader.php @@ -0,0 +1,81 @@ + 'Zend\Http\Header\Accept', + 'acceptcharset' => 'Zend\Http\Header\AcceptCharset', + 'acceptencoding' => 'Zend\Http\Header\AcceptEncoding', + 'acceptlanguage' => 'Zend\Http\Header\AcceptLanguage', + 'acceptranges' => 'Zend\Http\Header\AcceptRanges', + 'age' => 'Zend\Http\Header\Age', + 'allow' => 'Zend\Http\Header\Allow', + 'authenticationinfo' => 'Zend\Http\Header\AuthenticationInfo', + 'authorization' => 'Zend\Http\Header\Authorization', + 'cachecontrol' => 'Zend\Http\Header\CacheControl', + 'connection' => 'Zend\Http\Header\Connection', + 'contentdisposition' => 'Zend\Http\Header\ContentDisposition', + 'contentencoding' => 'Zend\Http\Header\ContentEncoding', + 'contentlanguage' => 'Zend\Http\Header\ContentLanguage', + 'contentlength' => 'Zend\Http\Header\ContentLength', + 'contentlocation' => 'Zend\Http\Header\ContentLocation', + 'contentmd5' => 'Zend\Http\Header\ContentMD5', + 'contentrange' => 'Zend\Http\Header\ContentRange', + 'contenttype' => 'Zend\Http\Header\ContentType', + 'cookie' => 'Zend\Http\Header\Cookie', + 'date' => 'Zend\Http\Header\Date', + 'etag' => 'Zend\Http\Header\Etag', + 'expect' => 'Zend\Http\Header\Expect', + 'expires' => 'Zend\Http\Header\Expires', + 'from' => 'Zend\Http\Header\From', + 'host' => 'Zend\Http\Header\Host', + 'ifmatch' => 'Zend\Http\Header\IfMatch', + 'ifmodifiedsince' => 'Zend\Http\Header\IfModifiedSince', + 'ifnonematch' => 'Zend\Http\Header\IfNoneMatch', + 'ifrange' => 'Zend\Http\Header\IfRange', + 'ifunmodifiedsince' => 'Zend\Http\Header\IfUnmodifiedSince', + 'keepalive' => 'Zend\Http\Header\KeepAlive', + 'lastmodified' => 'Zend\Http\Header\LastModified', + 'location' => 'Zend\Http\Header\Location', + 'maxforwards' => 'Zend\Http\Header\MaxForwards', + 'pragma' => 'Zend\Http\Header\Pragma', + 'proxyauthenticate' => 'Zend\Http\Header\ProxyAuthenticate', + 'proxyauthorization' => 'Zend\Http\Header\ProxyAuthorization', + 'range' => 'Zend\Http\Header\Range', + 'referer' => 'Zend\Http\Header\Referer', + 'refresh' => 'Zend\Http\Header\Refresh', + 'retryafter' => 'Zend\Http\Header\RetryAfter', + 'server' => 'Zend\Http\Header\Server', + 'setcookie' => 'Zend\Http\Header\SetCookie', + 'te' => 'Zend\Http\Header\TE', + 'trailer' => 'Zend\Http\Header\Trailer', + 'transferencoding' => 'Zend\Http\Header\TransferEncoding', + 'upgrade' => 'Zend\Http\Header\Upgrade', + 'useragent' => 'Zend\Http\Header\UserAgent', + 'vary' => 'Zend\Http\Header\Vary', + 'via' => 'Zend\Http\Header\Via', + 'warning' => 'Zend\Http\Header\Warning', + 'wwwauthenticate' => 'Zend\Http\Header\WWWAuthenticate' + ); +} diff --git a/src/Headers.php b/src/Headers.php new file mode 100644 index 0000000000..d293f0a908 --- /dev/null +++ b/src/Headers.php @@ -0,0 +1,461 @@ +[^()><@,;:\"\\/\[\]?=}{ \t]+):.*$/', $line, $matches)) { + if ($current) { + // a header name was present, then store the current complete line + $headers->headersKeys[] = static::createKey($current['name']); + $headers->headers[] = $current; + } + $current = array( + 'name' => $matches['name'], + 'line' => trim($line) + ); + } elseif (preg_match('/^\s+.*$/', $line, $matches)) { + // continuation: append to current line + $current['line'] .= trim($line); + } elseif (preg_match('/^\s*$/', $line)) { + // empty line indicates end of headers + break; + } else { + // Line does not match header format! + throw new Exception\RuntimeException(sprintf( + 'Line "%s"does not match header format!', + $line + )); + } + } + if ($current) { + $headers->headersKeys[] = static::createKey($current['name']); + $headers->headers[] = $current; + } + return $headers; + } + + /** + * Set an alternate implementation for the PluginClassLoader + * + * @param \Zend\Loader\PluginClassLocator $pluginClassLoader + * @return Headers + */ + public function setPluginClassLoader(PluginClassLocator $pluginClassLoader) + { + $this->pluginClassLoader = $pluginClassLoader; + return $this; + } + + /** + * Return an instance of a PluginClassLocator, lazyload and inject map if necessary + * + * @return PluginClassLocator + */ + public function getPluginClassLoader() + { + if ($this->pluginClassLoader === null) { + $this->pluginClassLoader = new HeaderLoader(); + } + return $this->pluginClassLoader; + } + + /** + * Add many headers at once + * + * Expects an array (or Traversable object) of type/value pairs. + * + * @param array|Traversable $headers + * @return Headers + * @throws Exception\InvalidArgumentException + */ + public function addHeaders($headers) + { + if (!is_array($headers) && !$headers instanceof \Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected array or Traversable; received "%s"', + (is_object($headers) ? get_class($headers) : gettype($headers)) + )); + } + + foreach ($headers as $name => $value) { + if (is_int($name)) { + if (is_string($value)) { + $this->addHeaderLine($value); + } elseif (is_array($value) && count($value) == 1) { + $this->addHeaderLine(key($value), current($value)); + } elseif (is_array($value) && count($value) == 2) { + $this->addHeaderLine($value[0], $value[1]); + } elseif ($value instanceof Header\HeaderInterface) { + $this->addHeader($value); + } + } elseif (is_string($name)) { + $this->addHeaderLine($name, $value); + } + + } + + return $this; + } + + /** + * Add a raw header line, either in name => value, or as a single string 'name: value' + * + * This method allows for lazy-loading in that the parsing and instantiation of Header object + * will be delayed until they are retrieved by either get() or current() + * + * @throws Exception\InvalidArgumentException + * @param string $headerFieldNameOrLine + * @param string $fieldValue optional + * @return Headers + */ + public function addHeaderLine($headerFieldNameOrLine, $fieldValue = null) + { + $matches = null; + if (preg_match('/^(?P[^()><@,;:\"\\/\[\]?=}{ \t]+):.*$/', $headerFieldNameOrLine, $matches) + && $fieldValue === null) { + // is a header + $headerName = $matches['name']; + $headerKey = static::createKey($matches['name']); + $line = $headerFieldNameOrLine; + } elseif ($fieldValue === null) { + throw new Exception\InvalidArgumentException('A field name was provided without a field value'); + } else { + $headerName = $headerFieldNameOrLine; + $headerKey = static::createKey($headerFieldNameOrLine); + if (is_array($fieldValue)) { + $fieldValue = implode(', ', $fieldValue); + } + $line = $headerFieldNameOrLine . ': ' . $fieldValue; + } + + $this->headersKeys[] = $headerKey; + $this->headers[] = array('name' => $headerName, 'line' => $line); + + return $this; + } + + /** + * Add a Header to this container, for raw values @see addHeaderLine() and addHeaders() + * + * @param Header\HeaderInterface $header + * @return Headers + */ + public function addHeader(Header\HeaderInterface $header) + { + $this->headersKeys[] = static::createKey($header->getFieldName()); + $this->headers[] = $header; + + return $this; + } + + /** + * Remove a Header from the container + * + * @param Header\HeaderInterface $header + * @return bool + */ + public function removeHeader(Header\HeaderInterface $header) + { + $index = array_search($header, $this->headers, true); + if ($index !== false) { + unset($this->headersKeys[$index]); + unset($this->headers[$index]); + + return true; + } + return false; + } + + /** + * Clear all headers + * + * Removes all headers from queue + * + * @return Headers + */ + public function clearHeaders() + { + $this->headers = $this->headersKeys = array(); + return $this; + } + + /** + * Get all headers of a certain name/type + * + * @param string $name + * @return bool|Header\HeaderInterface|ArrayIterator + */ + public function get($name) + { + $key = static::createKey($name); + if (!in_array($key, $this->headersKeys)) { + return false; + } + + $class = ($this->getPluginClassLoader()->load($key)) ?: 'Zend\Http\Header\GenericHeader'; + + if (in_array('Zend\Http\Header\MultipleHeaderInterface', class_implements($class, true))) { + $headers = array(); + foreach (array_keys($this->headersKeys, $key) as $index) { + if (is_array($this->headers[$index])) { + $this->lazyLoadHeader($index); + } + } + foreach (array_keys($this->headersKeys, $key) as $index) { + $headers[] = $this->headers[$index]; + } + return new ArrayIterator($headers); + } else { + $index = array_search($key, $this->headersKeys); + if ($index === false) { + return false; + } + if (is_array($this->headers[$index])) { + return $this->lazyLoadHeader($index); + } else { + return $this->headers[$index]; + } + } + } + + /** + * Test for existence of a type of header + * + * @param string $name + * @return bool + */ + public function has($name) + { + return (in_array(static::createKey($name), $this->headersKeys)); + } + + /** + * Advance the pointer for this object as an interator + * + * @return void + */ + public function next() + { + next($this->headers); + } + + /** + * Return the current key for this object as an iterator + * + * @return mixed + */ + public function key() + { + return (key($this->headers)); + } + + /** + * Is this iterator still valid? + * + * @return bool + */ + public function valid() + { + return (current($this->headers) !== false); + } + + /** + * Reset the internal pointer for this object as an iterator + * + * @return void + */ + public function rewind() + { + reset($this->headers); + } + + /** + * Return the current value for this iterator, lazy loading it if need be + * + * @return array|Header\HeaderInterface + */ + public function current() + { + $current = current($this->headers); + if (is_array($current)) { + $current = $this->lazyLoadHeader(key($this->headers)); + } + return $current; + } + + /** + * Return the number of headers in this contain, if all headers have not been parsed, actual count could + * increase if MultipleHeader objects exist in the Request/Response. If you need an exact count, iterate + * + * @return int count of currently known headers + */ + public function count() + { + return count($this->headers); + } + + /** + * Render all headers at once + * + * This method handles the normal iteration of headers; it is up to the + * concrete classes to prepend with the appropriate status/request line. + * + * @return string + */ + public function toString() + { + $headers = ''; + foreach ($this->toArray() as $fieldName => $fieldValue) { + if (is_array($fieldValue)) { + // Handle multi-value headers + foreach ($fieldValue as $value) { + $headers .= $fieldName . ': ' . $value . "\r\n"; + } + continue; + } + // Handle single-value headers + $headers .= $fieldName . ': ' . $fieldValue . "\r\n"; + } + return $headers; + } + + /** + * Return the headers container as an array + * + * @todo determine how to produce single line headers, if they are supported + * @return array + */ + public function toArray() + { + $headers = array(); + /* @var $header Header\HeaderInterface */ + foreach ($this->headers as $header) { + if ($header instanceof Header\MultipleHeaderInterface) { + $name = $header->getFieldName(); + if (!isset($headers[$name])) { + $headers[$name] = array(); + } + $headers[$name][] = $header->getFieldValue(); + } elseif ($header instanceof Header\HeaderInterface) { + $headers[$header->getFieldName()] = $header->getFieldValue(); + } else { + $matches = null; + preg_match('/^(?P[^()><@,;:\"\\/\[\]?=}{ \t]+):\s*(?P.*)$/', $header['line'], $matches); + if ($matches) { + $headers[$matches['name']] = $matches['value']; + } + } + } + return $headers; + } + + /** + * By calling this, it will force parsing and loading of all headers, after this count() will be accurate + * + * @return bool + */ + public function forceLoading() + { + foreach ($this as $item) { + // $item should now be loaded + } + return true; + } + + /** + * @param $index + * @return mixed|void + */ + protected function lazyLoadHeader($index) + { + $current = $this->headers[$index]; + + $key = $this->headersKeys[$index]; + /* @var $class Header\HeaderInterface */ + $class = ($this->getPluginClassLoader()->load($key)) ?: 'Zend\Http\Header\GenericHeader'; + + $headers = $class::fromString($current['line']); + if (is_array($headers)) { + $this->headers[$index] = $current = array_shift($headers); + foreach ($headers as $header) { + $this->headersKeys[] = $key; + $this->headers[] = $header; + } + return $current; + } else { + $this->headers[$index] = $current = $headers; + return $current; + } + + } + + /** + * Create array key from header name + * + * @param string $name + * @return string + */ + protected static function createKey($name) + { + return str_replace(array('-', '_', ' ', '.'), '', strtolower($name)); + } +} diff --git a/src/PhpEnvironment/Request.php b/src/PhpEnvironment/Request.php new file mode 100644 index 0000000000..713363eaca --- /dev/null +++ b/src/PhpEnvironment/Request.php @@ -0,0 +1,559 @@ +setEnv(new Parameters($_ENV)); + + if ($_GET) { + $this->setQuery(new Parameters($_GET)); + } + if ($_POST) { + $this->setPost(new Parameters($_POST)); + } + if ($_COOKIE) { + $this->setCookies(new Parameters($_COOKIE)); + } + if ($_FILES) { + // convert PHP $_FILES superglobal + $files = $this->mapPhpFiles(); + $this->setFiles(new Parameters($files)); + } + + $this->setServer(new Parameters($_SERVER)); + } + + /** + * Get raw request body + * + * @return string + */ + public function getContent() + { + if (empty($this->content)) { + $requestBody = file_get_contents('php://input'); + if (strlen($requestBody) > 0) { + $this->content = $requestBody; + } + } + + return $this->content; + } + + /** + * Set cookies + * + * Instantiate and set cookies. + * + * @param $cookie + * @return Request + */ + public function setCookies($cookie) + { + $this->getHeaders()->addHeader(new Cookie((array) $cookie)); + return $this; + } + + /** + * Set the request URI. + * + * @param string $requestUri + * @return self + */ + public function setRequestUri($requestUri) + { + $this->requestUri = $requestUri; + return $this; + } + + /** + * Get the request URI. + * + * @return string + */ + public function getRequestUri() + { + if ($this->requestUri === null) { + $this->requestUri = $this->detectRequestUri(); + } + return $this->requestUri; + } + + /** + * Set the base URL. + * + * @param string $baseUrl + * @return self + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = rtrim($baseUrl, '/'); + return $this; + } + + /** + * Get the base URL. + * + * @return string + */ + public function getBaseUrl() + { + if ($this->baseUrl === null) { + $this->setBaseUrl($this->detectBaseUrl()); + } + return $this->baseUrl; + } + + /** + * Set the base path. + * + * @param string $basePath + * @return self + */ + public function setBasePath($basePath) + { + $this->basePath = rtrim($basePath, '/'); + return $this; + } + + /** + * Get the base path. + * + * @return string + */ + public function getBasePath() + { + if ($this->basePath === null) { + $this->setBasePath($this->detectBasePath()); + } + + return $this->basePath; + } + + /** + * Provide an alternate Parameter Container implementation for server parameters in this object, + * (this is NOT the primary API for value setting, for that see getServer()) + * + * @param ParametersInterface $server + * @return Request + */ + public function setServer(ParametersInterface $server) + { + $this->serverParams = $server; + + // This seems to be the only way to get the Authorization header on Apache + if (function_exists('apache_request_headers')) { + $apacheRequestHeaders = apache_request_headers(); + if (!isset($this->serverParams['HTTP_AUTHORIZATION']) + && isset($apacheRequestHeaders['Authorization']) + ) { + $this->serverParams->set('HTTP_AUTHORIZATION', $apacheRequestHeaders['Authorization']); + } + } + + // set headers + $headers = array(); + + foreach ($server as $key => $value) { + if ($value && strpos($key, 'HTTP_') === 0) { + if (strpos($key, 'HTTP_COOKIE') === 0) { + // Cookies are handled using the $_COOKIE superglobal + continue; + } + $name = strtr(substr($key, 5), '_', ' '); + $name = strtr(ucwords(strtolower($name)), ' ', '-'); + } elseif ($value && strpos($key, 'CONTENT_') === 0) { + $name = substr($key, 8); // Content- + $name = 'Content-' . (($name == 'MD5') ? $name : ucfirst(strtolower($name))); + } else { + continue; + } + + $headers[$name] = $value; + } + + $this->getHeaders()->addHeaders($headers); + + // set method + if (isset($this->serverParams['REQUEST_METHOD'])) { + $this->setMethod($this->serverParams['REQUEST_METHOD']); + } + + // set HTTP version + if (isset($this->serverParams['SERVER_PROTOCOL']) + && strpos($this->serverParams['SERVER_PROTOCOL'], self::VERSION_10) !== false + ) { + $this->setVersion(self::VERSION_10); + } + + // set URI + $uri = new HttpUri(); + + // URI scheme + $scheme = (!empty($this->serverParams['HTTPS']) + && $this->serverParams['HTTPS'] !== 'off') ? 'https' : 'http'; + $uri->setScheme($scheme); + + // URI host & port + $host = null; + $port = null; + if (isset($this->serverParams['SERVER_NAME'])) { + $host = $this->serverParams['SERVER_NAME']; + if (isset($this->serverParams['SERVER_PORT'])) { + $port = (int) $this->serverParams['SERVER_PORT']; + } + // Check for missinterpreted IPv6-Address + // Reported at least for Safari on Windows + if (isset($this->serverParams['SERVER_ADDR']) && preg_match('/^\[[0-9a-fA-F\:]+\]$/', $host)) { + $host = '[' . $this->serverParams['SERVER_ADDR'] . ']'; + if ($port . ']' == substr($host, strrpos($host, ':')+1)) { + // The last digit of the IPv6-Address has been taken as port + // Unset the port so the default port can be used + $port = null; + } + } + } elseif ($this->getHeaders()->get('host')) { + $host = $this->getHeaders()->get('host')->getFieldValue(); + // works for regname, IPv4 & IPv6 + if (preg_match('|\:(\d+)$|', $host, $matches)) { + $host = substr($host, 0, -1 * (strlen($matches[1]) + 1)); + $port = (int) $matches[1]; + } + } + $uri->setHost($host); + $uri->setPort($port); + + // URI path + $requestUri = $this->getRequestUri(); + if (($qpos = strpos($requestUri, '?')) !== false) { + $requestUri = substr($requestUri, 0, $qpos); + } + + $uri->setPath($requestUri); + + // URI query + if (isset($this->serverParams['QUERY_STRING'])) { + $uri->setQuery($this->serverParams['QUERY_STRING']); + } + + $this->setUri($uri); + + return $this; + } + + /** + * Return the parameter container responsible for server parameters or a single parameter value. + * + * @param string|null $name Parameter name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the parameter is missing. + * @see http://www.faqs.org/rfcs/rfc3875.html + * @return \Zend\Stdlib\ParametersInterface|mixed + */ + public function getServer($name = null, $default = null) + { + if ($this->serverParams === null) { + $this->serverParams = new Parameters(); + } + + if ($name === null) { + return $this->serverParams; + } + + return $this->serverParams->get($name, $default); + } + + /** + * Provide an alternate Parameter Container implementation for env parameters in this object, + * (this is NOT the primary API for value setting, for that see env()) + * + * @param ParametersInterface $env + * @return Request + */ + public function setEnv(ParametersInterface $env) + { + $this->envParams = $env; + return $this; + } + + /** + * Return the parameter container responsible for env parameters or a single parameter value. + * + * @param string|null $name Parameter name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the parameter is missing. * @return \Zend\Stdlib\ParametersInterface + * @return \Zend\Stdlib\ParametersInterface|mixed + */ + public function getEnv($name = null, $default = null) + { + if ($this->envParams === null) { + $this->envParams = new Parameters(); + } + + if ($name === null) { + return $this->envParams; + } + + return $this->envParams->get($name, $default); + } + + /** + * Convert PHP superglobal $_FILES into more sane parameter=value structure + * This handles form file input with brackets (name=files[]) + * + * @return array + */ + protected function mapPhpFiles() + { + $files = array(); + foreach ($_FILES as $fileName => $fileParams) { + $files[$fileName] = array(); + foreach ($fileParams as $param => $data) { + if (!is_array($data)) { + $files[$fileName][$param] = $data; + } else { + foreach ($data as $i => $v) { + $this->mapPhpFileParam($files[$fileName], $param, $i, $v); + } + } + } + } + + return $files; + } + + /** + * @param array $array + * @param string $paramName + * @param int|string $index + * @param string|array $value + */ + protected function mapPhpFileParam(&$array, $paramName, $index, $value) + { + if (!is_array($value)) { + $array[$index][$paramName] = $value; + } else { + foreach ($value as $i => $v) { + $this->mapPhpFileParam($array[$index], $paramName, $i, $v); + } + } + } + + /** + * Detect the base URI for the request + * + * Looks at a variety of criteria in order to attempt to autodetect a base + * URI, including rewrite URIs, proxy URIs, etc. + * + * @return string + */ + protected function detectRequestUri() + { + $requestUri = null; + $server = $this->getServer(); + + // Check this first so IIS will catch. + $httpXRewriteUrl = $server->get('HTTP_X_REWRITE_URL'); + if ($httpXRewriteUrl !== null) { + $requestUri = $httpXRewriteUrl; + } + + // Check for IIS 7.0 or later with ISAPI_Rewrite + $httpXOriginalUrl = $server->get('HTTP_X_ORIGINAL_URL'); + if ($httpXOriginalUrl !== null) { + $requestUri = $httpXOriginalUrl; + } + + // IIS7 with URL Rewrite: make sure we get the unencoded url + // (double slash problem). + $iisUrlRewritten = $server->get('IIS_WasUrlRewritten'); + $unencodedUrl = $server->get('UNENCODED_URL', ''); + if ('1' == $iisUrlRewritten && '' !== $unencodedUrl) { + return $unencodedUrl; + } + + // HTTP proxy requests setup request URI with scheme and host [and port] + // + the URL path, only use URL path. + if (!$httpXRewriteUrl) { + $requestUri = $server->get('REQUEST_URI'); + } + + if ($requestUri !== null) { + return preg_replace('#^[^:]+://[^/]+#', '', $requestUri); + } + + // IIS 5.0, PHP as CGI. + $origPathInfo = $server->get('ORIG_PATH_INFO'); + if ($origPathInfo !== null) { + $queryString = $server->get('QUERY_STRING', ''); + if ($queryString !== '') { + $origPathInfo .= '?' . $queryString; + } + return $origPathInfo; + } + + return '/'; + } + + /** + * Auto-detect the base path from the request environment + * + * Uses a variety of criteria in order to detect the base URL of the request + * (i.e., anything additional to the document root). + * + * The base URL includes the schema, host, and port, in addition to the path. + * + * @return string + */ + protected function detectBaseUrl() + { + $baseUrl = ''; + $filename = $this->getServer()->get('SCRIPT_FILENAME', ''); + $scriptName = $this->getServer()->get('SCRIPT_NAME'); + $phpSelf = $this->getServer()->get('PHP_SELF'); + $origScriptName = $this->getServer()->get('ORIG_SCRIPT_NAME'); + + if ($scriptName !== null && basename($scriptName) === $filename) { + $baseUrl = $scriptName; + } elseif ($phpSelf !== null && basename($phpSelf) === $filename) { + $baseUrl = $phpSelf; + } elseif ($origScriptName !== null && basename($origScriptName) === $filename) { + // 1and1 shared hosting compatibility. + $baseUrl = $origScriptName; + } else { + // Backtrack up the SCRIPT_FILENAME to find the portion + // matching PHP_SELF. + + $baseUrl = '/'; + $basename = basename($filename); + if ($basename) { + $path = ($phpSelf ? trim($phpSelf, '/') : ''); + $baseUrl .= substr($path, 0, strpos($path, $basename)) . $basename; + } + } + + // Does the base URL have anything in common with the request URI? + $requestUri = $this->getRequestUri(); + + // Full base URL matches. + if (0 === strpos($requestUri, $baseUrl)) { + return $baseUrl; + } + + // Directory portion of base path matches. + $baseDir = str_replace('\\', '/', dirname($baseUrl)); + if (0 === strpos($requestUri, $baseDir)) { + return $baseDir; + } + + $truncatedRequestUri = $requestUri; + + if (false !== ($pos = strpos($requestUri, '?'))) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + + // No match whatsoever + if (empty($basename) || false === strpos($truncatedRequestUri, $basename)) { + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of the base path. $pos !== 0 makes sure it is not matching a + // value from PATH_INFO or QUERY_STRING. + if (strlen($requestUri) >= strlen($baseUrl) + && (false !== ($pos = strpos($requestUri, $baseUrl)) && $pos !== 0) + ) { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + + return $baseUrl; + } + + /** + * Autodetect the base path of the request + * + * Uses several criteria to determine the base path of the request. + * + * @return string + */ + protected function detectBasePath() + { + $filename = basename($this->getServer()->get('SCRIPT_FILENAME', '')); + $baseUrl = $this->getBaseUrl(); + + // Empty base url detected + if ($baseUrl === '') { + return ''; + } + + // basename() matches the script filename; return the directory + if (basename($baseUrl) === $filename) { + return str_replace('\\', '/', dirname($baseUrl)); + } + + // Base path is identical to base URL + return $baseUrl; + } +} diff --git a/src/PhpEnvironment/Response.php b/src/PhpEnvironment/Response.php new file mode 100644 index 0000000000..c8ed4e8f33 --- /dev/null +++ b/src/PhpEnvironment/Response.php @@ -0,0 +1,104 @@ +headersSent; + } + + /** + * @return bool + */ + public function contentSent() + { + return $this->contentSent; + } + + /** + * Send HTTP headers + * + * @return Response + */ + public function sendHeaders() + { + if ($this->headersSent()) { + return $this; + } + + $status = $this->renderStatusLine(); + header($status); + + /** @var \Zend\Http\Header\HeaderInterface $header */ + foreach ($this->getHeaders() as $header) { + if ($header instanceof MultipleHeaderInterface) { + header($header->toString(), false); + continue; + } + header($header->toString()); + } + + $this->headersSent = true; + return $this; + } + + /** + * Send content + * + * @return Response + */ + public function sendContent() + { + if ($this->contentSent()) { + return $this; + } + + echo $this->getContent(); + $this->contentSent = true; + return $this; + } + + /** + * Send HTTP response + * + * @return Response + */ + public function send() + { + $this->sendHeaders() + ->sendContent(); + return $this; + } +} \ No newline at end of file diff --git a/src/Request.php b/src/Request.php new file mode 100644 index 0000000000..4703b54adc --- /dev/null +++ b/src/Request.php @@ -0,0 +1,500 @@ +' . $methods . ')\s(?P[^ ]*)(?:\sHTTP\/(?P\d+\.\d+)){0,1}#'; + $firstLine = array_shift($lines); + if (!preg_match($regex, $firstLine, $matches)) { + throw new Exception\InvalidArgumentException( + 'A valid request line was not found in the provided string' + ); + } + + $request->setMethod($matches['method']); + $request->setUri($matches['uri']); + + if ($matches['version']) { + $request->setVersion($matches['version']); + } + + if (count($lines) == 0) { + return $request; + } + + $isHeader = true; + $headers = $rawBody = array(); + while ($lines) { + $nextLine = array_shift($lines); + if ($nextLine == '') { + $isHeader = false; + continue; + } + if ($isHeader) { + $headers[] = $nextLine; + } else { + $rawBody[] = $nextLine; + } + } + + if ($headers) { + $request->headers = implode("\r\n", $headers); + } + + if ($rawBody) { + $request->setContent(implode("\r\n", $rawBody)); + } + + return $request; + } + + /** + * Set the method for this request + * + * @param string $method + * @return Request + * @throws Exception\InvalidArgumentException + */ + public function setMethod($method) + { + $method = strtoupper($method); + if (!defined('static::METHOD_' . $method)) { + throw new Exception\InvalidArgumentException('Invalid HTTP method passed'); + } + $this->method = $method; + return $this; + } + + /** + * Return the method for this request + * + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Set the URI/URL for this request, this can be a string or an instance of Zend\Uri\Http + * + * @throws Exception\InvalidArgumentException + * @param string|HttpUri $uri + * @return Request + */ + public function setUri($uri) + { + if (is_string($uri)) { + try { + $uri = new HttpUri($uri); + } catch (UriException\InvalidUriPartException $e) { + throw new Exception\InvalidArgumentException( + sprintf('Invalid URI passed as string (%s)', (string) $uri), + $e->getCode(), + $e + ); + } + } elseif (!($uri instanceof HttpUri)) { + throw new Exception\InvalidArgumentException( + 'URI must be an instance of Zend\Uri\Http or a string' + ); + } + $this->uri = $uri; + + return $this; + } + + /** + * Return the URI for this request object + * + * @return HttpUri + */ + public function getUri() + { + if ($this->uri === null || is_string($this->uri)) { + $this->uri = new HttpUri($this->uri); + } + return $this->uri; + } + + /** + * Return the URI for this request object as a string + * + * @return string + */ + public function getUriString() + { + if ($this->uri instanceof HttpUri) { + return $this->uri->toString(); + } + return $this->uri; + } + + /** + * Provide an alternate Parameter Container implementation for query parameters in this object, + * (this is NOT the primary API for value setting, for that see getQuery()) + * + * @param \Zend\Stdlib\ParametersInterface $query + * @return Request + */ + public function setQuery(ParametersInterface $query) + { + $this->queryParams = $query; + return $this; + } + + /** + * Return the parameter container responsible for query parameters or a single query parameter + * + * @param string|null $name Parameter name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the parameter is missing. + * @return \Zend\Stdlib\ParametersInterface|mixed + */ + public function getQuery($name = null, $default = null) + { + if ($this->queryParams === null) { + $this->queryParams = new Parameters(); + } + + if ($name === null) { + return $this->queryParams; + } + + return $this->queryParams->get($name, $default); + } + + /** + * Provide an alternate Parameter Container implementation for post parameters in this object, + * (this is NOT the primary API for value setting, for that see getPost()) + * + * @param \Zend\Stdlib\ParametersInterface $post + * @return Request + */ + public function setPost(ParametersInterface $post) + { + $this->postParams = $post; + return $this; + } + + /** + * Return the parameter container responsible for post parameters or a single post parameter. + * + * @param string|null $name Parameter name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the parameter is missing. + * @return \Zend\Stdlib\ParametersInterface|mixed + */ + public function getPost($name = null, $default = null) + { + if ($this->postParams === null) { + $this->postParams = new Parameters(); + } + + if ($name === null) { + return $this->postParams; + } + + return $this->postParams->get($name, $default); + } + + /** + * Return the Cookie header, this is the same as calling $request->getHeaders()->get('Cookie'); + * + * @convenience $request->getHeaders()->get('Cookie'); + * @return Header\Cookie + */ + public function getCookie() + { + return $this->getHeaders()->get('Cookie'); + } + + /** + * Provide an alternate Parameter Container implementation for file parameters in this object, + * (this is NOT the primary API for value setting, for that see getFiles()) + * + * @param ParametersInterface $files + * @return Request + */ + public function setFiles(ParametersInterface $files) + { + $this->fileParams = $files; + return $this; + } + + /** + * Return the parameter container responsible for file parameters or a single file. + * + * @param string|null $name Parameter name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the parameter is missing. + * @return ParametersInterface|mixed + */ + public function getFiles($name = null, $default = null) + { + if ($this->fileParams === null) { + $this->fileParams = new Parameters(); + } + + if ($name === null) { + return $this->fileParams; + } + + return $this->fileParams->get($name, $default); + } + + /** + * Return the header container responsible for headers or all headers of a certain name/type + * + * @see \Zend\Http\Headers::get() + * @param string|null $name Header name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the requested header is missing. + * @return \Zend\Http\Headers|bool|\Zend\Http\Header\HeaderInterface|\ArrayIterator + */ + public function getHeaders($name = null, $default = false) + { + if ($this->headers === null || is_string($this->headers)) { + // this is only here for fromString lazy loading + $this->headers = (is_string($this->headers)) ? Headers::fromString($this->headers) : new Headers(); + } + + if ($name === null) { + return $this->headers; + } + + if ($this->headers->has($name)) { + return $this->headers->get($name); + } + + return $default; + } + + /** + * Get all headers of a certain name/type. + * + * @see Request::getHeaders() + * @param string|null $name Header name to retrieve, or null to get the whole container. + * @param mixed|null $default Default value to use when the requested header is missing. + * @return \Zend\Http\Headers|bool|\Zend\Http\Header\HeaderInterface|\ArrayIterator + */ + public function getHeader($name, $default = false) + { + return $this->getHeaders($name, $default); + } + + /** + * Is this an OPTIONS method request? + * + * @return bool + */ + public function isOptions() + { + return ($this->method === self::METHOD_OPTIONS); + } + + /** + * Is this a GET method request? + * + * @return bool + */ + public function isGet() + { + return ($this->method === self::METHOD_GET); + } + + /** + * Is this a HEAD method request? + * + * @return bool + */ + public function isHead() + { + return ($this->method === self::METHOD_HEAD); + } + + /** + * Is this a POST method request? + * + * @return bool + */ + public function isPost() + { + return ($this->method === self::METHOD_POST); + } + + /** + * Is this a PUT method request? + * + * @return bool + */ + public function isPut() + { + return ($this->method === self::METHOD_PUT); + } + + /** + * Is this a DELETE method request? + * + * @return bool + */ + public function isDelete() + { + return ($this->method === self::METHOD_DELETE); + } + + /** + * Is this a TRACE method request? + * + * @return bool + */ + public function isTrace() + { + return ($this->method === self::METHOD_TRACE); + } + + /** + * Is this a CONNECT method request? + * + * @return bool + */ + public function isConnect() + { + return ($this->method === self::METHOD_CONNECT); + } + + /* + * Is this a PATCH method request? + * + * @return bool + */ + public function isPatch() + { + return ($this->method === self::METHOD_PATCH); + } + + /** + * Is the request a Javascript XMLHttpRequest? + * + * Should work with Prototype/Script.aculo.us, possibly others. + * + * @return boolean + */ + public function isXmlHttpRequest() + { + $header = $this->getHeaders()->get('X_REQUESTED_WITH'); + return false !== $header && $header->getFieldValue() == 'XMLHttpRequest'; + } + + /** + * Is this a Flash request? + * + * @return boolean + */ + public function isFlashRequest() + { + $header = $this->getHeaders()->get('USER_AGENT'); + return false !== $header && stristr($header->getFieldValue(), ' flash'); + } + + /** + * Return the formatted request line (first line) for this http request + * + * @return string + */ + public function renderRequestLine() + { + return $this->method . ' ' . (string) $this->uri . ' HTTP/' . $this->version; + } + + /** + * @return string + */ + public function toString() + { + $str = $this->renderRequestLine() . "\r\n"; + if ($this->headers) { + $str .= $this->headers->toString(); + } + $str .= "\r\n"; + $str .= $this->getContent(); + return $str; + } +} diff --git a/src/Response.php b/src/Response.php new file mode 100644 index 0000000000..2de4dda190 --- /dev/null +++ b/src/Response.php @@ -0,0 +1,518 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + // SUCCESS CODES + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + // REDIRECTION CODES + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', // Deprecated + 307 => 'Temporary Redirect', + // CLIENT ERROR + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + // SERVER ERROR + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ); + + /** + * @var int Status code + */ + protected $statusCode = 200; + + /** + * @var string|null Null means it will be looked up from the $reasonPhrase list above + */ + protected $reasonPhrase = null; + + /** + * Populate object from string + * + * @param string $string + * @return Response + * @throws Exception\InvalidArgumentException + */ + public static function fromString($string) + { + $lines = explode("\r\n", $string); + if (!is_array($lines) || count($lines) == 1) { + $lines = explode("\n", $string); + } + + $firstLine = array_shift($lines); + + $response = new static(); + + $regex = '/^HTTP\/(?P1\.[01]) (?P\d{3})(?:[ ]+(?P.+))?$/'; + $matches = array(); + if (!preg_match($regex, $firstLine, $matches)) { + throw new Exception\InvalidArgumentException( + 'A valid response status line was not found in the provided string' + ); + } + + $response->version = $matches['version']; + $response->setStatusCode($matches['status']); + $response->setReasonPhrase((isset($matches['reason']) ? $matches['reason'] : '')); + + if (count($lines) == 0) { + return $response; + } + + $isHeader = true; + $headers = $content = array(); + + while ($lines) { + $nextLine = array_shift($lines); + + if ($isHeader && $nextLine == '') { + $isHeader = false; + continue; + } + if ($isHeader) { + $headers[] = $nextLine; + } else { + $content[] = $nextLine; + } + } + + if ($headers) { + $response->headers = implode("\r\n", $headers); + } + + if ($content) { + $response->setContent(implode("\r\n", $content)); + } + + return $response; + } + + /** + * @return Header\SetCookie[] + */ + public function getCookie() + { + return $this->getHeaders()->get('Set-Cookie'); + } + + /** + * Set HTTP status code and (optionally) message + * + * @param integer $code + * @throws Exception\InvalidArgumentException + * @return Response + */ + public function setStatusCode($code) + { + $const = get_called_class() . '::STATUS_CODE_' . $code; + if (!is_numeric($code) || !defined($const)) { + $code = is_scalar($code) ? $code : gettype($code); + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid status code provided: "%s"', + $code + )); + } + $this->statusCode = (int) $code; + return $this; + } + + /** + * Retrieve HTTP status code + * + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * @param string $reasonPhrase + * @return Response + */ + public function setReasonPhrase($reasonPhrase) + { + $this->reasonPhrase = trim($reasonPhrase); + return $this; + } + + /** + * Get HTTP status message + * + * @return string + */ + public function getReasonPhrase() + { + if ($this->reasonPhrase == null) { + return $this->recommendedReasonPhrases[$this->statusCode]; + } + return $this->reasonPhrase; + } + + /** + * Get the body of the response + * + * @return string + */ + public function getBody() + { + $body = (string) $this->getContent(); + + $transferEncoding = $this->getHeaders()->get('Transfer-Encoding'); + + if (!empty($transferEncoding)) { + if (strtolower($transferEncoding->getFieldValue()) == 'chunked') { + $body = $this->decodeChunkedBody($body); + } + } + + $contentEncoding = $this->getHeaders()->get('Content-Encoding'); + + if (!empty($contentEncoding)) { + $contentEncoding = $contentEncoding->getFieldValue(); + if ($contentEncoding =='gzip') { + $body = $this->decodeGzip($body); + } elseif ($contentEncoding == 'deflate') { + $body = $this->decodeDeflate($body); + } + } + + return $body; + } + + /** + * Does the status code indicate a client error? + * + * @return bool + */ + public function isClientError() + { + $code = $this->getStatusCode(); + return ($code < 500 && $code >= 400); + } + + /** + * Is the request forbidden due to ACLs? + * + * @return bool + */ + public function isForbidden() + { + return (403 == $this->getStatusCode()); + } + + /** + * Is the current status "informational"? + * + * @return bool + */ + public function isInformational() + { + $code = $this->getStatusCode(); + return ($code >= 100 && $code < 200); + } + + /** + * Does the status code indicate the resource is not found? + * + * @return bool + */ + public function isNotFound() + { + return (404 === $this->getStatusCode()); + } + + /** + * Do we have a normal, OK response? + * + * @return bool + */ + public function isOk() + { + return (200 === $this->getStatusCode()); + } + + /** + * Does the status code reflect a server error? + * + * @return bool + */ + public function isServerError() + { + $code = $this->getStatusCode(); + return (500 <= $code && 600 > $code); + } + + /** + * Do we have a redirect? + * + * @return bool + */ + public function isRedirect() + { + $code = $this->getStatusCode(); + return (300 <= $code && 400 > $code); + } + + /** + * Was the response successful? + * + * @return bool + */ + public function isSuccess() + { + $code = $this->getStatusCode(); + return (200 <= $code && 300 > $code); + } + + /** + * Render the status line header + * + * @return string + */ + public function renderStatusLine() + { + $status = sprintf( + 'HTTP/%s %d %s', + $this->getVersion(), + $this->getStatusCode(), + $this->getReasonPhrase() + ); + return trim($status); + } + + /** + * Render entire response as HTTP response string + * + * @return string + */ + public function toString() + { + $str = $this->renderStatusLine() . "\r\n"; + $str .= $this->getHeaders()->toString(); + $str .= "\r\n"; + $str .= $this->getContent(); + return $str; + } + + /** + * Decode a "chunked" transfer-encoded body and return the decoded text + * + * @param string $body + * @return string + * @throws Exception\RuntimeException + */ + protected function decodeChunkedBody($body) + { + $decBody = ''; + + while (trim($body)) { + if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) { + throw new Exception\RuntimeException( + "Error parsing body - doesn't seem to be a chunked message" + ); + } + + $length = hexdec(trim($m[1])); + $cut = strlen($m[0]); + $decBody .= substr($body, $cut, $length); + $body = substr($body, $cut + $length + 2); + } + + return $decBody; + } + + /** + * Decode a gzip encoded message (when Content-encoding = gzip) + * + * Currently requires PHP with zlib support + * + * @param string $body + * @return string + * @throws Exception\RuntimeException + */ + protected function decodeGzip($body) + { + if (!function_exists('gzinflate')) { + throw new Exception\RuntimeException( + 'zlib extension is required in order to decode "gzip" encoding' + ); + } + + return gzinflate(substr($body, 10)); + } + + /** + * Decode a zlib deflated message (when Content-encoding = deflate) + * + * Currently requires PHP with zlib support + * + * @param string $body + * @return string + * @throws Exception\RuntimeException + */ + protected function decodeDeflate($body) + { + if (!function_exists('gzuncompress')) { + throw new Exception\RuntimeException( + 'zlib extension is required in order to decode "deflate" encoding' + ); + } + + /** + * Some servers (IIS ?) send a broken deflate response, without the + * RFC-required zlib header. + * + * We try to detect the zlib header, and if it does not exsit we + * teat the body is plain DEFLATE content. + * + * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov + * + * @link http://framework.zend.com/issues/browse/ZF-6040 + */ + $zlibHeader = unpack('n', substr($body, 0, 2)); + + if ($zlibHeader[1] % 31 == 0) { + return gzuncompress($body); + } else { + return gzinflate($body); + } + } +} diff --git a/src/Response/Stream.php b/src/Response/Stream.php new file mode 100644 index 0000000000..dba7ff611a --- /dev/null +++ b/src/Response/Stream.php @@ -0,0 +1,277 @@ +stream; + } + + /** + * Set the response stream + * + * @param resource $stream + * @return Stream + */ + public function setStream($stream) + { + $this->stream = $stream; + return $this; + } + + /** + * Get the cleanup trigger + * + * @return boolean + */ + public function getCleanup() + { + return $this->cleanup; + } + + /** + * Set the cleanup trigger + * + * @param bool $cleanup + */ + public function setCleanup($cleanup = true) + { + $this->cleanup = $cleanup; + } + + /** + * Get file name associated with the stream + * + * @return string + */ + public function getStreamName() + { + return $this->streamName; + } + + /** + * Set file name associated with the stream + * + * @param string $streamName Name to set + * @return Stream + */ + public function setStreamName($streamName) + { + $this->streamName = $streamName; + return $this; + } + + /** + * Create a new Zend\Http\Response\Stream object from a stream + * + * @param string $responseString + * @param resource $stream + * @return Stream + * @throws Exception\InvalidArgumentException + * @throws Exception\OutOfRangeException + */ + public static function fromStream($responseString, $stream) + { + if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { + throw new Exception\InvalidArgumentException('A valid stream is required'); + } + + $headerComplete = false; + $headersString = ''; + + $responseArray = explode("\n", $responseString); + + while (count($responseArray)) { + $nextLine = array_shift($responseArray); + $headersString .= $nextLine."\n"; + $nextLineTrimmed = trim($nextLine); + if ($nextLineTrimmed == '') { + $headerComplete = true; + break; + } + } + + if (!$headerComplete) { + while (false !== ($nextLine = fgets($stream))) { + + $headersString .= trim($nextLine)."\r\n"; + if ($nextLine == "\r\n" || $nextLine == "\n") { + $headerComplete = true; + break; + } + } + } + + if (!$headerComplete) { + throw new Exception\OutOfRangeException('End of header not found'); + } + + /** @var Stream $response */ + $response = static::fromString($headersString); + + if (is_resource($stream)) { + $response->setStream($stream); + } + + if (count($responseArray)) { + $response->content = implode("\n", $responseArray); + } + + $headers = $response->getHeaders(); + foreach ($headers as $header) { + if ($header instanceof \Zend\Http\Header\ContentLength) { + $response->contentLength = (int) $header->getFieldValue(); + if (strlen($response->content) > $response->contentLength) { + throw new Exception\OutOfRangeException(sprintf( + 'Too much content was extracted from the stream (%d instead of %d bytes)', + strlen($response->content), + $response->contentLength + )); + } + break; + } + } + + return $response; + } + + /** + * Get the response body as string + * + * This method returns the body of the HTTP response (the content), as it + * should be in it's readable version - that is, after decoding it (if it + * was decoded), deflating it (if it was gzip compressed), etc. + * + * If you want to get the raw body (as transferred on wire) use + * $this->getRawBody() instead. + * + * @return string + */ + public function getBody() + { + if ($this->stream != null) { + $this->readStream(); + } + return parent::getBody(); + } + + /** + * Get the raw response body (as transferred "on wire") as string + * + * If the body is encoded (with Transfer-Encoding, not content-encoding - + * IE "chunked" body), gzip compressed, etc. it will not be decoded. + * + * @return string + */ + public function getRawBody() + { + if ($this->stream) { + $this->readStream(); + } + return $this->content; + } + + /** + * Read stream content and return it as string + * + * Function reads the remainder of the body from the stream and closes the stream. + * + * @return string + */ + protected function readStream() + { + if (!is_null($this->contentLength)) { + $bytes = $this->contentLength - $this->contentStreamed; + } else { + $bytes = -1; //Read the whole buffer + } + + if (!is_resource($this->stream) || $bytes == 0) { + return ''; + } + + $this->content .= stream_get_contents($this->stream, $bytes); + $this->contentStreamed += strlen($this->content); + + if ($this->contentLength == $this->contentStreamed) { + $this->stream = null; + } + } + + /** + * Destructor + */ + public function __destruct() + { + if (is_resource($this->stream)) { + $this->stream = null; //Could be listened by others + } + if ($this->cleanup) { + ErrorHandler::start(E_WARNING); + unlink($this->streamName); + ErrorHandler::stop(); + } + } +} diff --git a/test/Client/CommonHttpTests.php b/test/Client/CommonHttpTests.php new file mode 100644 index 0000000000..b75b7a5d09 --- /dev/null +++ b/test/Client/CommonHttpTests.php @@ -0,0 +1,1049 @@ +baseuri must point to a directory on a web server + * containing all the files under the files directory. You should symlink + * or copy these files and set 'baseuri' properly. + * + * You can also set the proper constant in your test configuration file to + * point to the right place. + * + * @category Zend + * @package Zend_Http_Client + * @subpackage UnitTests + * @group Zend_Http + * @group Zend_Http_Client + */ +abstract class CommonHttpTests extends \PHPUnit_Framework_TestCase +{ + /** + * The bast URI for this test, containing all files in the files directory + * Should be set in TestConfiguration.php or TestConfiguration.php.dist + * + * @var string + */ + protected $baseuri; + + /** + * Common HTTP client + * + * @var \Zend\Http\Client + */ + protected $client = null; + + /** + * Common HTTP client adapter + * + * @var \Zend\Http\Client\Adapter\AdapterInterface + */ + protected $_adapter = null; + + /** + * Configuration array + * + * @var array + */ + protected $config = array( + 'adapter' => 'Zend\Http\Client\Adapter\Socket' + ); + + /** + * Set up the test case + * + */ + protected function setUp() + { + if (defined('TESTS_ZEND_HTTP_CLIENT_BASEURI') + && (TESTS_ZEND_HTTP_CLIENT_BASEURI != false)) { + + $this->baseuri = TESTS_ZEND_HTTP_CLIENT_BASEURI; + if (substr($this->baseuri, -1) != '/') $this->baseuri .= '/'; + + $name = $this->getName(); + if (($pos = strpos($name, ' ')) !== false) { + $name = substr($name, 0, $pos); + } + + $uri = $this->baseuri . $name . '.php'; + + $this->_adapter = new $this->config['adapter']; + $this->client = new HTTPClient($uri, $this->config); + $this->client->setAdapter($this->_adapter); + + } else { + // Skip tests + $this->markTestSkipped("Zend_Http_Client dynamic tests are not enabled in TestConfiguration.php"); + } + } + + /** + * Clean up the test environment + * + */ + protected function tearDown() + { + $this->client = null; + $this->_adapter = null; + } + + /** + * Simple request tests + */ + + /** + * Test simple requests + * + */ + public function testSimpleRequests() + { + $methods= array(Request::METHOD_GET, Request::METHOD_POST, Request::METHOD_OPTIONS, + Request::METHOD_PUT, Request::METHOD_DELETE, Request::METHOD_PATCH); + + foreach ($methods as $method) { + $this->client->setMethod($method); + $res = $this->client->send(); + $this->assertTrue($res->isSuccess(), "HTTP {$method} request failed."); + } + } + + /** + * Test we can get the last request as string + * + */ + public function testGetLastRawRequest() + { + $this->client->setUri($this->baseuri . 'testHeaders.php'); + $this->client->setParameterGet(array('someinput' => 'somevalue')); + $this->client->setHeaders(array( + 'X-Powered-By' => 'My Glorious Golden Ass', + )); + + $this->client->setMethod('TRACE'); + $res = $this->client->send(); + if ($res->getStatusCode() == 405 || $res->getStatusCode() == 501) { + $this->markTestSkipped("Server does not allow the TRACE method"); + } + + $this->assertEquals($this->client->getLastRawRequest(), $res->getBody(), 'Response body should be exactly like the last request'); + } + + /** + * GET and POST parameters tests + */ + + /** + * Test we can properly send GET parameters + * + * @dataProvider parameterArrayProvider + */ + public function testGetData($params) + { + $this->client->setUri($this->client->getUri() . '?name=Arthur'); + $this->client->setParameterGet($params); + $res = $this->client->send(); + $this->assertEquals(serialize(array_merge(array('name' => 'Arthur'), $params)), $res->getBody()); + } + + /** + * Test we can properly send POST parameters with + * application/x-www-form-urlencoded content type + * + * @dataProvider parameterArrayProvider + */ + public function testPostDataUrlEncoded($params) + { + $this->client->setUri($this->baseuri . 'testPostData.php'); + $this->client->setEncType(HTTPClient::ENC_URLENCODED); + + $this->client->setParameterPost($params); + + $this->client->setMethod('POST'); + $this->assertFalse($this->client->getRequest()->isPatch()); + $res = $this->client->send(); + $this->assertEquals(serialize($params), $res->getBody(), "POST data integrity test failed"); + } + + /** + * Test we can properly send PATCH parameters with + * application/x-www-form-urlencoded content type + * + * @dataProvider parameterArrayProvider + */ + public function testPatchData($params) + { + $client = $this->client; + $client->setUri($this->baseuri . 'testPatchData.php'); + + $client->setRawBody(serialize($params)); + + $client->setMethod('PATCH'); + $this->assertEquals($client::ENC_URLENCODED, $this->client->getEncType()); + $this->assertTrue($client->getRequest()->isPatch()); + $res = $this->client->send(); + $this->assertEquals(serialize($params), $res->getBody(), "PATCH data integrity test failed"); + } + + /** + * Test we can properly send POST parameters with + * multipart/form-data content type + * + * @dataProvider parameterArrayProvider + */ + public function testPostDataMultipart($params) + { + $this->client->setUri($this->baseuri . 'testPostData.php'); + $this->client->setEncType(HTTPClient::ENC_FORMDATA); + $this->client->setParameterPost($params); + $this->client->setMethod('POST'); + $res = $this->client->send(); + $this->assertEquals(serialize($params), $res->getBody(), "POST data integrity test failed"); + } + + /** + * Test using raw HTTP POST data + * + */ + public function testRawPostData() + { + $data = "Chuck Norris never wet his bed as a child. The bed wet itself out of fear."; + + $this->client->setRawBody($data); + $this->client->setEncType('text/html'); + $this->client->setMethod('POST'); + $res = $this->client->send(); + $this->assertEquals($data, $res->getBody(), 'Response body does not contain the expected data'); + } + + /** + * Make sure we can reset the parameters between consecutive requests + * + */ + public function testResetParameters() + { + $params = array( + 'quest' => 'To seek the holy grail', + 'YourMother' => 'Was a hamster', + 'specialChars' => '<>$+ &?=[]^%', + 'array' => array('firstItem', 'secondItem', '3rdItem') + ); + + $headers = array("X-Foo" => "bar"); + + $this->client->setParameterPost($params); + $this->client->setParameterGet($params); + $this->client->setHeaders($headers); + $this->client->setMethod('POST'); + + $res = $this->client->send(); + + $this->assertContains(serialize($params) . "\n" . serialize($params), + $res->getBody(), "returned body does not contain all GET and POST parameters (it should!)"); + + $this->client->resetParameters(); + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $this->assertNotContains(serialize($params), $res->getBody(), + "returned body contains GET or POST parameters (it shouldn't!)"); + $headerXFoo= $this->client->getHeader("X-Foo"); + $this->assertTrue(empty($headerXFoo), "Header not preserved by reset"); + + } + + /** + * Test parameters get reset when we unset them + * + */ + public function testParameterUnset() + { + $this->client->setUri($this->baseuri . 'testResetParameters.php'); + + $gparams = array ( + 'cheese' => 'camambert', + 'beer' => 'jever pilnsen', + ); + + $pparams = array ( + 'from' => 'bob', + 'to' => 'alice' + ); + + $this->client->setParameterGet($gparams)->setParameterPost($pparams); + + // Remove some parameters + $this->client->setParameterGet(array ('cheese' => null)) + ->setParameterPost(array('to' => null)); + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $this->assertNotContains('cheese', $res->getBody(), 'The "cheese" GET parameter was expected to be unset'); + $this->assertNotContains('alice', $res->getBody(), 'The "to" POST parameter was expected to be unset'); + } + + /** + * Header Tests + */ + + /** + * Make sure we can set a single header + * + */ + public function testHeadersSingle() + { + $this->client->setUri($this->baseuri . 'testHeaders.php'); + + $headers = array( + 'Accept-encoding' => 'gzip,deflate', + 'X-baz' => 'Foo', + 'X-powered-by' => 'A large wooden badger', + 'Accept' => 'text/xml,text/html,*/*' + ); + + $this->client->setHeaders($headers); + $this->client->setMethod('TRACE'); + + $res = $this->client->send(); + if ($res->getStatusCode() == 405 || $res->getStatusCode() == 501) { + $this->markTestSkipped("Server does not allow the TRACE method"); + } + + $body = strtolower($res->getBody()); + + foreach ($headers as $key => $val) + $this->assertContains(strtolower("$key: $val"), $body); + } + + /** + * Test we can set an array of headers + * + */ + public function testHeadersArray() + { + $this->client->setUri($this->baseuri . 'testHeaders.php'); + + $headers = array( + 'Accept-encoding' => 'gzip,deflate', + 'X-baz' => 'Foo', + 'X-powered-by' => 'A large wooden badger', + 'Accept: text/xml,text/html,*/*' + ); + + $this->client->setHeaders($headers); + $this->client->setMethod('TRACE'); + + $res = $this->client->send(); + if ($res->getStatusCode() == 405 || $res->getStatusCode() == 501) { + $this->markTestSkipped("Server does not allow the TRACE method"); + } + + $body = strtolower($res->getBody()); + + foreach ($headers as $key => $val) { + if (is_string($key)) { + $this->assertContains(strtolower("$key: $val"), $body); + } else { + $this->assertContains(strtolower($val), $body); + } + } + } + + /** + * Test we can set a set of values for one header + * + */ + public function testMultipleHeader() + { + $this->client->setUri($this->baseuri . 'testHeaders.php'); + $headers = array( + 'Accept-encoding' => 'gzip,deflate', + 'X-baz' => 'Foo', + 'X-powered-by' => array( + 'A large wooden badger', + 'My Shiny Metal Ass', + 'Dark Matter' + ), + 'Cookie' => array( + 'foo=bar', + 'baz=waka' + ) + ); + + $this->client->setHeaders($headers); + $this->client->setMethod('TRACE'); + + $res = $this->client->send(); + if ($res->getStatusCode() == 405 || $res->getStatusCode() == 501) { + $this->markTestSkipped("Server does not allow the TRACE method"); + } + $body = strtolower($res->getBody()); + + foreach ($headers as $key => $val) { + if (is_array($val)) + $val = implode(', ', $val); + + $this->assertContains(strtolower("$key: $val"), $body); + } + } + + /** + * Redirection tests + */ + + /** + * Test the client properly redirects in default mode + * + */ + public function testRedirectDefault() + { + $this->client->setUri($this->baseuri . 'testRedirections.php'); + + // Set some parameters + $this->client->setParameterGet(array('swallow' => 'african')); + $this->client->setParameterPost(array('Camelot' => 'A silly place')); + + // Request + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $this->assertEquals(3, $this->client->getRedirectionsCount(), 'Redirection counter is not as expected'); + + // Make sure the body does *not* contain the set parameters + $this->assertNotContains('swallow', $res->getBody()); + $this->assertNotContains('Camelot', $res->getBody()); + } + + /** + * @group ZF-4136 + * @link http://framework.zend.com/issues/browse/ZF2-122 + */ + public function testRedirectPersistsCookies() + { + $this->client->setUri($this->baseuri . 'testRedirections.php'); + + // Set some parameters + $this->client->setParameterGet(array('swallow' => 'african')); + $this->client->setParameterPost(array('Camelot' => 'A silly place')); + + // Send POST request + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $this->assertEquals(3, $this->client->getRedirectionsCount(), 'Redirection counter is not as expected'); + + // Make sure the body does *not* contain the set parameters + $this->assertNotContains('swallow', $res->getBody()); + $this->assertNotContains('Camelot', $res->getBody()); + + // Check that we have received and persisted expected cookies + $cookies = $this->client->getCookies(); + $this->assertInternalType('array', $cookies, 'Client is not sending cookies on redirect'); + $this->assertArrayHasKey('zf2testSessionCookie', $cookies, 'Client is not sending cookies on redirect'); + $this->assertArrayHasKey('zf2testLongLivedCookie', $cookies, 'Client is not sending cookies on redirect'); + $this->assertEquals('positive', $cookies['zf2testSessionCookie']->getValue()); + $this->assertEquals('positive', $cookies['zf2testLongLivedCookie']->getValue()); + + // Check that expired cookies are not passed on + $this->assertArrayNotHasKey('zf2testExpiredCookie', $cookies, 'Expired cookies are not removed.'); + } + + /** + * Make sure the client properly redirects in strict mode + * + */ + public function testRedirectStrict() + { + $this->client->setUri($this->baseuri . 'testRedirections.php'); + + // Set some parameters + $this->client->setParameterGet(array('swallow' => 'african')); + $this->client->setParameterPost(array('Camelot' => 'A silly place')); + + // Set strict redirections + $this->client->setOptions(array('strictredirects' => true)); + + // Request + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $this->assertEquals(3, $this->client->getRedirectionsCount(), 'Redirection counter is not as expected'); + + // Make sure the body *does* contain the set parameters + $this->assertContains('swallow', $res->getBody()); + $this->assertContains('Camelot', $res->getBody()); + } + + /** + * Make sure redirections stop when limit is exceeded + * + */ + public function testMaxRedirectsExceeded() + { + $this->client->setUri($this->baseuri . 'testRedirections.php'); + + // Set some parameters + $this->client->setParameterGet(array('swallow' => 'african')); + $this->client->setParameterPost(array('Camelot' => 'A silly place')); + + // Set lower max redirections + // Try with strict redirections first + $this->client->setOptions(array('strictredirects' => true, 'maxredirects' => 2)); + + $this->client->setMethod('POST'); + $res = $this->client->send(); + $this->assertTrue($res->isRedirect(), + "Last response was not a redirection as expected. Response code: {$res->getStatusCode()}. Redirections counter: {$this->client->getRedirectionsCount()} (when strict redirects are on)"); + + // Then try with normal redirections + $this->client->setParameterGet(array('redirection' => '0')); + $this->client->setOptions(array('strictredirects' => false)); + $this->client->setMethod('POST'); + $res = $this->client->send(); + $this->assertTrue($res->isRedirect(), + "Last response was not a redirection as expected. Response code: {$res->getStatusCode()}. Redirections counter: {$this->client->getRedirectionsCount()} (when strict redirects are off)"); + } + + /** + * Test we can properly redirect to an absolute path (not full URI) + * + */ + public function testAbsolutePathRedirect() + { + $this->client->setUri($this->baseuri . 'testRelativeRedirections.php'); + $this->client->setParameterGet(array('redirect' => 'abpath')); + $this->client->setOptions(array('maxredirects' => 1)); + + // Get the host and port part of our baseuri + $port = ($this->client->getUri()->getPort() == 80) ? '' : ':' .$this->client->getUri()->getPort(); + $uri = $this->client->getUri()->getScheme() . '://' . $this->client->getUri()->getHost() . $port; + + $res = $this->client->send(); + + $this->assertEquals("{$uri}/path/to/fake/file.ext?redirect=abpath", $this->client->getUri()->toString(), + "The new location is not as expected: {$this->client->getUri()->toString()}"); + } + + /** + * Test we can properly redirect to a relative path + * + */ + public function testRelativePathRedirect() + { + $this->client->setUri($this->baseuri . 'testRelativeRedirections.php'); + $this->client->setParameterGet(array('redirect' => 'relpath')); + $this->client->setOptions(array('maxredirects' => 1)); + + // Set the new expected URI + $uri = clone $this->client->getUri(); + $uri->setPath(rtrim(dirname($uri->getPath()), '/') . '/path/to/fake/file.ext'); + $uri = $uri->__toString(); + + $res = $this->client->send(); + + $this->assertEquals("{$uri}?redirect=relpath", $this->client->getUri()->toString(), + "The new location is not as expected: {$this->client->getUri()->toString()}"); + } + + /** + * HTTP Authentication Tests + * + */ + + /** + * Test we can properly use Basic HTTP authentication + * + */ + public function testHttpAuthBasic() + { + $this->client->setUri($this->baseuri. 'testHttpAuth.php'); + $this->client->setParameterGet(array( + 'user' => 'alice', + 'pass' => 'secret', + 'method' => 'Basic' + )); + + // First - fail password + $this->client->setAuth('alice', 'wrong'); + $res = $this->client->send(); + $this->assertEquals(401, $res->getStatusCode(), 'Expected HTTP 401 response was not received'); + + // Now use good password + $this->client->setAuth('alice', 'secret'); + $res = $this->client->send(); + $this->assertEquals(200, $res->getStatusCode(), 'Expected HTTP 200 response was not received'); + } + + /** + * Test that we can properly use Basic HTTP authentication by specifying username and password + * in the URI + * + */ + public function testHttpAuthBasicWithCredentialsInUri() + { + $uri = str_replace('http://', 'http://%s:%s@', $this->baseuri) . 'testHttpAuth.php'; + + $this->client->setParameterGet(array( + 'user' => 'alice', + 'pass' => 'secret', + 'method' => 'Basic' + )); + + // First - fail password + $this->client->setUri(sprintf($uri, 'alice', 'wrong')); + $this->client->setMethod('GET'); + $res = $this->client->send(); + $this->assertEquals(401, $res->getStatusCode(), 'Expected HTTP 401 response was not received'); + + // Now use good password + $this->client->setUri(sprintf($uri, 'alice', 'secret')); + $this->client->setMethod('GET'); + $res = $this->client->send(); + $this->assertEquals(200, $res->getStatusCode(), 'Expected HTTP 200 response was not received'); + } + + /** + * Cookie and Cookies Tests + * + */ + + /** + * Test we can set string cookies with no jar + * + */ + public function testCookiesStringNoJar() + { + $this->client->setUri($this->baseuri. 'testCookies.php'); + + $cookies = array( + 'name' => 'value', + 'cookie' => 'crumble' + ); + + $this->client->setCookies($cookies); + + $res = $this->client->send(); + + $this->assertEquals($res->getBody(), serialize($cookies), 'Response body does not contain the expected cookies'); + } + + + /** + * Make sure we can set an array of object cookies + * + */ + public function testSetCookieObjectArray() + { + $this->client->setUri($this->baseuri. 'testCookies.php'); + $refuri = $this->client->getUri(); + + $cookies = array( + 'chocolate' => 'chips', + 'crumble' => 'apple', + 'another' => 'cookie' + ); + + $this->client->setCookies($cookies); + + $res = $this->client->send(); + $this->assertEquals($res->getBody(), serialize($cookies), 'Response body does not contain the expected cookies'); + } + + /** + * Make sure we can set an array of string cookies + * + */ + public function testSetCookieStringArray() + { + $this->client->setUri($this->baseuri. 'testCookies.php'); + + $cookies = array( + 'chocolate' => 'chips', + 'crumble' => 'apple', + 'another' => 'cookie' + ); + + $this->client->setCookies($cookies); + + $res = $this->client->send(); + $this->assertEquals($res->getBody(), serialize($cookies), 'Response body does not contain the expected cookies'); + } + + /** + * File Upload Tests + * + */ + + /** + * Test we can upload raw data as a file + * + */ + public function testUploadRawData() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('File uploads disabled.'); + } + + $this->client->setUri($this->baseuri. 'testUploads.php'); + + $rawdata = file_get_contents(__FILE__); + $this->client->setFileUpload('myfile.txt', 'uploadfile', $rawdata, 'text/plain'); + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $body = 'uploadfile myfile.txt text/plain ' . strlen($rawdata) . "\n"; + $this->assertEquals($body, $res->getBody(), 'Response body does not include expected upload parameters'); + } + + /** + * Test we can upload an existing file + * + */ + public function testUploadLocalFile() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('File uploads disabled.'); + } + + $this->client->setUri($this->baseuri. 'testUploads.php'); + $this->client->setFileUpload(__FILE__, 'uploadfile', null, 'text/x-foo-bar'); + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $size = filesize(__FILE__); + + $body = "uploadfile " . basename(__FILE__) . " text/x-foo-bar $size\n"; + $this->assertEquals($body, $res->getBody(), 'Response body does not include expected upload parameters'); + } + + public function testUploadLocalDetectMime() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('File uploads disabled.'); + } + + $detect = null; + if (function_exists('finfo_file')) { + $f = @finfo_open(FILEINFO_MIME); + if ($f) $detect = 'finfo'; + + } elseif (function_exists('mime_content_type')) { + if (mime_content_type(__FILE__)) { + $detect = 'mime_magic'; + } + } + + if (! $detect) { + $this->markTestSkipped('No MIME type detection capability (fileinfo or mime_magic extensions) is available'); + } + + $file = dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'staticFile.jpg'; + + $this->client->setUri($this->baseuri. 'testUploads.php'); + $this->client->setFileUpload($file, 'uploadfile'); + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $size = filesize($file); + $body = "uploadfile " . basename($file) . " image/jpeg $size\n"; + $this->assertEquals($body, $res->getBody(), 'Response body does not include expected upload parameters (detect: ' . $detect . ')'); + } + + public function testUploadNameWithSpecialChars() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('File uploads disabled.'); + } + + $this->client->setUri($this->baseuri. 'testUploads.php'); + + $rawdata = file_get_contents(__FILE__); + $this->client->setFileUpload('/some strage/path%/with[!@#$&]/myfile.txt', 'uploadfile', $rawdata, 'text/plain'); + $this->client->setMethod('POST'); + $res = $this->client->send(); + + $body = 'uploadfile myfile.txt text/plain ' . strlen($rawdata) . "\n"; + $this->assertEquals($body, $res->getBody(), 'Response body does not include expected upload parameters'); + } + + public function testStaticLargeFileDownload() + { + $this->client->setUri($this->baseuri . 'staticFile.jpg'); + + $got = $this->client->send()->getBody(); + $expected = $this->_getTestFileContents('staticFile.jpg'); + + $this->assertEquals($expected, $got, 'Downloaded file does not seem to match!'); + } + + /** + * Test that one can upload multiple files with the same form name, as an + * array + * + * @link http://framework.zend.com/issues/browse/ZF-5744 + */ + public function testMutipleFilesWithSameFormNameZF5744() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('File uploads disabled.'); + } + + $rawData = 'Some test raw data here...'; + + $this->client->setUri($this->baseuri . 'testUploads.php'); + + $files = array('file1.txt', 'file2.txt', 'someotherfile.foo'); + + $expectedBody = ''; + foreach ($files as $filename) { + $this->client->setFileUpload($filename, 'uploadfile[]', $rawData, 'text/plain'); + $expectedBody .= "uploadfile $filename text/plain " . strlen($rawData) . "\n"; + } + $this->client->setMethod('POST'); + + $res = $this->client->send(); + + $this->assertEquals($expectedBody, $res->getBody(), 'Response body does not include expected upload parameters'); + } + + /** + * Test that lines that might be evaluated as boolean false do not break + * the reading prematurely. + * + * @group ZF-4238 + */ + public function testZF4238FalseLinesInResponse() + { + $this->client->setUri($this->baseuri . 'ZF4238-zerolineresponse.txt'); + + $got = $this->client->send()->getBody(); + $expected = $this->_getTestFileContents('ZF4238-zerolineresponse.txt'); + $this->assertEquals($expected, $got); + } + + public function testStreamResponse() + { + if (!($this->client->getAdapter() instanceof Adapter\StreamInterface)) { + $this->markTestSkipped('Current adapter does not support streaming'); + return; + } + $this->client->setUri($this->baseuri . 'staticFile.jpg'); + $this->client->setStream(); + + $response = $this->client->send(); + + $this->assertTrue($response instanceof Response\Stream, 'Request did not return stream response!'); + $this->assertTrue(is_resource($response->getStream()), 'Request does not contain stream!'); + + $stream_name = $response->getStreamName(); + + $stream_read = stream_get_contents($response->getStream()); + $file_read = file_get_contents($stream_name); + + $expected = $this->_getTestFileContents('staticFile.jpg'); + + $this->assertEquals($expected, $stream_read, 'Downloaded stream does not seem to match!'); + $this->assertEquals($expected, $file_read, 'Downloaded file does not seem to match!'); + } + + public function testStreamResponseBody() + { + $this->markTestSkipped('To check with the new ZF2 implementation'); + + if (!($this->client->getAdapter() instanceof Adapter\StreamInterface)) { + $this->markTestSkipped('Current adapter does not support streaming'); + return; + } + $this->client->setUri($this->baseuri . 'staticFile.jpg'); + $this->client->setStream(); + + $response = $this->client->send(); + + $this->assertTrue($response instanceof Response\Stream, 'Request did not return stream response!'); + $this->assertTrue(is_resource($response->getStream()), 'Request does not contain stream!'); + + $body = $response->getBody(); + + $expected = $this->_getTestFileContents('staticFile.jpg'); + $this->assertEquals($expected, $body, 'Downloaded stream does not seem to match!'); + } + + public function testStreamResponseNamed() + { + if (!($this->client->getAdapter() instanceof Adapter\StreamInterface)) { + $this->markTestSkipped('Current adapter does not support streaming'); + return; + } + $this->client->setUri($this->baseuri . 'staticFile.jpg'); + $outfile = tempnam(sys_get_temp_dir(), "outstream"); + $this->client->setStream($outfile); + + $response = $this->client->send(); + + $this->assertTrue($response instanceof Response\Stream, 'Request did not return stream response!'); + $this->assertTrue(is_resource($response->getStream()), 'Request does not contain stream!'); + + $this->assertEquals($outfile, $response->getStreamName()); + + $stream_read = stream_get_contents($response->getStream()); + $file_read = file_get_contents($outfile); + + $expected = $this->_getTestFileContents('staticFile.jpg'); + + $this->assertEquals($expected, $stream_read, 'Downloaded stream does not seem to match!'); + $this->assertEquals($expected, $file_read, 'Downloaded file does not seem to match!'); + } + + public function testStreamRequest() + { + if (!($this->client->getAdapter() instanceof Adapter\StreamInterface)) { + $this->markTestSkipped('Current adapter does not support streaming'); + return; + } + $data = fopen(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'staticFile.jpg', "r"); + $this->client->setRawBody($data); + $this->client->setEncType('image/jpeg'); + $this->client->setMethod('PUT'); + $res = $this->client->send(); + $expected = $this->_getTestFileContents('staticFile.jpg'); + $this->assertEquals($expected, $res->getBody(), 'Response body does not contain the expected data'); + } + + /** + * Test that we can deal with double Content-Length headers + * + * @link http://framework.zend.com/issues/browse/ZF-9404 + */ + public function testZF9404DoubleContentLengthHeader() + { + $this->client->setUri($this->baseuri . 'ZF9404-doubleContentLength.php'); + $expect = filesize(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'ZF9404-doubleContentLength.php'); + + $response = $this->client->send(); + if (! $response->isSuccess()) { + throw new AdapterException\RuntimeException("Error requesting test URL"); + } + + $clen = $response->getHeaders()->get('Content-Length'); + + if (! (is_array($clen))) { + $this->markTestSkipped("Didn't get multiple Content-length headers"); + } + + $this->assertEquals($expect, strlen($response->getBody())); + } + + + /** + * @group ZF2-78 + * @dataProvider parameterArrayProvider + */ + public function testContentTypeAdditionlInfo($params) + { + $content_type = 'application/x-www-form-urlencoded; charset=UTF-8'; + + $this->client->setUri($this->baseuri . 'testPostData.php'); + $this->client->setHeaders(array( + 'Content-Type' => $content_type + )); + $this->client->setMethod(\Zend\Http\Request::METHOD_POST); + + $this->client->setParameterPost($params); + + $this->client->send(); + $request = Request::fromString($this->client->getLastRawRequest()); + $this->assertEquals($content_type, + $request->getHeaders()->get('Content-Type')->getFieldValue()); + } + + /** + * Internal helpder function to get the contents of test files + * + * @param string $file + * @return string + */ + protected function _getTestFileContents($file) + { + return file_get_contents(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . + '_files' . DIRECTORY_SEPARATOR . $file); + } + + /** + * Data provider for complex, nesting parameter arrays + * + * @return array + */ + public static function parameterArrayProvider() + { + return array( + array( + array( + 'quest' => 'To seek the holy grail', + 'YourMother' => 'Was a hamster', + 'specialChars' => '<>$+ &?=[]^%', + 'array' => array('firstItem', 'secondItem', '3rdItem') + ) + ), + + array( + array( + 'someData' => array( + "1", + "2", + 'key' => 'value', + 'nesting' => array( + 'a' => 'AAA', + 'b' => 'BBB' + ) + ), + 'someOtherData' => array('foo', 'bar') + ) + ), + + array( + array( + 'foo1' => 'bar', + 'foo2' => array('baz', 'w00t') + ) + ) + ); + } + + /** + * Data provider for invalid configuration containers + * + * @return array + */ + public static function invalidConfigProvider() + { + return array( + array(false), + array('foo => bar'), + array(null), + array(new \stdClass), + array(55) + ); + } + +} diff --git a/test/Client/CurlTest.php b/test/Client/CurlTest.php new file mode 100644 index 0000000000..269bef91b9 --- /dev/null +++ b/test/Client/CurlTest.php @@ -0,0 +1,326 @@ +baseuri must point to a directory on a web server + * containing all the files under the files directory. You should symlink + * or copy these files and set 'baseuri' properly. + * + * You can also set the proper constand in your test configuration file to + * point to the right place. + * + * @category Zend + * @package Zend_Http_Client + * @subpackage UnitTests + * @group Zend_Http + * @group Zend_Http_Client + */ +class CurlTest extends CommonHttpTests +{ + /** + * Configuration array + * + * @var array + */ + protected $config = array( + 'adapter' => 'Zend\Http\Client\Adapter\Curl', + 'curloptions' => array( + CURLOPT_INFILESIZE => 102400000, + ), + ); + + protected function setUp() + { + if (!extension_loaded('curl')) { + $this->markTestSkipped('cURL is not installed, marking all Http Client Curl Adapter tests skipped.'); + } + parent::setUp(); + } + + /** + * Off-line common adapter tests + */ + + /** + * Test that we can set a valid configuration array with some options + * + */ + public function testConfigSetAsArray() + { + $config = array( + 'timeout' => 500, + 'someoption' => 'hasvalue' + ); + + $this->_adapter->setOptions($config); + + $hasConfig = $this->_adapter->getConfig(); + foreach ($config as $k => $v) { + $this->assertEquals($v, $hasConfig[$k]); + } + } + + /** + * Test that a Zend_Config object can be used to set configuration + * + * @link http://framework.zend.com/issues/browse/ZF-5577 + */ + public function testConfigSetAsZendConfig() + { + + $config = new Config(array( + 'timeout' => 400, + 'nested' => array( + 'item' => 'value', + ) + )); + + $this->_adapter->setOptions($config); + + $hasConfig = $this->_adapter->getConfig(); + $this->assertEquals($config->timeout, $hasConfig['timeout']); + $this->assertEquals($config->nested->item, $hasConfig['nested']['item']); + } + + /** + * Check that an exception is thrown when trying to set invalid config + * + * @dataProvider invalidConfigProvider + */ + public function testSetConfigInvalidConfig($config) + { + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\InvalidArgumentException', + 'Array or Traversable object expected'); + + $this->_adapter->setOptions($config); + } + + /** + * CURLOPT_CLOSEPOLICY never worked and returns false on setopt always: + * @link http://de2.php.net/manual/en/function.curl-setopt.php#84277 + * + * This should throw an exception. + */ + public function testSettingInvalidCurlOption() + { + $config = array( + 'adapter' => 'Zend\Http\Client\Adapter\Curl', + 'curloptions' => array(CURLOPT_CLOSEPOLICY => true), + ); + $this->client = new \Zend\Http\Client($this->client->getUri(true), $config); + + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\RuntimeException', + 'Unknown or erroreous cURL option' + ); + $this->client->send(); + } + + public function testRedirectWithGetOnly() + { + $this->client->setUri($this->baseuri . 'testRedirections.php'); + + // Set some parameters + $this->client->setParameterGet(array('swallow', 'african')); + + // Request + $res = $this->client->send(); + + $this->assertEquals(3, $this->client->getRedirectionsCount(), 'Redirection counter is not as expected'); + + // Make sure the body does *not* contain the set parameters + $this->assertNotContains('swallow', $res->getBody()); + $this->assertNotContains('Camelot', $res->getBody()); + } + + /** + * This is a specific problem of the request type: If you let cURL handle redirects internally + * but start with a POST request that sends data then the location ping-pong will lead to an + * Content-Length: x\r\n GET request of the client that the server won't answer because no content is sent. + * + * Set CURLOPT_FOLLOWLOCATION = false for this type of request and let the Zend_Http_Client handle redirects + * in his own loop. + * + */ + public function testRedirectPostToGetWithCurlFollowLocationOptionLeadsToTimeout() + { + $adapter = new Adapter\Curl(); + $this->client->setAdapter($adapter); + $adapter->setOptions(array( + 'curloptions' => array( + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 1, + )) + ); + + $this->client->setUri($this->baseuri . 'testRedirections.php'); + + // Set some parameters + $this->client->setParameterGet(array ('swallow' => 'african')); + $this->client->setParameterPost(array ('Camelot' => 'A silly place')); + $this->client->setMethod('POST'); + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\RuntimeException', + 'Error in cURL request: Operation timed out after 1000 milliseconds with 0 bytes received'); + $this->client->send(); + } + + /** + * @group ZF-3758 + * @link http://framework.zend.com/issues/browse/ZF-3758 + */ + public function testPutFileContentWithHttpClient() + { + // Method 1: Using the binary string of a file to PUT + $this->client->setUri($this->baseuri . 'testRawPostData.php'); + $putFileContents = file_get_contents(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . + '_files' . DIRECTORY_SEPARATOR . 'staticFile.jpg'); + + $this->client->setRawBody($putFileContents); + $this->client->setMethod('PUT'); + $this->client->send(); + $this->assertEquals($putFileContents, $this->client->getResponse()->getBody()); + } + + /** + * @group ZF-3758 + * @link http://framework.zend.com/issues/browse/ZF-3758 + */ + public function testPutFileHandleWithHttpClient() + { + $this->client->setUri($this->baseuri . 'testRawPostData.php'); + $putFileContents = file_get_contents(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . + '_files' . DIRECTORY_SEPARATOR . 'staticFile.jpg'); + + // Method 2: Using a File-Handle to the file to PUT the data + $putFilePath = dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . + '_files' . DIRECTORY_SEPARATOR . 'staticFile.jpg'; + $putFileHandle = fopen($putFilePath, "r"); + $putFileSize = filesize($putFilePath); + + $adapter = new Adapter\Curl(); + $this->client->setAdapter($adapter); + $adapter->setOptions(array( + 'curloptions' => array(CURLOPT_INFILE => $putFileHandle, CURLOPT_INFILESIZE => $putFileSize) + )); + $this->client->setMethod('PUT'); + $this->client->send(); + $this->assertEquals(gzcompress($putFileContents), gzcompress($this->client->getResponse()->getBody())); + } + + public function testWritingAndNotConnectedWithCurlHandleThrowsException() + { + $adapter = new Adapter\Curl(); + $this->setExpectedException('Zend\Http\Client\Adapter\Exception\RuntimeException', + 'Trying to write but we are not connected'); + $adapter->write("GET", "someUri"); + } + + public function testSetConfigIsNotArray() + { + $adapter = new Adapter\Curl(); + $this->setExpectedException('Zend\Http\Client\Adapter\Exception\InvalidArgumentException'); + $adapter->setOptions("foo"); + } + + public function testSetCurlOptions() + { + $adapter = new Adapter\Curl(); + + $adapter->setCurlOption('foo', 'bar') + ->setCurlOption('bar', 'baz'); + + $this->assertEquals( + array('curloptions' => array('foo' => 'bar', 'bar' => 'baz')), + $this->readAttribute($adapter, 'config') + ); + } + + public function testWorkWithProxyConfiguration() + { + $adapter = new Adapter\Curl(); + $adapter->setOptions(array( + 'proxy_host' => 'localhost', + 'proxy_port' => 80, + 'proxy_user' => 'foo', + 'proxy_pass' => 'baz', + )); + + $expected = array( + 'curloptions' => array( + CURLOPT_PROXYUSERPWD => 'foo:baz', + CURLOPT_PROXY => 'localhost', + CURLOPT_PROXYPORT => 80, + ), + ); + + $this->assertEquals( + $expected, $this->readAttribute($adapter, 'config') + ); + } + + /** + * @group ZF-7040 + */ + public function testGetCurlHandle() + { + $adapter = new Adapter\Curl(); + $adapter->setOptions(array('timeout' => 2, 'maxredirects' => 1)); + $adapter->connect("http://framework.zend.com"); + + $this->assertTrue(is_resource($adapter->getHandle())); + } + + /** + * @group ZF-9857 + */ + public function testHeadRequest() + { + $this->client->setUri($this->baseuri . 'testRawPostData.php'); + $adapter = new Adapter\Curl(); + $this->client->setAdapter($adapter); + $this->client->setMethod('HEAD'); + $this->client->send(); + $this->assertEquals('', $this->client->getResponse()->getBody()); + } + + public function testAuthorizeHeader() + { + // We just need someone to talk to + $this->client->setUri($this->baseuri. 'testHttpAuth.php'); + $adapter = new Adapter\Curl(); + $this->client->setAdapter($adapter); + + $uid = 'alice'; + $pwd = 'secret'; + + $hash = base64_encode($uid . ':' . $pwd); + $header = 'Authorization: Basic ' . $hash; + + $this->client->setAuth($uid, $pwd); + $res = $this->client->send(); + + $curlInfo = curl_getinfo($adapter->getHandle()); + $this->assertArrayHasKey('request_header', $curlInfo, 'Expecting request_header in curl_getinfo() return value'); + + $this->assertContains($header, $curlInfo['request_header'], 'Expecting valid basic authorization header'); + } +} diff --git a/test/Client/ProxyAdapterTest.php b/test/Client/ProxyAdapterTest.php new file mode 100644 index 0000000000..b39e2795bd --- /dev/null +++ b/test/Client/ProxyAdapterTest.php @@ -0,0 +1,113 @@ +markTestSkipped('No valid proxy host name or address specified.'); + + $port = (int) $port; + if ($port == 0) { + $port = 8080; + } else { + if (($port < 1 || $port > 65535)) + $this->markTestSkipped("$port is not a valid proxy port number. Should be between 1 and 65535."); + } + + $user = ''; + $pass = ''; + if (defined('TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_USER') && + TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_USER) + $user = TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_USER; + + if (defined('TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_PASS') && + TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_PASS) + $pass = TESTS_ZEND_HTTP_CLIENT_HTTP_PROXY_PASS; + + + $this->config = array( + 'adapter' => '\Zend\Http\Client\Adapter\Proxy', + 'proxy_host' => $host, + 'proxy_port' => $port, + 'proxy_user' => $user, + 'proxy_pass' => $pass, + ); + + parent::setUp(); + + } else { + $this->markTestSkipped('Zend\Http\Client proxy server tests are not enabled in TestConfiguration.php'); + } + } + + /** + * Test that when no proxy is set the adapter falls back to direct connection + */ + public function testFallbackToSocket() + { + $this->_adapter->setOptions(array( + 'proxy_host' => null, + )); + + $this->client->setUri($this->baseuri . 'testGetLastRequest.php'); + $res = $this->client->setMethod(\Zend\Http\Request::METHOD_TRACE)->send(); + if ($res->getStatusCode() == 405 || $res->getStatusCode() == 501) { + $this->markTestSkipped('Server does not allow the TRACE method'); + } + + $this->assertEquals($this->client->getLastRawRequest(), $res->getBody(), 'Response body should be exactly like the last request'); + } + + public function testGetLastRequest() + { + /** + * This test will never work for the proxy adapter (and shouldn't!) + * because the proxy server modifies the request which is sent back in + * the TRACE response + */ + } + + public function testDefaultConfig() + { + $config = $this->_adapter->getConfig(); + $this->assertEquals(TRUE, $config['sslverifypeer']); + $this->assertEquals(FALSE, $config['sslallowselfsigned']); + } +} diff --git a/test/Client/SocketKeepaliveTest.php b/test/Client/SocketKeepaliveTest.php new file mode 100644 index 0000000000..2b208ce059 --- /dev/null +++ b/test/Client/SocketKeepaliveTest.php @@ -0,0 +1,43 @@ +baseuri must point to a directory on a web server + * containing all the files under the files directory. You should symlink + * or copy these files and set 'baseuri' properly. + * + * You can also set the proper constand in your test configuration file to + * point to the right place. + * + * @category Zend + * @package Zend_Http_Client + * @subpackage UnitTests + * @group Zend_Http + * @group Zend_Http_Client + */ +class SocketKeepaliveTest extends SocketTest +{ + /** + * Configuration array + * + * @var array + */ + protected $config = array( + 'adapter' => 'Zend\Http\Client\Adapter\Socket', + 'keepalive' => true + ); +} diff --git a/test/Client/SocketPersistentTest.php b/test/Client/SocketPersistentTest.php new file mode 100644 index 0000000000..cd455c769f --- /dev/null +++ b/test/Client/SocketPersistentTest.php @@ -0,0 +1,44 @@ +baseuri must point to a directory on a web server + * containing all the files under the files directory. You should symlink + * or copy these files and set 'baseuri' properly. + * + * You can also set the proper constand in your test configuration file to + * point to the right place. + * + * @category Zend + * @package Zend_Http_Client + * @subpackage UnitTests + * @group Zend_Http + * @group Zend_Http_Client + */ +class SocketPersistentTest extends SocketTest +{ + /** + * Configuration array + * + * @var array + */ + protected $config = array( + 'adapter' => 'Zend\Http\Client\Adapter\Socket', + 'persistent' => true, + 'keepalive' => true + ); +} diff --git a/test/Client/SocketTest.php b/test/Client/SocketTest.php new file mode 100644 index 0000000000..7956b00fcf --- /dev/null +++ b/test/Client/SocketTest.php @@ -0,0 +1,270 @@ +baseuri must point to a directory on a web server + * containing all the files under the files directory. You should symlink + * or copy these files and set 'baseuri' properly. + * + * You can also set the proper constant in your test configuration file to + * point to the right place. + * + * @category Zend + * @package Zend_Http_Client + * @subpackage UnitTests + * @group Zend_Http + * @group Zend_Http_Client + */ +class SocketTest extends CommonHttpTests +{ + /** + * Configuration array + * + * @var array + */ + protected $config = array( + 'adapter' => 'Zend\Http\Client\Adapter\Socket' + ); + + /** + * Off-line common adapter tests + */ + + /** + * Test that we can set a valid configuration array with some options + * @group ZHC001 + */ + public function testConfigSetAsArray() + { + $config = array( + 'timeout' => 500, + 'someoption' => 'hasvalue' + ); + + $this->_adapter->setOptions($config); + + $hasConfig = $this->_adapter->getConfig(); + foreach ($config as $k => $v) { + $this->assertEquals($v, $hasConfig[$k]); + } + } + + public function testDefaultConfig() + { + $config = $this->_adapter->getConfig(); + $this->assertEquals(TRUE, $config['sslverifypeer']); + $this->assertEquals(FALSE, $config['sslallowselfsigned']); + } + + public function testConnectingViaSslEnforcesDefaultSslOptionsOnContext() + { + $config = array('timeout' => 30); + $this->_adapter->setOptions($config); + try { + $this->_adapter->connect('localhost', 443, true); + } catch (\Zend\Http\Client\Adapter\Exception\RuntimeException $e) { + // Test is designed to allow connect failure because we're interested + // only in the stream context state created within that method. + } + $context = $this->_adapter->getStreamContext(); + $options = stream_context_get_options($context); + $this->assertTrue($options['ssl']['verify_peer']); + $this->assertFalse($options['ssl']['allow_self_signed']); + } + + /** + * Test that a Zend_Config object can be used to set configuration + * + * @link http://framework.zend.com/issues/browse/ZF-5577 + */ + public function testConfigSetAsZendConfig() + { + + $config = new \Zend\Config\Config(array( + 'timeout' => 400, + 'nested' => array( + 'item' => 'value', + ) + )); + + $this->_adapter->setOptions($config); + + $hasConfig = $this->_adapter->getConfig(); + $this->assertEquals($config->timeout, $hasConfig['timeout']); + $this->assertEquals($config->nested->item, $hasConfig['nested']['item']); + } + + /** + * Check that an exception is thrown when trying to set invalid config + * + * @dataProvider invalidConfigProvider + */ + public function testSetConfigInvalidConfig($config) + { + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\InvalidArgumentException', + 'Array or Zend_Config object expected'); + + $this->_adapter->setOptions($config); + } + + /** + * Stream context related tests + */ + + public function testGetNewStreamContext() + { + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; + $context = $adapter->getStreamContext(); + + $this->assertEquals('stream-context', get_resource_type($context)); + } + + public function testSetNewStreamContextResource() + { + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; + $context = stream_context_create(); + + $adapter->setStreamContext($context); + + $this->assertEquals($context, $adapter->getStreamContext()); + } + + public function testSetNewStreamContextOptions() + { + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; + $options = array( + 'socket' => array( + 'bindto' => '1.2.3.4:0' + ), + 'ssl' => array( + 'capath' => null, + 'verify_peer' => true, + 'allow_self_signed' => false + ) + ); + + $adapter->setStreamContext($options); + + $this->assertEquals($options, stream_context_get_options($adapter->getStreamContext())); + } + + /** + * Test that setting invalid options / context causes an exception + * + * @dataProvider invalidContextProvider + */ + public function testSetInvalidContextOptions($invalid) + { + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\InvalidArgumentException', + 'Expecting either a stream context resource or array'); + + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; + $adapter->setStreamContext($invalid); + } + + public function testSetHttpsStreamContextParam() + { + if ($this->client->getUri()->getScheme() != 'https') { + $this->markTestSkipped(); + } + + $adapterClass = $this->config['adapter']; + $adapter = new $adapterClass; + $adapter->setStreamContext(array( + 'ssl' => array( + 'capture_peer_cert' => true, + 'capture_peer_chain' => true + ) + )); + + $this->client->setAdapter($adapter); + $this->client->setUri($this->baseuri . '/testSimpleRequests.php'); + $this->client->request(); + + $opts = stream_context_get_options($adapter->getStreamContext()); + $this->assertTrue(isset($opts['ssl']['peer_certificate'])); + } + + /** + * Test that we get the right exception after a socket timeout + * + * @link http://framework.zend.com/issues/browse/ZF-7309 + */ + public function testExceptionOnReadTimeout() + { + // Set 1 second timeout + $this->client->setOptions(array('timeout' => 1)); + + $start = microtime(true); + + try { + $this->client->send(); + $this->fail('Expected a timeout Zend\Http\Client\Adapter\Exception\TimeoutException'); + } catch (Adapter\Exception\TimeoutException $e) { + $this->assertEquals(Adapter\Exception\TimeoutException::READ_TIMEOUT, $e->getCode()); + } + + $time = (microtime(true) - $start); + + // We should be very close to 1 second + $this->assertLessThan(2, $time); + } + + /** + * Test that a chunked response with multibyte characters is properly read + * + * This can fail in various PHP environments - for example, when mbstring + * overloads substr() and strlen(), and mbstring's internal encoding is + * not a single-byte encoding. + * + * @link http://framework.zend.com/issues/browse/ZF-6218 + */ + public function testMultibyteChunkedResponseZF6218() + { + $md5 = '7667818873302f9995be3798d503d8d3'; + + $response = $this->client->send(); + $this->assertEquals($md5, md5($response->getBody())); + } + + /** + * Data Providers + */ + + /** + * Provide invalid context resources / options + * + * @return array + */ + public static function invalidContextProvider() + { + return array( + array(new \stdClass()), + array(fopen('data://text/plain,', 'r')), + array(false), + array(null) + ); + } +} diff --git a/test/Client/StaticClientTest.php b/test/Client/StaticClientTest.php new file mode 100644 index 0000000000..9dfa6ba2ec --- /dev/null +++ b/test/Client/StaticClientTest.php @@ -0,0 +1,122 @@ +baseuri = TESTS_ZEND_HTTP_CLIENT_BASEURI; + if (substr($this->baseuri, -1) != '/') $this->baseuri .= '/'; + + } else { + // Skip tests + $this->markTestSkipped("Zend_Http_Client dynamic tests are not enabled in TestConfiguration.php"); + } + } + + /** + * Test simple GET + */ + public function testHttpSimpleGet() + { + $response= HTTPClient::get($this->baseuri . 'testSimpleRequests.php'); + $this->assertTrue($response->isSuccess()); + } + + /** + * Test GET with query string in URI + */ + public function testHttpGetWithParamsInUri() + { + $response= HTTPClient::get($this->baseuri . 'testGetData.php?foo'); + $this->assertTrue($response->isSuccess()); + $this->assertContains('foo',$response->getBody()); + } + + /** + * Test GET with query as params + */ + public function testHttpMultiGetWithParam() + { + $response= HTTPClient::get($this->baseuri . 'testGetData.php',array('foo' => 'bar')); + $this->assertTrue($response->isSuccess()); + $this->assertContains('foo',$response->getBody()); + $this->assertContains('bar',$response->getBody()); + } + + /** + * Test simple POST + */ + public function testHttpSimplePost() + { + $response= HTTPClient::post($this->baseuri . 'testPostData.php',array('foo' => 'bar')); + $this->assertTrue($response->isSuccess()); + $this->assertContains('foo',$response->getBody()); + $this->assertContains('bar',$response->getBody()); + } + + /** + * Test POST with header Content-Type + */ + public function testHttpPostContentType() + { + $response= HTTPClient::post($this->baseuri . 'testPostData.php', + array('foo' => 'bar'), + array('Content-Type' => Client::ENC_URLENCODED)); + $this->assertTrue($response->isSuccess()); + $this->assertContains('foo',$response->getBody()); + $this->assertContains('bar',$response->getBody()); + } + + /** + * Test POST with body + */ + public function testHttpPostWithBody() + { + $postBody = 'foo'; + + $response= HTTPClient::post($this->baseuri . 'testRawPostData.php', + array('foo' => 'bar'), + array('Content-Type' => Client::ENC_URLENCODED), + $postBody); + + $this->assertTrue($response->isSuccess()); + $this->assertContains($postBody, $response->getBody()); + } +} diff --git a/test/Client/StaticTest.php b/test/Client/StaticTest.php new file mode 100644 index 0000000000..b00330c392 --- /dev/null +++ b/test/Client/StaticTest.php @@ -0,0 +1,613 @@ +_client = new MockClient('http://www.example.com'); + } + + /** + * Clean up after running a test + * + */ + public function tearDown() + { + $this->_client = null; + } + + /** + * URI Tests + */ + + /** + * Test we can SET and GET a URI as string + * + */ + public function testSetGetUriString() + { + $uristr = 'http://www.zend.com:80/'; + + $this->_client->setUri($uristr); + + $uri = $this->_client->getUri(); + $this->assertTrue($uri instanceof UriHttp, 'Returned value is not a Uri object as expected'); + $this->assertEquals($uri->__toString(), $uristr, 'Returned Uri object does not hold the expected URI'); + + $uri = $this->_client->getUri()->toString(); + $this->assertTrue(is_string($uri), 'Returned value expected to be a string, ' . gettype($uri) . ' returned'); + $this->assertEquals($uri, $uristr, 'Returned string is not the expected URI'); + } + + /** + * Test we can SET and GET a URI as object + * + */ + public function testSetGetUriObject() + { + $uriobj = new UriHttp('http://www.zend.com:80/'); + + $this->_client->setUri($uriobj); + + $uri = $this->_client->getUri(); + $this->assertTrue($uri instanceof UriHttp, 'Returned value is not a Uri object as expected'); + $this->assertEquals($uri, $uriobj, 'Returned object is not the excepted Uri object'); + } + + /** + * Test that setting the same parameter twice in the query string does not + * get reduced to a single value only. + * + */ + public function testDoubleGetParameter() + { + $qstr = 'foo=bar&foo=baz'; + + $this->_client->setUri('http://example.com/test/?' . $qstr); + $this->_client->setAdapter('\\Zend\\Http\\Client\\Adapter\\Test'); + $this->_client->setMethod('GET'); + $res = $this->_client->send(); + + $this->assertContains($qstr, $this->_client->getLastRawRequest(), + 'Request is expected to contain the entire query string'); + } + + /** + * Header Tests + */ + + /** + * Test we can get already set headers + * + */ + public function testGetHeader() + { + $this->_client->setHeaders(array( + 'Accept-encoding' => 'gzip,deflate', + 'Accept-language' => 'en,de,*', + )); + + $this->assertEquals($this->_client->getHeader('Accept-encoding'), 'gzip, deflate', 'Returned value of header is not as expected'); + $this->assertEquals($this->_client->getHeader('X-Fake-Header'), null, 'Non-existing header should not return a value'); + } + + /** + * Authentication tests + */ + + /** + * Test setAuth (dynamic method) fails when trying to use an unsupported + * authentication scheme + * + */ + public function testExceptUnsupportedAuthDynamic() + { + $this->setExpectedException( + 'Zend\Http\Exception\InvalidArgumentException', + 'Invalid or not supported authentication type: \'SuperStrongAlgo\''); + + $this->_client->setAuth('shahar', '1234', 'SuperStrongAlgo'); + } + + /** + * Cookie and Cookie Jar tests + */ + + /** + * Test we can properly set a new cookies + * + */ + public function testSetNewCookies() + { + $this->_client->addCookie('cookie', 'value'); + $this->_client->addCookie('chocolate', 'chips'); + $cookies = $this->_client->getCookies(); + + // Check we got the right cookiejar + $this->assertTrue((is_array($cookies) && $cookies['chocolate'] instanceof SetCookie), '$cookie is not an array of Zend\Http\Header\SetCookie'); + $this->assertEquals(count($cookies), 2, '$cookies does not contain 2 SetCokie as expected'); + } + + /** + * Test we can unset a cookie jar + * + */ + public function testUnsetCookies() + { + // Set the cookie jar just like in testSetNewCookieJar + $this->_client->addCookie('cookie', 'value'); + $this->_client->addCookie('chocolate', 'chips'); + $cookies = $this->_client->getCookies(); + + // Try unsetting the cookies + $this->_client->clearCookies(); + $cookies = $this->_client->getCookies(); + + $this->assertTrue(empty($cookies), 'Cookies is expected to be null but it is not'); + } + + /** + * Make sure using an invalid cookie jar object throws an exception + * + */ + public function testSetInvalidCookies() + { + $this->setExpectedException( + 'Zend\Http\Exception\InvalidArgumentException', + 'Invalid parameter type passed as Cookie'); + + $this->_client->addCookie('cookie'); + } + + /** + * Configuration Handling + */ + + /** + * Test that we can set a valid configuration array with some options + * + */ + public function testConfigSetAsArray() + { + + $config = array( + 'timeout' => 500, + 'someoption' => 'hasvalue' + ); + + $this->_client->setOptions($config); + + $hasConfig = $this->_client->config; + + foreach ($config as $k => $v) { + $this->assertEquals($v, $hasConfig[$k]); + } + } + + /** + * Test that a Zend_Config object can be used to set configuration + * + * @link http://framework.zend.com/issues/browse/ZF-5577 + */ + public function testConfigSetAsZendConfig() + { + + $config = new \Zend\Config\Config(array( + 'timeout' => 400, + 'nested' => array( + 'item' => 'value', + ) + )); + + $this->_client->setOptions($config); + + $hasConfig = $this->_client->config; + $this->assertEquals($config->timeout, $hasConfig['timeout']); + $this->assertEquals($config->nested->item, $hasConfig['nested']['item']); + } + + /** + * Test that passing invalid variables to setConfig() causes an exception + * + * @dataProvider invalidConfigProvider + */ + public function testConfigSetInvalid($config) + { + $this->setExpectedException( + 'Zend\Http\Client\Exception\InvalidArgumentException', + 'Config parameter is not valid'); + + $this->_client->setOptions($config); + } + + /** + * Test that configuration options are passed to the adapter after the + * adapter is instantiated + * + * @group ZF-4557 + */ + public function testConfigPassToAdapterZF4557() + { + + $adapter = new MockAdapter(); + + // test that config passes when we set the adapter + $this->_client->setOptions(array('param' => 'value1')); + $this->_client->setAdapter($adapter); + $adapterCfg = $adapter->config; + $this->assertEquals('value1', $adapterCfg['param']); + + // test that adapter config value changes when we set client config + $this->_client->setOptions(array('param' => 'value2')); + $adapterCfg = $adapter->config; + $this->assertEquals('value2', $adapterCfg['param']); + } + + /** + * Other Tests + */ + + /** + * Test the getLastRawResponse() method actually returns the last response + * + */ + public function testGetLastRawResponse() + { + // First, make sure we get null before the request + $this->assertEquals(null, $this->_client->getLastRawResponse(), + 'getLastRawResponse() is still expected to return null'); + + // Now, test we get a proper response after the request + $this->_client->setUri('http://example.com/foo/bar'); + $this->_client->setAdapter('Zend\Http\Client\Adapter\Test'); + + $response = $this->_client->send(); + $this->assertTrue(($response === $this->_client->getResponse()), + 'Response is expected to be identical to the result of getResponse()'); + } + + /** + * Test that getLastRawResponse returns null when not storing + * + */ + public function testGetLastRawResponseWhenNotStoring() + { + // Now, test we get a proper response after the request + $this->_client->setUri('http://example.com/foo/bar'); + $this->_client->setAdapter('Zend\Http\Client\Adapter\Test'); + $this->_client->setOptions(array('storeresponse' => false)); + + $response = $this->_client->send(); + + $this->assertNull($this->_client->getLastRawResponse(), + 'getLastRawResponse is expected to be null when not storing'); + } + + /** + * Check we get an exception when trying to send a POST request with an + * invalid content-type header + * + */ + public function testInvalidPostContentType() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->setExpectedException( + 'Zend\Http\Exception\RuntimeException', + 'Cannot handle content type \'x-foo/something-fake\' automatically'); + + $this->_client->setEncType('x-foo/something-fake'); + $this->_client->setParameterPost(array('parameter' => 'value')); + $this->_client->setMethod('POST'); + // This should throw an exception + $this->_client->send(); + } + + /** + * Check we get an exception if there's an error in the socket + * + */ + public function testSocketErrorException() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\RuntimeException', + 'Unable to Connect to tcp://255.255.255.255:80'); + + // Try to connect to an invalid host + $this->_client->setUri('http://255.255.255.255'); + + // Reduce timeout to 3 seconds to avoid waiting + $this->_client->setOptions(array('timeout' => 3)); + + // This call should cause an exception + $this->_client->send(); + } + + /** + * Check that an exception is thrown if non-word characters are used in + * the request method. + * + * @dataProvider invalidMethodProvider + */ + public function testSettingInvalidMethodThrowsException($method) + { + $this->setExpectedException( + 'Zend\Http\Exception\InvalidArgumentException', + 'Invalid HTTP method passed'); + + $this->_client->setMethod($method); + } + + /** + * Test that POST data with mutli-dimentional array is properly encoded as + * multipart/form-data + * + */ + public function testFormDataEncodingWithMultiArrayZF7038() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->_client->setAdapter('Zend\Http\Client\Adapter\Test'); + $this->_client->setUri('http://example.com'); + $this->_client->setEncType(HTTPClient::ENC_FORMDATA); + + $this->_client->setParameterPost(array( + 'test' => array( + 'v0.1', + 'v0.2', + 'k1' => 'v1.0', + 'k2' => array( + 'v2.1', + 'k2.1' => 'v2.1.0' + )) + )); + + $this->_client->setMethod('POST'); + $this->_client->send(); + + $expectedLines = file(__DIR__ . '/_files/ZF7038-multipartarrayrequest.txt'); + + $gotLines = explode("\n", $this->_client->getLastRawRequest()); + + $this->assertEquals(count($expectedLines), count($gotLines)); + + while (($expected = array_shift($expectedLines)) && + ($got = array_shift($gotLines))) { + + $expected = trim($expected); + $got = trim($got); + $this->assertRegExp("/^$expected$/", $got); + } + } + + /** + * Test that we properly calculate the content-length of multibyte-encoded + * request body + * + * This may file in case that mbstring overloads the substr and strlen + * functions, and the mbstring internal encoding is a multibyte encoding. + * + * @link http://framework.zend.com/issues/browse/ZF-2098 + */ + public function testMultibyteRawPostDataZF2098() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->_client->setAdapter('Zend\Http\Client\Adapter\Test'); + $this->_client->setUri('http://example.com'); + + $bodyFile = __DIR__ . '/_files/ZF2098-multibytepostdata.txt'; + + $this->_client->setRawBody(file_get_contents($bodyFile)); + $this->_client->setEncType('text/plain'); + $this->_client->setMethod('POST'); + $this->_client->send(); + $request = $this->_client->getLastRawRequest(); + + if (! preg_match('/^content-length:\s+(\d+)/mi', $request, $match)) { + $this->fail("Unable to find content-length header in request"); + } + + $this->assertEquals(filesize($bodyFile), (int) $match[1]); + } + + /** + * Testing if the connection isn't closed + * + * @group ZF-9685 + */ + public function testOpenTempStreamWithValidFileDoesntThrowsException() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $url = 'http://www.example.com/'; + $config = array ( + 'outputstream' => realpath(__DIR__ . '/_files/zend_http_client_stream.file'), + ); + $client = new HTTPClient($url, $config); + + $result = $client->send(); + + // we can safely return until we can verify link is still active + // @todo verify link is still active + return; + } + + /** + * Testing if the connection can be closed + * + * @group ZF-9685 + */ + public function testOpenTempStreamWithBogusFileClosesTheConnection() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->setExpectedException( + 'Zend\Http\Exception\RuntimeException', + 'Could not open temp file /path/to/bogus/file.ext'); + + $url = 'http://www.example.com'; + $config = array ( + 'outputstream' => '/path/to/bogus/file.ext', + ); + $client = new HTTPClient($url, $config); + $client->setMethod('GET'); + $result = $client->send(); + } + + /** + * Test sending cookie with encoded value + * + * @group fix-double-encoding-problem-about-cookie-value + */ + public function testEncodedCookiesInRequestHeaders() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->_client->addCookie('foo', 'bar=baz'); + $this->_client->send(); + $cookieValue = 'Cookie: foo='.urlencode('bar=baz'); + $this->assertContains($cookieValue, $this->_client->getLastRawRequest(), + 'Request is expected to contain the entire cookie "keyname=encoded_value"'); + } + + /** + * Test sending cookie header with raw value + * + * @group fix-double-encoding-problem-about-cookie-value + */ + public function testRawCookiesInRequestHeaders() + { + if (!constant('TESTS_ZEND_HTTP_CLIENT_ONLINE')) { + $this->markTestSkipped('Zend\Http\Client online tests are not enabled'); + } + $this->_client->setOptions(array('encodecookies' => false)); + $this->_client->addCookie('foo', 'bar=baz'); + $this->_client->send(); + $cookieValue = 'Cookie: foo=bar=baz'; + $this->assertContains($cookieValue, $this->_client->getLastRawRequest(), + 'Request is expected to contain the entire cookie "keyname=raw_value"'); + } + + /** + * Data providers + */ + + /** + * Data provider of valid non-standard HTTP methods + * + * @return array + */ + public static function validMethodProvider() + { + return array( + array('OPTIONS'), + array('POST'), + array('DOSOMETHING'), + array('PROPFIND'), + array('Some_Characters'), + array('X-MS-ENUMATTS') + ); + } + + /** + * Data provider of invalid HTTP methods + * + * @return array + */ + public static function invalidMethodProvider() + { + return array( + array('N@5TYM3T#0D'), + array('TWO WORDS'), + array('GET http://foo.com/?'), + array("Injected\nnewline") + ); + } + + /** + * Data provider for invalid configuration containers + * + * @return array + */ + public static function invalidConfigProvider() + { + return array( + array(false), + array('foo => bar'), + array(null), + array(new \stdClass), + array(55) + ); + } +} + +class MockClient extends HTTPClient +{ + public $config = array( + 'maxredirects' => 5, + 'strictredirects' => false, + 'useragent' => 'Zend_Http_Client', + 'timeout' => 10, + 'adapter' => 'Zend\\Http\\Client\\Adapter\\Socket', + 'httpversion' => Request::VERSION_11, + 'keepalive' => false, + 'storeresponse' => true, + 'strict' => true, + 'outputstream' => false, + 'encodecookies' => true, + ); +} + +class MockAdapter extends \Zend\Http\Client\Adapter\Test +{ + public $config = array(); +} diff --git a/test/Client/TestAdapterTest.php b/test/Client/TestAdapterTest.php new file mode 100644 index 0000000000..0d90dc2eab --- /dev/null +++ b/test/Client/TestAdapterTest.php @@ -0,0 +1,219 @@ +adapter = new \Zend\Http\Client\Adapter\Test(); + } + + /** + * Tear down the test adapter after running the test + * + */ + public function tearDown() + { + $this->adapter = null; + } + + /** + * Make sure an exception is thrown on invalid cofiguration + * + */ + public function testSetConfigThrowsOnInvalidConfig() + { + $this->setExpectedException( + 'Zend\Http\Client\Adapter\Exception\InvalidArgumentException', + 'Array or Traversable object expected'); + + $this->adapter->setOptions('foo'); + } + + public function testSetConfigReturnsQuietly() + { + $this->adapter->setOptions(array('foo' => 'bar')); + } + + public function testConnectReturnsQuietly() + { + $this->adapter->connect('http://foo'); + } + + public function testCloseReturnsQuietly() + { + $this->adapter->close(); + } + + public function testFailRequestOnDemand() + { + $this->adapter->setNextRequestWillFail(true); + + try { + // Make a connection that will fail + $this->adapter->connect('http://foo'); + $this->fail(); + } catch (\Zend\Http\Client\Adapter\Exception\RuntimeException $e) { + // Connect again to see that the next request does not fail + $this->adapter->connect('http://foo'); + } + } + + public function testReadDefaultResponse() + { + $expected = "HTTP/1.1 400 Bad Request\r\n\r\n"; + $this->assertEquals($expected, $this->adapter->read()); + } + + public function testReadingSingleResponse() + { + $expected = "HTTP/1.1 200 OK\r\n\r\n"; + $this->adapter->setResponse($expected); + $this->assertEquals($expected, $this->adapter->read()); + $this->assertEquals($expected, $this->adapter->read()); + } + + public function testReadingResponseCycles() + { + $expected = array("HTTP/1.1 200 OK\r\n\r\n", + "HTTP/1.1 302 Moved Temporarily\r\n\r\n"); + + $this->adapter->setResponse($expected[0]); + $this->adapter->addResponse($expected[1]); + + $this->assertEquals($expected[0], $this->adapter->read()); + $this->assertEquals($expected[1], $this->adapter->read()); + $this->assertEquals($expected[0], $this->adapter->read()); + } + + /** + * Test that responses could be added as strings + * + * @dataProvider validHttpResponseProvider + */ + public function testAddResponseAsString($testResponse) + { + $this->adapter->read(); // pop out first response + + $this->adapter->addResponse($testResponse); + $this->assertEquals($testResponse, $this->adapter->read()); + } + + /** + * Test that responses could be added as objects (ZF-7009) + * + * @link http://framework.zend.com/issues/browse/ZF-7009 + * @dataProvider validHttpResponseProvider + */ + public function testAddResponseAsObject($testResponse) + { + $this->adapter->read(); // pop out first response + + $respObj = \Zend\Http\Response::fromString($testResponse); + + $this->adapter->addResponse($respObj); + $this->assertEquals($testResponse, $this->adapter->read()); + } + + public function testReadingResponseCyclesWhenSetByArray() + { + $expected = array("HTTP/1.1 200 OK\r\n\r\n", + "HTTP/1.1 302 Moved Temporarily\r\n\r\n"); + + $this->adapter->setResponse($expected); + + $this->assertEquals($expected[0], $this->adapter->read()); + $this->assertEquals($expected[1], $this->adapter->read()); + $this->assertEquals($expected[0], $this->adapter->read()); + } + + public function testSettingNextResponseByIndex() + { + $expected = array("HTTP/1.1 200 OK\r\n\r\n", + "HTTP/1.1 302 Moved Temporarily\r\n\r\n", + "HTTP/1.1 404 Not Found\r\n\r\n"); + + $this->adapter->setResponse($expected); + $this->assertEquals($expected[0], $this->adapter->read()); + + foreach ($expected as $i => $expected) { + $this->adapter->setResponseIndex($i); + $this->assertEquals($expected, $this->adapter->read()); + } + } + + public function testSettingNextResponseToAnInvalidIndex() + { + $indexes = array(-1, 1); + foreach ($indexes as $i) { + try { + $this->adapter->setResponseIndex($i); + $this->fail(); + } catch (\Exception $e) { + $this->assertInstanceOf('Zend\\Http\\Client\\Adapter\\Exception\\OutOfRangeException', $e); + $this->assertRegexp('/out of range/i', $e->getMessage()); + } + } + } + + /** + * Data Providers + */ + + /** + * Provide valid HTTP responses as string + * + * @return array + */ + public static function validHttpResponseProvider() + { + return array( + array("HTTP/1.1 200 OK\r\n\r\n"), + array("HTTP/1.1 302 Moved Temporarily\r\nLocation: http://example.com/baz\r\n\r\n"), + array("HTTP/1.1 404 Not Found\r\n" . + "Date: Sun, 14 Jun 2009 10:40:06 GMT\r\n" . + "Server: Apache/2.2.3 (CentOS)\r\n" . + "Content-length: 281\r\n" . + "Connection: close\r\n" . + "Content-type: text/html; charset=iso-8859-1\r\n\r\n" . + "\n" . + "\n" . + "404 Not Found\n" . + "\n" . + "

Not Found

\n" . + "

The requested URL /foo/bar was not found on this server.

\n" . + "
\n" . + "
Apache/2.2.3 (CentOS) Server at example.com Port 80
\n" . + "") + ); + } +} diff --git a/test/Client/UseCaseTest.php b/test/Client/UseCaseTest.php new file mode 100644 index 0000000000..13fa611024 --- /dev/null +++ b/test/Client/UseCaseTest.php @@ -0,0 +1,110 @@ + 'Zend\Http\Client\Adapter\Socket' + ); + + /** + * Set up the test case + */ + protected function setUp() + { + if (defined('TESTS_ZEND_HTTP_CLIENT_BASEURI') + && (TESTS_ZEND_HTTP_CLIENT_BASEURI != false) + ) { + $this->baseuri = TESTS_ZEND_HTTP_CLIENT_BASEURI; + $this->client = new HTTPClient($this->baseuri); + } else { + // Skip tests + $this->markTestSkipped("Zend_Http_Client dynamic tests are not enabled in TestConfiguration.php"); + } + } + + /** + * Clean up the test environment + * + */ + protected function tearDown() + { + $this->client = null; + } + + public function testHttpGet() + { + $this->client->setMethod(Request::METHOD_GET); + $response= $this->client->send(); + $this->assertTrue($response->isSuccess()); + } + + public function testStaticHttpGet() + { +// $response= HTTPClient::get($this->baseuri); +// $this->assertTrue($response->isSuccess()); + } + + public function testRequestHttpGet() + { + $client= new HTTPClient(); + $request= new Request(); + $request->setUri($this->baseuri); + $request->setMethod(Request::METHOD_GET); + $response= $client->send($request); + $this->assertTrue($response->isSuccess()); + } + +} diff --git a/test/Client/_files/ZF2098-multibytepostdata.txt b/test/Client/_files/ZF2098-multibytepostdata.txt new file mode 100644 index 0000000000..247951c2bd --- /dev/null +++ b/test/Client/_files/ZF2098-multibytepostdata.txt @@ -0,0 +1,21 @@ +הָעֵינַיִם הָרְעֵבוֹת + +הָעֵינַיִם הָרְעֵבוֹת הָאֵלֶּה שֶׁכָּכָה תִּתְבַּעְנָה, +הַשְּׂפָתַיִם הַצְּמֵאוֹת הָאֵלֶּה הַשֹּׁאֲלוֹת: נַשְּׁקֵנוּ! +הָעֳפָרִים הָעֹרְגִים הָאֵלֶּה הַקּוֹרְאִים: תָּפְשֵׂנוּ! +חֲמוּדוֹתַיִךְ הַצְּפוּנוֹת שֶׁשָּׂבְעָה כִשְׁאוֹל לֹא-תֵדַעְנָה; + +כָּל-עֲתֶרֶת הַגְּוִיָּה הַזֹּאת, שִׁפְעַת חֶמְדָּה מְלֵאָה, +כָּל-הַשְּׁאֵר הַלָּזֶה, כָּל-הַבְּשָׂרִים הָאֵלֶּה שֶׁכָּכָה +הִלְעִיטוּנִי מִמְּקוֹר תַּעֲנוּגִים, מִמַּעְיַן הַבְּרָכָה – +לוּ יָדַעתְּ, יָפָתִי, מַה-קָּצָה בָּם נַפְשִׁי הַשְּׂבֵעָה. + +זַךְ הָיִיתִי, לֹא-דָלַח הַסַּעַר רִגְשׁוֹתַי הַזַּכִּים +עדַ שֶׁבָּאת, יְפֵה-פִיָּה, וּבְרוּחֵךְ נָשַׁפְתְּ וְנִדְלַחְתִּי. +וַאֲנִי, נַעַר פֹּתֶה, לְרַגְלַיִךְ בְּלִי-חֶמְלָה הִשְׁלַכְתִּי +תֹּם לְבָבִי, בֹּר רוּחִי, כָּל-פִּרְחֵי נְעוּרַי הָרַכִּים. + +רֶגַע קָטָן מְאֻשָּׁר הָיִיתִי בְּלִי-חֹק, וָאֲבָרֵךְ +אֶת-הַיָּד הַחֹלֶקֶת לִי מַכְאוֹב הָעֹנֶג הֶעָרֵב; +וּבְרֶגַע קָטָן שֶׁל-תַּעֲנוּג, שֶׁל-אֹשֶׁר וָגִיל, עָלַי חָרֵב +עוֹלָם מָלֵא – מַה-גָּדוֹל הַמְּחִיר שֶׁנָּתַתִּי בִּבְשָׂרֵךְ! diff --git a/test/Client/_files/ZF4238-zerolineresponse.txt b/test/Client/_files/ZF4238-zerolineresponse.txt new file mode 100644 index 0000000000..7ac8e5fbc9 --- /dev/null +++ b/test/Client/_files/ZF4238-zerolineresponse.txt @@ -0,0 +1,3 @@ +0 + +some text here diff --git a/test/Client/_files/ZF7038-multipartarrayrequest.txt b/test/Client/_files/ZF7038-multipartarrayrequest.txt new file mode 100644 index 0000000000..5c90500fff --- /dev/null +++ b/test/Client/_files/ZF7038-multipartarrayrequest.txt @@ -0,0 +1,30 @@ +POST \/ HTTP\/1\.1 +Host: example\.com +Connection: close +Accept-encoding: gzip, deflate +User-Agent: Zend_Http_Client +Content-Type: multipart\/form-data; boundary=---ZENDHTTPCLIENT-\w+ +Content-Length: \d+ + +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="test\[\]" + +v0\.1 +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="test\[\]" + +v0\.2 +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="test\[k1\]" + +v1\.0 +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="test\[k2\]\[\]" + +v2\.1 +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="test\[k2\]\[k2.1\]" + +v2\.1\.0 +-----ZENDHTTPCLIENT-\w+-- + diff --git a/test/Client/_files/ZF9404-doubleContentLength.php b/test/Client/_files/ZF9404-doubleContentLength.php new file mode 100644 index 0000000000..728d3543e3 --- /dev/null +++ b/test/Client/_files/ZF9404-doubleContentLength.php @@ -0,0 +1,8 @@ +cI|P)4Pvj-R*llgrKI-gpvpyPsL>ilZKa~rh_;l= zRc)0D+8DLnD6SFIXx`j+ocFwczMtnj-*cYx8PE4&PBOm&eAbp2O8^7{0asWBFy{av zbTlpi0I*o#8~^}tRxTpv8*sCj0Xk805&!->x%yZ#s&egL&0zsi{=ABU^X@g3=TO3`=FDA^$`KG05_SrEFfSvEcD300f5-R zP#A=rMQS2g13McG28R6m7ysIUfJ*ZV2&p@gM$DXi!tRvy$Y_v}vd%7lZ85(9Tws<6 z0Y(5OfN9eisPN+3m7C;GmufeVVQ2>qyre|IU|)ztdXitHT1AHp6Hrs}rYIa|UVP{5 zTu}8^M;YvkZSy9FEf}`==l7Cme;hd_bsQs8WGAv-o;9gYjF5JC@u7YemAHd1$pL3` zc1snl8Wi{ZLO@4L;T-l&5(#ChpQ&*R_c09wY$ircD#l^oAThtrtNqA2y<0-pTP(A`t2NDc~eD+;9B{c+O3*2e^9 z-$ci-Erms9DBbF`lz;NVZgrZxDaLI)A6f4>_(72#dayoUVhU)0r9UQn60-t}7V7;> z873wik48za8pG_JQwFXJK6cluPCywOL|3OFy`r4H&RErHSIVAT@-3<6&73*Qh)B~f z`kPHev_+_LFUCIcbh4n%oTcAqg=e`(KdG}=XiJMK6aSAje(hHC#3cQ*O&F2$V z_ujieqD(n7e7EV)SxOJ`M)0Ho(mvwbwBtc@sv_CX@<9s!c~Bo)f=l<5{3r3zhaOfB zsy&(*D%J00Zkk2$q9i!mgpNo{1R1FRH@-2&pYC5&UIFZ!rN}DD=S*hP$ zQj{8xGUBb?<~Cfre5Z`CXPt9=d8IDh?E^>IT5#V@e<{RvBNO19!mExKtkGA|!nucz z1JxDbQ35r=m<_x03n3HI?jG+}aZ{XkPW;+*r$>v~YD)oapvI$)TLU}q>G;^=i1^{M z_?SA_tjVjsaA1;D5c_e(@+&?N(xh9u8TO6y=2BF3+sB~`4ksezA%PWpJfoz^yiMl> zijB9E{U}vjbF6;@gigM8rc%8j%_Nv|Y|m3GLMx(1^N+g`bvrQuIbOewW0Y3cB9y~j zkh>&c+(O)AWD;t#0>!D%y_avqy+hO_wjFOpn*>c9TO2fi5pxH*0lj!hG4c4Pm2u(u zBVzYSLaSTBJ!QK&({es1YN}snw%rbz7c$9STaPs=&~F^BP%PzWASMu9(*i@Tn%l+{ zq9W2}Grp6Se+?T~x~aMB_-o&@{R=-Bmbd4NRx$dNNR5iB?5fz#@d0t0qL{g#c;{Va zqPh~+ytjDfUuGXDFtDmg8WW&y@1?UTH>ta_GyPf}8+cEhYyv2>U=20uC{{s7qOGQA zP`)oa)}b<6a2Kd_Sr20Pxp`3O<2Bv(^5~~tFFHk)>Se35+(?IZdzRZ><;3AQkN*6h z22KNC3Ze-E%ZKOvuW9q+V^+Uiv|hi^@;NSvK?(i7Ya7-~Y1Pl0wUCfd$NOdY>tkl@ zgJ^>5Cr{$2*L93IOMY*ClV^Mt{OX0-?jI*YOn^RkRgn~Q!;GkVgE)AcUHY+eq<>;! z`W#x0*Kk~*ogUYjFjO>Xj}Al!Kll!J;$Oc5_cuCr=?0XMz*uLOa6omAQZn9iFNxtu z@mW`Tl&P`bnuJdUsn@Q?6Ai<(E1+81?qLa*axKrVyopFZiiiHdHM-5WHUKa!P!pJp zI*1G2bd9G@Lr`P8f;pmBZpfoc1*?rEX>sE*s|wCN$FA<$Mi9I^28$NWE_Zvbl4KM( zB~bQDr&5yiR8Eg-;%@)Gn*5bKzPZYuxWLbGKW5*nY8Z^4%)DeU^aFMJr){sBdg23m zT~eIo?&_ZOP3<~qcS>8x`B!B$yKySiEG8@8zpDaOk8Mq}^%)G0mg4PX5TI@(31AH?FFVBEXD-pnv9pTh6dSAfK9g5!HyTKYJfje~#26u8^b7xpH7|X1b`0UdqYuj|9?h3%elI zef39*)r@mreGD6V?K{1rbN-~*J5#T!UZh`3o5+1l6o8s4FX{_Lh>Lc$)0ZY;sT~K7 z_*2+vzt^;Iac>n{V+a1J=R@x`f zR<_fMa*-CL+>3h(=(Gt)IZZ-BZ)~KnIIoQoqiE&-toDV=!&?9Ckht)RrlO0_UdnVy zF`A=zPUWX5Ix`e>P`V(lnB{oFV)?DHhp9GZsf{PGc5^*}qvEd)KO3Y#94BjMb`lQH zT&r^8r;UoYUCL<>HS5z2p(uoYC8iZ`>duyHIm<&-5@aY5EIm649W}H)vCji%`Hm6} zWzjCaBJT>aTZv!j`JkCXx5G#$haMXd5ZO}11jxx17?O{pU)=WBz~x^bHmX}2&NqCm z`uKp_#^f`poP24}Oo(>dTGHkZjT+aP{I4-?tp(VR(+sQ-8 c8gGG4%=ME`&dwMu^AVMXoAce3GiQGO2Sm_)KmY&$ literal 0 HcmV?d00001 diff --git a/test/Client/_files/testCookies.php b/test/Client/_files/testCookies.php new file mode 100644 index 0000000000..52804f93f2 --- /dev/null +++ b/test/Client/_files/testCookies.php @@ -0,0 +1,11 @@ + $file) { + if (is_array($file['name'])) { + foreach ($file['name'] as $k => $v) { + echo "$name $v {$file['type'][$k]} {$file['size'][$k]}\n"; + } + } else { + echo "$name {$file['name']} {$file['type']} {$file['size']}\n"; + } + } +} diff --git a/test/ClientTest.php b/test/ClientTest.php new file mode 100644 index 0000000000..ac1d939d98 --- /dev/null +++ b/test/ClientTest.php @@ -0,0 +1,82 @@ +setMethod('post'); + $this->assertEquals(Client::ENC_URLENCODED, $client->getEncType()); + } + + public function testIfZeroValueCookiesCanBeSet() + { + $client = new Client(); + $client->addCookie("test", 0); + $client->addCookie("test2", "0"); + $client->addCookie("test3", false); + } + + /** + * @expectedException Zend\Http\Exception\InvalidArgumentException + */ + public function testIfNullValueCookiesThrowsException() + { + $client = new Client(); + $client->addCookie("test", null); + } + + public function testIfCookieHeaderCanBeSet() + { + $header = new SetCookie('foo'); + + $client = new Client(); + $client->addCookie($header); + + $cookies = $client->getCookies(); + $this->assertEquals(1, count($cookies)); + $this->assertEquals($header, $cookies['foo']); + } + + public function testIfArrayOfHeadersCanBeSet() + { + $headers = array( + new SetCookie('foo'), + new SetCookie('bar') + ); + + $client = new Client(); + $client->addCookie($headers); + + $cookies = $client->getCookies(); + $this->assertEquals(2, count($cookies)); + } + + public function testIfArrayIteratorOfHeadersCanBeSet() + { + $headers = new \ArrayIterator(array( + new SetCookie('foo'), + new SetCookie('bar') + )); + + $client = new Client(); + $client->addCookie($headers); + + $cookies = $client->getCookies(); + $this->assertEquals(2, count($cookies)); + } +} diff --git a/test/Header/AcceptCharsetTest.php b/test/Header/AcceptCharsetTest.php new file mode 100644 index 0000000000..3c68e92a4a --- /dev/null +++ b/test/Header/AcceptCharsetTest.php @@ -0,0 +1,82 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $acceptCharsetHeader); + $this->assertInstanceOf('Zend\Http\Header\AcceptCharset', $acceptCharsetHeader); + } + + public function testAcceptCharsetGetFieldNameReturnsHeaderName() + { + $acceptCharsetHeader = new AcceptCharset(); + $this->assertEquals('Accept-Charset', $acceptCharsetHeader->getFieldName()); + } + + public function testAcceptCharsetGetFieldValueReturnsProperValue() + { + $acceptCharsetHeader = AcceptCharset::fromString('Accept-Charset: xxx'); + $this->assertEquals('xxx', $acceptCharsetHeader->getFieldValue()); + } + + public function testAcceptCharsetToStringReturnsHeaderFormattedString() + { + + $acceptCharsetHeader = new AcceptCharset(); + $acceptCharsetHeader->addCharset('iso-8859-5', 0.8) + ->addCharset('unicode-1-1', 1); + + $this->assertEquals('Accept-Charset: iso-8859-5;q=0.8, unicode-1-1', $acceptCharsetHeader->toString()); + } + + /** Implmentation specific tests here */ + + public function testCanParseCommaSeparatedValues() + { + $header = AcceptCharset::fromString('Accept-Charset: iso-8859-5;q=0.8,unicode-1-1'); + $this->assertTrue($header->hasCharset('iso-8859-5')); + $this->assertTrue($header->hasCharset('unicode-1-1')); + } + + public function testPrioritizesValuesBasedOnQParameter() + { + $header = AcceptCharset::fromString('Accept-Charset: iso-8859-5;q=0.8,unicode-1-1,*;q=0.4'); + $expected = array( + 'unicode-1-1', + 'iso-8859-5', + '*' + ); + + $test = array(); + foreach ($header->getPrioritized() as $type) { + $this->assertEquals(array_shift($expected), $type->getCharset()); + } + } + + public function testWildcharCharset() + { + $acceptHeader = new AcceptCharset(); + $acceptHeader->addCharset('iso-8859-5', 0.8) + ->addCharset('*', 0.4); + + $this->assertTrue($acceptHeader->hasCharset('iso-8859-5')); + $this->assertTrue($acceptHeader->hasCharset('unicode-1-1')); + $this->assertEquals('Accept-Charset: iso-8859-5;q=0.8, *;q=0.4', $acceptHeader->toString()); + } +} + diff --git a/test/Header/AcceptEncodingTest.php b/test/Header/AcceptEncodingTest.php new file mode 100644 index 0000000000..fe0a0ed37b --- /dev/null +++ b/test/Header/AcceptEncodingTest.php @@ -0,0 +1,82 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $acceptEncodingHeader); + $this->assertInstanceOf('Zend\Http\Header\AcceptEncoding', $acceptEncodingHeader); + } + + public function testAcceptEncodingGetFieldNameReturnsHeaderName() + { + $acceptEncodingHeader = new AcceptEncoding(); + $this->assertEquals('Accept-Encoding', $acceptEncodingHeader->getFieldName()); + } + + public function testAcceptEncodingGetFieldValueReturnsProperValue() + { + $acceptEncodingHeader = AcceptEncoding::fromString('Accept-Encoding: xxx'); + $this->assertEquals('xxx', $acceptEncodingHeader->getFieldValue()); + } + + public function testAcceptEncodingToStringReturnsHeaderFormattedString() + { + + $acceptEncodingHeader = new AcceptEncoding(); + $acceptEncodingHeader->addEncoding('compress', 0.5) + ->addEncoding('gzip', 1); + + $this->assertEquals('Accept-Encoding: compress;q=0.5, gzip', $acceptEncodingHeader->toString()); + } + + /** Implmentation specific tests here */ + + public function testCanParseCommaSeparatedValues() + { + $header = AcceptEncoding::fromString('Accept-Encoding: compress;q=0.5,gzip'); + $this->assertTrue($header->hasEncoding('compress')); + $this->assertTrue($header->hasEncoding('gzip')); + } + + public function testPrioritizesValuesBasedOnQParameter() + { + $header = AcceptEncoding::fromString('Accept-Encoding: compress;q=0.8,gzip,*;q=0.4'); + $expected = array( + 'gzip', + 'compress', + '*' + ); + + $test = array(); + foreach ($header->getPrioritized() as $type) { + $this->assertEquals(array_shift($expected), $type->getEncoding()); + } + } + + public function testWildcharEncoder() + { + $acceptHeader = new AcceptEncoding(); + $acceptHeader->addEncoding('compress', 0.8) + ->addEncoding('*', 0.4); + + $this->assertTrue($acceptHeader->hasEncoding('compress')); + $this->assertTrue($acceptHeader->hasEncoding('gzip')); + $this->assertEquals('Accept-Encoding: compress;q=0.8, *;q=0.4', $acceptHeader->toString()); + } +} + diff --git a/test/Header/AcceptLanguageTest.php b/test/Header/AcceptLanguageTest.php new file mode 100644 index 0000000000..825094d1cf --- /dev/null +++ b/test/Header/AcceptLanguageTest.php @@ -0,0 +1,96 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $acceptLanguageHeader); + $this->assertInstanceOf('Zend\Http\Header\AcceptLanguage', $acceptLanguageHeader); + } + + public function testAcceptLanguageGetFieldNameReturnsHeaderName() + { + $acceptLanguageHeader = new AcceptLanguage(); + $this->assertEquals('Accept-Language', $acceptLanguageHeader->getFieldName()); + } + + public function testAcceptLanguageGetFieldValueReturnsProperValue() + { + $acceptLanguageHeader = AcceptLanguage::fromString('Accept-Language: xxx'); + $this->assertEquals('xxx', $acceptLanguageHeader->getFieldValue()); + } + + public function testAcceptLanguageToStringReturnsHeaderFormattedString() + { + $acceptLanguageHeader = new AcceptLanguage(); + $acceptLanguageHeader->addLanguage('da', 0.8) + ->addLanguage('en-gb', 1); + + $this->assertEquals('Accept-Language: da;q=0.8, en-gb', $acceptLanguageHeader->toString()); + } + + /** Implmentation specific tests here */ + + public function testCanParseCommaSeparatedValues() + { + $header = AcceptLanguage::fromString('Accept-Language: da;q=0.8, en-gb'); + $this->assertTrue($header->hasLanguage('da')); + $this->assertTrue($header->hasLanguage('en-gb')); + } + + public function testPrioritizesValuesBasedOnQParameter() + { + $header = AcceptLanguage::fromString('Accept-Language: da;q=0.8, en-gb, *;q=0.4'); + $expected = array( + 'en-gb', + 'da', + '*' + ); + + $test = array(); + foreach ($header->getPrioritized() as $type) { + $this->assertEquals(array_shift($expected), $type->typeString); + } + $this->assertEquals($expected, $test); + } + + public function testWildcharLanguage() + { + $acceptHeader = new AcceptLanguage(); + $acceptHeader->addLanguage('da', 0.8) + ->addLanguage('*', 0.4); + + $this->assertTrue($acceptHeader->hasLanguage('da')); + $this->assertTrue($acceptHeader->hasLanguage('en')); + $this->assertEquals('Accept-Language: da;q=0.8, *;q=0.4', $acceptHeader->toString()); + } + + public function testWildcards() + { + $accept = AcceptLanguage::fromString('*, en-*, en-us'); + $res = $accept->getPrioritized(); + + $this->assertEquals('en-us', $res[0]->getLanguage()); + $this->assertEquals('en', $res[0]->getPrimaryTag()); + $this->assertEquals('us', $res[0]->getSubTag()); + + $this->assertEquals('en-*', $res[1]->getLanguage()); + $this->assertEquals('en', $res[1]->getPrimaryTag()); + + $this->assertTrue($accept->hasLanguage('nl')); + } +} diff --git a/test/Header/AcceptRangesTest.php b/test/Header/AcceptRangesTest.php new file mode 100644 index 0000000000..8c4462a33b --- /dev/null +++ b/test/Header/AcceptRangesTest.php @@ -0,0 +1,50 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $acceptRangesHeader); + $this->assertInstanceOf('Zend\Http\Header\AcceptRanges', $acceptRangesHeader); + } + + public function testAcceptRangesGetFieldNameReturnsHeaderName() + { + $acceptRangesHeader = new AcceptRanges(); + $this->assertEquals('Accept-Ranges', $acceptRangesHeader->getFieldName()); + } + + public function testAcceptRangesGetFieldValueReturnsProperValue() + { + $acceptRangesHeader = AcceptRanges::fromString('Accept-Ranges: bytes'); + $this->assertEquals('bytes', $acceptRangesHeader->getFieldValue()); + $this->assertEquals('bytes', $acceptRangesHeader->getRangeUnit()); + } + + public function testAcceptRangesToStringReturnsHeaderFormattedString() + { + $acceptRangesHeader = new AcceptRanges(); + $acceptRangesHeader->setRangeUnit('bytes'); + + // @todo set some values, then test output + $this->assertEquals('Accept-Ranges: bytes', $acceptRangesHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/AcceptTest.php b/test/Header/AcceptTest.php new file mode 100644 index 0000000000..c22605b9c3 --- /dev/null +++ b/test/Header/AcceptTest.php @@ -0,0 +1,372 @@ +setExpectedException('Zend\Http\Header\Exception\InvalidArgumentException'); + $acceptHeader = Accept::fromString(''); + } + + public function testAcceptFromStringCreatesValidAcceptHeader() + { + $acceptHeader = Accept::fromString('Accept: xxx'); + $this->assertInstanceOf('Zend\Http\Header\HeaderInterface', $acceptHeader); + $this->assertInstanceOf('Zend\Http\Header\Accept', $acceptHeader); + } + + public function testAcceptGetFieldNameReturnsHeaderName() + { + $acceptHeader = new Accept(); + $this->assertEquals('Accept', $acceptHeader->getFieldName()); + } + + public function testAcceptGetFieldValueReturnsProperValue() + { + $acceptHeader = Accept::fromString('Accept: xxx'); + $this->assertEquals('xxx', $acceptHeader->getFieldValue()); + } + + public function testAcceptToStringReturnsHeaderFormattedString() + { + $acceptHeader = new Accept(); + $acceptHeader->addMediaType('text/html', 0.8) + ->addMediaType('application/json', 1) + ->addMediaType('application/atom+xml', 0.9); + + // @todo set some values, then test output + $this->assertEquals( + 'Accept: text/html;q=0.8, application/json, application/atom+xml;q=0.9', + $acceptHeader->toString()); + + $this->setExpectedException('Zend\Http\Header\Exception\InvalidArgumentException'); + $acceptHeader->addMediaType('\\', 0.9); + + } + + + /** Implementation specific tests here */ + + public function testCanParseCommaSeparatedValues() + { + $header = Accept::fromString('Accept: text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c'); + $this->assertTrue($header->hasMediaType('text/plain')); + $this->assertTrue($header->hasMediaType('text/html')); + $this->assertTrue($header->hasMediaType('text/x-dvi')); + $this->assertTrue($header->hasMediaType('text/x-c')); + } + + public function testPrioritizesValuesBasedOnQParameter() + { + $header = Accept::fromString('Accept: text/plain; q=0.5, text/html, text/xml; q=0, text/x-dvi; q=0.8, text/x-c'); + $expected = array( + 'text/html', + 'text/x-c', + 'text/x-dvi', + 'text/plain', + 'text/xml', + ); + + $test = array(); + foreach ($header->getPrioritized() as $type) { + $this->assertEquals(array_shift($expected), $type->typeString); + } + } + + public function testLevel() + { + $acceptHeader = new Accept(); + $acceptHeader->addMediaType('text/html', 0.8, array('level' => 1)) + ->addMediaType('text/html', 0.4, array('level' => 2)) + ->addMediaType('application/atom+xml', 0.9); + + $this->assertEquals( + 'Accept: text/html;q=0.8;level=1, ' + .'text/html;q=0.4;level=2, application/atom+xml;q=0.9', + $acceptHeader->toString() + ); + } + + public function testPrioritizedLevel() + { + $header = Accept::fromString('Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1,' + .'text/html;level=2;q=0.4, */*;q=0.5'); + + $expected = array ( + 'text/html;level=1', + 'text/html;q=0.7', + '*/*;q=0.5', + 'text/html;level=2;q=0.4', + 'text/*;q=0.3' + ); + + $test = array(); + foreach ($header->getPrioritized() as $type) { + $this->assertEquals(array_shift($expected), $type->raw); + } + } + + public function testPrios() + { + $values = array('invalidPrio' => false, + -0.0001 => false, + 1.0001 => false, + 1.000 => true, + 0.999 => true, + 0.000 => true, + 0.001 => true, + 1 => true, + 0 => true + ); + + $header = new Accept(); + foreach ($values as $prio => $shouldPass) { + try { + $header->addMediaType('text/html', $prio); + if (!$shouldPass) { + $this->fail('Exception expected'); + } + } catch (\Zend\Http\Header\Exception\InvalidArgumentException $e) { + if ($shouldPass) { + throw $e; + } + } + } + } + + public function testWildcharMediaType() + { + $acceptHeader = new Accept(); + $acceptHeader->addMediaType('text/*', 0.8) + ->addMediaType('application/*', 1) + ->addMediaType('*/*', 0.4); + + $this->assertTrue($acceptHeader->hasMediaType('text/html')); + $this->assertTrue($acceptHeader->hasMediaType('application/atom+xml')); + $this->assertTrue($acceptHeader->hasMediaType('audio/basic')); + $this->assertEquals('Accept: application/*, text/*;q=0.8, */*;q=0.4', $acceptHeader->toString()); + } + + + public function testMatchWildCard() + { + $acceptHeader = Accept::fromString('Accept: */*'); + $this->assertTrue($acceptHeader->hasMediaType('application/vnd.foobar+json')); + + $acceptHeader = Accept::fromString('Accept: application/*'); + $this->assertTrue($acceptHeader->hasMediaType('application/vnd.foobar+json')); + $this->assertTrue($acceptHeader->hasMediaType('application/vnd.foobar+*')); + + $acceptHeader = Accept::fromString('Accept: application/vnd.foobar+html'); + $this->assertTrue($acceptHeader->hasMediaType('*/html')); + $this->assertTrue($acceptHeader->hasMediaType('application/vnd.foobar+*')); + + $acceptHeader = Accept::fromString('Accept: text/html'); + $this->assertTrue($acceptHeader->hasMediaType('*/html')); + $this->assertTrue($acceptHeader->hasMediaType('*/*+html')); + + $this->assertTrue($acceptHeader->hasMediaType('text/*')); + } + + + public function testParsingAndAssemblingQuotedStrings() + { + $acceptStr = 'Accept: application/vnd.foobar+html;q=1;version="2\\' + . chr(22).'3\"";level="foo;, bar", text/json;level=1, text/xml;level=2;q=0.4'; + $acceptHeader = Accept::fromString($acceptStr); + + $this->assertEquals($acceptStr, $acceptHeader->getFieldName().': '.$acceptHeader->getFieldValue()); + } + + + public function testVersioning() + { + $acceptStr = 'Accept: text/html;q=1; version=23; level=5, text/json;level=1,' . + 'text/xml;level=2;q=0.4'; + $acceptHeader = Accept::fromString($acceptStr); + + $expected = array('typeString' => 'text/html', + 'type' => 'text', + 'subtype' => 'html', + 'subtypeRaw' => 'html', + 'format' => 'html', + 'priority' => 1, + 'params' => array('q' => 1, 'version' => 23, 'level' => 5), + 'raw' => 'text/html;q=1; version=23; level=5'); + + $this->assertFalse($acceptHeader->match('text/html; version=22')); + + $res = $acceptHeader->match('text/html; version=23'); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + + $this->assertFalse($acceptHeader->match('text/html; version=24')); + + $res = $acceptHeader->match('text/html; version=22-24'); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + + $this->assertFalse($acceptHeader->match('text/html; version=20|22|24')); + + $res = $acceptHeader->match('text/html; version=22|23|24'); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + + } + + public function testWildcardWithDifferentParamsAndRanges() + { + $acceptHeader = Accept::fromString('Accept: */*; version=21'); + $this->assertFalse($acceptHeader->match('*/*; version=20|22')); + + $acceptHeader = Accept::fromString('Accept: */*; version=19'); + $this->assertFalse($acceptHeader->match('*/*; version=20-22')); + + $acceptHeader = Accept::fromString('Accept: */*; version=23'); + $this->assertFalse($acceptHeader->match('*/*; version=20-22')); + + $acceptHeader = Accept::fromString('Accept: */*; version=21'); + $res = $acceptHeader->match('*/*; version=20-22'); + $this->assertInstanceOf('Zend\Http\Header\Accept\FieldValuePart\AcceptFieldValuePart', $res); + $this->assertEquals('21', $res->getParams()->version); + } + + public function testVersioningAndPriorization() + { + $acceptStr = 'Accept: text/html; version=23, text/json; version=15.3; q=0.9,' . + 'text/html;level=2;q=0.4'; + $acceptHeader = Accept::fromString($acceptStr); + + $expected = array('typeString' => 'text/json', + 'type' => 'text', + 'subtype' => 'json', + 'subtypeRaw' => 'json', + 'format' => 'json', + 'priority' => 0.9, + 'params' => array('q' => 0.9, 'version' => 15.3), + 'raw' => 'text/json; version=15.3; q=0.9'); + + $str = 'text/html; version=17, text/json; version=15-16'; + $res = $acceptHeader->match($str); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + + $expected = (object) array('typeString' => 'text/html', + 'type' => 'text', + 'subtype' => 'html', + 'subtypeRaw' => 'html', + 'format' => 'html', + 'priority' => 0.4, + 'params' => array('q' => 0.4, 'level' => 2), + 'raw' => 'text/html;level=2;q=0.4'); + + $str = 'text/html; version=17,text/json; version=15-16; q=0.5'; + $res = $acceptHeader->match($str); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + } + + + public function testPrioritizing() + { + // Example is copy/paste from rfc2616 + $acceptStr = 'Accept: text/*;q=0.3, */*,text/html;q=1, text/html;level=1,' + . 'text/html;level=2;q=0.4, */*;q=0.5'; + $acceptHdr = Accept::fromString($acceptStr); + + $expected = array('typeString' => 'text/html', + 'type' => 'text', + 'subtype' => 'html', + 'subtypeRaw' => 'html', + 'format' => 'html', + 'priority' => 1, + 'params' => array('level' => 1), + 'raw' => 'text/html;level=1'); + + $res = $acceptHdr->match('text/html'); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + + $res = $acceptHdr->match('text'); + foreach ($expected as $key => $value) { + $this->assertEquals($value, $res->$key); + } + + } + + public function testPrioritizing_2() + { + $accept = Accept::fromString("Accept: application/text, \tapplication/*"); + $res = $accept->getPrioritized(); + $this->assertEquals('application/text', $res[0]->raw); + $this->assertEquals('application/*', $res[1]->raw); + + $accept = Accept::fromString("Accept: \tapplication/*, application/text"); + $res = $accept->getPrioritized(); + $this->assertEquals('application/text', $res[0]->raw); + $this->assertEquals('application/*', $res[1]->raw); + + $accept = Accept::fromString("Accept: text/xml, application/xml"); + $res = $accept->getPrioritized(); + $this->assertEquals('application/xml', $res[0]->raw); + $this->assertEquals('text/xml', $res[1]->raw); + + $accept = Accept::fromString("Accept: application/xml, text/xml"); + $res = $accept->getPrioritized(); + $this->assertEquals('application/xml', $res[0]->raw); + $this->assertEquals('text/xml', $res[1]->raw); + + $accept = Accept::fromString("Accept: application/vnd.foobar+xml; q=0.9, text/xml"); + $res = $accept->getPrioritized(); + $this->assertEquals(1.0, $res[0]->getPriority()); + $this->assertEquals(0.9, $res[1]->getPriority()); + $this->assertEquals('application/vnd.foobar+xml', $res[1]->getTypeString()); + $this->assertEquals('vnd.foobar+xml', $res[1]->getSubtypeRaw()); + $this->assertEquals('vnd.foobar', $res[1]->getSubtype()); + $this->assertEquals('xml', $res[1]->getFormat()); + + $accept = Accept::fromString('Accept: text/xml, application/vnd.foobar+xml; version="\'Ѿ"'); + $res = $accept->getPrioritized(); + $this->assertEquals('application/vnd.foobar+xml; version="\'Ѿ"', $res[0]->getRaw()); + } + + + public function testWildcardDefaults() + { + $this->markTestIncomplete('No wildcard defaults implemented yet'); + + $expected = (object)array('typeString' => 'image', + 'type' => 'image', + 'subtype' => '*', + 'subtypeRaw' => '', + 'format' => 'jpeg', + 'priority' => 1, + 'params' => array(), + 'raw' => 'image'); + + $this->assertEquals($expected, $acceptHdr->match('image')); + // $this->assertEquals($expected, $this->_handler->match('text')); + } + +} diff --git a/test/Header/AgeTest.php b/test/Header/AgeTest.php new file mode 100644 index 0000000000..42f3a75670 --- /dev/null +++ b/test/Header/AgeTest.php @@ -0,0 +1,53 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $ageHeader); + $this->assertInstanceOf('Zend\Http\Header\Age', $ageHeader); + $this->assertEquals('12', $ageHeader->getDeltaSeconds()); + } + + public function testAgeGetFieldNameReturnsHeaderName() + { + $ageHeader = new Age(); + $this->assertEquals('Age', $ageHeader->getFieldName()); + } + + public function testAgeGetFieldValueReturnsProperValue() + { + $ageHeader = new Age(); + $ageHeader->setDeltaSeconds('12'); + $this->assertEquals('12', $ageHeader->getFieldValue()); + } + + public function testAgeToStringReturnsHeaderFormattedString() + { + $ageHeader = new Age(); + $ageHeader->setDeltaSeconds('12'); + $this->assertEquals('Age: 12', $ageHeader->toString()); + } + + public function testAgeCorrectsDeltaSecondsOverflow() + { + $ageHeader = new Age(); + $ageHeader->setDeltaSeconds(PHP_INT_MAX); + $this->assertEquals('Age: 2147483648', $ageHeader->toString()); + } +} + diff --git a/test/Header/AllowTest.php b/test/Header/AllowTest.php new file mode 100644 index 0000000000..df7f814be3 --- /dev/null +++ b/test/Header/AllowTest.php @@ -0,0 +1,82 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $allowHeader); + $this->assertInstanceOf('Zend\Http\Header\Allow', $allowHeader); + $this->assertEquals(array('GET', 'POST', 'PUT'), $allowHeader->getAllowedMethods()); + } + + public function testAllowFromStringSupportsExtensionMethods() + { + $allowHeader = Allow::fromString('Allow: GET, POST, PROCREATE'); + $this->assertTrue($allowHeader->isAllowedMethod('PROCREATE')); + } + + public function testAllowGetFieldNameReturnsHeaderName() + { + $allowHeader = new Allow(); + $this->assertEquals('Allow', $allowHeader->getFieldName()); + } + + public function testAllowListAllDefinedMethods() + { + $methods = array( + 'OPTIONS' => false, + 'GET' => true, + 'HEAD' => false, + 'POST' => true, + 'PUT' => false, + 'DELETE' => false, + 'TRACE' => false, + 'CONNECT' => false, + 'PATCH' => false, + ); + $allowHeader = new Allow(); + $this->assertEquals($methods, $allowHeader->getAllMethods()); + } + + public function testAllowGetDefaultAllowedMethods() + { + $allowHeader = new Allow(); + $this->assertEquals(array('GET', 'POST'), $allowHeader->getAllowedMethods()); + } + + public function testAllowGetFieldValueReturnsProperValue() + { + $allowHeader = new Allow(); + $allowHeader->allowMethods(array('GET', 'POST', 'TRACE')); + $this->assertEquals('GET, POST, TRACE', $allowHeader->getFieldValue()); + } + + public function testAllowToStringReturnsHeaderFormattedString() + { + $allowHeader = new Allow(); + $allowHeader->allowMethods(array('GET', 'POST', 'TRACE')); + $this->assertEquals('Allow: GET, POST, TRACE', $allowHeader->toString()); + } + + public function testAllowChecksAllowedMethod() + { + $allowHeader = new Allow(); + $allowHeader->allowMethods(array('GET', 'POST', 'TRACE')); + $this->assertTrue($allowHeader->isAllowedMethod('TRACE')); + } +} + diff --git a/test/Header/AuthenticationInfoTest.php b/test/Header/AuthenticationInfoTest.php new file mode 100644 index 0000000000..eea0fb98ac --- /dev/null +++ b/test/Header/AuthenticationInfoTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $authenticationInfoHeader); + $this->assertInstanceOf('Zend\Http\Header\AuthenticationInfo', $authenticationInfoHeader); + } + + public function testAuthenticationInfoGetFieldNameReturnsHeaderName() + { + $authenticationInfoHeader = new AuthenticationInfo(); + $this->assertEquals('Authentication-Info', $authenticationInfoHeader->getFieldName()); + } + + public function testAuthenticationInfoGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('AuthenticationInfo needs to be completed'); + + $authenticationInfoHeader = new AuthenticationInfo(); + $this->assertEquals('xxx', $authenticationInfoHeader->getFieldValue()); + } + + public function testAuthenticationInfoToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('AuthenticationInfo needs to be completed'); + + $authenticationInfoHeader = new AuthenticationInfo(); + + // @todo set some values, then test output + $this->assertEmpty('Authentication-Info: xxx', $authenticationInfoHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/AuthorizationTest.php b/test/Header/AuthorizationTest.php new file mode 100644 index 0000000000..a8b3f7b477 --- /dev/null +++ b/test/Header/AuthorizationTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $authorizationHeader); + $this->assertInstanceOf('Zend\Http\Header\Authorization', $authorizationHeader); + } + + public function testAuthorizationGetFieldNameReturnsHeaderName() + { + $authorizationHeader = new Authorization(); + $this->assertEquals('Authorization', $authorizationHeader->getFieldName()); + } + + public function testAuthorizationGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Authorization needs to be completed'); + + $authorizationHeader = new Authorization(); + $this->assertEquals('xxx', $authorizationHeader->getFieldValue()); + } + + public function testAuthorizationToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Authorization needs to be completed'); + + $authorizationHeader = new Authorization(); + + // @todo set some values, then test output + $this->assertEmpty('Authorization: xxx', $authorizationHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/CacheControlTest.php b/test/Header/CacheControlTest.php new file mode 100644 index 0000000000..2194d68a85 --- /dev/null +++ b/test/Header/CacheControlTest.php @@ -0,0 +1,103 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $cacheControlHeader); + $this->assertInstanceOf('Zend\Http\Header\CacheControl', $cacheControlHeader); + } + + public function testCacheControlGetFieldNameReturnsHeaderName() + { + $cacheControlHeader = new CacheControl(); + $this->assertEquals('Cache-Control', $cacheControlHeader->getFieldName()); + } + + public function testCacheControlGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('CacheControl needs to be completed'); + + $cacheControlHeader = new CacheControl(); + $this->assertEquals('xxx', $cacheControlHeader->getFieldValue()); + } + + public function testCacheControlToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('CacheControl needs to be completed'); + + $cacheControlHeader = new CacheControl(); + + // @todo set some values, then test output + $this->assertEmpty('Cache-Control: xxx', $cacheControlHeader->toString()); + } + + /** Implmentation specific tests here */ + + public function testCacheControlIsEmpty() + { + $cacheControlHeader = new CacheControl(); + $this->assertTrue($cacheControlHeader->isEmpty()); + $cacheControlHeader->addDirective('xxx'); + $this->assertFalse($cacheControlHeader->isEmpty()); + $cacheControlHeader->removeDirective('xxx'); + $this->assertTrue($cacheControlHeader->isEmpty()); + } + + public function testCacheControlAddHasGetRemove() + { + $cacheControlHeader = new CacheControl(); + $cacheControlHeader->addDirective('xxx'); + $this->assertTrue($cacheControlHeader->hasDirective('xxx')); + $this->assertTrue($cacheControlHeader->getDirective('xxx')); + $cacheControlHeader->removeDirective('xxx'); + $this->assertFalse($cacheControlHeader->hasDirective('xxx')); + $this->assertNull($cacheControlHeader->getDirective('xxx')); + + $cacheControlHeader->addDirective('xxx', 'foo'); + $this->assertTrue($cacheControlHeader->hasDirective('xxx')); + $this->assertEquals('foo', $cacheControlHeader->getDirective('xxx')); + $cacheControlHeader->removeDirective('xxx'); + $this->assertFalse($cacheControlHeader->hasDirective('xxx')); + $this->assertNull($cacheControlHeader->getDirective('xxx')); + } + + public function testCacheControlGetFieldValue() + { + $cacheControlHeader = new CacheControl(); + $this->assertEmpty($cacheControlHeader->getFieldValue()); + $cacheControlHeader->addDirective('xxx'); + $this->assertEquals('xxx', $cacheControlHeader->getFieldValue()); + $cacheControlHeader->addDirective('aaa'); + $this->assertEquals('aaa, xxx', $cacheControlHeader->getFieldValue()); + $cacheControlHeader->addDirective('yyy', 'foo'); + $this->assertEquals('aaa, xxx, yyy=foo', $cacheControlHeader->getFieldValue()); + $cacheControlHeader->addDirective('zzz', 'bar, baz'); + $this->assertEquals('aaa, xxx, yyy=foo, zzz="bar, baz"', $cacheControlHeader->getFieldValue()); + } + + public function testCacheControlParse() + { + $cacheControlHeader = CacheControl::fromString('Cache-Control: a, b=foo, c="bar, baz"'); + $this->assertTrue($cacheControlHeader->hasDirective('a')); + $this->assertTrue($cacheControlHeader->getDirective('a')); + $this->assertTrue($cacheControlHeader->hasDirective('b')); + $this->assertEquals('foo', $cacheControlHeader->getDirective('b')); + $this->assertTrue($cacheControlHeader->hasDirective('c')); + $this->assertEquals('bar, baz', $cacheControlHeader->getDirective('c')); + } +} diff --git a/test/Header/ConnectionTest.php b/test/Header/ConnectionTest.php new file mode 100644 index 0000000000..f1c6ecb527 --- /dev/null +++ b/test/Header/ConnectionTest.php @@ -0,0 +1,59 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $connectionHeader); + $this->assertInstanceOf('Zend\Http\Header\Connection', $connectionHeader); + $this->assertEquals('close', $connectionHeader->getFieldValue()); + $this->assertFalse($connectionHeader->isPersistent()); + } + + public function testConnectionGetFieldNameReturnsHeaderName() + { + $connectionHeader = new Connection(); + $this->assertEquals('Connection', $connectionHeader->getFieldName()); + } + + public function testConnectionGetFieldValueReturnsProperValue() + { + $connectionHeader = new Connection(); + $connectionHeader->setValue('Keep-Alive'); + $this->assertEquals('keep-alive', $connectionHeader->getFieldValue()); + } + + public function testConnectionToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Connection needs to be completed'); + + $connectionHeader = new Connection(); + $connectionHeader->setValue('close'); + $this->assertEmpty('Connection: close', $connectionHeader->toString()); + } + + public function testConnectionSetPersistentReturnsProperValue() + { + $connectionHeader = new Connection(); + $connectionHeader->setPersistent(true); + $this->assertEquals('keep-alive', $connectionHeader->getFieldValue()); + $connectionHeader->setPersistent(false); + $this->assertEquals('close', $connectionHeader->getFieldValue()); + + } + +} + diff --git a/test/Header/ContentDispositionTest.php b/test/Header/ContentDispositionTest.php new file mode 100644 index 0000000000..9f8a493db8 --- /dev/null +++ b/test/Header/ContentDispositionTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentDispositionHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentDisposition', $contentDispositionHeader); + } + + public function testContentDispositionGetFieldNameReturnsHeaderName() + { + $contentDispositionHeader = new ContentDisposition(); + $this->assertEquals('Content-Disposition', $contentDispositionHeader->getFieldName()); + } + + public function testContentDispositionGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentDisposition needs to be completed'); + + $contentDispositionHeader = new ContentDisposition(); + $this->assertEquals('xxx', $contentDispositionHeader->getFieldValue()); + } + + public function testContentDispositionToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentDisposition needs to be completed'); + + $contentDispositionHeader = new ContentDisposition(); + + // @todo set some values, then test output + $this->assertEmpty('Content-Disposition: xxx', $contentDispositionHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ContentEncodingTest.php b/test/Header/ContentEncodingTest.php new file mode 100644 index 0000000000..d52dbc72b2 --- /dev/null +++ b/test/Header/ContentEncodingTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentEncodingHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentEncoding', $contentEncodingHeader); + } + + public function testContentEncodingGetFieldNameReturnsHeaderName() + { + $contentEncodingHeader = new ContentEncoding(); + $this->assertEquals('Content-Encoding', $contentEncodingHeader->getFieldName()); + } + + public function testContentEncodingGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentEncoding needs to be completed'); + + $contentEncodingHeader = new ContentEncoding(); + $this->assertEquals('xxx', $contentEncodingHeader->getFieldValue()); + } + + public function testContentEncodingToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentEncoding needs to be completed'); + + $contentEncodingHeader = new ContentEncoding(); + + // @todo set some values, then test output + $this->assertEmpty('Content-Encoding: xxx', $contentEncodingHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ContentLanguageTest.php b/test/Header/ContentLanguageTest.php new file mode 100644 index 0000000000..a00d9e15b6 --- /dev/null +++ b/test/Header/ContentLanguageTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentLanguageHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentLanguage', $contentLanguageHeader); + } + + public function testContentLanguageGetFieldNameReturnsHeaderName() + { + $contentLanguageHeader = new ContentLanguage(); + $this->assertEquals('Content-Language', $contentLanguageHeader->getFieldName()); + } + + public function testContentLanguageGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentLanguage needs to be completed'); + + $contentLanguageHeader = new ContentLanguage(); + $this->assertEquals('xxx', $contentLanguageHeader->getFieldValue()); + } + + public function testContentLanguageToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentLanguage needs to be completed'); + + $contentLanguageHeader = new ContentLanguage(); + + // @todo set some values, then test output + $this->assertEmpty('Content-Language: xxx', $contentLanguageHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ContentLengthTest.php b/test/Header/ContentLengthTest.php new file mode 100644 index 0000000000..feacf47ed8 --- /dev/null +++ b/test/Header/ContentLengthTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentLengthHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentLength', $contentLengthHeader); + } + + public function testContentLengthGetFieldNameReturnsHeaderName() + { + $contentLengthHeader = new ContentLength(); + $this->assertEquals('Content-Length', $contentLengthHeader->getFieldName()); + } + + public function testContentLengthGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentLength needs to be completed'); + + $contentLengthHeader = new ContentLength(); + $this->assertEquals('xxx', $contentLengthHeader->getFieldValue()); + } + + public function testContentLengthToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentLength needs to be completed'); + + $contentLengthHeader = new ContentLength(); + + // @todo set some values, then test output + $this->assertEmpty('Content-Length: xxx', $contentLengthHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ContentLocationTest.php b/test/Header/ContentLocationTest.php new file mode 100644 index 0000000000..841b75e2be --- /dev/null +++ b/test/Header/ContentLocationTest.php @@ -0,0 +1,65 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentLocationHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentLocation', $contentLocationHeader); + } + + public function testContentLocationGetFieldValueReturnsProperValue() + { + $contentLocationHeader = new ContentLocation(); + $contentLocationHeader->setUri('http://www.example.com/'); + $this->assertEquals('http://www.example.com/', $contentLocationHeader->getFieldValue()); + + $contentLocationHeader->setUri('/path'); + $this->assertEquals('/path', $contentLocationHeader->getFieldValue()); + } + + public function testContentLocationToStringReturnsHeaderFormattedString() + { + $contentLocationHeader = new ContentLocation(); + $contentLocationHeader->setUri('http://www.example.com/path?query'); + + $this->assertEquals('Content-Location: http://www.example.com/path?query', $contentLocationHeader->toString()); + } + + /** Implementation specific tests */ + + public function testContentLocationCanSetAndAccessAbsoluteUri() + { + $contentLocationHeader = ContentLocation::fromString('Content-Location: http://www.example.com/path'); + $uri = $contentLocationHeader->uri(); + $this->assertInstanceOf('Zend\Uri\UriInterface', $uri); + $this->assertTrue($uri->isAbsolute()); + $this->assertEquals('http://www.example.com/path', $contentLocationHeader->getUri()); + } + + public function testContentLocationCanSetAndAccessRelativeUri() + { + $contentLocationHeader = ContentLocation::fromString('Content-Location: /path/to'); + $uri = $contentLocationHeader->uri(); + $this->assertInstanceOf('Zend\Uri\UriInterface', $uri); + $this->assertFalse($uri->isAbsolute()); + $this->assertEquals('/path/to', $contentLocationHeader->getUri()); + } + +} + diff --git a/test/Header/ContentMD5Test.php b/test/Header/ContentMD5Test.php new file mode 100644 index 0000000000..c451990b45 --- /dev/null +++ b/test/Header/ContentMD5Test.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentMD5Header); + $this->assertInstanceOf('Zend\Http\Header\ContentMD5', $contentMD5Header); + } + + public function testContentMD5GetFieldNameReturnsHeaderName() + { + $contentMD5Header = new ContentMD5(); + $this->assertEquals('Content-MD5', $contentMD5Header->getFieldName()); + } + + public function testContentMD5GetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentMD5 needs to be completed'); + + $contentMD5Header = new ContentMD5(); + $this->assertEquals('xxx', $contentMD5Header->getFieldValue()); + } + + public function testContentMD5ToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentMD5 needs to be completed'); + + $contentMD5Header = new ContentMD5(); + + // @todo set some values, then test output + $this->assertEmpty('Content-MD5: xxx', $contentMD5Header->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ContentRangeTest.php b/test/Header/ContentRangeTest.php new file mode 100644 index 0000000000..c99714249a --- /dev/null +++ b/test/Header/ContentRangeTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentRangeHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentRange', $contentRangeHeader); + } + + public function testContentRangeGetFieldNameReturnsHeaderName() + { + $contentRangeHeader = new ContentRange(); + $this->assertEquals('Content-Range', $contentRangeHeader->getFieldName()); + } + + public function testContentRangeGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentRange needs to be completed'); + + $contentRangeHeader = new ContentRange(); + $this->assertEquals('xxx', $contentRangeHeader->getFieldValue()); + } + + public function testContentRangeToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentRange needs to be completed'); + + $contentRangeHeader = new ContentRange(); + + // @todo set some values, then test output + $this->assertEmpty('Content-Range: xxx', $contentRangeHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ContentTypeTest.php b/test/Header/ContentTypeTest.php new file mode 100644 index 0000000000..9d9c06f7c2 --- /dev/null +++ b/test/Header/ContentTypeTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $contentTypeHeader); + $this->assertInstanceOf('Zend\Http\Header\ContentType', $contentTypeHeader); + } + + public function testContentTypeGetFieldNameReturnsHeaderName() + { + $contentTypeHeader = new ContentType(); + $this->assertEquals('Content-Type', $contentTypeHeader->getFieldName()); + } + + public function testContentTypeGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ContentType needs to be completed'); + + $contentTypeHeader = new ContentType(); + $this->assertEquals('xxx', $contentTypeHeader->getFieldValue()); + } + + public function testContentTypeToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ContentType needs to be completed'); + + $contentTypeHeader = new ContentType(); + + // @todo set some values, then test output + $this->assertEmpty('Content-Type: xxx', $contentTypeHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/CookieTest.php b/test/Header/CookieTest.php new file mode 100644 index 0000000000..b139569e45 --- /dev/null +++ b/test/Header/CookieTest.php @@ -0,0 +1,644 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $cookieHeader); + $this->assertInstanceOf('ArrayObject', $cookieHeader); + $this->assertInstanceOf('Zend\Http\Header\Cookie', $cookieHeader); + } + + public function testCookieFromStringCreatesValidCookieHeadersWithMultipleValues() + { + $cookieHeader = Cookie::fromString('Cookie: name=value; foo=bar'); + $this->assertEquals('value', $cookieHeader->name); + $this->assertEquals('bar', $cookieHeader['foo']); + } + + public function testCookieFromSetCookieArrayProducesASingleCookie() + { + $setCookies = array( + new SetCookie('foo', 'bar'), + new SetCookie('name', 'value') + ); + + $cookie = Cookie::fromSetCookieArray($setCookies); + $this->assertEquals('Cookie: foo=bar; name=value', $cookie->toString()); + } + + public function testCookieGetFieldNameReturnsHeaderName() + { + $cookieHeader = new Cookie(); + $this->assertEquals('Cookie', $cookieHeader->getFieldName()); + } + + public function testCookieGetFieldValueReturnsProperValue() + { + $cookieHeader = new Cookie(); + $cookieHeader->foo = 'bar'; + $this->assertEquals('foo=bar', $cookieHeader->getFieldValue()); + } + + public function testCookieToStringReturnsHeaderFormattedString() + { + $cookieHeader = new Cookie(); + + $cookieHeader->foo = 'bar'; + $this->assertEquals('Cookie: foo=bar', $cookieHeader->toString()); + } + +// /** +// * Cookie creation and data accessors tests +// */ +// +// /** +// * Make sure we can't set invalid names +// * +// * @dataProvider invalidCookieNameCharProvider +// */ +// public function testSetInvalidName($char) +// { +// $this->setExpectedException( +// 'Zend\Http\Exception\InvalidArgumentException', +// 'Cookie name cannot contain these characters'); +// +// $cookie = new Http\Cookie("cookie_$char", 'foo', 'example.com'); +// } +// +// /** +// * Test we get the cookie name properly +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testGetName($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// if (isset($cInfo['name'])) { +// $this->assertEquals($cInfo['name'], $cookie->getName()); +// } +// } +// +// /** +// * Make sure we get the correct value if it was set through the constructor +// * +// * @param string $value +// * @dataProvider validCookieValueProvider +// */ +// public function testGetValueConstructor($val) +// { +// $cookie = new Http\Cookie('cookie', $val, 'example.com', time(), '/', true); +// $this->assertEquals($val, $cookie->getValue()); +// } +// +// /** +// * Make sure we get the correct value if it was set through fromString() +// * +// * @param string $value +// * @dataProvider validCookieValueProvider +// */ +// public function testGetValueFromString($val) +// { +// $cookie = Http\Cookie::fromString('cookie=' . urlencode($val) . '; domain=example.com'); +// $this->assertEquals($val, $cookie->getValue()); +// } +// +// /** +// * Make sure we get the correct value if it was set through fromString() +// * +// * @param string $value +// * @dataProvider validCookieValueProvider +// */ +// public function testGetRawValueFromString($val) +// { +// // Because ';' has special meaning in the cookie, strip it out for this test. +// $val = str_replace(';', '', $val); +// $cookie = Http\Cookie::fromString('cookie=' . $val . '; domain=example.com', null, false); +// $this->assertEquals($val, $cookie->getValue()); +// } +// +// /** +// * Make sure we get the correct value if it was set through fromString() +// * +// * @param string $value +// * @dataProvider validCookieValueProvider +// */ +// public function testGetRawValueFromStringToString($val) +// { +// // Because ';' has special meaning in the cookie, strip it out for this test. +// $val = str_replace(';', '', $val); +// $cookie = Http\Cookie::fromString('cookie=' . $val . '; domain=example.com', null, false); +// $this->assertEquals('cookie=' . $val . ';', (string)$cookie); +// } +// +// /** +// * Make sure we get the correct value if it was set through fromString() +// * +// * @param string $value +// * @dataProvider validCookieValueProvider +// */ +// public function testGetValueFromStringEncodedToString($val) +// { +// // Because ';' has special meaning in the cookie, strip it out for this test. +// $val = str_replace(';', '', $val); +// $cookie = Http\Cookie::fromString('cookie=' . $val . '; domain=example.com', null, true); +// $this->assertEquals('cookie=' . urlencode($val) . ';', (string)$cookie); +// } +// +// /** +// * Make sure we get the correct domain when it's set in the cookie string +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testGetDomainInStr($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// if (isset($cInfo['domain'])) { +// $this->assertEquals($cInfo['domain'], $cookie->getDomain()); +// } +// } +// +// /** +// * Make sure we get the correct domain when it's set in a reference URL +// * +// * @dataProvider refUrlProvider +// */ +// public function testGetDomainInRefUrl(Uri\Uri $uri) +// { +// $domain = $uri->getHost(); +// $cookie = Http\Cookie::fromString('foo=baz; path=/', 'http://' . $domain); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object with URL '$uri'"); +// } +// +// $this->assertEquals($domain, $cookie->getDomain()); +// } +// +// /** +// * Make sure we get the correct path when it's set in the cookie string +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testGetPathInStr($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// if (isset($cInfo['path'])) { +// $this->assertEquals($cInfo['path'], $cookie->getPath()); +// } +// } +// +// /** +// * Make sure we get the correct path when it's set a reference URL +// * +// * @dataProvider refUrlProvider +// */ +// public function testGetPathInRefUrl(Uri\Uri $uri) +// { +// $path = $uri->getPath(); +// if (substr($path, -1, 1) == '/') $path .= 'x'; +// $path = dirname($path); +// if (($path == DIRECTORY_SEPARATOR) || empty($path)) { +// $path = '/'; +// } +// +// $cookie = Http\Cookie::fromString('foo=bar', (string) $uri); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object with URL '$uri'"); +// } +// +// $this->assertEquals($path, $cookie->getPath()); +// } +// +// /** +// * Test we get the correct expiry time +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testGetExpiryTime($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// if (isset($cInfo['expires'])) { +// $this->assertEquals($cInfo['expires'], $cookie->getExpiryTime()); +// } +// } +// +// /** +// * Make sure the "is secure" flag is correctly set +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testIsSecure($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// if (isset($cInfo['secure'])) { +// $this->assertEquals($cInfo['secure'], $cookie->isSecure()); +// } +// } +// +// /** +// * Cookie expiry time tests +// */ +// +// /** +// * Make sure we get the correct value for 'isExpired' +// * +// * @dataProvider cookieWithExpiredFlagProvider +// */ +// public function testIsExpired($cStr, $expired) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// $this->assertEquals($expired, $cookie->isExpired()); +// } +// +// /** +// * Make sure we get the correct value for 'isExpired', when time is manually set +// */ +// public function testIsExpiredDifferentTime() +// { +// $notexpired = time() + 3600; +// $expired = time() - 3600; +// $now = time() + 7200; +// +// $cookies = array( +// 'cookie=foo; domain=example.com; expires=' . date(DATE_COOKIE, $notexpired), +// 'cookie=foo; domain=example.com; expires=' . date(DATE_COOKIE, $expired) +// ); +// +// // Make sure all cookies are expired +// foreach ($cookies as $cstr) { +// $cookie = Http\Cookie::fromString($cstr); +// if (! $cookie) $this->fail('Got no cookie object from a valid cookie string'); +// $this->assertTrue($cookie->isExpired($now), 'Cookie is expected to be expired'); +// } +// +// // Make sure all cookies are not expired +// $now = time() - 7200; +// foreach ($cookies as $cstr) { +// $cookie = Http\Cookie::fromString($cstr); +// if (! $cookie) $this->fail('Got no cookie object from a valid cookie string'); +// $this->assertFalse($cookie->isExpired($now), 'Cookie is expected not to be expired'); +// } +// } +// +// /** +// * Test we can properly check if a cookie is a session cookie (has no expiry time) +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testIsSessionCookie($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// if (array_key_exists('expires', $cInfo)) { +// $this->assertEquals(($cInfo['expires'] === null), $cookie->isSessionCookie()); +// } +// } +// +// +// /** +// * Make sure cookies are properly converted back to strings +// * +// * @dataProvider validCookieWithInfoProvider +// */ +// public function testToString($cStr, $cInfo) +// { +// $cookie = Http\Cookie::fromString($cStr); +// if (! $cookie instanceof Http\Cookie) { +// $this->fail("Failed creating a cookie object from '$cStr'"); +// } +// +// $expected = substr($cStr, 0, strpos($cStr, ';') + 1); +// $this->assertEquals($expected, (string) $cookie); +// } +// +// public function testGarbageInStrIsIgnored() +// { +// $cookies = array( +// 'name=value; domain=foo.com; silly=place; secure', +// 'foo=value; someCrap; secure; domain=foo.com; ', +// 'anothercookie=value; secure; has some crap; ignore=me; domain=foo.com; ' +// ); +// +// foreach ($cookies as $cstr) { +// $cookie = Http\Cookie::fromString($cstr); +// if (! $cookie) $this->fail('Got no cookie object from a valid cookie string'); +// $this->assertEquals('value', $cookie->getValue(), 'Value is not as expected'); +// $this->assertEquals('foo.com', $cookie->getDomain(), 'Domain is not as expected'); +// $this->assertTrue($cookie->isSecure(), 'Cookie is expected to be secure'); +// } +// } +// +// /** +// * Test the match() method against a domain +// * +// * @dataProvider domainMatchTestProvider +// */ +// public function testMatchDomain($cookieStr, $uri, $match) +// { +// $cookie = Http\Cookie::fromString($cookieStr); +// $this->assertEquals($match, $cookie->match($uri)); +// } +// +// static public function domainMatchTestProvider() +// { +// $uri = new Uri\Uri('http://www.foo.com/some/file.txt'); +// +// return array( +// array('foo=bar; domain=.example.com;', 'http://www.example.com/foo/bar.php', true), +// array('foo=bar; domain=.example.com;', 'http://example.com/foo/bar.php', true), +// array('foo=bar; domain=.example.com;', 'http://www.somexample.com/foo/bar.php', false), +// array('foo=bar; domain=example.com;', 'http://www.somexample.com/foo/bar.php', false), +// array('cookie=value; domain=www.foo.com', $uri, true), +// array('cookie=value; domain=www.foo.com', 'http://il.www.foo.com', true), +// array('cookie=value; domain=www.foo.com', 'http://bar.foo.com', false) +// ); +// } +// +// /** +// * Test the match() method against a domain +// * +// */ +// public function testMatchPath() +// { +// $cookie = Http\Cookie::fromString('foo=bar; domain=.example.com; path=/foo'); +// $this->assertTrue($cookie->match('http://www.example.com/foo/bar.php'), 'Cookie expected to match, but didn\'t'); +// $this->assertFalse($cookie->match('http://www.example.com/bar.php'), 'Cookie expected not to match, but did'); +// +// $cookie = Http\Cookie::fromString('cookie=value; domain=www.foo.com; path=/some/long/path'); +// $this->assertTrue($cookie->match('http://www.foo.com/some/long/path/file.txt'), 'Cookie expected to match, but didn\'t'); +// $this->assertTrue($cookie->match('http://www.foo.com/some/long/path/and/even/more'), 'Cookie expected to match, but didn\'t'); +// $this->assertFalse($cookie->match('http://www.foo.com/some/long/file.txt'), 'Cookie expected not to match, but did'); +// $this->assertFalse($cookie->match('http://www.foo.com/some/different/path/file.txt'), 'Cookie expected not to match, but did'); +// } +// +// /** +// * Test the match() method against secure / non secure connections +// * +// */ +// public function testMatchSecure() +// { +// // A non secure cookie, should match both +// $cookie = Http\Cookie::fromString('foo=bar; domain=.example.com;'); +// $this->assertTrue($cookie->match('http://www.example.com/foo/bar.php'), 'Cookie expected to match, but didn\'t'); +// $this->assertTrue($cookie->match('https://www.example.com/bar.php'), 'Cookie expected to match, but didn\'t'); +// +// // A secure cookie, should match secure connections only +// $cookie = Http\Cookie::fromString('foo=bar; domain=.example.com; secure'); +// $this->assertFalse($cookie->match('http://www.example.com/foo/bar.php'), 'Cookie expected not to match, but it did'); +// $this->assertTrue($cookie->match('https://www.example.com/bar.php'), 'Cookie expected to match, but didn\'t'); +// } +// +// /** +// * Test the match() method against different expiry times +// * +// */ +// public function testMatchExpire() +// { +// // A session cookie - should always be valid +// $cookie = Http\Cookie::fromString('foo=bar; domain=.example.com;'); +// $this->assertTrue($cookie->match('http://www.example.com/'), 'Cookie expected to match, but didn\'t'); +// $this->assertTrue($cookie->match('http://www.example.com/', true, time() + 3600), 'Cookie expected to match, but didn\'t'); +// +// // A session cookie, should not match +// $this->assertFalse($cookie->match('https://www.example.com/', false), 'Cookie expected not to match, but it did'); +// $this->assertFalse($cookie->match('https://www.example.com/', false, time() - 3600), 'Cookie expected not to match, but it did'); +// +// // A cookie with expiry time in the future +// $cookie = Http\Cookie::fromString('foo=bar; domain=.example.com; expires=' . date(DATE_COOKIE, time() + 3600)); +// $this->assertTrue($cookie->match('http://www.example.com/'), 'Cookie expected to match, but didn\'t'); +// $this->assertFalse($cookie->match('https://www.example.com/', true, time() + 7200), 'Cookie expected not to match, but it did'); +// +// // A cookie with expiry time in the past +// $cookie = Http\Cookie::fromString('foo=bar; domain=.example.com; expires=' . date(DATE_COOKIE, time() - 3600)); +// $this->assertFalse($cookie->match('http://www.example.com/'), 'Cookie expected not to match, but it did'); +// $this->assertTrue($cookie->match('https://www.example.com/', true, time() - 7200), 'Cookie expected to match, but didn\'t'); +// } +// +// public function testFromStringFalse() +// { +// $cookie = Http\Cookie::fromString('foo; domain=www.exmaple.com'); +// $this->assertEquals(false, $cookie, 'fromString was expected to fail and return false'); +// +// $cookie = Http\Cookie::fromString('=bar; secure; domain=foo.nl'); +// $this->assertEquals(false, $cookie, 'fromString was expected to fail and return false'); +// +// $cookie = Http\Cookie::fromString('fo;o=bar; secure; domain=foo.nl'); +// $this->assertEquals(false, $cookie, 'fromString was expected to fail and return false'); +// } +// +// /** +// * Test that cookies with far future expiry date (beyond the 32 bit unsigned int range) are +// * not mistakenly marked as 'expired' +// * +// * @todo re-enable once Locale is working +// * @group disable +// * @link http://framework.zend.com/issues/browse/ZF-5690 +// */ +// public function testZF5690OverflowingExpiryDate() +// { +// $expTime = "Sat, 29-Jan-2039 00:54:42 GMT"; +// $cookie = Http\Cookie::fromString("foo=bar; domain=.example.com; expires=$expTime"); +// $this->assertFalse($cookie->isExpired(), 'Expiry: ' . $cookie->getExpiryTime()); +// } +// +// /** +// * Data Providers +// */ +// +// /** +// * Provide characters which are invalid in cookie names +// * +// * @return array +// */ +// static public function invalidCookieNameCharProvider() +// { +// return array( +// array("="), +// array(","), +// array(";"), +// array("\t"), +// array("\r"), +// array("\n"), +// array("\013"), +// array("\014") +// ); +// } +// +// /** +// * Provide valid cookie values +// * +// * @return array +// */ +// static public function validCookieValueProvider() +// { +// return array( +// array('simpleCookie'), +// array('space cookie'), +// array('!@#$%^*&()* ][{}?;'), +// array("line\n\rbreaks"), +// array("0000j8CydACPu_-J9bE8uTX91YU:12a83ks4k"), // value from: Alexander Cheshchevik's comment on issue: ZF-1850 +// +// // Long cookie value - 2kb +// array(str_repeat(md5(time()), 64)) +// ); +// } +// +// /** +// * Provider of valid reference URLs to be used for creating cookies +// * +// * @return array +// */ +// static public function refUrlProvider() +// { +// return array( +// array(new Uri\Uri('http://example.com/')), +// array(new Uri\Uri('http://www.example.com/foo/bar/')), +// array(new Uri\Uri('http://some.really.deep.domain.com')), +// array(new Uri\Uri('http://localhost/path/to/very/deep/file.php')), +// array(new Uri\Uri('http://arr.gr/some%20path/text%2Ffile')) +// ); +// } +// +// /** +// * Provide valid cookie strings with information about them +// * +// * @return array +// */ +// static public function validCookieWithInfoProvider() +// { +// $now = time(); +// $yesterday = $now - (3600 * 24); +// +// return array( +// array( +// 'justacookie=foo; domain=example.com', +// array( +// 'name' => 'justacookie', +// 'domain' => 'example.com', +// 'path' => '/', +// 'expires' => null, +// 'secure' => false +// ) +// ), +// array( +// 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com', +// array( +// 'name' => 'expires', +// 'domain' => '.example.com', +// 'path' => '/Space Out/', +// 'expires' => strtotime('Tue, 21-Nov-2006 08:33:44 GMT'), +// 'secure' => true +// ) +// ), +// array( +// 'domain=unittests; expires=' . date(DATE_COOKIE, $now) . '; domain=example.com; path=/some%20value/', +// array( +// 'name' => 'domain', +// 'domain' => 'example.com', +// 'path' => '/some%20value/', +// 'expires' => $now, +// 'secure' => false, +// ) +// ), +// array( +// 'path=indexAction; path=/; domain=.foo.com; expires=' . date(DATE_COOKIE, $yesterday), +// array( +// 'name' => 'path', +// 'domain' => '.foo.com', +// 'path' => '/', +// 'expires' => $yesterday, +// 'secure' => false +// ) +// ), +// +// array( +// 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com', +// array( +// 'name' => 'secure', +// 'domain' => 'some.really.deep.domain.com', +// 'path' => '/', +// 'expires' => null, +// 'secure' => true +// ) +// ), +// array( +// 'PHPSESSID=123456789+abcd%2Cef; secure; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;', +// array( +// 'name' => 'PHPSESSID', +// 'domain' => '.localdomain', +// 'path' => '/foo/baz', +// 'expires' => strtotime('Tue, 21-Nov-2006 08:33:44 GMT'), +// 'secure' => true +// ) +// ), +// ); +// } +// +// /** +// * Cookie with 'expired' flag, used to test if Cookie->isExpired() +// * +// * @todo re-enable commented cookie arguments once Locale is working +// * @return array +// */ +// public static function cookieWithExpiredFlagProvider() +// { +// return array( +// array('cookie=foo;domain=example.com;expires=' . date(DATE_COOKIE, time() + 12 * 3600), false), +// array('cookie=foo;domain=example.com;expires=' . date(DATE_COOKIE, time() - 15), true), +// array('cookie=foo;domain=example.com;', false), +// // array('cookie=foo;domain=example.com;expires=Fri, 01-Mar-2109 00:19:21 GMT', false), +// // array('cookie=foo;domain=example.com;expires=Fri, 06-Jun-1966 00:19:21 GMT', true), +// ); +// } +} + diff --git a/test/Header/DateTest.php b/test/Header/DateTest.php new file mode 100644 index 0000000000..62dd9a4445 --- /dev/null +++ b/test/Header/DateTest.php @@ -0,0 +1,101 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $dateHeader); + $this->assertInstanceOf('Zend\Http\Header\Date', $dateHeader); + } + + public function testDateGetFieldNameReturnsHeaderName() + { + $dateHeader = new Date(); + $this->assertEquals('Date', $dateHeader->getFieldName()); + } + + public function testDateGetFieldValueReturnsProperValue() + { + $dateHeader = new Date(); + $dateHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $dateHeader->getFieldValue()); + } + + public function testDateToStringReturnsHeaderFormattedString() + { + $dateHeader = new Date(); + $dateHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Date: Sun, 06 Nov 1994 08:49:37 GMT', $dateHeader->toString()); + } + + /** Implementation specific tests */ + + public function testDateReturnsDateTimeObject() + { + $dateHeader = new Date(); + $this->assertInstanceOf('\DateTime', $dateHeader->date()); + } + + public function testDateFromStringCreatesValidDateTime() + { + $dateHeader = Date::fromString('Date: Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertInstanceOf('\DateTime', $dateHeader->date()); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $dateHeader->date()->format('D, d M Y H:i:s \G\M\T')); + } + + public function testDateReturnsProperlyFormattedDate() + { + $date = new DateTime('now', new DateTimeZone('GMT')); + + $dateHeader = new Date(); + $dateHeader->setDate($date); + $this->assertEquals($date->format('D, d M Y H:i:s \G\M\T'), $dateHeader->getDate()); + } + + public function testDateThrowsExceptionForInvalidDate() + { + $this->setExpectedException('Zend\Http\Header\Exception\InvalidArgumentException', 'Invalid date'); + $dateHeader = new Date(); + $dateHeader->setDate('~~~~'); + } + + public function testDateCanCompareDates() + { + $dateHeader = new Date(); + $dateHeader->setDate('1 day ago'); + $this->assertEquals(-1, $dateHeader->compareTo(new DateTime('now'))); + } + + public function testDateCanOutputDatesInOldFormats() + { + Date::setDateFormat(Date::DATE_ANSIC); + + $dateHeader = new Date(); + $dateHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + + $this->assertEquals('Date: Sun Nov 6 08:49:37 1994', $dateHeader->toString()); + } +} + diff --git a/test/Header/EtagTest.php b/test/Header/EtagTest.php new file mode 100644 index 0000000000..d595679232 --- /dev/null +++ b/test/Header/EtagTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $etagHeader); + $this->assertInstanceOf('Zend\Http\Header\Etag', $etagHeader); + } + + public function testEtagGetFieldNameReturnsHeaderName() + { + $etagHeader = new Etag(); + $this->assertEquals('Etag', $etagHeader->getFieldName()); + } + + public function testEtagGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Etag needs to be completed'); + + $etagHeader = new Etag(); + $this->assertEquals('xxx', $etagHeader->getFieldValue()); + } + + public function testEtagToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Etag needs to be completed'); + + $etagHeader = new Etag(); + + // @todo set some values, then test output + $this->assertEmpty('Etag: xxx', $etagHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ExpectTest.php b/test/Header/ExpectTest.php new file mode 100644 index 0000000000..00de719ab4 --- /dev/null +++ b/test/Header/ExpectTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $expectHeader); + $this->assertInstanceOf('Zend\Http\Header\Expect', $expectHeader); + } + + public function testExpectGetFieldNameReturnsHeaderName() + { + $expectHeader = new Expect(); + $this->assertEquals('Expect', $expectHeader->getFieldName()); + } + + public function testExpectGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Expect needs to be completed'); + + $expectHeader = new Expect(); + $this->assertEquals('xxx', $expectHeader->getFieldValue()); + } + + public function testExpectToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Expect needs to be completed'); + + $expectHeader = new Expect(); + + // @todo set some values, then test output + $this->assertEmpty('Expect: xxx', $expectHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ExpiresTest.php b/test/Header/ExpiresTest.php new file mode 100644 index 0000000000..d15e20ae47 --- /dev/null +++ b/test/Header/ExpiresTest.php @@ -0,0 +1,50 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $expiresHeader); + $this->assertInstanceOf('Zend\Http\Header\Expires', $expiresHeader); + } + + public function testExpiresGetFieldNameReturnsHeaderName() + { + $expiresHeader = new Expires(); + $this->assertEquals('Expires', $expiresHeader->getFieldName()); + } + + public function testExpiresGetFieldValueReturnsProperValue() + { + $expiresHeader = new Expires(); + $expiresHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $expiresHeader->getFieldValue()); + } + + public function testExpiresToStringReturnsHeaderFormattedString() + { + $expiresHeader = new Expires(); + $expiresHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Expires: Sun, 06 Nov 1994 08:49:37 GMT', $expiresHeader->toString()); + } + + /** + * Implementation specific tests are covered by DateTest + * @see ZendTest\Http\Header\DateTest + */ + +} + diff --git a/test/Header/FromTest.php b/test/Header/FromTest.php new file mode 100644 index 0000000000..d54deaed39 --- /dev/null +++ b/test/Header/FromTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $fromHeader); + $this->assertInstanceOf('Zend\Http\Header\From', $fromHeader); + } + + public function testFromGetFieldNameReturnsHeaderName() + { + $fromHeader = new From(); + $this->assertEquals('From', $fromHeader->getFieldName()); + } + + public function testFromGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('From needs to be completed'); + + $fromHeader = new From(); + $this->assertEquals('xxx', $fromHeader->getFieldValue()); + } + + public function testFromToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('From needs to be completed'); + + $fromHeader = new From(); + + // @todo set some values, then test output + $this->assertEmpty('From: xxx', $fromHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/HostTest.php b/test/Header/HostTest.php new file mode 100644 index 0000000000..d02a9ccec5 --- /dev/null +++ b/test/Header/HostTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $hostHeader); + $this->assertInstanceOf('Zend\Http\Header\Host', $hostHeader); + } + + public function testHostGetFieldNameReturnsHeaderName() + { + $hostHeader = new Host(); + $this->assertEquals('Host', $hostHeader->getFieldName()); + } + + public function testHostGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Host needs to be completed'); + + $hostHeader = new Host(); + $this->assertEquals('xxx', $hostHeader->getFieldValue()); + } + + public function testHostToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Host needs to be completed'); + + $hostHeader = new Host(); + + // @todo set some values, then test output + $this->assertEmpty('Host: xxx', $hostHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/IfMatchTest.php b/test/Header/IfMatchTest.php new file mode 100644 index 0000000000..110fc0d60d --- /dev/null +++ b/test/Header/IfMatchTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $ifMatchHeader); + $this->assertInstanceOf('Zend\Http\Header\IfMatch', $ifMatchHeader); + } + + public function testIfMatchGetFieldNameReturnsHeaderName() + { + $ifMatchHeader = new IfMatch(); + $this->assertEquals('If-Match', $ifMatchHeader->getFieldName()); + } + + public function testIfMatchGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('IfMatch needs to be completed'); + + $ifMatchHeader = new IfMatch(); + $this->assertEquals('xxx', $ifMatchHeader->getFieldValue()); + } + + public function testIfMatchToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('IfMatch needs to be completed'); + + $ifMatchHeader = new IfMatch(); + + // @todo set some values, then test output + $this->assertEmpty('If-Match: xxx', $ifMatchHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/IfModifiedSinceTest.php b/test/Header/IfModifiedSinceTest.php new file mode 100644 index 0000000000..b4d5be47c2 --- /dev/null +++ b/test/Header/IfModifiedSinceTest.php @@ -0,0 +1,51 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $ifModifiedSinceHeader); + $this->assertInstanceOf('Zend\Http\Header\IfModifiedSince', $ifModifiedSinceHeader); + } + + public function testIfModifiedSinceGetFieldNameReturnsHeaderName() + { + $ifModifiedSinceHeader = new IfModifiedSince(); + $this->assertEquals('If-Modified-Since', $ifModifiedSinceHeader->getFieldName()); + } + + public function testIfModifiedSinceGetFieldValueReturnsProperValue() + { + $ifModifiedSinceHeader = new IfModifiedSince(); + $ifModifiedSinceHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $ifModifiedSinceHeader->getFieldValue()); + } + + public function testIfModifiedSinceToStringReturnsHeaderFormattedString() + { + $ifModifiedSinceHeader = new IfModifiedSince(); + $ifModifiedSinceHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('If-Modified-Since: Sun, 06 Nov 1994 08:49:37 GMT', $ifModifiedSinceHeader->toString()); + } + + /** + * Implementation specific tests are covered by DateTest + * @see ZendTest\Http\Header\DateTest + */ + +} + diff --git a/test/Header/IfNoneMatchTest.php b/test/Header/IfNoneMatchTest.php new file mode 100644 index 0000000000..5db5ec6766 --- /dev/null +++ b/test/Header/IfNoneMatchTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $ifNoneMatchHeader); + $this->assertInstanceOf('Zend\Http\Header\IfNoneMatch', $ifNoneMatchHeader); + } + + public function testIfNoneMatchGetFieldNameReturnsHeaderName() + { + $ifNoneMatchHeader = new IfNoneMatch(); + $this->assertEquals('If-None-Match', $ifNoneMatchHeader->getFieldName()); + } + + public function testIfNoneMatchGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('IfNoneMatch needs to be completed'); + + $ifNoneMatchHeader = new IfNoneMatch(); + $this->assertEquals('xxx', $ifNoneMatchHeader->getFieldValue()); + } + + public function testIfNoneMatchToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('IfNoneMatch needs to be completed'); + + $ifNoneMatchHeader = new IfNoneMatch(); + + // @todo set some values, then test output + $this->assertEmpty('If-None-Match: xxx', $ifNoneMatchHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/IfRangeTest.php b/test/Header/IfRangeTest.php new file mode 100644 index 0000000000..8a59722d7f --- /dev/null +++ b/test/Header/IfRangeTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $ifRangeHeader); + $this->assertInstanceOf('Zend\Http\Header\IfRange', $ifRangeHeader); + } + + public function testIfRangeGetFieldNameReturnsHeaderName() + { + $ifRangeHeader = new IfRange(); + $this->assertEquals('If-Range', $ifRangeHeader->getFieldName()); + } + + public function testIfRangeGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('IfRange needs to be completed'); + + $ifRangeHeader = new IfRange(); + $this->assertEquals('xxx', $ifRangeHeader->getFieldValue()); + } + + public function testIfRangeToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('IfRange needs to be completed'); + + $ifRangeHeader = new IfRange(); + + // @todo set some values, then test output + $this->assertEmpty('If-Range: xxx', $ifRangeHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/IfUnmodifiedSinceTest.php b/test/Header/IfUnmodifiedSinceTest.php new file mode 100644 index 0000000000..35bb49152c --- /dev/null +++ b/test/Header/IfUnmodifiedSinceTest.php @@ -0,0 +1,51 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $ifUnmodifiedSinceHeader); + $this->assertInstanceOf('Zend\Http\Header\IfUnmodifiedSince', $ifUnmodifiedSinceHeader); + } + + public function testIfUnmodifiedSinceGetFieldNameReturnsHeaderName() + { + $ifUnmodifiedSinceHeader = new IfUnmodifiedSince(); + $this->assertEquals('If-Unmodified-Since', $ifUnmodifiedSinceHeader->getFieldName()); + } + + public function testIfUnmodifiedSinceGetFieldValueReturnsProperValue() + { + $ifUnmodifiedSinceHeader = new IfUnmodifiedSince(); + $ifUnmodifiedSinceHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $ifUnmodifiedSinceHeader->getFieldValue()); + } + + public function testIfUnmodifiedSinceToStringReturnsHeaderFormattedString() + { + $ifUnmodifiedSinceHeader = new IfUnmodifiedSince(); + $ifUnmodifiedSinceHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('If-Unmodified-Since: Sun, 06 Nov 1994 08:49:37 GMT', $ifUnmodifiedSinceHeader->toString()); + } + + /** + * Implementation specific tests are covered by DateTest + * @see ZendTest\Http\Header\DateTest + */ + +} + diff --git a/test/Header/KeepAliveTest.php b/test/Header/KeepAliveTest.php new file mode 100644 index 0000000000..5bf1ee5633 --- /dev/null +++ b/test/Header/KeepAliveTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $keepAliveHeader); + $this->assertInstanceOf('Zend\Http\Header\KeepAlive', $keepAliveHeader); + } + + public function testKeepAliveGetFieldNameReturnsHeaderName() + { + $keepAliveHeader = new KeepAlive(); + $this->assertEquals('Keep-Alive', $keepAliveHeader->getFieldName()); + } + + public function testKeepAliveGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('KeepAlive needs to be completed'); + + $keepAliveHeader = new KeepAlive(); + $this->assertEquals('xxx', $keepAliveHeader->getFieldValue()); + } + + public function testKeepAliveToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('KeepAlive needs to be completed'); + + $keepAliveHeader = new KeepAlive(); + + // @todo set some values, then test output + $this->assertEmpty('Keep-Alive: xxx', $keepAliveHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/LastModifiedTest.php b/test/Header/LastModifiedTest.php new file mode 100644 index 0000000000..c526315b95 --- /dev/null +++ b/test/Header/LastModifiedTest.php @@ -0,0 +1,51 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $lastModifiedHeader); + $this->assertInstanceOf('Zend\Http\Header\LastModified', $lastModifiedHeader); + } + + public function testLastModifiedGetFieldNameReturnsHeaderName() + { + $lastModifiedHeader = new LastModified(); + $this->assertEquals('Last-Modified', $lastModifiedHeader->getFieldName()); + } + + public function testLastModifiedGetFieldValueReturnsProperValue() + { + $lastModifiedHeader = new LastModified(); + $lastModifiedHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $lastModifiedHeader->getFieldValue()); + } + + public function testLastModifiedToStringReturnsHeaderFormattedString() + { + $lastModifiedHeader = new LastModified(); + $lastModifiedHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Last-Modified: Sun, 06 Nov 1994 08:49:37 GMT', $lastModifiedHeader->toString()); + } + + /** + * Implementation specific tests are covered by DateTest + * @see ZendTest\Http\Header\DateTest + */ + +} + diff --git a/test/Header/LocationTest.php b/test/Header/LocationTest.php new file mode 100644 index 0000000000..f76a278a47 --- /dev/null +++ b/test/Header/LocationTest.php @@ -0,0 +1,126 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $locationHeader); + $this->assertInstanceOf('Zend\Http\Header\Location', $locationHeader); + } + + public function locationFromStringCreatesValidLocationHeaderProvider() + { + return array( + array('http://www.example.com'), + array('https://www.example.com'), + array('mailto://www.example.com'), + array('file://www.example.com'), + ); + } + + /** + * Test that we can set a redirect to different URI-Schemes + * + * @param string $uri + * @param string $expectedClass + * + * @dataProvider locationCanSetDifferentSchemeUrisProvider + */ + public function testLocationCanSetDifferentSchemeUris($uri, $expectedClass) + { + $locationHeader = new Location; + $locationHeader->setUri($uri); + $this->assertAttributeInstanceof($expectedClass, 'uri', $locationHeader); + } + + /** + * Test that we can set a redirect to different URI-schemes via a class + * + * @param string $uri + * @param string $expectedClass + * + * @dataProvider locationCanSetDifferentSchemeUrisProvider + */ + public function testLocationCanSetDifferentSchemeUriObjects($uri, $expectedClass) + { + $uri = \Zend\Uri\UriFactory::factory($uri); + $locationHeader = new Location; + $locationHeader->setUri($uri); + $this->assertAttributeInstanceof($expectedClass, 'uri', $locationHeader); + + } + + /** + * Provide data to the locationCanSetDifferentSchemeUris-test + * + * @return array + */ + public function locationCanSetDifferentSchemeUrisProvider() + { + return array( + array('http://www.example.com', '\Zend\Uri\Http'), + array('https://www.example.com', '\Zend\Uri\Http'), + array('mailto://www.example.com', '\Zend\Uri\Mailto'), + array('file://www.example.com', '\Zend\Uri\File'), + ); + } + + public function testLocationGetFieldValueReturnsProperValue() + { + $locationHeader = new Location(); + $locationHeader->setUri('http://www.example.com/'); + $this->assertEquals('http://www.example.com/', $locationHeader->getFieldValue()); + + $locationHeader->setUri('/path'); + $this->assertEquals('/path', $locationHeader->getFieldValue()); + } + + public function testLocationToStringReturnsHeaderFormattedString() + { + $locationHeader = new Location(); + $locationHeader->setUri('http://www.example.com/path?query'); + + $this->assertEquals('Location: http://www.example.com/path?query', $locationHeader->toString()); + } + + /** Implementation specific tests */ + + public function testLocationCanSetAndAccessAbsoluteUri() + { + $locationHeader = Location::fromString('Location: http://www.example.com/path'); + $uri = $locationHeader->uri(); + $this->assertInstanceOf('Zend\Uri\Http', $uri); + $this->assertTrue($uri->isAbsolute()); + $this->assertEquals('http://www.example.com/path', $locationHeader->getUri()); + } + + public function testLocationCanSetAndAccessRelativeUri() + { + $locationHeader = Location::fromString('Location: /path/to'); + $uri = $locationHeader->uri(); + $this->assertInstanceOf('Zend\Uri\Uri', $uri); + $this->assertFalse($uri->isAbsolute()); + $this->assertEquals('/path/to', $locationHeader->getUri()); + } + +} + diff --git a/test/Header/MaxForwardsTest.php b/test/Header/MaxForwardsTest.php new file mode 100644 index 0000000000..9b143a55ff --- /dev/null +++ b/test/Header/MaxForwardsTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $maxForwardsHeader); + $this->assertInstanceOf('Zend\Http\Header\MaxForwards', $maxForwardsHeader); + } + + public function testMaxForwardsGetFieldNameReturnsHeaderName() + { + $maxForwardsHeader = new MaxForwards(); + $this->assertEquals('Max-Forwards', $maxForwardsHeader->getFieldName()); + } + + public function testMaxForwardsGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('MaxForwards needs to be completed'); + + $maxForwardsHeader = new MaxForwards(); + $this->assertEquals('xxx', $maxForwardsHeader->getFieldValue()); + } + + public function testMaxForwardsToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('MaxForwards needs to be completed'); + + $maxForwardsHeader = new MaxForwards(); + + // @todo set some values, then test output + $this->assertEmpty('Max-Forwards: xxx', $maxForwardsHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/PragmaTest.php b/test/Header/PragmaTest.php new file mode 100644 index 0000000000..cf65b32360 --- /dev/null +++ b/test/Header/PragmaTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $pragmaHeader); + $this->assertInstanceOf('Zend\Http\Header\Pragma', $pragmaHeader); + } + + public function testPragmaGetFieldNameReturnsHeaderName() + { + $pragmaHeader = new Pragma(); + $this->assertEquals('Pragma', $pragmaHeader->getFieldName()); + } + + public function testPragmaGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Pragma needs to be completed'); + + $pragmaHeader = new Pragma(); + $this->assertEquals('xxx', $pragmaHeader->getFieldValue()); + } + + public function testPragmaToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Pragma needs to be completed'); + + $pragmaHeader = new Pragma(); + + // @todo set some values, then test output + $this->assertEmpty('Pragma: xxx', $pragmaHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ProxyAuthenticateTest.php b/test/Header/ProxyAuthenticateTest.php new file mode 100644 index 0000000000..eef06071fe --- /dev/null +++ b/test/Header/ProxyAuthenticateTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $proxyAuthenticateHeader); + $this->assertInstanceOf('Zend\Http\Header\ProxyAuthenticate', $proxyAuthenticateHeader); + } + + public function testProxyAuthenticateGetFieldNameReturnsHeaderName() + { + $proxyAuthenticateHeader = new ProxyAuthenticate(); + $this->assertEquals('Proxy-Authenticate', $proxyAuthenticateHeader->getFieldName()); + } + + public function testProxyAuthenticateGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ProxyAuthenticate needs to be completed'); + + $proxyAuthenticateHeader = new ProxyAuthenticate(); + $this->assertEquals('xxx', $proxyAuthenticateHeader->getFieldValue()); + } + + public function testProxyAuthenticateToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ProxyAuthenticate needs to be completed'); + + $proxyAuthenticateHeader = new ProxyAuthenticate(); + + // @todo set some values, then test output + $this->assertEmpty('Proxy-Authenticate: xxx', $proxyAuthenticateHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ProxyAuthorizationTest.php b/test/Header/ProxyAuthorizationTest.php new file mode 100644 index 0000000000..fe71b2a300 --- /dev/null +++ b/test/Header/ProxyAuthorizationTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $proxyAuthorizationHeader); + $this->assertInstanceOf('Zend\Http\Header\ProxyAuthorization', $proxyAuthorizationHeader); + } + + public function testProxyAuthorizationGetFieldNameReturnsHeaderName() + { + $proxyAuthorizationHeader = new ProxyAuthorization(); + $this->assertEquals('Proxy-Authorization', $proxyAuthorizationHeader->getFieldName()); + } + + public function testProxyAuthorizationGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('ProxyAuthorization needs to be completed'); + + $proxyAuthorizationHeader = new ProxyAuthorization(); + $this->assertEquals('xxx', $proxyAuthorizationHeader->getFieldValue()); + } + + public function testProxyAuthorizationToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('ProxyAuthorization needs to be completed'); + + $proxyAuthorizationHeader = new ProxyAuthorization(); + + // @todo set some values, then test output + $this->assertEmpty('Proxy-Authorization: xxx', $proxyAuthorizationHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/RangeTest.php b/test/Header/RangeTest.php new file mode 100644 index 0000000000..1a23eba22f --- /dev/null +++ b/test/Header/RangeTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $rangeHeader); + $this->assertInstanceOf('Zend\Http\Header\Range', $rangeHeader); + } + + public function testRangeGetFieldNameReturnsHeaderName() + { + $rangeHeader = new Range(); + $this->assertEquals('Range', $rangeHeader->getFieldName()); + } + + public function testRangeGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Range needs to be completed'); + + $rangeHeader = new Range(); + $this->assertEquals('xxx', $rangeHeader->getFieldValue()); + } + + public function testRangeToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Range needs to be completed'); + + $rangeHeader = new Range(); + + // @todo set some values, then test output + $this->assertEmpty('Range: xxx', $rangeHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/RefererTest.php b/test/Header/RefererTest.php new file mode 100644 index 0000000000..be3f9ba1b2 --- /dev/null +++ b/test/Header/RefererTest.php @@ -0,0 +1,71 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $refererHeader); + $this->assertInstanceOf('Zend\Http\Header\Referer', $refererHeader); + } + + public function testRefererGetFieldValueReturnsProperValue() + { + $refererHeader = new Referer(); + $refererHeader->setUri('http://www.example.com/'); + $this->assertEquals('http://www.example.com/', $refererHeader->getFieldValue()); + + $refererHeader->setUri('/path'); + $this->assertEquals('/path', $refererHeader->getFieldValue()); + } + + public function testRefererToStringReturnsHeaderFormattedString() + { + $refererHeader = new Referer(); + $refererHeader->setUri('http://www.example.com/path?query'); + + $this->assertEquals('Referer: http://www.example.com/path?query', $refererHeader->toString()); + } + + /** Implementation specific tests */ + + public function testRefererCanSetAndAccessAbsoluteUri() + { + $refererHeader = Referer::fromString('Referer: http://www.example.com/path'); + $uri = $refererHeader->uri(); + $this->assertInstanceOf('Zend\Uri\Http', $uri); + $this->assertTrue($uri->isAbsolute()); + $this->assertEquals('http://www.example.com/path', $refererHeader->getUri()); + } + + public function testRefererCanSetAndAccessRelativeUri() + { + $refererHeader = Referer::fromString('Referer: /path/to'); + $uri = $refererHeader->uri(); + $this->assertInstanceOf('Zend\Uri\Uri', $uri); + $this->assertFalse($uri->isAbsolute()); + $this->assertEquals('/path/to', $refererHeader->getUri()); + } + + public function testRefererDoesNotHaveUriFragment() + { + $refererHeader = new Referer(); + $refererHeader->setUri('http://www.example.com/path?query#fragment'); + $this->assertEquals('Referer: http://www.example.com/path?query', $refererHeader->toString()); + } +} + diff --git a/test/Header/RefreshTest.php b/test/Header/RefreshTest.php new file mode 100644 index 0000000000..4b0f5d9d6d --- /dev/null +++ b/test/Header/RefreshTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $refreshHeader); + $this->assertInstanceOf('Zend\Http\Header\Refresh', $refreshHeader); + } + + public function testRefreshGetFieldNameReturnsHeaderName() + { + $refreshHeader = new Refresh(); + $this->assertEquals('Refresh', $refreshHeader->getFieldName()); + } + + public function testRefreshGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Refresh needs to be completed'); + + $refreshHeader = new Refresh(); + $this->assertEquals('xxx', $refreshHeader->getFieldValue()); + } + + public function testRefreshToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Refresh needs to be completed'); + + $refreshHeader = new Refresh(); + + // @todo set some values, then test output + $this->assertEmpty('Refresh: xxx', $refreshHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/RetryAfterTest.php b/test/Header/RetryAfterTest.php new file mode 100644 index 0000000000..35708b2d29 --- /dev/null +++ b/test/Header/RetryAfterTest.php @@ -0,0 +1,59 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $retryAfterHeader); + $this->assertInstanceOf('Zend\Http\Header\RetryAfter', $retryAfterHeader); + $this->assertEquals('10', $retryAfterHeader->getDeltaSeconds()); + } + + public function testRetryAfterFromStringCreatesValidRetryAfterHeaderFromDate() + { + $retryAfterHeader = RetryAfter::fromString('Retry-After: Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $retryAfterHeader->getDate()); + } + + public function testRetryAfterGetFieldNameReturnsHeaderName() + { + $retryAfterHeader = new RetryAfter(); + $this->assertEquals('Retry-After', $retryAfterHeader->getFieldName()); + } + + public function testRetryAfterGetFieldValueReturnsProperValue() + { + $retryAfterHeader = new RetryAfter(); + $retryAfterHeader->setDeltaSeconds(3600); + $this->assertEquals('3600', $retryAfterHeader->getFieldValue()); + $retryAfterHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Sun, 06 Nov 1994 08:49:37 GMT', $retryAfterHeader->getFieldValue()); + } + + public function testRetryAfterToStringReturnsHeaderFormattedString() + { + $retryAfterHeader = new RetryAfter(); + + $retryAfterHeader->setDeltaSeconds(3600); + $this->assertEquals('Retry-After: 3600', $retryAfterHeader->toString()); + + $retryAfterHeader->setDate('Sun, 06 Nov 1994 08:49:37 GMT'); + $this->assertEquals('Retry-After: Sun, 06 Nov 1994 08:49:37 GMT', $retryAfterHeader->toString()); + + } +} + diff --git a/test/Header/ServerTest.php b/test/Header/ServerTest.php new file mode 100644 index 0000000000..976917634f --- /dev/null +++ b/test/Header/ServerTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $serverHeader); + $this->assertInstanceOf('Zend\Http\Header\Server', $serverHeader); + } + + public function testServerGetFieldNameReturnsHeaderName() + { + $serverHeader = new Server(); + $this->assertEquals('Server', $serverHeader->getFieldName()); + } + + public function testServerGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Server needs to be completed'); + + $serverHeader = new Server(); + $this->assertEquals('xxx', $serverHeader->getFieldValue()); + } + + public function testServerToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Server needs to be completed'); + + $serverHeader = new Server(); + + // @todo set some values, then test output + $this->assertEmpty('Server: xxx', $serverHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/SetCookieTest.php b/test/Header/SetCookieTest.php new file mode 100644 index 0000000000..701961beda --- /dev/null +++ b/test/Header/SetCookieTest.php @@ -0,0 +1,329 @@ +assertEquals('myname', $setCookieHeader->getName()); + $this->assertEquals('myvalue', $setCookieHeader->getValue()); + $this->assertEquals('Wed, 13-Jan-2021 22:23:01 GMT', $setCookieHeader->getExpires()); + $this->assertEquals('/accounts', $setCookieHeader->getPath()); + $this->assertEquals('docs.foo.com', $setCookieHeader->getDomain()); + $this->assertTrue($setCookieHeader->isSecure()); + $this->assertTrue($setCookieHeader->isHttpOnly()); + $this->assertEquals(99, $setCookieHeader->getMaxAge()); + $this->assertEquals(9, $setCookieHeader->getVersion()); + } + + public function testSetCookieFromStringCreatesValidSetCookieHeader() + { + $setCookieHeader = SetCookie::fromString('Set-Cookie: xxx'); + $this->assertInstanceOf('Zend\Http\Header\MultipleHeaderInterface', $setCookieHeader); + $this->assertInstanceOf('Zend\Http\Header\HeaderInterface', $setCookieHeader); + $this->assertInstanceOf('Zend\Http\Header\SetCookie', $setCookieHeader); + } + + public function testSetCookieFromStringCanCreateSingleHeader() + { + $setCookieHeader = SetCookie::fromString('Set-Cookie: myname=myvalue'); + $this->assertInstanceOf('Zend\Http\Header\HeaderInterface', $setCookieHeader); + $this->assertEquals('myname', $setCookieHeader->getName()); + $this->assertEquals('myvalue', $setCookieHeader->getValue()); + + $setCookieHeader = SetCookie::fromString( + 'set-cookie: myname=myvalue; Domain=docs.foo.com; Path=/accounts;' + . 'Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly' + ); + $this->assertInstanceOf('Zend\Http\Header\MultipleHeaderInterface', $setCookieHeader); + $this->assertEquals('myname', $setCookieHeader->getName()); + $this->assertEquals('myvalue', $setCookieHeader->getValue()); + $this->assertEquals('docs.foo.com', $setCookieHeader->getDomain()); + $this->assertEquals('/accounts', $setCookieHeader->getPath()); + $this->assertEquals('Wed, 13-Jan-2021 22:23:01 GMT', $setCookieHeader->getExpires()); + $this->assertTrue($setCookieHeader->isSecure()); + $this->assertTrue($setCookieHeader->isHttponly()); + } + + public function testSetCookieFromStringCanCreateMultipleHeaders() + { + $setCookieHeaders = SetCookie::fromString( + 'Set-Cookie: myname=myvalue, ' + . 'someothername=someothervalue; Domain=docs.foo.com; Path=/accounts;' + . 'Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly' + ); + + $this->assertInternalType('array', $setCookieHeaders); + + $setCookieHeader = $setCookieHeaders[0]; + $this->assertInstanceOf('Zend\Http\Header\MultipleHeaderInterface', $setCookieHeader); + $this->assertEquals('myname', $setCookieHeader->getName()); + $this->assertEquals('myvalue', $setCookieHeader->getValue()); + + $setCookieHeader = $setCookieHeaders[1]; + $this->assertInstanceOf('Zend\Http\Header\MultipleHeaderInterface', $setCookieHeader); + $this->assertEquals('someothername', $setCookieHeader->getName()); + $this->assertEquals('someothervalue', $setCookieHeader->getValue()); + $this->assertEquals('Wed, 13-Jan-2021 22:23:01 GMT', $setCookieHeader->getExpires()); + $this->assertEquals('docs.foo.com', $setCookieHeader->getDomain()); + $this->assertEquals('/accounts', $setCookieHeader->getPath()); + $this->assertTrue($setCookieHeader->isSecure()); + $this->assertTrue($setCookieHeader->isHttponly()); + + } + + public function testSetCookieGetFieldNameReturnsHeaderName() + { + $setCookieHeader = new SetCookie(); + $this->assertEquals('Set-Cookie', $setCookieHeader->getFieldName()); + + } + + public function testSetCookieGetFieldValueReturnsProperValue() + { + $setCookieHeader = new SetCookie(); + $setCookieHeader->setName('myname'); + $setCookieHeader->setValue('myvalue'); + $setCookieHeader->setExpires('Wed, 13-Jan-2021 22:23:01 GMT'); + $setCookieHeader->setDomain('docs.foo.com'); + $setCookieHeader->setPath('/accounts'); + $setCookieHeader->setSecure(true); + $setCookieHeader->setHttponly(true); + + $target = 'myname=myvalue; Expires=Wed, 13-Jan-2021 22:23:01 GMT;' + . ' Domain=docs.foo.com; Path=/accounts;' + . ' Secure; HttpOnly'; + + $this->assertEquals($target, $setCookieHeader->getFieldValue()); + } + + public function testSetCookieToStringReturnsHeaderFormattedString() + { + $setCookieHeader = new SetCookie(); + $setCookieHeader->setName('myname'); + $setCookieHeader->setValue('myvalue'); + $setCookieHeader->setExpires('Wed, 13-Jan-2021 22:23:01 GMT'); + $setCookieHeader->setDomain('docs.foo.com'); + $setCookieHeader->setPath('/accounts'); + $setCookieHeader->setSecure(true); + $setCookieHeader->setHttponly(true); + + $target = 'Set-Cookie: myname=myvalue; Expires=Wed, 13-Jan-2021 22:23:01 GMT;' + . ' Domain=docs.foo.com; Path=/accounts;' + . ' Secure; HttpOnly'; + + $this->assertEquals($target, $setCookieHeader->toString()); + } + + public function testSetCookieCanAppendOtherHeadersInWhenCreatingString() + { + $setCookieHeader = new SetCookie(); + $setCookieHeader->setName('myname'); + $setCookieHeader->setValue('myvalue'); + $setCookieHeader->setExpires('Wed, 13-Jan-2021 22:23:01 GMT'); + $setCookieHeader->setDomain('docs.foo.com'); + $setCookieHeader->setPath('/accounts'); + $setCookieHeader->setSecure(true); + $setCookieHeader->setHttponly(true); + + $appendCookie = new SetCookie('othername', 'othervalue'); + $headerLine = $setCookieHeader->toStringMultipleHeaders(array($appendCookie)); + + $target = 'Set-Cookie: myname=myvalue; Expires=Wed, 13-Jan-2021 22:23:01 GMT;' + . ' Domain=docs.foo.com; Path=/accounts;' + . ' Secure; HttpOnly, othername=othervalue'; + $this->assertEquals($target, $headerLine); + } + + /** Implmentation specific tests here */ + + /** + * @group ZF2-169 + */ + public function testZF2_169() + { + $cookie = 'Set-Cookie: leo_auth_token="example"; Version=1; Max-Age=1799; Expires=Mon, 20-Feb-2012 02:49:57 GMT; Path=/'; + $setCookieHeader = SetCookie::fromString($cookie); + $this->assertEquals($cookie, $setCookieHeader->toString()); + } + + /** + * @group ZF2-169 + */ + public function testDoesNotAcceptCookieNameFromArbitraryLocationInHeaderValue() + { + $cookie = 'Set-Cookie: Version=1; Max-Age=1799; Expires=Mon, 20-Feb-2012 02:49:57 GMT; Path=/; leo_auth_token="example"'; + $setCookieHeader = SetCookie::fromString($cookie); + $this->assertNotEquals('leo_auth_token', $setCookieHeader->getName()); + } + + public function testGetFieldName() + { + $c = new SetCookie(); + $this->assertEquals('Set-Cookie', $c->getFieldName()); + } + + /** + * @dataProvider validCookieWithInfoProvider + */ + public function testGetFieldValue($cStr, $info, $expected) + { + $cookie = SetCookie::fromString($cStr); + if (! $cookie instanceof SetCookie) { + $this->fail("Failed creating a cookie object from '$cStr'"); + } + $this->assertEquals($expected, $cookie->getFieldValue()); + $this->assertEquals($cookie->getFieldName() . ': ' . $expected, $cookie->toString()); + } + + /** + * @dataProvider validCookieWithInfoProvider + */ + public function testToString($cStr, $info, $expected) + { + $cookie = SetCookie::fromString($cStr); + if (! $cookie instanceof SetCookie) { + $this->fail("Failed creating a cookie object from '$cStr'"); + } + $this->assertEquals($cookie->getFieldName() . ': ' . $expected, $cookie->toString()); + } + + /** + * Provide valid cookie strings with information about them + * + * @return array + */ + public static function validCookieWithInfoProvider() + { + $now = time(); + $yesterday = $now - (3600 * 24); + + return array( + array( + 'Set-Cookie: justacookie=foo; domain=example.com', + array( + 'name' => 'justacookie', + 'value' => 'foo', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => null, + 'secure' => false, + 'httponly'=> false + ), + 'justacookie=foo; Domain=example.com' + ), + array( + 'Set-Cookie: expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com', + array( + 'name' => 'expires', + 'value' => 'tomorrow', + 'domain' => '.example.com', + 'path' => '/Space Out/', + 'expires' => strtotime('Tue, 21-Nov-2006 08:33:44 GMT'), + 'secure' => true, + 'httponly'=> false + ), + 'expires=tomorrow; Expires=Tue, 21-Nov-2006 08:33:44 GMT; Domain=.example.com; Path=/Space Out/; Secure' + ), + array( + 'Set-Cookie: domain=unittests; expires=' . gmdate('D, d-M-Y H:i:s', $now) . ' GMT; domain=example.com; path=/some%20value/', + array( + 'name' => 'domain', + 'value' => 'unittests', + 'domain' => 'example.com', + 'path' => '/some%20value/', + 'expires' => $now, + 'secure' => false, + 'httponly'=> false + ), + 'domain=unittests; Expires=' . gmdate('D, d-M-Y H:i:s', $now) . ' GMT; Domain=example.com; Path=/some%20value/' + ), + array( + 'Set-Cookie: path=indexAction; path=/; domain=.foo.com; expires=' . gmdate('D, d-M-Y H:i:s', $yesterday) . ' GMT', + array( + 'name' => 'path', + 'value' => 'indexAction', + 'domain' => '.foo.com', + 'path' => '/', + 'expires' => $yesterday, + 'secure' => false, + 'httponly'=> false + ), + 'path=indexAction; Expires=' . gmdate('D, d-M-Y H:i:s', $yesterday) . ' GMT; Domain=.foo.com; Path=/' + ), + + array( + 'Set-Cookie: secure=sha1; secure; SECURE; domain=some.really.deep.domain.com', + array( + 'name' => 'secure', + 'value' => 'sha1', + 'domain' => 'some.really.deep.domain.com', + 'path' => '/', + 'expires' => null, + 'secure' => true, + 'httponly'=> false + ), + 'secure=sha1; Domain=some.really.deep.domain.com; Secure' + ), + array( + 'Set-Cookie: justacookie=foo; domain=example.com; httpOnly', + array( + 'name' => 'justacookie', + 'value' => 'foo', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => null, + 'secure' => false, + 'httponly'=> true + ), + 'justacookie=foo; Domain=example.com; HttpOnly' + ), + array( + 'Set-Cookie: PHPSESSID=123456789+abcd%2Cef; secure; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;', + array( + 'name' => 'PHPSESSID', + 'value' => '123456789+abcd%2Cef', + 'domain' => '.localdomain', + 'path' => '/foo/baz', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => true, + 'httponly'=> false + ), + 'PHPSESSID=123456789%2Babcd%252Cef; Expires=Tue, 21-Nov-2006 08:33:44 GMT; Domain=.localdomain; Path=/foo/baz; Secure' + ), + array( + 'Set-Cookie: myname=myvalue; Domain=docs.foo.com; Path=/accounts; Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly', + array( + 'name' => 'myname', + 'value' => 'myvalue', + 'domain' => 'docs.foo.com', + 'path' => '/accounts', + 'expires' => 'Wed, 13-Jan-2021 22:23:01 GMT', + 'secure' => true, + 'httponly'=> true + ), + 'myname=myvalue; Expires=Wed, 13-Jan-2021 22:23:01 GMT; Domain=docs.foo.com; Path=/accounts; Secure; HttpOnly' + ), + ); + } + +} + diff --git a/test/Header/TETest.php b/test/Header/TETest.php new file mode 100644 index 0000000000..0161739c23 --- /dev/null +++ b/test/Header/TETest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $tEHeader); + $this->assertInstanceOf('Zend\Http\Header\TE', $tEHeader); + } + + public function testTEGetFieldNameReturnsHeaderName() + { + $tEHeader = new TE(); + $this->assertEquals('TE', $tEHeader->getFieldName()); + } + + public function testTEGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('TE needs to be completed'); + + $tEHeader = new TE(); + $this->assertEquals('xxx', $tEHeader->getFieldValue()); + } + + public function testTEToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('TE needs to be completed'); + + $tEHeader = new TE(); + + // @todo set some values, then test output + $this->assertEmpty('TE: xxx', $tEHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/TrailerTest.php b/test/Header/TrailerTest.php new file mode 100644 index 0000000000..b94980e2ad --- /dev/null +++ b/test/Header/TrailerTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $trailerHeader); + $this->assertInstanceOf('Zend\Http\Header\Trailer', $trailerHeader); + } + + public function testTrailerGetFieldNameReturnsHeaderName() + { + $trailerHeader = new Trailer(); + $this->assertEquals('Trailer', $trailerHeader->getFieldName()); + } + + public function testTrailerGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Trailer needs to be completed'); + + $trailerHeader = new Trailer(); + $this->assertEquals('xxx', $trailerHeader->getFieldValue()); + } + + public function testTrailerToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Trailer needs to be completed'); + + $trailerHeader = new Trailer(); + + // @todo set some values, then test output + $this->assertEmpty('Trailer: xxx', $trailerHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/TransferEncodingTest.php b/test/Header/TransferEncodingTest.php new file mode 100644 index 0000000000..233127a9ad --- /dev/null +++ b/test/Header/TransferEncodingTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $transferEncodingHeader); + $this->assertInstanceOf('Zend\Http\Header\TransferEncoding', $transferEncodingHeader); + } + + public function testTransferEncodingGetFieldNameReturnsHeaderName() + { + $transferEncodingHeader = new TransferEncoding(); + $this->assertEquals('Transfer-Encoding', $transferEncodingHeader->getFieldName()); + } + + public function testTransferEncodingGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('TransferEncoding needs to be completed'); + + $transferEncodingHeader = new TransferEncoding(); + $this->assertEquals('xxx', $transferEncodingHeader->getFieldValue()); + } + + public function testTransferEncodingToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('TransferEncoding needs to be completed'); + + $transferEncodingHeader = new TransferEncoding(); + + // @todo set some values, then test output + $this->assertEmpty('Transfer-Encoding: xxx', $transferEncodingHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/UpgradeTest.php b/test/Header/UpgradeTest.php new file mode 100644 index 0000000000..449fd34cf2 --- /dev/null +++ b/test/Header/UpgradeTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $upgradeHeader); + $this->assertInstanceOf('Zend\Http\Header\Upgrade', $upgradeHeader); + } + + public function testUpgradeGetFieldNameReturnsHeaderName() + { + $upgradeHeader = new Upgrade(); + $this->assertEquals('Upgrade', $upgradeHeader->getFieldName()); + } + + public function testUpgradeGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Upgrade needs to be completed'); + + $upgradeHeader = new Upgrade(); + $this->assertEquals('xxx', $upgradeHeader->getFieldValue()); + } + + public function testUpgradeToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Upgrade needs to be completed'); + + $upgradeHeader = new Upgrade(); + + // @todo set some values, then test output + $this->assertEmpty('Upgrade: xxx', $upgradeHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/UserAgentTest.php b/test/Header/UserAgentTest.php new file mode 100644 index 0000000000..8e49fd67e2 --- /dev/null +++ b/test/Header/UserAgentTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $userAgentHeader); + $this->assertInstanceOf('Zend\Http\Header\UserAgent', $userAgentHeader); + } + + public function testUserAgentGetFieldNameReturnsHeaderName() + { + $userAgentHeader = new UserAgent(); + $this->assertEquals('User-Agent', $userAgentHeader->getFieldName()); + } + + public function testUserAgentGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('UserAgent needs to be completed'); + + $userAgentHeader = new UserAgent(); + $this->assertEquals('xxx', $userAgentHeader->getFieldValue()); + } + + public function testUserAgentToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('UserAgent needs to be completed'); + + $userAgentHeader = new UserAgent(); + + // @todo set some values, then test output + $this->assertEmpty('User-Agent: xxx', $userAgentHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/VaryTest.php b/test/Header/VaryTest.php new file mode 100644 index 0000000000..eb2ef81064 --- /dev/null +++ b/test/Header/VaryTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $varyHeader); + $this->assertInstanceOf('Zend\Http\Header\Vary', $varyHeader); + } + + public function testVaryGetFieldNameReturnsHeaderName() + { + $varyHeader = new Vary(); + $this->assertEquals('Vary', $varyHeader->getFieldName()); + } + + public function testVaryGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Vary needs to be completed'); + + $varyHeader = new Vary(); + $this->assertEquals('xxx', $varyHeader->getFieldValue()); + } + + public function testVaryToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Vary needs to be completed'); + + $varyHeader = new Vary(); + + // @todo set some values, then test output + $this->assertEmpty('Vary: xxx', $varyHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/ViaTest.php b/test/Header/ViaTest.php new file mode 100644 index 0000000000..c963775b6a --- /dev/null +++ b/test/Header/ViaTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $viaHeader); + $this->assertInstanceOf('Zend\Http\Header\Via', $viaHeader); + } + + public function testViaGetFieldNameReturnsHeaderName() + { + $viaHeader = new Via(); + $this->assertEquals('Via', $viaHeader->getFieldName()); + } + + public function testViaGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Via needs to be completed'); + + $viaHeader = new Via(); + $this->assertEquals('xxx', $viaHeader->getFieldValue()); + } + + public function testViaToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Via needs to be completed'); + + $viaHeader = new Via(); + + // @todo set some values, then test output + $this->assertEmpty('Via: xxx', $viaHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/WWWAuthenticateTest.php b/test/Header/WWWAuthenticateTest.php new file mode 100644 index 0000000000..fa18284453 --- /dev/null +++ b/test/Header/WWWAuthenticateTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $wWWAuthenticateHeader); + $this->assertInstanceOf('Zend\Http\Header\WWWAuthenticate', $wWWAuthenticateHeader); + } + + public function testWWWAuthenticateGetFieldNameReturnsHeaderName() + { + $wWWAuthenticateHeader = new WWWAuthenticate(); + $this->assertEquals('WWW-Authenticate', $wWWAuthenticateHeader->getFieldName()); + } + + public function testWWWAuthenticateGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('WWWAuthenticate needs to be completed'); + + $wWWAuthenticateHeader = new WWWAuthenticate(); + $this->assertEquals('xxx', $wWWAuthenticateHeader->getFieldValue()); + } + + public function testWWWAuthenticateToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('WWWAuthenticate needs to be completed'); + + $wWWAuthenticateHeader = new WWWAuthenticate(); + + // @todo set some values, then test output + $this->assertEmpty('WWW-Authenticate: xxx', $wWWAuthenticateHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/Header/WarningTest.php b/test/Header/WarningTest.php new file mode 100644 index 0000000000..7b0b0111b9 --- /dev/null +++ b/test/Header/WarningTest.php @@ -0,0 +1,52 @@ +assertInstanceOf('Zend\Http\Header\HeaderInterface', $warningHeader); + $this->assertInstanceOf('Zend\Http\Header\Warning', $warningHeader); + } + + public function testWarningGetFieldNameReturnsHeaderName() + { + $warningHeader = new Warning(); + $this->assertEquals('Warning', $warningHeader->getFieldName()); + } + + public function testWarningGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Warning needs to be completed'); + + $warningHeader = new Warning(); + $this->assertEquals('xxx', $warningHeader->getFieldValue()); + } + + public function testWarningToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Warning needs to be completed'); + + $warningHeader = new Warning(); + + // @todo set some values, then test output + $this->assertEmpty('Warning: xxx', $warningHeader->toString()); + } + + /** Implmentation specific tests here */ + +} + diff --git a/test/HeadersTest.php b/test/HeadersTest.php new file mode 100644 index 0000000000..1b4aca7a37 --- /dev/null +++ b/test/HeadersTest.php @@ -0,0 +1,273 @@ +assertInstanceOf('Iterator', $headers); + $this->assertInstanceOf('Countable', $headers); + } + + public function testHeadersCanGetPluginClassLoader() + { + $headers = new Headers(); + $this->assertInstanceOf('Zend\Http\HeaderLoader', $headers->getPluginClassLoader()); + } + + public function testHeadersFromStringFactoryCreatesSingleObject() + { + $headers = Headers::fromString("Fake: foo-bar"); + $this->assertEquals(1, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar', $header->getFieldValue()); + } + + public function testHeadersFromStringFactoryCreatesSingleObjectWithContinuationLine() + { + $headers = Headers::fromString("Fake: foo-bar,\r\n blah-blah"); + $this->assertEquals(1, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar,blah-blah', $header->getFieldValue()); + } + + public function testHeadersFromStringFactoryCreatesSingleObjectWithHeaderBreakLine() + { + $headers = Headers::fromString("Fake: foo-bar\r\n\r\n"); + $this->assertEquals(1, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar', $header->getFieldValue()); + } + + public function testHeadersFromStringFactoryThrowsExceptionOnMalformedHeaderLine() + { + $this->setExpectedException('Zend\Http\Exception\RuntimeException', 'does not match'); + Headers::fromString("Fake = foo-bar\r\n\r\n"); + } + + public function testHeadersFromStringFactoryCreatesMultipleObjects() + { + $headers = Headers::fromString("Fake: foo-bar\r\nAnother-Fake: boo-baz"); + $this->assertEquals(2, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar', $header->getFieldValue()); + + $header = $headers->get('anotherfake'); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $header); + $this->assertEquals('Another-Fake', $header->getFieldName()); + $this->assertEquals('boo-baz', $header->getFieldValue()); + } + + public function testHeadersFromStringMultiHeaderWillAggregateLazyLoadedHeaders() + { + $headers = new Headers(); + /* @var $pcl \Zend\Loader\PluginClassLoader */ + $pcl = $headers->getPluginClassLoader(); + $pcl->registerPlugin('foo', 'Zend\Http\Header\GenericMultiHeader'); + $headers->addHeaderLine('foo: bar1,bar2,bar3'); + $headers->forceLoading(); + $this->assertEquals(3, $headers->count()); + } + + public function testHeadersHasAndGetWorkProperly() + { + $headers = new Headers(); + $headers->addHeaders(array($f = new Header\GenericHeader('Foo', 'bar'), new Header\GenericHeader('Baz', 'baz'))); + $this->assertFalse($headers->has('foobar')); + $this->assertTrue($headers->has('foo')); + $this->assertTrue($headers->has('Foo')); + $this->assertSame($f, $headers->get('foo')); + } + + public function testHeadersAggregatesHeaderObjects() + { + $fakeHeader = new Header\GenericHeader('Fake', 'bar'); + $headers = new Headers(); + $headers->addHeader($fakeHeader); + $this->assertEquals(1, $headers->count()); + $this->assertSame($fakeHeader, $headers->get('Fake')); + } + + public function testHeadersAggregatesHeaderThroughAddHeader() + { + $headers = new Headers(); + $headers->addHeader(new Header\GenericHeader('Fake', 'bar')); + $this->assertEquals(1, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Fake')); + } + + public function testHeadersAggregatesHeaderThroughAddHeaderLine() + { + $headers = new Headers(); + $headers->addHeaderLine('Fake', 'bar'); + $this->assertEquals(1, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Fake')); + } + + public function testHeadersAddHeaderLineThrowsExceptionOnMissingFieldValue() + { + $this->setExpectedException('Zend\Http\Exception\InvalidArgumentException', 'without a field'); + $headers = new Headers(); + $headers->addHeaderLine('Foo'); + } + + public function testHeadersAggregatesHeadersThroughAddHeaders() + { + $headers = new Headers(); + $headers->addHeaders(array(new Header\GenericHeader('Foo', 'bar'), new Header\GenericHeader('Baz', 'baz'))); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array('Foo: bar', 'Baz: baz')); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array(array('Foo' => 'bar'), array('Baz' => 'baz'))); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array(array('Foo', 'bar'), array('Baz', 'baz'))); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + } + + public function testHeadersAddHeadersThrowsExceptionOnInvalidArguments() + { + $this->setExpectedException('Zend\Http\Exception\InvalidArgumentException', 'Expected array or Trav'); + $headers = new Headers(); + $headers->addHeaders('foo'); + } + + public function testHeadersCanRemoveHeader() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $header = $headers->get('foo'); + $this->assertEquals(2, $headers->count()); + $headers->removeHeader($header); + $this->assertEquals(1, $headers->count()); + $this->assertFalse($headers->get('foo')); + } + + public function testHeadersCanClearAllHeaders() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals(2, $headers->count()); + $headers->clearHeaders(); + $this->assertEquals(0, $headers->count()); + } + + public function testHeadersCanBeIterated() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $iterations = 0; + /** @var \Zend\Http\Header\HeaderInterface $header */ + foreach ($headers as $index => $header) { + $iterations++; + $this->assertInstanceOf('Zend\Http\Header\GenericHeader', $header); + switch ($index) { + case 0: + $this->assertEquals('bar', $header->getFieldValue()); + break; + case 1: + $this->assertEquals('baz', $header->getFieldValue()); + break; + default: + $this->fail('Invalid index returned from iterator'); + } + } + $this->assertEquals(2, $iterations); + } + + public function testHeadersCanBeCastToString() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals('Foo: bar' . "\r\n" . 'Baz: baz' . "\r\n", $headers->toString()); + } + + public function testHeadersCanBeCastToArray() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals(array('Foo' => 'bar', 'Baz' => 'baz'), $headers->toArray()); + } + + public function testCastingToArrayReturnsMultiHeadersAsArrays() + { + $headers = new Headers(); + $cookie1 = new Header\SetCookie('foo', 'bar'); + $cookie2 = new Header\SetCookie('bar', 'baz'); + $headers->addHeader($cookie1); + $headers->addHeader($cookie2); + $array = $headers->toArray(); + $expected = array( + 'Set-Cookie' => array( + $cookie1->getFieldValue(), + $cookie2->getFieldValue(), + ), + ); + $this->assertEquals($expected, $array); + } + + public function testCastingToStringReturnsAllMultiHeaderValues() + { + $headers = new Headers(); + $cookie1 = new Header\SetCookie('foo', 'bar'); + $cookie2 = new Header\SetCookie('bar', 'baz'); + $headers->addHeader($cookie1); + $headers->addHeader($cookie2); + $string = $headers->toString(); + $expected = array( + 'Set-Cookie: ' . $cookie1->getFieldValue(), + 'Set-Cookie: ' . $cookie2->getFieldValue(), + ); + $expected = implode("\r\n", $expected) . "\r\n"; + $this->assertEquals($expected, $string); + } +} diff --git a/test/PhpEnvironment/RequestTest.php b/test/PhpEnvironment/RequestTest.php new file mode 100644 index 0000000000..ac20469a0a --- /dev/null +++ b/test/PhpEnvironment/RequestTest.php @@ -0,0 +1,682 @@ +originalEnvironment = array( + 'post' => $_POST, + 'get' => $_GET, + 'cookie' => $_COOKIE, + 'server' => $_SERVER, + 'env' => $_ENV, + 'files' => $_FILES, + ); + + $_POST = array(); + $_GET = array(); + $_COOKIE = array(); + $_SERVER = array(); + $_ENV = array(); + $_FILES = array(); + } + + /** + * Restore the original environment + */ + public function tearDown() + { + $_POST = $this->originalEnvironment['post']; + $_GET = $this->originalEnvironment['get']; + $_COOKIE = $this->originalEnvironment['cookie']; + $_SERVER = $this->originalEnvironment['server']; + $_ENV = $this->originalEnvironment['env']; + $_FILES = $this->originalEnvironment['files']; + } + + /** + * Data provider for testing base URL and path detection. + */ + public static function baseUrlAndPathProvider() + { + return array( + array( + array( + 'REQUEST_URI' => '/index.php/news/3?var1=val1&var2=val2', + 'QUERY_URI' => 'var1=val1&var2=val2', + 'SCRIPT_NAME' => '/index.php', + 'PHP_SELF' => '/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/index.php', + '' + ), + array( + array( + 'REQUEST_URI' => '/public/index.php/news/3?var1=val1&var2=val2', + 'QUERY_URI' => 'var1=val1&var2=val2', + 'SCRIPT_NAME' => '/public/index.php', + 'PHP_SELF' => '/public/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/public/index.php', + ), + '/public/index.php', + '/public' + ), + array( + array( + 'REQUEST_URI' => '/index.php/news/3?var1=val1&var2=val2', + 'SCRIPT_NAME' => '/home.php', + 'PHP_SELF' => '/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/index.php', + '' + ), + array( + array( + 'REQUEST_URI' => '/index.php/news/3?var1=val1&var2=val2', + 'SCRIPT_NAME' => '/home.php', + 'PHP_SELF' => '/home.php', + 'ORIG_SCRIPT_NAME' => '/index.php', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/index.php', + '' + ), + array( + array( + 'REQUEST_URI' => '/index.php/news/3?var1=val1&var2=val2', + 'PHP_SELF' => '/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/index.php', + '' + ), + array( + array( + 'HTTP_X_REWRITE_URL' => '/index.php/news/3?var1=val1&var2=val2', + 'PHP_SELF' => '/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/index.php', + '' + ), + array( + array( + 'ORIG_PATH_INFO' => '/index.php/news/3', + 'QUERY_STRING' => 'var1=val1&var2=val2', + 'PHP_SELF' => '/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/index.php', + '' + ), + array( + array( + 'REQUEST_URI' => '/article/archive?foo=index.php', + 'QUERY_STRING' => 'foo=index.php', + 'SCRIPT_FILENAME' => '/var/www/zftests/index.php', + ), + '', + '' + ), + array( + array( + 'REQUEST_URI' => '/html/index.php/news/3?var1=val1&var2=val2', + 'PHP_SELF' => '/html/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/html/index.php', + '/html' + ), + array( + array( + 'REQUEST_URI' => '/dir/action', + 'PHP_SELF' => '/dir/index.php', + 'SCRIPT_FILENAME' => '/var/web/dir/index.php', + ), + '/dir', + '/dir' + ), + array( + array( + 'SCRIPT_NAME' => '/~username/public/index.php', + 'REQUEST_URI' => '/~username/public/', + 'PHP_SELF' => '/~username/public/index.php', + 'SCRIPT_FILENAME' => '/Users/username/Sites/public/index.php', + 'ORIG_SCRIPT_NAME'=> null + ), + '/~username/public', + '/~username/public' + ), + // ZF2-206 + array( + array( + 'SCRIPT_NAME' => '/zf2tut/index.php', + 'REQUEST_URI' => '/zf2tut/', + 'PHP_SELF' => '/zf2tut/index.php', + 'SCRIPT_FILENAME' => 'c:/ZF2Tutorial/public/index.php', + 'ORIG_SCRIPT_NAME'=> null + ), + '/zf2tut', + '/zf2tut' + ), + array( + array( + 'REQUEST_URI' => '/html/index.php/news/3?var1=val1&var2=/index.php', + 'PHP_SELF' => '/html/index.php/news/3', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/html/index.php', + '/html' + ), + array( + array( + 'REQUEST_URI' => '/html/index.php/news/index.php', + 'PHP_SELF' => '/html/index.php/news/index.php', + 'SCRIPT_FILENAME' => '/var/web/html/index.php', + ), + '/html/index.php', + '/html' + ), + ); + } + + /** + * @dataProvider baseUrlAndPathProvider + * @param array $server + * @param string $baseUrl + * @param string $basePath + */ + public function testBasePathDetection(array $server, $baseUrl, $basePath) + { + $_SERVER = $server; + $request = new Request(); + + $this->assertEquals($baseUrl, $request->getBaseUrl()); + $this->assertEquals($basePath, $request->getBasePath()); + } + + /** + * Data provider for testing server provided headers. + */ + public static function serverHeaderProvider() + { + return array( + array( + array( + 'HTTP_USER_AGENT' => 'Dummy', + ), + 'User-Agent', + 'Dummy' + ), + array( + array( + 'CONTENT_TYPE' => 'text/html', + ), + 'Content-Type', + 'text/html' + ), + array( + array( + 'CONTENT_LENGTH' => 12, + ), + 'Content-Length', + 12 + ), + array( + array( + 'CONTENT_MD5' => md5('a'), + ), + 'Content-MD5', + md5('a') + ), + ); + } + + /** + * @dataProvider serverHeaderProvider + * @param array $server + * @param string $name + * @param string $value + */ + public function testHeadersWithMinus(array $server, $name, $value) + { + $_SERVER = $server; + $request = new Request(); + + $header = $request->getHeaders()->get($name); + $this->assertNotEquals($header, false); + $this->assertEquals($name, $header->getFieldName($value)); + $this->assertEquals($value, $header->getFieldValue($value)); + } + + /** + * @dataProvider serverHeaderProvider + * @param array $server + * @param string $name + */ + public function testRequestStringHasCorrectHeaderName(array $server, $name) + { + $_SERVER = $server; + $request = new Request(); + + $this->assertContains($name, $request->toString()); + } + + /** + * Data provider for testing server hostname. + */ + public static function serverHostnameProvider() + { + return array( + array( + array( + 'SERVER_NAME' => 'test.example.com', + 'REQUEST_URI' => 'http://test.example.com/news', + ), + 'test.example.com', + '80', + '/news', + ), + array( + array( + 'HTTP_HOST' => 'test.example.com', + 'REQUEST_URI' => 'http://test.example.com/news', + ), + 'test.example.com', + '80', + '/news', + ), + array( + array( + 'SERVER_NAME' => '[1:2:3:4:5:6::6]', + 'SERVER_ADDR' => '1:2:3:4:5:6::6', + 'SERVER_PORT' => '80', + 'REQUEST_URI' => 'http://[1:2:3:4:5:6::6]/news', + ), + '[1:2:3:4:5:6::6]', + '80', + '/news', + ), + // Test for broken $_SERVER implementation from Windows-Safari + array( + array( + 'SERVER_NAME' => '[1:2:3:4:5:6:]', + 'SERVER_ADDR' => '1:2:3:4:5:6::6', + 'SERVER_PORT' => '6', + 'REQUEST_URI' => 'http://[1:2:3:4:5:6::6]/news', + ), + '[1:2:3:4:5:6::6]', + '80', + '/news', + ), + array( + array( + 'SERVER_NAME' => 'test.example.com', + 'SERVER_PORT' => '8080', + 'REQUEST_URI' => 'http://test.example.com/news', + ), + 'test.example.com', + '8080', + '/news', + ), + array( + array( + 'SERVER_NAME' => 'test.example.com', + 'SERVER_PORT' => '443', + 'HTTPS' => 'on', + 'REQUEST_URI' => 'https://test.example.com/news', + ), + 'test.example.com', + '443', + '/news', + ), + ); + } + + /** + * @dataProvider serverHostnameProvider + * @param array $server + * @param string $name + * @param string $value + */ + public function testServerHostnameProvider(array $server, $expectedHost, $expectedPort, $expectedRequestUri) + { + $_SERVER = $server; + $request = new Request(); + + $host = $request->getUri()->getHost(); + $this->assertEquals($expectedHost, $host); + + $port = $request->getUri()->getPort(); + $this->assertEquals($expectedPort, $port); + + $requestUri = $request->getRequestUri(); + $this->assertEquals($expectedRequestUri, $requestUri); + } + + /** + * Data provider for testing mapping $_FILES + * + * @return array + */ + public static function filesProvider() + { + return array( + // single file + array( + array( + 'file' => array ( + 'name' => 'test1.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + ), + array( + 'file' => array ( + 'name' => 'test1.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + ), + ), + + // file name with brackets and int keys + // file[], file[] + array( + array( + 'file' => array ( + 'name' => array ( + 0 => 'test1.txt', + 1 => 'test2.txt', + ), + 'type' => array ( + 0 => 'text/plain', + 1 => 'text/plain', + ), + 'tmp_name' => array ( + 0 => '/tmp/phpXXX', + 1 => '/tmp/phpXXX', + ), + 'error' => array ( + 0 => 0, + 1 => 0, + ), + 'size' => array ( + 0 => 1, + 1 => 1, + ), + ), + ), + array( + 'file' => array ( + 0 => array( + 'name' => 'test1.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + 1 => array( + 'name' => 'test2.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + ), + ), + ), + + // file name with brackets and string keys + // file[one], file[two] + array( + array( + 'file' => array ( + 'name' => array ( + 'one' => 'test1.txt', + 'two' => 'test2.txt', + ), + 'type' => array ( + 'one' => 'text/plain', + 'two' => 'text/plain', + ), + 'tmp_name' => array ( + 'one' => '/tmp/phpXXX', + 'two' => '/tmp/phpXXX', + ), + 'error' => array ( + 'one' => 0, + 'two' => 0, + ), + 'size' => array ( + 'one' => 1, + 'two' => 1, + ), + ), + ), + array( + 'file' => array ( + 'one' => array( + 'name' => 'test1.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + 'two' => array( + 'name' => 'test2.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + ), + ), + ), + + // multilevel file name + // file[], file[][], file[][][] + array( + array ( + 'file' => array ( + 'name' => array ( + 0 => 'test_0.txt', + 1 => array ( + 0 => 'test_10.txt', + ), + 2 => array ( + 0 => array( + 0 => 'test_200.txt', + ), + ), + ), + 'type' => array( + 0 => 'text/plain', + 1 => array( + 0 => 'text/plain', + ), + 2 => array( + 0 => array( + 0 => 'text/plain', + ), + ), + ), + 'tmp_name' => array ( + 0 => '/tmp/phpXXX', + 1 => array( + 0 => '/tmp/phpXXX', + ), + 2 => array ( + 0 => array( + 0 => '/tmp/phpXXX', + ), + ), + ), + 'error' => array( + 0 => 0, + 1 => array( + 0 => 0, + ), + 2 => array ( + 0 => array( + 0 => 0, + ), + ), + ), + 'size' => array( + 0 => 1, + 1 => array( + 0 => 1, + ), + 2 => array( + 0 => array( + 0 => 1, + ), + ), + ), + ) + ), + array( + 'file' => array( + 0 => array( + 'name' => 'test_0.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + 1 => array( + 0 => array( + 'name' => 'test_10.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + ), + 2 => array( + 0 => array( + 0 => array( + 'name' => 'test_200.txt', + 'type' => 'text/plain', + 'tmp_name' => '/tmp/phpXXX', + 'error' => 0, + 'size' => 1, + ), + ), + ), + ) + ), + ), + ); + } + + /** + * @param array $files + * @param array $expectedFiles + * @dataProvider filesProvider + */ + public function testRequestMapsPhpFies(array $files, array $expectedFiles) + { + $_FILES = $files; + $request = new Request(); + $this->assertEquals($expectedFiles, $request->getFiles()->toArray()); + } + + public function testParameterRetrievalDefaultValue() + { + $request = new Request(); + $p = new \Zend\Stdlib\Parameters(array( + 'foo' => 'bar' + )); + $request->setQuery($p); + $request->setPost($p); + $request->setFiles($p); + $request->setServer($p); + $request->setEnv($p); + + $default = 15; + $this->assertSame($default, $request->getQuery('baz', $default)); + $this->assertSame($default, $request->getPost('baz', $default)); + $this->assertSame($default, $request->getFiles('baz', $default)); + $this->assertSame($default, $request->getServer('baz', $default)); + $this->assertSame($default, $request->getEnv('baz', $default)); + $this->assertSame($default, $request->getHeaders('baz', $default)); + $this->assertSame($default, $request->getHeader('baz', $default)); + } + + public function testRetrievingASingleValueForParameters() + { + $request = new Request(); + $p = new \Zend\Stdlib\Parameters(array( + 'foo' => 'bar' + )); + $request->setQuery($p); + $request->setPost($p); + $request->setFiles($p); + $request->setServer($p); + $request->setEnv($p); + + $this->assertSame('bar', $request->getQuery('foo')); + $this->assertSame('bar', $request->getPost('foo')); + $this->assertSame('bar', $request->getFiles('foo')); + $this->assertSame('bar', $request->getServer('foo')); + $this->assertSame('bar', $request->getEnv('foo')); + + $headers = new Headers(); + $h = new GenericHeader('foo','bar'); + $headers->addHeader($h); + + $request->setHeaders($headers); + $this->assertSame($headers, $request->getHeaders()); + $this->assertSame($h, $request->getHeaders()->get('foo')); + $this->assertSame($h, $request->getHeader('foo')); + } + + /** + * @group ZF2-480 + */ + public function testBaseurlFallsBackToRootPathIfScriptFilenameIsNotSet() + { + $request = new Request(); + $server = $request->getServer(); + $server->set('SCRIPT_NAME', null); + $server->set('PHP_SELF', null); + $server->set('ORIG_SCRIPT_NAME', null); + $server->set('ORIG_SCRIPT_NAME', null); + $server->set('SCRIPT_FILENAME', null); + + $this->assertEquals('', $request->getBaseUrl()); + } +} diff --git a/test/RequestTest.php b/test/RequestTest.php new file mode 100644 index 0000000000..ab84eca0d4 --- /dev/null +++ b/test/RequestTest.php @@ -0,0 +1,254 @@ +assertEquals(Request::METHOD_GET, $request->getMethod()); + $this->assertEquals('/foo', $request->getUri()); + $this->assertEquals(Request::VERSION_11, $request->getVersion()); + $this->assertEquals('Some Content', $request->getContent()); + } + + public function testRequestUsesParametersContainerByDefault() + { + $request = new Request(); + $this->assertInstanceOf('Zend\Stdlib\Parameters', $request->getQuery()); + $this->assertInstanceOf('Zend\Stdlib\Parameters', $request->getPost()); + $this->assertInstanceOf('Zend\Stdlib\Parameters', $request->getFiles()); + } + + public function testRequestAllowsSettingOfParameterContainer() + { + $request = new Request(); + $p = new \Zend\Stdlib\Parameters(); + $request->setQuery($p); + $request->setPost($p); + $request->setFiles($p); + + $this->assertSame($p, $request->getQuery()); + $this->assertSame($p, $request->getPost()); + $this->assertSame($p, $request->getFiles()); + + $headers = new Headers(); + $request->setHeaders($headers); + $this->assertSame($headers, $request->getHeaders()); + } + + public function testRetrievingASingleValueForParameters() + { + $request = new Request(); + $p = new \Zend\Stdlib\Parameters(array( + 'foo' => 'bar' + )); + $request->setQuery($p); + $request->setPost($p); + $request->setFiles($p); + + $this->assertSame('bar', $request->getQuery('foo')); + $this->assertSame('bar', $request->getPost('foo')); + $this->assertSame('bar', $request->getFiles('foo')); + + $headers = new Headers(); + $h = new GenericHeader('foo','bar'); + $headers->addHeader($h); + + $request->setHeaders($headers); + $this->assertSame($headers, $request->getHeaders()); + $this->assertSame($h, $request->getHeaders()->get('foo')); + $this->assertSame($h, $request->getHeader('foo')); + } + + public function testParameterRetrievalDefaultValue() + { + $request = new Request(); + $p = new \Zend\Stdlib\Parameters(array( + 'foo' => 'bar' + )); + $request->setQuery($p); + $request->setPost($p); + $request->setFiles($p); + + $default = 15; + $this->assertSame($default, $request->getQuery('baz', $default)); + $this->assertSame($default, $request->getPost('baz', $default)); + $this->assertSame($default, $request->getFiles('baz', $default)); + $this->assertSame($default, $request->getHeaders('baz', $default)); + $this->assertSame($default, $request->getHeader('baz', $default)); + } + + public function testRequestPersistsRawBody() + { + $request = new Request(); + $request->setContent('foo'); + $this->assertEquals('foo', $request->getContent()); + } + + public function testRequestUsesHeadersContainerByDefault() + { + $request = new Request(); + $this->assertInstanceOf('Zend\Http\Headers', $request->getHeaders()); + } + + public function testRequestCanSetHeaders() + { + $request = new Request(); + $headers = new \Zend\Http\Headers(); + + $ret = $request->setHeaders($headers); + $this->assertInstanceOf('Zend\Http\Request', $ret); + $this->assertSame($headers, $request->getHeaders()); + } + + public function testRequestCanSetAndRetrieveValidMethod() + { + $request = new Request(); + $request->setMethod('POST'); + $this->assertEquals('POST', $request->getMethod()); + } + + public function testRequestCanAlwaysForcesUppecaseMethodName() + { + $request = new Request(); + $request->setMethod('get'); + $this->assertEquals('GET', $request->getMethod()); + } + + /** + * @dataProvider uriDataProvider + */ + public function testRequestCanSetAndRetrieveUri($uri) + { + $request = new Request(); + $request->setUri($uri); + $this->assertEquals($uri, $request->getUri()); + $this->assertInstanceOf('Zend\Uri\Uri', $request->getUri()); + $this->assertEquals($uri, $request->getUri()->toString()); + $this->assertEquals($uri, $request->getUriString()); + } + + public function uriDataProvider() + { + return array( + array('/foo'), + array('/foo#test'), + array('/hello?what=true#noway') + ); + } + + public function testRequestSetUriWillThrowExceptionOnInvalidArgument() + { + $request = new Request(); + + $this->setExpectedException('Zend\Http\Exception\InvalidArgumentException', 'must be an instance of'); + $request->setUri(new \stdClass()); + } + + public function testRequestCanSetAndRetrieveVersion() + { + $request = new Request(); + $this->assertEquals('1.1', $request->getVersion()); + $request->setVersion(Request::VERSION_10); + $this->assertEquals('1.0', $request->getVersion()); + } + + public function testRequestSetVersionWillThrowExceptionOnInvalidArgument() + { + $request = new Request(); + + $this->setExpectedException('Zend\Http\Exception\InvalidArgumentException', + 'Not valid or not supported HTTP version'); + $request->setVersion('1.2'); + } + + /** + * @dataProvider getMethods + */ + public function testRequestMethodCheckWorksForAllMethods($methodName) + { + $request = new Request; + $request->setMethod($methodName); + + foreach ($this->getMethods(false, $methodName) as $testMethodName => $testMethodValue) { + $this->assertEquals($testMethodValue, $request->{'is' . $testMethodName}()); + } + } + + public function testRequestCanBeCastToAString() + { + $request = new Request(); + $request->setMethod(Request::METHOD_GET); + $request->setUri('/'); + $request->setContent('foo=bar&bar=baz'); + $this->assertEquals("GET / HTTP/1.1\r\n\r\nfoo=bar&bar=baz", $request->toString()); + } + + public function testRequestIsXmlHttpRequest() + { + $request = new Request(); + $this->assertFalse($request->isXmlHttpRequest()); + + $request = new Request(); + $request->getHeaders()->addHeaderLine('X_REQUESTED_WITH', 'FooBazBar'); + $this->assertFalse($request->isXmlHttpRequest()); + + $request = new Request(); + $request->getHeaders()->addHeaderLine('X_REQUESTED_WITH', 'XMLHttpRequest'); + $this->assertTrue($request->isXmlHttpRequest()); + } + + public function testRequestIsFlashRequest() + { + $request = new Request(); + $this->assertFalse($request->isFlashRequest()); + + $request = new Request(); + $request->getHeaders()->addHeaderLine('USER_AGENT', 'FooBazBar'); + $this->assertFalse($request->isFlashRequest()); + + $request = new Request(); + $request->getHeaders()->addHeaderLine('USER_AGENT', 'Shockwave Flash'); + $this->assertTrue($request->isFlashRequest()); + } + + /** + * PHPUNIT DATA PROVIDER + * + * @param $providerContext + * @param null $trueMethod + * @return array + */ + public function getMethods($providerContext, $trueMethod = null) + { + $refClass = new \ReflectionClass('Zend\Http\Request'); + $return = array(); + foreach ($refClass->getConstants() as $cName => $cValue) { + if (substr($cName, 0, 6) == 'METHOD') { + if ($providerContext) { + $return[] = array($cValue); + } else { + $return[strtolower($cValue)] = ($trueMethod == $cValue) ? true : false; + } + } + } + return $return; + } +} diff --git a/test/Response/ResponseStreamTest.php b/test/Response/ResponseStreamTest.php new file mode 100644 index 0000000000..3a05a2afc0 --- /dev/null +++ b/test/Response/ResponseStreamTest.php @@ -0,0 +1,112 @@ +assertEquals(200, $response->getStatusCode()); + $this->assertEquals("Foo Bar\r\nBar Foo", $response->getBody()); + } + + public function testGzipResponse () + { + $stream = fopen(__DIR__ . '/../_files/response_gzip','rb'); + + $headers = ''; + while (false!== ($newLine = fgets($stream))) { + $headers .= $newLine; + if ($headers == "\n" || $headers == "\r\n") { + break; + } + } + + + $headers .= fread($stream, 100); //Should accept also part of body as text + + $res = Stream::fromStream($headers, $stream); + + $this->assertEquals('gzip', $res->getHeaders()->get('Content-encoding')->getFieldValue()); + $this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody())); + $this->assertEquals('f24dd075ba2ebfb3bf21270e3fdc5303', md5($res->getContent())); + } + + + public function test300isRedirect() + { + $values = $this->readResponse('response_302'); + $response = Stream::fromStream($values['data'],$values['stream']); + + $this->assertEquals(302, $response->getStatusCode(), 'Response code is expected to be 302, but it\'s not.'); + $this->assertFalse($response->isClientError(), 'Response is an error, but isClientError() returned true'); + $this->assertFalse($response->isForbidden(), 'Response is an error, but isForbidden() returned true'); + $this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true'); + $this->assertFalse($response->isNotFound(), 'Response is an error, but isNotFound() returned true'); + $this->assertFalse($response->isOk(), 'Response is an error, but isOk() returned true'); + $this->assertFalse($response->isServerError(), 'Response is an error, but isServerError() returned true'); + $this->assertTrue($response->isRedirect(), 'Response is an error, but isRedirect() returned false'); + $this->assertFalse($response->isSuccess(), 'Response is an error, but isSuccess() returned true'); + } + + + public function testMultilineHeader() + { + $values = $this->readResponse('response_multiline_header'); + $response = Stream::fromStream($values['data'],$values['stream']); + + // Make sure we got the corrent no. of headers + $this->assertEquals(6, count($response->getHeaders()), 'Header count is expected to be 6'); + + // Check header integrity + $this->assertEquals('timeout=15,max=100', $response->getHeaders()->get('keep-alive')->getFieldValue()); + $this->assertEquals('text/html;charset=iso-8859-1', $response->getHeaders()->get('content-type')->getFieldValue()); + } + + + /** + * Helper function: read test response from file + * + * @param string $response + * @return string + */ + protected function readResponse($response) + { + + $stream = fopen(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $response, 'rb'); + + $data = ''; + while (false!== ($newLine = fgets($stream))) { + $data .= $newLine; + if ($newLine == "\n" || $newLine == "\r\n") { + break; + } + } + + + $data .= fread($stream, 100); //Should accept also part of body as text + + $return = array(); + $return['stream'] = $stream; + $return['data'] = $data; + + return $return; + } +} diff --git a/test/ResponseTest.php b/test/ResponseTest.php new file mode 100644 index 0000000000..651a2cc4ea --- /dev/null +++ b/test/ResponseTest.php @@ -0,0 +1,344 @@ +assertEquals(200, $response->getStatusCode()); + $this->assertEquals('Foo Bar', $response->getContent()); + } + + public function testResponseCanRenderStatusLine() + { + $response = new Response; + $response->setVersion(1.1); + $response->setStatusCode(Response::STATUS_CODE_404); + $this->assertEquals('HTTP/1.1 404 Not Found', $response->renderStatusLine()); + + $response->setReasonPhrase('Foo Bar'); + $this->assertEquals('HTTP/1.1 404 Foo Bar', $response->renderStatusLine()); + } + + public function testResponseUsesHeadersContainerByDefault() + { + $response = new Response(); + $this->assertInstanceOf('Zend\Http\Headers', $response->getHeaders()); + } + + public function testRequestCanSetHeaders() + { + $response = new Response(); + $headers = new \Zend\Http\Headers(); + + $ret = $response->setHeaders($headers); + $this->assertInstanceOf('Zend\Http\Response', $ret); + $this->assertSame($headers, $response->getHeaders()); + } + + public function testResponseCanSetStatusCode() + { + $response = new Response; + $this->assertEquals(200, $response->getStatusCode()); + $response->setStatusCode('303'); + $this->assertEquals(303, $response->getStatusCode()); + } + + public function testResponseSetStatusCodeThrowsExceptionOnInvalidCode() + { + $response = new Response; + $this->setExpectedException('Zend\Http\Exception\InvalidArgumentException', 'Invalid status code'); + $response->setStatusCode(606); + } + + public function testResponseEndsAtStatusCode() + { + $string = 'HTTP/1.0 200' . "\r\n\r\n" . 'Foo Bar'; + $response = Response::fromString($string); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('Foo Bar', $response->getContent()); + } + + public function testGzipResponse () + { + $response_text = file_get_contents(__DIR__ . '/_files/response_gzip'); + + $res = Response::fromString($response_text); + + $this->assertEquals('gzip', $res->getHeaders()->get('Content-encoding')->getFieldValue()); + $this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody())); + $this->assertEquals('f24dd075ba2ebfb3bf21270e3fdc5303', md5($res->getContent())); + } + + public function testDeflateResponse () + { + $response_text = file_get_contents(__DIR__ . '/_files/response_deflate'); + + $res = Response::fromString($response_text); + + $this->assertEquals('deflate', $res->getHeaders()->get('Content-encoding')->getFieldValue()); + $this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody())); + $this->assertEquals('ad62c21c3aa77b6a6f39600f6dd553b8', md5($res->getContent())); + } + + /** + * Make sure wer can handle non-RFC complient "deflate" responses. + * + * Unlike stanrdard 'deflate' response, those do not contain the zlib header + * and trailer. Unfortunately some buggy servers (read: IIS) send those and + * we need to support them. + * + * @link http://framework.zend.com/issues/browse/ZF-6040 + */ + public function testNonStandardDeflateResponseZF6040() + { + $this->markTestSkipped('Not correctly handling non-RFC complient "deflate" responses'); + $response_text = file_get_contents(__DIR__ . '/_files/response_deflate_iis'); + + $res = Response::fromString($response_text); + + $this->assertEquals('deflate', $res->getHeaders()->get('Content-encoding')->getFieldValue()); + $this->assertEquals('d82c87e3d5888db0193a3fb12396e616', md5($res->getBody())); + $this->assertEquals('c830dd74bb502443cf12514c185ff174', md5($res->getContent())); + } + + public function testChunkedResponse () + { + $response_text = file_get_contents(__DIR__ . '/_files/response_chunked'); + + $res = Response::fromString($response_text); + + $this->assertEquals('chunked', $res->getHeaders()->get('Transfer-encoding')->getFieldValue()); + $this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody())); + $this->assertEquals('c0cc9d44790fa2a58078059bab1902a9', md5($res->getContent())); + } + + public function testChunkedResponseCaseInsensitiveZF5438() + { + $response_text = file_get_contents(__DIR__ . '/_files/response_chunked_case'); + + $res = Response::fromString($response_text); + + $this->assertEquals('chunked', strtolower($res->getHeaders()->get('Transfer-encoding')->getFieldValue())); + $this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody())); + $this->assertEquals('c0cc9d44790fa2a58078059bab1902a9', md5($res->getContent())); + } + + public function testLineBreaksCompatibility() + { + $response_text_lf = $this->readResponse('response_lfonly'); + $res_lf = Response::fromString($response_text_lf); + + $response_text_crlf = $this->readResponse('response_crlf'); + $res_crlf = Response::fromString($response_text_crlf); + + $this->assertEquals($res_lf->getHeaders()->toString(), $res_crlf->getHeaders()->toString(), 'Responses headers do not match'); + + $this->markTestIncomplete('Something is fishy with the response bodies in the test responses'); + $this->assertEquals($res_lf->getBody(), $res_crlf->getBody(), 'Response bodies do not match'); + } + + public function test404IsClientErrorAndNotFound() + { + $response_text = $this->readResponse('response_404'); + $response = Response::fromString($response_text); + + $this->assertEquals(404, $response->getStatusCode(), 'Response code is expected to be 404, but it\'s not.'); + $this->assertTrue($response->isClientError(), 'Response is an error, but isClientError() returned false'); + $this->assertFalse($response->isForbidden(), 'Response is an error, but isForbidden() returned true'); + $this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true'); + $this->assertTrue($response->isNotFound(), 'Response is an error, but isNotFound() returned false'); + $this->assertFalse($response->isOk(), 'Response is an error, but isOk() returned true'); + $this->assertFalse($response->isServerError(), 'Response is an error, but isServerError() returned true'); + $this->assertFalse($response->isRedirect(), 'Response is an error, but isRedirect() returned true'); + $this->assertFalse($response->isSuccess(), 'Response is an error, but isSuccess() returned true'); + } + + public function test500isError() + { + $response_text = $this->readResponse('response_500'); + $response = Response::fromString($response_text); + + $this->assertEquals(500, $response->getStatusCode(), 'Response code is expected to be 500, but it\'s not.'); + $this->assertFalse($response->isClientError(), 'Response is an error, but isClientError() returned true'); + $this->assertFalse($response->isForbidden(), 'Response is an error, but isForbidden() returned true'); + $this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true'); + $this->assertFalse($response->isNotFound(), 'Response is an error, but isNotFound() returned true'); + $this->assertFalse($response->isOk(), 'Response is an error, but isOk() returned true'); + $this->assertTrue($response->isServerError(), 'Response is an error, but isServerError() returned false'); + $this->assertFalse($response->isRedirect(), 'Response is an error, but isRedirect() returned true'); + $this->assertFalse($response->isSuccess(), 'Response is an error, but isSuccess() returned true'); + } + + /** + * @group ZF-5520 + */ + public function test302LocationHeaderMatches() + { + $headerName = 'Location'; + $headerValue = 'http://www.google.com/ig?hl=en'; + $response = Response::fromString($this->readResponse('response_302')); + $responseIis = Response::fromString($this->readResponse('response_302_iis')); + + $this->assertEquals($headerValue, $response->getHeaders()->get($headerName)->getFieldValue()); + $this->assertEquals($headerValue, $responseIis->getHeaders()->get($headerName)->getFieldValue()); + } + + public function test300isRedirect() + { + $response = Response::fromString($this->readResponse('response_302')); + + $this->assertEquals(302, $response->getStatusCode(), 'Response code is expected to be 302, but it\'s not.'); + $this->assertFalse($response->isClientError(), 'Response is an error, but isClientError() returned true'); + $this->assertFalse($response->isForbidden(), 'Response is an error, but isForbidden() returned true'); + $this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true'); + $this->assertFalse($response->isNotFound(), 'Response is an error, but isNotFound() returned true'); + $this->assertFalse($response->isOk(), 'Response is an error, but isOk() returned true'); + $this->assertFalse($response->isServerError(), 'Response is an error, but isServerError() returned true'); + $this->assertTrue($response->isRedirect(), 'Response is an error, but isRedirect() returned false'); + $this->assertFalse($response->isSuccess(), 'Response is an error, but isSuccess() returned true'); + } + + public function test200Ok() + { + $response = Response::fromString($this->readResponse('response_deflate')); + + $this->assertEquals(200, $response->getStatusCode(), 'Response code is expected to be 200, but it\'s not.'); + $this->assertFalse($response->isClientError(), 'Response is an error, but isClientError() returned true'); + $this->assertFalse($response->isForbidden(), 'Response is an error, but isForbidden() returned true'); + $this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true'); + $this->assertFalse($response->isNotFound(), 'Response is an error, but isNotFound() returned true'); + $this->assertTrue($response->isOk(), 'Response is an error, but isOk() returned false'); + $this->assertFalse($response->isServerError(), 'Response is an error, but isServerError() returned true'); + $this->assertFalse($response->isRedirect(), 'Response is an error, but isRedirect() returned true'); + $this->assertTrue($response->isSuccess(), 'Response is an error, but isSuccess() returned false'); + } + + public function test100Continue() + { + $this->markTestIncomplete(); + } + + public function testAutoMessageSet() + { + $response = Response::fromString($this->readResponse('response_403_nomessage')); + + $this->assertEquals(403, $response->getStatusCode(), 'Response status is expected to be 403, but it isn\'t'); + $this->assertEquals('Forbidden', $response->getReasonPhrase(), 'Response is 403, but message is not "Forbidden" as expected'); + + // While we're here, make sure it's classified as error... + $this->assertTrue($response->isClientError(), 'Response is an error, but isClientError() returned false'); + $this->assertTrue($response->isForbidden(), 'Response is an error, but isForbidden() returned false'); + $this->assertFalse($response->isInformational(), 'Response is an error, but isInformational() returned true'); + $this->assertFalse($response->isNotFound(), 'Response is an error, but isNotFound() returned true'); + $this->assertFalse($response->isOk(), 'Response is an error, but isOk() returned true'); + $this->assertFalse($response->isServerError(), 'Response is an error, but isServerError() returned true'); + $this->assertFalse($response->isRedirect(), 'Response is an error, but isRedirect() returned true'); + $this->assertFalse($response->isSuccess(), 'Response is an error, but isSuccess() returned true'); + } + + public function testToString() + { + $response_str = $this->readResponse('response_404'); + $response = Response::fromString($response_str); + + $this->assertEquals(strtolower(str_replace("\n", "\r\n", $response_str)), strtolower($response->toString()), 'Response convertion to string does not match original string'); + $this->assertEquals(strtolower(str_replace("\n", "\r\n", $response_str)), strtolower((string)$response), 'Response convertion to string does not match original string'); + } + + public function testToStringGzip() + { + $response_str = $this->readResponse('response_gzip'); + $response = Response::fromString($response_str); + + $this->assertEquals(strtolower($response_str), strtolower($response->toString()), 'Response convertion to string does not match original string'); + $this->assertEquals(strtolower($response_str), strtolower((string)$response), 'Response convertion to string does not match original string'); + } + + public function testGetHeaders() + { + $response = Response::fromString($this->readResponse('response_deflate')); + $headers = $response->getHeaders(); + + $this->assertEquals(8, count($headers), 'Header count is not as expected'); + $this->assertEquals('Apache', $headers->get('Server')->getFieldValue(), 'Server header is not as expected'); + $this->assertEquals('deflate', $headers->get('Content-encoding')->getFieldValue(), 'Content-type header is not as expected'); + } + + public function testGetVersion() + { + $response = Response::fromString($this->readResponse('response_chunked')); + $this->assertEquals(1.1, $response->getVersion(), 'Version is expected to be 1.1'); + } + + public function testUnknownCode() + { + $response_str = $this->readResponse('response_unknown'); + $this->setExpectedException('InvalidArgumentException', 'Invalid status code provided: "550"'); + $response = Response::fromString($response_str); + } + + public function testMultilineHeader() + { + $response = Response::fromString($this->readResponse('response_multiline_header')); + + // Make sure we got the corrent no. of headers + $this->assertEquals(6, count($response->getHeaders()), 'Header count is expected to be 6'); + + // Check header integrity + $this->assertEquals('timeout=15,max=100', $response->getHeaders()->get('keep-alive')->getFieldValue()); + $this->assertEquals('text/html;charset=iso-8859-1', $response->getHeaders()->get('content-type')->getFieldValue()); + } + + /** + * Make sure a response with some leading whitespace in the response body + * does not get modified (see ZF-1924) + * + */ + public function testLeadingWhitespaceBody() + { + $response = Response::fromString($this->readResponse('response_leadingws')); + $this->assertEquals($response->getContent(), "\r\n\t \n\r\tx", 'Extracted body is not identical to expected body'); + } + + /** + * Test that parsing a multibyte-encoded chunked response works. + * + * This can potentially fail on different PHP environments - for example + * when mbstring.func_overload is set to overload strlen(). + * + */ + public function testMultibyteChunkedResponse() + { + $this->markTestSkipped('Looks like the headers are split with \n and the body with \r\n'); + $md5 = 'ab952f1617d0e28724932401f2d3c6ae'; + + $response = Response::fromString($this->readResponse('response_multibyte_body')); + $this->assertEquals($md5, md5($response->getBody())); + } + + /** + * Helper function: read test response from file + * + * @param string $response + * @return string + */ + protected function readResponse($response) + { + return file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . $response); + } +} diff --git a/test/_files/body b/test/_files/body new file mode 100644 index 0000000000..b5406c451e --- /dev/null +++ b/test/_files/body @@ -0,0 +1,158 @@ + + + + + + + + Paamayim Nekudotayim - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + +
+
+
+ +
Your continued donations keep Wikipedia running!    
+

Paamayim Nekudotayim

+
+

From Wikipedia, the free encyclopedia

+
+
Jump to: navigation, search
+

The Scope Resolution Operator (::) in PHP is officially called Paamayim Nekudotayim (IPA: [paʔamajim nəkudotajim]). It means 'twice colon' or 'double colon' in Hebrew.

+


+ +

+

Etymology

+

Nekudotayim (נקודתיים) means 'colon'; it comes from nekuda (IPA: [nəkuda]), 'point' or 'dot', and the dual suffix ayim (יים-), hence 'two points'. Similarly, the word paamayim (פעמיים) is derived by attaching the dual suffix to paam (IPA: [paʔam]) ('one time' or 'once'), thus yielding 'twice'.

+

The name was introduced in the Zend Engine 0.5 used in PHP 3. Although it has been confusing to many developers, it is still being used in PHP 5, as in this sample error message:

+
+Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in...
+
+ +

+

See also

+ + +

+

References

+
    +
  • [1] PHP5 OOP Manual chapter that explains the Paamayim Nekudotayim
  • +
+ + + + + +
+
+
+
+
+
+
Views
+ +
+
+
Personal tools
+ +
+ + + + + +
+
In other languages
+
+ +
+
+
+
+ + +
+ + + diff --git a/test/_files/response_302 b/test/_files/response_302 new file mode 100644 index 0000000000000000000000000000000000000000..e274f0aada166e932d98cb7178a31b291460f136 GIT binary patch literal 592 zcmYLF>rN9v7_Ar^oG^iKiCU8~qTFJ)duc$khR_I71EmnUi9Z^DY?fhn!!lEr>BT1U z2Kq~%z@PF8#=pjA@EIh&0cTt&oAaG>=JK7{nYW&|Z`x&>RV;^X$&NUrq%H$r#ZtH| zQmXBmX0O*Phf;u9|4Ji~C%J4q4xyuyqD>G6j2_;RQc6sWu+PkLd zlx<49)E8$*JT=~kKpw^-bXgE}#4*QtHz~JE;UI2Pvgs?NHhooM_Dj}~UFKNUDzhrC zWxLJ_tL=Jd_JfEUIwF;c%i3w&MO%sW4IN_0VffoI2eQbh}s<3!?_&XO7;9cBrN9v7_Ar^oG^iKiCU8~qTFJ)duc$khR_I71EmnUi9Z^DY?fhn!!lEr>BT1U z2Kq~%z@PF8#=pjA@EIh&0cTt&oAaG>=JK7{nYW&|Z`x&>RV;^X$&NUrq%H$r#ZtJD zQf=2Xd%a#clrl_sIglr29Ii*n8W)soNTIk;hWDY(U8Z8ld6U$TbmGRLx3nN@Ku z+jUl0ZP!DyA4J^H5vfdE)=uLt+Dfc%=nz95ug%R-LP=1eWcKpskI|g|M&RnGopF#g z0RnbWwe%~CZ|V>nW;jGla!oJ!xTyfU`nzjaI4kX zhCE4reS^&C=H>ZO@U#D$25>`E4~+N2!5G{})aJk*&gI}%s^|A1LBFU^0@j@+a3LGC zUgqJU?O77+sukVi~yeTCXC080#6gU6? literal 0 HcmV?d00001 diff --git a/test/_files/response_403_nomessage b/test/_files/response_403_nomessage new file mode 100644 index 0000000000..e917ade189 --- /dev/null +++ b/test/_files/response_403_nomessage @@ -0,0 +1,18 @@ +HTTP/1.1 403 +Date: Fri, 17 Nov 2006 22:27:35 GMT +Server: Apache +Content-Length: 264 +Keep-Alive: timeout=15, max=99 +Connection: Keep-Alive +Content-Type: text/html; charset=iso-8859-1 + + + +403 Forbidden + +

Forbidden

+

You don't have permission to access / +on this server.

+
+
Apache Server at shahar.local Port 80
+ diff --git a/test/_files/response_404 b/test/_files/response_404 new file mode 100644 index 0000000000..39caf7fbe3 --- /dev/null +++ b/test/_files/response_404 @@ -0,0 +1,17 @@ +HTTP/1.1 404 Not Found +Date: Fri, 17 Nov 2006 22:22:40 GMT +Server: Apache +Content-Length: 272 +Keep-Alive: timeout=15, max=100 +Connection: Keep-Alive +Content-Type: text/html; charset=iso-8859-1 + + + +404 Not Found + +

Not Found

+

The requested URL /some/wrong/path was not found on this server.

+
+
Apache Server at localhost Port 80
+ diff --git a/test/_files/response_500 b/test/_files/response_500 new file mode 100644 index 0000000000..c8b8a1c52e --- /dev/null +++ b/test/_files/response_500 @@ -0,0 +1,24 @@ +HTTP/1.1 500 Internal Server Error +Date: Sat, 18 Nov 2006 03:10:33 GMT +Server: Apache +Content-Length: 596 +Connection: close +Content-Type: text/html; charset=iso-8859-1 + + + +500 Internal Server Error + +

Internal Server Error

+

The server encountered an internal error or +misconfiguration and was unable to complete +your request.

+

Please contact the server administrator, + root@localhost and inform them of the time the error occurred, +and anything you might have done that may have +caused the error.

+

More information about this error may be available +in the server error log.

+
+
Apache Server at shahar.local Port 80
+ diff --git a/test/_files/response_chunked b/test/_files/response_chunked new file mode 100644 index 0000000000..d81506caed --- /dev/null +++ b/test/_files/response_chunked @@ -0,0 +1,191 @@ +HTTP/1.1 200 OK +Date: Sun, 25 Jun 2006 19:55:19 GMT +Server: Apache +X-powered-by: PHP/5.1.4-pl3-gentoo +Connection: close +Transfer-encoding: chunked +Content-type: text/html + +1f98 + + + + + + + + Paamayim Nekudotayim - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + + + +
+
+
+ +
Your continued donations keep Wikipedia running!    
+ +

Paamayim Nekudotayim

+
+

From Wikipedia, the free encyclopedia

+
+
Jump to: navigation, search
+ +

The Scope Resolution Operator (::) in PHP is officially called Paamayim Nekudotayim (IPA: [paʔamajim nəkudotajim]). It means 'twice colon' or 'double colon' in Hebrew.

+ +


+ +

+

Etymology

+

Nekudotayim (נקודתיים) means 'colon'; it comes from nekuda (IPA: [nəkuda]), 'point' or 'dot', and the dual suffix ayim (יים-), hence 'two points'. Similarly, the word paamayim (פעמיים) is derived by attaching the dual suffix to paam (IPA: [paʔam]) ('one time' or 'once'), thus yielding 'twice'.

+ +

The name was introduced in the Zend Engine 0.5 used in PHP 3. Although it has been confusing to many developers, it is still being used in PHP 5, as in this sample error message:

+
+Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in...
+
+ +

+

See also

+ + +

+

References

+
    +
  • [1] PHP5 OOP Manual chapter that explains the Paamayim Nekudotayim
  • + +
+ + + + + + +
+
+
+
+
+
+
Views
+ +
+
+ +
Personal tools
+ +
+ + + + + +
+
In other languages
+
+ + +
+
+
+
+ + + +
+ + + +0 + diff --git a/test/_files/response_chunked_case b/test/_files/response_chunked_case new file mode 100644 index 0000000000..65240b8ad9 --- /dev/null +++ b/test/_files/response_chunked_case @@ -0,0 +1,191 @@ +HTTP/1.1 200 OK +Date: Sun, 25 Jun 2006 19:55:19 GMT +Server: Apache +X-powered-by: PHP/5.1.4-pl3-gentoo +Connection: close +Transfer-encoding: ChunkeD +Content-type: text/html + +1f98 + + + + + + + + Paamayim Nekudotayim - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + + + +
+
+
+ +
Your continued donations keep Wikipedia running!    
+ +

Paamayim Nekudotayim

+
+

From Wikipedia, the free encyclopedia

+
+
Jump to: navigation, search
+ +

The Scope Resolution Operator (::) in PHP is officially called Paamayim Nekudotayim (IPA: [paʔamajim nəkudotajim]). It means 'twice colon' or 'double colon' in Hebrew.

+ +


+ +

+

Etymology

+

Nekudotayim (נקודתיים) means 'colon'; it comes from nekuda (IPA: [nəkuda]), 'point' or 'dot', and the dual suffix ayim (יים-), hence 'two points'. Similarly, the word paamayim (פעמיים) is derived by attaching the dual suffix to paam (IPA: [paʔam]) ('one time' or 'once'), thus yielding 'twice'.

+ +

The name was introduced in the Zend Engine 0.5 used in PHP 3. Although it has been confusing to many developers, it is still being used in PHP 5, as in this sample error message:

+
+Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in...
+
+ +

+

See also

+ + +

+

References

+
    +
  • [1] PHP5 OOP Manual chapter that explains the Paamayim Nekudotayim
  • + +
+ + + + + + +
+
+
+
+
+
+
Views
+ +
+
+ +
Personal tools
+ +
+ + + + + +
+
In other languages
+
+ + +
+
+
+
+ + + +
+ + + +0 + diff --git a/test/_files/response_crlf b/test/_files/response_crlf new file mode 100644 index 0000000000..2fb3103507 --- /dev/null +++ b/test/_files/response_crlf @@ -0,0 +1,174 @@ +HTTP/1.0 200 OK +Date: Sun, 02 Jul 2006 20:17:26 GMT +Server: Apache +X-powered-by: PHP/5.1.2 +Content-language: en +Etag: W/"enwiki:pcache:idhash:1794050-0!1!0!0!!en!2--20060614224938" +Vary: Accept-Encoding,Cookie +Cache-control: private, s-maxage=0, max-age=0, must-revalidate +Last-modified: Wed, 14 Jun 2006 22:49:38 GMT +Content-type: text/html; charset=utf-8 +X-cache: MISS from srv8.wikimedia.org +X-cache: MISS from iris.knams.wikimedia.org +X-cache-lookup: MISS from srv8.wikimedia.org:80 +X-cache-lookup: MISS from iris.knams.wikimedia.org:80 +Connection: close + + + + + + + + + Paamayim Nekudotayim - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + +
+
+
+ +
Your continued donations keep Wikipedia running!    
+

Paamayim Nekudotayim

+
+

From Wikipedia, the free encyclopedia

+
+
Jump to: navigation, search
+

The Scope Resolution Operator (::) in PHP is officially called Paamayim Nekudotayim (IPA: [paʔamajim nəkudotajim]). It means 'twice colon' or 'double colon' in Hebrew.

+


+ +

+

Etymology

+

Nekudotayim (נקודתיים) means 'colon'; it comes from nekuda (IPA: [nəkuda]), 'point' or 'dot', and the dual suffix ayim (יים-), hence 'two points'. Similarly, the word paamayim (פעמיים) is derived by attaching the dual suffix to paam (IPA: [paʔam]) ('one time' or 'once'), thus yielding 'twice'.

+

The name was introduced in the Zend Engine 0.5 used in PHP 3. Although it has been confusing to many developers, it is still being used in PHP 5, as in this sample error message:

+
+Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in...
+
+ +

+

See also

+ + +

+

References

+
    +
  • [1] PHP5 OOP Manual chapter that explains the Paamayim Nekudotayim
  • +
+ + + + + +
+
+
+
+
+
+
Views
+ +
+
+
Personal tools
+ +
+ + + + + +
+
In other languages
+
+ +
+
+
+
+ + +
+ + + diff --git a/test/_files/response_deflate b/test/_files/response_deflate new file mode 100644 index 0000000000000000000000000000000000000000..4295149294962004c14512bdfc95f5ef9e63ef3a GIT binary patch literal 4064 zcmV<64`>3R7irc4cxpAVF|pV`ya!3Ro?0Z+B&KWn?X4c{(6aNKh{|E-@}NEpTiz zEoWtJbZ>7B3PW#hbY*UIEoE+FZ)9n1XF4EcWoB$)bY%?+R$+2^Iv_z~V`Xr3Ek$l) zZ)9n1XAKHNZ*FvDZgeedWo~D5XgVM>I5aQ~3PW#hZe?S1X>V>iAY*KAb7c(*LvL<$ zWo~pWba`-PIv{jqcyuplbZu-63JnT)oXi^Aa@)A|Ir|kbcQ&#!qA1&OF0#a}*LD)O zYey41O}Ckh1|lH|F-dR%(6T!Hi?$E@&`zh*nVsox^r`&;|4z>Vc+<^xl6`2BiAdn! z+;DJiV0Yu__2I=2{bMrGaYXuWzx)2=kT|aAz1uzXyrYXF^1D|Tr{9z9R-0T%nkcTh zNND7F$7c?4CR(Q*&znxCt?6z{$gy|v#=Ayb+qgJ?baicIE7YOmf7LYvuj43Dy%lJ- zpFe+YS33yQiD)wJIV^EV^%Qd1b0RGP+k@_e(GY$gbYrF|L4+>*G2>S~=TIb?CEC51 zrOY9Led{@zU26{s?2}+ZrDD35>5==)AzpWi%pVN#hs>19(odc*tb)6&p%6H4Nn2=kuo4Hg`vZf@mBLZ8;$t2 zk*6E+~ch;G!P&f_AuYN zK*4^*2nI3*urz6c^dDJ(s&RbpQFr70;nBgx!TU!Z`4x{-5Suujy5xy+w_AH&ED|vk z;?j#LPg;QP+vmgngwd0Nr=cKc3BcDoF*K=M@AsePnZ)Q&nt$zYW+oE%$XAAv8R zWPn%uFdl<{&_YB*Dr;USF^6@Lxm$Vhuc#!*3F(nWA$ViojH0M1)f1emxNYpu@%CAg zPT`~vxkgbztv>3H6l3J0BEb146PbEE3E6ckouuCyKhbj^(m36x#sl;uotoD%OL|7Z zNE3n(TcWqtz05n2uPW17CVh&hAN^V9&`691^svTBU{DvBAF7yM!;bcLo;-c_xZQU4 z?}_|0->_d4y<5kG>OvGt7*;q&)aWaSyX4$x(x?^2A9Ot%jk`k;&Ik!2svyXNMYKr~ zu0wuBc-V8skr>kGoup~X%uF>EK$wdYH#hdVRkg7P-6BeBkve7;A7Fdw95&qfGXde0 z!5j`6G3;i$G~(l=V}jg183A%v@jtN6v-UTR|AWXR=?;CMGavq7Ka9^Gi7W|C&^DnE z$GsOYBq>)Ahn+%xAjGE-H;m4ma(Sl?>!V~m$yi82VKV`Rl61YHPc9ittC&e-mL#xd z<6$yX>HhNnT@S$bn3iph4t%7{y)apw%pRnZx?aC=UFxRr-BwFA1{QnBaR(D7eL z5#J-EPj>C{g}pQ=a+V(ye@dW_S)96BxCy;-{9nTh(V|1jE%`dP6-byIA7J>{s17yP ztU)P8WiUBO(hOv+ zdnM3;E-fNql|9%5u3Als)sDY*3YjOHoz4~kcV1%+EfA8z5;s2k@SlO_iU`OH15Tr8 zMj#uFz}UdPS6cy?-R1)3xh!>1WT}H9OD(D1k^!Tb z>fh`rbs^AA;)?agC52+$Hz4&%ZL0dqcDYsuE5NpuC21i5T%{$_~ke_iye!B`*T*w z!76Y5e)IFqUvB<%^RJtq;O}3ziXFE*vQIdk;+QFLLg1JHMS>`(T@8lx>2_z0jh1%S zB#l%+CT|U5VgI4YvI zEL@Cz^_BUx3%Fm?sL`VfVokthGdw$j7y(tIMb3H5BPyer_3T&>UCC|> zoSUAAY&=2Zn*f3#l%*I;Mwv2#3lh_023omd7!ajuA|&WZX&yxYj!<=+dtfcf5=SUF zjNo&alpqn9wgLyzvF9e0kdyVXk71`fM9mUSukBltWC^=Y;k*Lsi$VY3;Pl{!lheW3 z@o(N9y}rO_Aku2JAX_k){-375-8Gi@hK-m+pQ&!Q#TuNk#`?NBd#onbS!AsV2LiPqxAMTXfK&BY;uXn7 zONex@Rk&z3sl>!Z^~wY@wTRG6i-n-`OVIJ(Z+|cr0Ow_oynfv$rv?Q!UQ-|qWYgNv15ZWL<`9Op}g2>x1Y41Y(L)F-GzUq45%BT>GF+)(57g8 z!?ffK88NAbv$#ntX-$i|sHzcHS)b#7@)A_6Qx#5CT}3rEF43~mob5(2*6?pFWFSsJ zb$^93S?ZZknz>*Zs2WrA(KTAqLC{jV09LZlbTH^ve z&V2og3C>dWTQmJDuHfh%Mo>Q23ije*X<(_JV`}tTFDG>a>uy!)kFZk}vAn%A+4FzP z*%Te&o*6KwAG-qt9Aa+jZUV=4=wc_o#G+Va@*QU>jITU+2iCl+P`gM%)97*u*98oA zRwFzrBKxnX)FC$qZd1W8!LVQ2mQKN!VzLd@@{C!((N|{gpbJP6f`HMEv`<_@s?+c zK%qvlb+fs(Pv&~{m3)m{z6Q$INZqOv*C>wiS*bJOSqm5`Z;JhLhQ)=`KnOA0OQCc3*9_as#~ZOu!9P=7$U{c zIyylnAvx5LaX<>aMB~uT4vOjgo!IwvI3){U8WOT(yn^rI*u0sCvack6UyB-2D-o~U zxe~7d0fgqaP{2Bb6BeaQs9wQfC+|=z47^dg)&@*C4D_u=U?2t=D$qVxz3p{;VJlo^ zDO#BG+4lusALWhzLCo|-guuYj{8hjm4yPVkZIgR#VC_b92({QqNDP;?=J) zD6%ZBq>^JSRO02^ikj$psFqKRCwf=~(+j*T7T2E|MYTJfOCv#-)5$WW$fxZu z#LKz^tFR~wj_z>1pFN)g%HK?Gr`9+%?be*vLXrr(cWYD^L+c6gBf5<6AUScv?N*)48X(Q&j=0y(u#)H*lRhMBadrr7QGmN z?#l1-e2&(xdl!A}NkW7MdoOFjTQl6H!yi_w;V91tZOvvuvXgFe-R>cKq{q!wS z8p&l!BYA&HFCYQ<_sbGrAHt~8Tf`QY<;Mqi z)T(n$lCp&}ciTLIBykF!DICu5d$^TNHxtTHXd^wq@OZrL>z6!vFOd$)#e-+hu6MUF zorfqEX~@}&Pd&_sJ>})}B7fyKd8_$blWNRyS*s`vtUEMO$?`H3GGvexv~#xJ6;R|w ziPBkQ5bQN)FM`6*nfK?gAvzY|9?3;frkX1j949crM;yFwyWQE_X_B2Ca!O~W3D;T< z4b=yU{y_xQA_Q2xLV*f~N5*LZho-3rhZ!7-DohB}l-C*DUQI`hHQC<{Z&X6y3HAcbj%Z0k7E^gy=wsEUHpxj6w2JHJr6>b& z;)({dPt0!ncGVyi5r@UgpTOh*BnR;4-&ZFHbSSdhGWY{CU(i|{_s!2;DLr0=L=>2m zw-9>C5+-Rhr~xli&4EZA<5Bs1hiANhxh}Khb%N=~De7NKP<|N{GE- zr+Y2xYFvCof$2`FZhL<|)uC^CHYjg@}aU)^N~ z@4Wj403i_o&>`d$%i2RnRqsi*PZ7%6?cHbQUtb78F0&-#F4BP&8i7HC#ff-B)}BKQ z85K%OF5^9c4O%BQ-^glyQ{bN>;&HNb2Xm)m38@Jp-pI&^c$VJ!&WuM&Nfc>^&NCwv ziG*eP0m`rO^XDgQM3S4AOp7Up@{#~RgyAZnYjwov#q#0yLI}RGFl}X^(FB%+L`g){ zrQgL|W_fv6c6;CDRTKTFfwopTx10f&)lAK!V;a8y3cEbL4j0fY`JmvIYJ&1yCup1+{b-CUp(P z6yUzgwG|)!I|eg@@fqbByG4KrD(aTV5}cOf*X3`4nz8mCtOD!4hZWuB!RD$YEN$m;%~%&k>Go8!jl^xt zm#8~7M2JSdjsgX+9%{SN1WZKQ5_(xfxl8NqXXeeNwVGhMESoBf6|`oc^0rLW1^bHe|Ck7w80`$3>%S_Fxm|WM1&1s^8V6VU`&ZSp zNjZpLRshDv|4{PVYMH7C`USiXFO7w&HdD)+>% zI_de+yBUZauQYFJ)Z1_|D<_#)bcqTW%}krthTzBNAL>p$xjc77`NGc+w|Ih5B)^dY zj%Al8_y(<-UX?Fdd|i6&lpQR8$nNY0)uDT< zSK5A-?e_N%UX>0|i)Rki#?IB{W{-Elc#r-3-EE1SlPyCuFRkAp$u9JtvyZn?UKd5M z%a`nr6(fdKwt+1pm>Pt{Fm`VFV!B_7ms}V1#l$v56w-P$a$bH(DxiCi#HZHW@_BSR zEgxwH@a(yFP6^MwES%#zvc`!j#X?ia;NevdFBC-2l{+;|l+AQ@wktBHGaH9rF^s<- z(ujPag`a>F?5R%nRwf7dy6ar~)CKFjv+keswYJ7fJ={a6BDbBhqVj|nCf}s%0RC%L zPUdwrRkl#j&B3fl!`OF;4t+TrDunN%v)N9m&pj_2l5b08-g5~>Ps6QKVrs*G`EqPH zy{p!}1Zz+#eM&v%N?0br(|*3K3mz6-BER(lxLtL~yS9>?&U_|FXDrs;P!cX-ZEXlymVq6Sp08oCwQ;%&M*KN3in;%KVl!k!F8H%-dCqX;%=7-K8NxCD7#yTQv|D zuxvibp7EXJhfwwNrQ<8S(hz$I3*8a2==SG9_g_taX^8P2&ti+Bh4DszkHrycj2b1O zg_hEqN@$fPNM#HH^1b{|9z$xp=!+yGTI-Vpqsq8|2q-N3dVA9^LRCwv06(&X&n3yG zMW~fUR=MLni<+vr^%{2#y@)1p*+%=2ivth@3P(zi1~L%u`r8nuy+ANG~!zU?SwOifKsov)CCB$+{t zxxj%Ja+aP_ySy?4_bbdw$$6M67XK0O;bYABvx7G>T+NUxmnJ9EDws3f6W5Y7I2Zt) zOD;V_sKh-Gj7(H8G{;|A^S&R;<$t!&0;FB;s~?K-_ubpqc~6@&vt$>2wPQHHh6Hs?&Th%!j&Lad! zl+KD$mDBU;VEGb9os^uKr0NuOv`|b*_~bUxPKE{ zR19&c3?tMmvqr(C;*z2?O|g7png}6aE>;6o-8J1appUr>0~|oPv_#j!;ITlOf~f}O zdll2PK;p2O&gBkkJQUOCsbFQ#R+D6CJBk(snU@Avu85hU6bYAUj9TRSygm`Y1IOoK7d^1RvSB*I_MSU%}Zh=LCC@hHF{7Z&?C>Eq1t5LB;DZ)dTcA&&%S&(&S&lsczW9y+R9VhZ@Be5u-QpFO% z8q@&ym4n9^l72r5lF2l{J_iYA`?d`%8;BqV>Zk$!THrYRcBz8ynM8_Lu=@O<Y&I#b<9L6b$WA_FMNtB<6Z z-P9TZ^?=_h<2zG1%N;~BI$}B9jL}Ziw>35xBaDPYTM#6~2jyF$-|*PpP%J?81;*D= zP14knfY+)eD(Lp<4~S(iXj0Z5A_bYM>U22`EwjH$S8ZHUp#Q*;$`K;Az03r;2<#cW zxAbXlDg%OLm_f!o!AfAodK4B8x}*R@_X(9l-SnEp8VcjN#9|Qe7vM^!+Cf=1m%#r% z19f3=pUM%HAFgb#MMpzx8In)2zZJvFo(ei8$pwhARReqGj(wStg@EVfV1tl`GTFvI z9~p2_Coe(b6`@3X)*HYgHi-URDJwOc3eD`UE52ttBExD z!_Q~U7PJ<4PJ^06Xpl{CP;fgUtsQ~nuV>wx&iim!A0@dysNK_o37LOx2UJI(30S!_Qh8SvcY1|A`)@M$ zp5S;hTTO$ys9iL}{u=>On#4mWLx_-law$(_BU$P64EE6EJDVxK;3?4_N3bg3_37(u zku?@nva`3v48`{w`^!gtfygscF|K$!l7KUQgJb_C;%s$w4Zjz;kdBo;8u7H3yoE@F zSSd3&t)&oIhKiGqL;6LQNT?TjZIZ}Rby{Nwe9sh@#ufdora7KUmQ=}q&i|WH`-}bT z)Un*%oz>7PGXPRlqd?0DNwsQEBp)-MEn~yB@yJSf_fSh0fqXo$b{mz_ z--pJcZ2Le&Eqx_|JR!o!pKj4#Rr$zm4R78a@g9!y>)P67h_j?5#b~-?Q=)!epCoj~+uC9hQ{^HGb4upxB@K}qo5A^rqtVVtH>gY5^HMp@BGct$}ho( zI*-h5SiP|)Z13S`w=k3oeXeCn_pTGq zq@6#HTcU3GZN6tnX&`K?G)74DYZ%@&!z87J{=hJI?zjKFQ{=81gzxalMcnM7MWc$w zkH}B0QYD6*Ia73g_;?O;YPDhz&$f0b@vye;F3qtayT@&3GKCfoO3ddgLbeL1IVH(U zn=$CfL@|C{aG?aBTR(j=gPQkBeyOdtKJ$_KJfc0O$ig)w_VV*Brf!t7xa%+asHRgl zuI!twJM_TK$*AIlLshc;Llmz2(9D2t{hy7~8SW*;--yS3f}qkQay#j$8pZiRwBjYn>91TYuv)MzPQC7>;7F`%CmK8%pdeV{cOG| z5(72=5h^j5-DY&=v}4}g51DCdS&V1){#=LmyX(wOI|TOZ4^Fh+rjq>Yi~6bAF&ScBNnHKb$8nIHr({HN2yXVkw=nZrSUKq6`9PZ;zuJ?U z;klZ6+A#+yYcDNS0@5Z5ES9&)KLI#s0*$Jd`^n`3Jo}y0c^LDRf&~ zOx%N&{IqB9NGn^sT0bDd3mI10zS^(fGry7g{ZdK?WRARy^&d23R)kYVHOsx$6);A) z7Aq`xBCXl9$NJZu1Zh@~w$y@6p1N|L#-kpsGjrB2-}$Ql6mBzeMWZfS&eQgk>+(LU z+a25Q{zvzd)qY@E#PjEb4~8oP1CNZKaO8KR!n~{!_<=@C+3TGeAh`7IJ180OX{xoWMdw(B{^_H(S3*Z3tt*jX7$!N82oxQ$e}woA+v8zcus z3UXv}+w%R3*2tv$?0>A@&bB~eNv*PydoGn@iyvga|kc0*62#fwAAMCfqC~x3m0Nj8!qIW)W|UPCHz{9{-vDUlqYlZ_V`!KAYa# zcXU|n;q&a~PS8@eQ}7dC$v^5n3=uV0@A(5R?B9`|?_T%L`E(z#=k`4zL=D<>nn{dH zgQ`Pz4YkD>-q=eBaq&Z&2l(!Cbp~(yJ#~1mlVqHw7w`e=lZdYN(M~#{mv%*qe(zw4NEY;s>Afn%C}quq(m|S*=mGl3D^RpNt(CI9dK)FD%19^FrQiwv87>m|fd0d&n&L znp`G!sOulX?d#9UnX=H4>uikD8&96Ovx8Rl4B`{!9u`@yk;bk#I+o1_OHTw&vv+#P zZ*Dq8t{B(zg=3Il2>u@Zd?Ae0aw^dDC`$q8jP5bW6U*B}Zp?BZ)FYcP3 zbLG*a6vLe>{cgFcpHmT*Zyhb~C+$P7!;-5?DETaTd3o>%sK{`6e_GRx3l>_B_9X#! zMiph7JEw!2PwEG4?~5!5hrQYl)Tsny+;GNn={8%=B%S%OZzID>E>^GPJNQn5tn+`$ zY%0!@`EXE1eyb?n`4N&K`#b%q`rlevnrq<}^O^$z7P&0vHMEF^mZB$hJ0Ej?reaQ?})8zo^v(LY`Xp1ntxYpw@a z46u|g=mmw|{P(#Lln%z}$7j`d|4dWYvo+-knHm--E_ghdcCh_)+u946g8F-VJMJr# z;ZHLl0ME&NBjY~W(sR}(KigkhKP2rhQMw21MSZep_$}_R-2Jucji^nI1an~8MkL;Pmrb(^+9>q`9?Yf7AmaF&Yc0FC4*rjFgZHn;4i|7dzPZqO! ziE#B57_#70yoh+pLdM~iKD3JUc@E89QHYnsc3ouHJCD67ZCkFsnODa`8QW*hnumkS zd=GEg2l442IuiKfjiu1JB-q3SWocB`JhFdYPX1u}{7%_~6?%2(rV_6>{n&aVQG-}@9K@p&$U=N)u85uufcUsY|6ogF zl}N6*F(!mHzd4n!r4;kh6^?`6MbBM4A?xv<{}XmV22Jbr0{m~jTbh6NOK)sxsdKZ? zX|owgL5&^88V(I+;6bBB$t0$@9!E(e%)$veQ{c)=zg!#Vt>xO`S#$1HD-fw!=egr zzh{w~zu=oS9!0%I##ZY89OH^1dTW!8c_Xa`LqhOV*qn*6x{H!=XGB`x6O`2laLXAs zM322}X{Q|TgE>Gdwyd$PBzTM_x;Hx&gZkd5!o2#AJ)zEffnOb*VdO}*JVoVtV?=&x z8HF>Sw72DW1`Q8(RiLvr1q~D+48^X;0?uKVKkKDScLuN(QORd&*slYuQ=H6s5P+rd z`|<6g$t{M9!Oi82*tscypKnJsS=Ily?|1+i&IoF`uVHgXnJw^pLf`O})9||Y2~#*5 zhMnl!D!e&ucqdBj&$3LTGPk}*j?Xo`a0s#_9e8$6*5Pi2nKJLxwahvGB*+MS_ZVd`^4Q!e_5{!#1bT}KjxBZup85WGMx%RNky*e}M>o3bV^OA(9 zlXJInV2o+W(b(?I$jvbGLA|TbE9YDU*`rQwXkE@j(et9a6bCc??OE>E4Wx{q+vfqQR8!T`xDxHV!kysj4l7N~iFSpo4+O)}KDLcXL z!1@U7S*t4eerVXJeRa?&snoPa)6Jo1wf{8#SN_@dfglH!Tl>sP2C{mhN`$Ej!yj4Y_!-l0y-yu(lEWp4k@f zDi&s*Cj0Xz4%Y6dn&%ymqyD{$)JlC^80OOUG;xojesEzkNB6}-*rcw$@zKs%^(`O4x65(; zUmF)JWvH6h?PI^ovYmYM!~e;O54cT}y)x>C^Kts@=jD{Ux1?vxJw$Y`({$OxU95Y) zTQ}}#TB{Z8J+l6-F((!$ZNzr=Yp+}YQswLxEndRs=%_;eFjlq5== z?)$=z1(D9!DovF%g#w0D$y9(Jex({*-h}F+_U)-nFRuTTP&ah>xq}`5d5_3h@9anl zU7iJFa+r7084>R2$BT<$?%hKapp?TsfmV-xE`$-jQR{N`jUF}C+D!>#+HN-U4%}Na zk+mw8H@$xsplV%pZl;#mw^Jue5*mj$kg3MrX;+}%8gy25hG+{Q&X&1d>6c-8%C5#Q zaGK`O*LV11##9Ga09VfI*wbU&G{V8&#-!XY|U|HbWzvcXhEoxL85=Gyo|ald-LLO zH}Ct7&qL@jNC-lC>ASJxzNd|+h906K&iY`@zWPb-Yl6PfAlkrUs~#6pYT*3j3tqFG zx*t@_59@(a!N=4KU>FeXv00XokzQ0c$FvCG+*&JnFdmjDrdgjHq=56blI%{F5Ud|( zblp3Bff@&y5n)i+JZ1{K`DJu^@!i7*PHap`@u;!U+Kp0{U{s*B+4midc@HA90*7oV zJOx}GKrdajklfUMOaE6xLOJ6|JH4O0?MW2+=7e!}gW6*E?E+`L8)eTsU#*zC#Op$3t3|02dK;12(oFCqvVA-BbQKvWt@4b~OQPS8II5 z-+TXu)yY}Hgkw*3K8<##Qz`Nb2WA+{`2HE#V2J(1*0P+!cbk4KDIZ?|*q>oqrM-2m zp#WV$9FK7WPFKd#kwy;WlFvRkvO6y`cGr86WLK`&zpNww&?oW+REXRyMFtCXpNFEOFE6VpPwcPy^zvLKoi_|BGX_ZUP~9; zZi+ZRuZ83D)@AiagFLf*+_r7B(EDx_zhJB(K9B;fR7;uvu zOLKo=yp4|3)L1xs_~obguXFBTvwfXesPl5k?$=FNz59k23+t-uUf7hSa(w_3N|F<#@&%(H^uw z9;ed)__cypgP!CtcPe7z&g60}l!!OQScV%k#@vz<8K+XxXpeJg?^as#9`rHQn3fYn cnSu{^pqvf9pCRw5C<+#uv4#uItJF9BFCY~fdjJ3c literal 0 HcmV?d00001 diff --git a/test/_files/response_gzip b/test/_files/response_gzip new file mode 100644 index 0000000000000000000000000000000000000000..8c5575030304ab8dd152a387c6b2b87e4840a63f GIT binary patch literal 4073 zcmV`>3R7irc4cxpAVF|pV`ya!3Ro?0Z+B&KWn?X4c{(6aNKh{|E-@}NEpTiz zEoWtJbZ>7B3PW#hbY*UIEoE+FZ)9n1XF4EfdTDSC3RYoqc{(6LV`F7-bS*`0V{c?> zZf6Y&LvL<$Wo~pWY-Mg|bZ9ytGdMLe4GKeVZf<2`bZKvHIv`_gZ*yf03PW#hbY*UI zEp&NsWjY{qWq5QiXmo9C4GIkkABzY80000000Yb#+j85u^*Q?$Fn2bxGomQlaW1mN zt=D!Ew`)feJ59Hlj0Peh2{B1<0noBK{fo8_`_N9O)0v&=Z}h4C0sl_V0eI8Rc9MN) zl8H#*;M{OHg*I3OUSWz@y5GGT-&%fe{^+iWh>O7fT-0o#M_gwYUwA9Q1;DM5rT`!VBJJ?Bs) znkCx3n5E1ifqm;anq6xT3G9<#LZxE5m+6uF%pqQdC!sOxIhSlU6*5${)qP51I^!`p zW0zSdG(I;EMI;iknPy3#Gm7GFHIIaD9%VElV@cx}1_O9bvUtd3^A#IPHZ>Aubj+G3 zfLtcj>WTCxB4Gee4x)5IhfFumfEyy25>bXQ%`^Vvk6GC4zv?&71CcTyErp@Olkryb zJR6PpwUMVA@#GSgMmuGLdZ5bG#9~!uJ;Zx3~Z>&m z?LwG{jkGrkqn6z@t3}Oq#Qas-A})EyFL}yBPMbte7#T^%2up%l5D7Ef_3Uy)q%;s9 z8ul>Xx=tC^3h1k-1xW@vo>P$O-9@Mj?1(-;AQDDb*94s<>_J z&++zIl1|~I54lEBL9IUOj}&9%qawihCli@^JPFx#E1jg@8b8r%u2K2DTNnlVHm>;T`Uc-*|cAh+a z_PE`4_V0=OG~cja6un!=gz7>ROBhx-M%3slh`Z$6Xws+^#vgP&8;!d|5zYt+BB~(B zgGIDS5w1giMR?eA#*rA(=$)i#%FIkP6+oDa6E`>ZxmC5X2i+n{Ymqu;79U`H=^QrP z`7;6Gl))Sh8Zqo5XZe2FeE8g5Qm*YejvoB5I2m@opO1n4(p?2Jjqx{LSZujg_3l=p-(OuORJbk zWR@hbX5(QpRO$Zm|6LEn1naiHpo+A zFlnlVta~NUfi5i~VU<1D1g=_5iq($4b_$s%o1M-U0e4)ZaifjlW!;CgoY=BMf2SsvU;$BGl9p4Th;#Sv7 z@xZ2jcPKHi7F#~3ZTX_H zn)jS$Z@ZM@7^X-*+#$z$7K0wfGsnj@D9eZZETb@mhpxcPp{}j6#}K8R?D*w4IEx*N zf%|h-$-ydb{(kfG&0lW*bn~y9pWyFbw~8INJF-tWp5mA(a6;gi07Zf*s9g<)^yzkI zjg6Le)+CKoKqhYuVqyQG@GG^2&pvQ(tfxt65*qKgxB|+=L9I+2*fO!gvbu}}%rIfj z_5#A904!XLef5?3wF|gk)2Pv-3t~;cWivcGf*1i+qeaen%p)qJnf2^g5aaSRx1k)x zKX3kV^S5F{z}19I@+)vK!x^Dk(_jJ-$E-)5iRA=*d4cH*Hpe=Ja&O3H0~`*lkD1*b z0U|ZFkoioJ8D|j&zuZ4Gs*{Ia9<7y3sRBY;iZBa60HBF%{~j`Ja-6^^W2D{MBbl;u z;9bdX3!Iyth-^GT~oO@s` z$`VH?IE>(Pn3Nz9n6?53(y`|zm5`J5v5#S=J4DSAO|R`+lVl0IPT{-)>We}D;NbM& zhm+I6+3|1Q9=*Q6XCTsQwIEwCnEs!<&dwUIa}K!$jg+|E=>XKgKpCG?zRkOw0#)vD zvdDBm{CmdKt_QHI7TA;k<4o0;smGZWU7I4@&ka^YR#CyW(4cAymDm^BMUKBPVAG@N zga=@+Nf2crI7>=Gu7XS{JZNG>l;NhnXpOA48C4OtufF{?&PLcHbS0)N%Kb`d>v>e1 zN)GAoI49znefU4J#N9QP_=b&`M4zc{x5XNqvBvtkIeV-o)>&k&2?qkTAh+_swSZIg zSmG7QMN5cuuT{8cH>t$LMfJ)AGqs4&OpAq}^Gne2-*0~~769jEkGy`}C#MDlHeOR8 z4Q4?#0sjGDHi=|Dn9tX}6!WpKL$g+1-VIrVOYXqUrLD zgwUpFeZ#cm3>h)0hO@XyENM-Px~QrVS6QFqfASJktWyTUzV*)pI@F$L@V2(z3DWNOJI52drgK^McE% zbRCk^AZy9>x*2=gOsYWscyEr@<@5^xOUeBglI_DvHNcV-&IfA3WIszvKqyi{nH5s5e6|ua% zGuiWh%h?ni;hq^Vrysim1RP>+>TUwZcIaX!zr>tVc4jBg23*uFRqC zGxJ@|cOA1|>#7px3s?vv%ihi`Xv#WW0j!;@G^IhqA9Ee_x9l zQY#U!+_@630Re>Ow@|=3gcBB}OQ>GKU?=ZTD-66*y4D6vI1KcyMqnTY87j~|SH10Z zd|@kGWhq*i^V#tSu|zFGW7~AjkXC+_~B- zmb>tevM91FuB4JEEd;g$9V~QI7OtK;h0ny%L`15@GnS&W%OcM+#h_obH5e&e?-p>dQ0n&E_tOG1+Q86+NA?)dq%^~*AM>LBxK_9}X(p$t9 zmgUC>chstLO_H*OGk4oOf+TSYo+%v8@O!wGO*a$DQD`GQ!0>py?(3I4c`uO;%Eg0c z&#rg3F`b7f7HP=Yi%&hwhdt%x^df)dH+if1Ta#+caapS<46HjeQOWW$6f$Ix6tr`; z-W5>fMTyc`WDx8%XD@=n(3$t=upv4Y;2z0EQKp(J791xq!ABgtZ@b;u+i8-W9db%% zrU}m}Ji&P~H&60;bFE7@@qWR6Lvl|J4htDg z{#iVipMpSg00%*Xz^b)H>}_$CpFWgqyl+%O;0g8u&5meELl#qcS?FWcrZ&k*610lz z=cOnEapH;wvro)!`*zhJ6%mKU%b&pH03-+S=igT+2y`g2+cNkAGhfhJ9QV!7T`4_Y zg+vsXl(!Ii$r2`MG^hbDQ_X=$9ph2?e1~Vef4MHR + + + + + + + Paamayim Nekudotayim - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + +
+
+
+ +
Your continued donations keep Wikipedia running!    
+

Paamayim Nekudotayim

+
+

From Wikipedia, the free encyclopedia

+
+
Jump to: navigation, search
+

The Scope Resolution Operator (::) in PHP is officially called Paamayim Nekudotayim (IPA: [paʔamajim nəkudotajim]). It means 'twice colon' or 'double colon' in Hebrew.

+


+ +

+

Etymology

+

Nekudotayim (נקודתיים) means 'colon'; it comes from nekuda (IPA: [nəkuda]), 'point' or 'dot', and the dual suffix ayim (יים-), hence 'two points'. Similarly, the word paamayim (פעמיים) is derived by attaching the dual suffix to paam (IPA: [paʔam]) ('one time' or 'once'), thus yielding 'twice'.

+

The name was introduced in the Zend Engine 0.5 used in PHP 3. Although it has been confusing to many developers, it is still being used in PHP 5, as in this sample error message:

+
+Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in...
+
+ +

+

See also

+ + +

+

References

+
    +
  • [1] PHP5 OOP Manual chapter that explains the Paamayim Nekudotayim
  • +
+ + + + + +
+
+
+
+
+
+
Views
+ +
+
+
Personal tools
+ +
+ + + + + +
+
In other languages
+
+ +
+
+
+
+ + +
+ + + diff --git a/test/_files/response_multibyte_body b/test/_files/response_multibyte_body new file mode 100644 index 0000000000..510950d331 --- /dev/null +++ b/test/_files/response_multibyte_body @@ -0,0 +1,34 @@ +HTTP/1.1 200 OK +Date: Fri, 24 Jul 2009 15:22:05 GMT +Server: Apache/2.2.11 (Unix) PHP/5.3.0 mod_ssl/2.2.11 OpenSSL/0.9.8k +X-powered-by: PHP/5.3.0 ZendServer/4.0 +Transfer-encoding: chunked +Content-type: text/plain; charset=utf8 + +2ed +אמא בואי והסירי את כוכב רב הסמל הזה מעלי +אין לי על מי להסתלבט איתו כאן +הנה מתאפללות עיני +דופק לי על שער הגן + +טוק טוק טוק +על דלתי מרום (אני דופק לי...) +טוק טוק טוק +על דלתי מרום (לא דופק...) +knoking' on heaven's door +like so meny times before + +אמא שימי ארצה את תותחי +אין לי כח עוד ללחום +ענן שחור ארוך נוחת עלי +דופק לי על דלתי מרום + +טוק טוק טוק +על דלתי מרום (אני דופק לי...) +טוק טוק טוק +על דלתי מרום (לא דופק...) +דפוק דפוק דפוק +על דלתי מרום +כמו שעשית עד היום +0 + diff --git a/test/_files/response_multiline_header b/test/_files/response_multiline_header new file mode 100644 index 0000000000..01c586104e --- /dev/null +++ b/test/_files/response_multiline_header @@ -0,0 +1,19 @@ +HTTP/1.1 404 Not Found +Date: Fri, 17 Nov 2006 22:22:40 GMT +Server: Apache +Content-Length: 272 +Keep-Alive: timeout=15, + max=100 +Connection: Keep-Alive +Content-Type: text/html; + charset=iso-8859-1 + + + +404 Not Found + +

Not Found

+

The requested URL /some/wrong/path was not found on this server.

+
+
Apache Server at localhost Port 80
+ diff --git a/test/_files/response_unknown b/test/_files/response_unknown new file mode 100644 index 0000000000..fb5454edd1 --- /dev/null +++ b/test/_files/response_unknown @@ -0,0 +1,14 @@ +HTTP/1.1 550 Printer On Fire +Server: Apache +Content-Length: 264 +Content-Type: text/html; charset=iso-8859-1 + + + +550 Printer On Fire + +

Help! Help!

+

Please call the fire brigade

+
+
Apache Server at shahar.local Port 80
+ diff --git a/test/_files/response_with_cookies b/test/_files/response_with_cookies new file mode 100644 index 0000000000..6d550197e5 --- /dev/null +++ b/test/_files/response_with_cookies @@ -0,0 +1,13 @@ +HTTP/1.1 200 OK +Date: Wed, 22 Nov 2006 22:31:39 GMT +Server: Apache +X-Powered-By: PHP/5.1.6-pl8-gentoo +Set-Cookie: foo=bar +Set-Cookie: BOFH=Feature+was+not+beta+tested; expires=Thu, 23-Nov-2006 00:31:40 GMT +Set-Cookie: time=1164234700 +Content-Length: 26 +Keep-Alive: timeout=15, max=100 +Connection: Keep-Alive +Content-Type: text/html + +This is some response body \ No newline at end of file diff --git a/test/_files/response_with_single_cookie b/test/_files/response_with_single_cookie new file mode 100644 index 0000000000..bb33bb4ede --- /dev/null +++ b/test/_files/response_with_single_cookie @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +Date: Wed, 22 Nov 2006 22:31:39 GMT +Server: Apache +X-Powered-By: PHP/5.1.6-pl8-gentoo +Set-Cookie: BOFH=Feature+was+not+beta+tested; expires=Thu, 23-Nov-2006 00:31:40 GMT +Content-Length: 26 +Keep-Alive: timeout=15, max=100 +Connection: Keep-Alive +Content-Type: text/html + +This is some response body \ No newline at end of file diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 0000000000..5b23dbde09 --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,34 @@ +