diff --git a/lib/.configsnapshots/configsnapshot-2011-04-27 13-54-20.xml b/lib/.configsnapshots/configsnapshot-2011-04-27 13-54-20.xml new file mode 100644 index 0000000000000000000000000000000000000000..69268a57e943db8247fa5fb2e31e8ebb48dbe9db --- /dev/null +++ b/lib/.configsnapshots/configsnapshot-2011-04-27 13-54-20.xml @@ -0,0 +1,2 @@ +<?xml version="1.0"?> +<pearconfig version="1.0"><php_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/php</php_dir><ext_dir>/usr/lib/php/extensions/no-debug-non-zts-20090626</ext_dir><cfg_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/cfg</cfg_dir><doc_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/docs</doc_dir><bin_dir>/usr/bin</bin_dir><data_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/data</data_dir><www_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/www</www_dir><test_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/tests</test_dir><src_dir>/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/src</src_dir><php_bin>/usr/bin/php</php_bin><php_ini>/private/etc/php.ini</php_ini><php_prefix></php_prefix><php_suffix></php_suffix></pearconfig> diff --git a/lib/.pear2registry b/lib/.pear2registry index 981d851a89012802bf650edc0cf7e399f415cbc1..2c744411401dd7b7a83105483f03f40659058bd2 100644 Binary files a/lib/.pear2registry and b/lib/.pear2registry differ diff --git a/lib/.xmlregistry/channels/channel-simplecas.googlecode.com##svn.xml b/lib/.xmlregistry/channels/channel-simplecas.googlecode.com##svn.xml new file mode 100644 index 0000000000000000000000000000000000000000..dc7b71483c6060965e62c14efb7b13b5e9480af3 --- /dev/null +++ b/lib/.xmlregistry/channels/channel-simplecas.googlecode.com##svn.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<channel version="1.0" xmlns="http://pear.php.net/channel-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/channel-1.0http://pear.php.net/dtd/channel-1.0.xsd"> + <name>simplecas.googlecode.com/svn</name> + <suggestedalias>simplecas</suggestedalias> + <summary>simplecas</summary> + <servers> + <primary> + <rest> + <baseurl type="REST1.0">http://simplecas.googlecode.com/svn/rest/</baseurl> + <baseurl type="REST1.1">http://simplecas.googlecode.com/svn/rest/</baseurl> + <baseurl type="REST1.3">http://simplecas.googlecode.com/svn/rest/</baseurl> + </rest> + </primary> + </servers> +</channel> diff --git a/lib/.xmlregistry/channels/channelalias-simplecas.txt b/lib/.xmlregistry/channels/channelalias-simplecas.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdae489857385937081e3051151920aa6c6e6422 --- /dev/null +++ b/lib/.xmlregistry/channels/channelalias-simplecas.txt @@ -0,0 +1 @@ +simplecas.googlecode.com/svn \ No newline at end of file diff --git a/lib/.xmlregistry/packages/pear.php.net/HTTP_Request2/2.0.0beta3-info.xml b/lib/.xmlregistry/packages/pear.php.net/HTTP_Request2/2.0.0beta3-info.xml new file mode 100644 index 0000000000000000000000000000000000000000..6a1f8a8bf4bb63b6ddb024e238bb23bf28edc5e4 --- /dev/null +++ b/lib/.xmlregistry/packages/pear.php.net/HTTP_Request2/2.0.0beta3-info.xml @@ -0,0 +1,456 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.2" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> + <name>HTTP_Request2</name> + <channel>pear.php.net</channel> + <extends>HTTP_Request</extends> + <summary>Provides an easy way to perform HTTP requests.</summary> + <description>PHP5 rewrite of HTTP_Request package (with parts of HTTP_Client). Provides +cleaner API and pluggable Adapters: + * Socket adapter, based on old HTTP_Request code, + * Curl adapter, wraps around PHP's cURL extension, + * Mock adapter, to use for testing packages dependent on HTTP_Request2. +Supports POST requests with data and file uploads, basic and digest +authentication, cookies, managing cookies across requests, proxies, gzip and +deflate encodings, redirects, monitoring the request progress with Observers...</description> + <lead> + <name>Alexey Borzov</name> + <user>avb</user> + <email>avb@php.net</email> + <active>yes</active> + </lead> + <date>2011-04-27</date> + <time>13:58:16</time> + <version> + <release>2.0.0beta3</release> + <api>2.0.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* Added getEffectiveUrl() method to Response object, it returns the URL + response was received from, possibly after redirects (request #18412) +* Curl Adapter didn't send body for PUT requests sometimes (bug #18421) + </notes> + <contents> + <dir name="/"> + <file baseinstalldir="HTTP" md5sum="1740e401dcff9571ad169bf19aa448cc" name="tests/_network/uploads.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="5e7fd3d5d3b00d47e00537c439f0a41a" name="tests/_network/timeout.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="c46e248a0638a6e020526be68693d988" name="tests/_network/setcookie.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="fda4c343b048f49ecc1e6028e03f17d7" name="tests/_network/redirects.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="91254800e447670614c62002717d92da" name="tests/_network/rawpostdata.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="db9014f908c679bdbea37b5c00b62dab" name="tests/_network/postparameters.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="21da3787253321dde0c4d7991f2d8a9c" name="tests/_network/getparameters.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="9d9f06f2307c02781238eb2532774c27" name="tests/_network/digestauth.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="7d29cd6f3df453a40f362ea31f079fb7" name="tests/_network/cookies.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="415f4b4ff843b6cb7530b9571ae3f962" name="tests/_network/basicauth.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="1fb55dfe18831f8fe6280280e72ad216" name="tests/_files/response_headers" role="test"/> + <file baseinstalldir="HTTP" md5sum="722328bfe89a9c9f7de5a020ed2c4589" name="tests/_files/response_gzip_broken" role="test"/> + <file baseinstalldir="HTTP" md5sum="c36530c79c044fde1745b244c38d381f" name="tests/_files/response_gzip" role="test"/> + <file baseinstalldir="HTTP" md5sum="12d80db889f528922a31b5c03f693647" name="tests/_files/response_deflate" role="test"/> + <file baseinstalldir="HTTP" md5sum="d1d2beb78782f56e8611100a009fb1f6" name="tests/_files/response_cookies" role="test"/> + <file baseinstalldir="HTTP" md5sum="120ea8a25e5d487bf68b5f7096440019" name="tests/_files/plaintext.txt" role="test"/> + <file baseinstalldir="HTTP" md5sum="fc94fb0c3ed8a8f909dbc7630a0987ff" name="tests/_files/empty.gif" role="test"/> + <file baseinstalldir="HTTP" md5sum="16f23f14921a2aa607c85664efa47d41" name="tests/_files/bug_18169" role="test"/> + <file baseinstalldir="HTTP" md5sum="22d7f11b85dd00bd8919a4226a5a0388" name="tests/_files/bug_15305" role="test"/> + <file baseinstalldir="HTTP" md5sum="6dc93662dd42cf76a457c6fff5b8df20" name="tests/TestHelper.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="c9839810b6416ce1521ab1c721853ef6" name="tests/Request2Test.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="9be337588f6c6d2b795d27d185cd1c92" name="tests/Request2/ResponseTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="c427be4a318a1002ef0df67b05a39276" name="tests/Request2/MultipartBodyTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="fc16142b51b1a110455911d3d5f4685a" name="tests/Request2/CookieJarTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="8bdcae5a16da6a577681309c7958fe68" name="tests/Request2/AllTests.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="866a71b2fcf1632f3ffddf11142fd20e" name="tests/Request2/Adapter/SocketTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="416ae359f326d574871755d6d630a2b0" name="tests/Request2/Adapter/SocketProxyTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="6ac2d3e86cb81eb739a094b72f18a609" name="tests/Request2/Adapter/SkippedTests.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="4ba446d73bb389557284233c88f26dbd" name="tests/Request2/Adapter/MockTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="d825a52bb83675833ec0f022efa50688" name="tests/Request2/Adapter/CurlTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="8a9c7037d525fc6c723db266f74ecddf" name="tests/Request2/Adapter/CommonNetworkTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="80b934c16d2876f8ef1fb6e5afc7ae19" name="tests/Request2/Adapter/AllTests.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="f9fa9893f4241ba54d8b9f758f4a5dd5" name="tests/ObserverTest.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="62c675708ac7a594cfd18137ac869dfc" name="tests/NetworkConfig.php.dist" role="test"/> + <file baseinstalldir="HTTP" md5sum="0d6142d9cbf8b81c5b22c9982a750215" name="tests/AllTests.php" role="test"/> + <file baseinstalldir="HTTP" md5sum="0ec6a02c97f25ef76ead19b0cdbd87d3" name="Request2/Response.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="cf5d76e80409a85597a91de1e91c6ee1" name="Request2/Observer/Log.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="68ec0b653d74c8eb279882e5ee9dc8ad" name="Request2/MultipartBody.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="2df8b0b6b1393db00621fea5c12adb25" name="Request2/Exception.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="88bca86278b570e760d1b4bece6ebbb0" name="Request2/CookieJar.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + <tasks:replace from="@data_dir@" to="data_dir" type="pear-config"/> + </file> + <file baseinstalldir="HTTP" md5sum="f276265e7e4b84a85c8215e2f74caea7" name="Request2/Adapter/Socket.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="aa47b70e43fda0e597cd91e3b13fe717" name="Request2/Adapter/Mock.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="448312f853d4447325c974486e5d6b3c" name="Request2/Adapter/Curl.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="0d68ac49d4a4b87c13e0f523c069ee3b" name="Request2/Adapter.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="0a34a995006ebace077e2dd87a292a8f" name="Request2.php" role="php"> + <tasks:replace from="@package_version@" to="version" type="package-info"/> + </file> + <file baseinstalldir="HTTP" md5sum="7e6017dfdf042dbd443ce6c8c024f40d" name="docs/examples/upload-rapidshare.php" role="doc"/> + <file baseinstalldir="HTTP" md5sum="46d7644aa37dd6bf9b200820a29a277c" name="data/public-suffix-list.php" role="data"/> + <file baseinstalldir="HTTP" md5sum="389143b973e6c1d87926d803ca5fceec" name="data/generate-list.php" role="data"/> + </dir> + </contents> + <dependencies> + <required> + <php> + <min>5.2.0</min> + </php> + <pearinstaller> + <min>1.5.4</min> + </pearinstaller> + <package> + <name>Net_URL2</name> + <channel>pear.php.net</channel> + <min>0.3.0</min> + </package> + </required> + <optional> + <extension> + <name>curl</name> + </extension> + <extension> + <name>fileinfo</name> + </extension> + <extension> + <name>zlib</name> + </extension> + <extension> + <name>openssl</name> + </extension> + </optional> + </dependencies> + <phprelease> + <filelist> + <install as="generate-list.php" name="data/generate-list.php"/> + <install as="public-suffix-list.php" name="data/public-suffix-list.php"/> + <install as="examples/upload-rapidshare.php" name="docs/examples/upload-rapidshare.php"/> + <install as="AllTests.php" name="tests/AllTests.php"/> + <install as="NetworkConfig.php.dist" name="tests/NetworkConfig.php.dist"/> + <install as="ObserverTest.php" name="tests/ObserverTest.php"/> + <install as="Request2/Adapter/AllTests.php" name="tests/Request2/Adapter/AllTests.php"/> + <install as="Request2/Adapter/CommonNetworkTest.php" name="tests/Request2/Adapter/CommonNetworkTest.php"/> + <install as="Request2/Adapter/CurlTest.php" name="tests/Request2/Adapter/CurlTest.php"/> + <install as="Request2/Adapter/MockTest.php" name="tests/Request2/Adapter/MockTest.php"/> + <install as="Request2/Adapter/SkippedTests.php" name="tests/Request2/Adapter/SkippedTests.php"/> + <install as="Request2/Adapter/SocketProxyTest.php" name="tests/Request2/Adapter/SocketProxyTest.php"/> + <install as="Request2/Adapter/SocketTest.php" name="tests/Request2/Adapter/SocketTest.php"/> + <install as="Request2/AllTests.php" name="tests/Request2/AllTests.php"/> + <install as="Request2/CookieJarTest.php" name="tests/Request2/CookieJarTest.php"/> + <install as="Request2/MultipartBodyTest.php" name="tests/Request2/MultipartBodyTest.php"/> + <install as="Request2/ResponseTest.php" name="tests/Request2/ResponseTest.php"/> + <install as="Request2Test.php" name="tests/Request2Test.php"/> + <install as="TestHelper.php" name="tests/TestHelper.php"/> + <install as="_files/bug_15305" name="tests/_files/bug_15305"/> + <install as="_files/bug_18169" name="tests/_files/bug_18169"/> + <install as="_files/empty.gif" name="tests/_files/empty.gif"/> + <install as="_files/plaintext.txt" name="tests/_files/plaintext.txt"/> + <install as="_files/response_cookies" name="tests/_files/response_cookies"/> + <install as="_files/response_deflate" name="tests/_files/response_deflate"/> + <install as="_files/response_gzip" name="tests/_files/response_gzip"/> + <install as="_files/response_gzip_broken" name="tests/_files/response_gzip_broken"/> + <install as="_files/response_headers" name="tests/_files/response_headers"/> + <install as="_network/basicauth.php" name="tests/_network/basicauth.php"/> + <install as="_network/cookies.php" name="tests/_network/cookies.php"/> + <install as="_network/digestauth.php" name="tests/_network/digestauth.php"/> + <install as="_network/getparameters.php" name="tests/_network/getparameters.php"/> + <install as="_network/postparameters.php" name="tests/_network/postparameters.php"/> + <install as="_network/rawpostdata.php" name="tests/_network/rawpostdata.php"/> + <install as="_network/redirects.php" name="tests/_network/redirects.php"/> + <install as="_network/setcookie.php" name="tests/_network/setcookie.php"/> + <install as="_network/timeout.php" name="tests/_network/timeout.php"/> + <install as="_network/uploads.php" name="tests/_network/uploads.php"/> + </filelist> + </phprelease> + <changelog> + <release> + <version> + <release>2.0.0beta2</release> + <api>2.0.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2011-03-25</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* Unit tests can now be run under recent PHPUnit versions (3.5+) +* Public Suffix List updated to current version +* PHP warning produced by stream_socket_client() in Socket adapter is now + added to Exception message (bug #18331) + </notes> + </release> + <release> + <version> + <release>2.0.0beta1</release> + <api>2.0.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2011-02-27</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +Additions and changes: + * Implemented cookie jar that allows to store and pass cookies across HTTP + requests (see request #18225) + * Added several specialized subclasses of HTTP_Request2_Exception, they are + now thrown instead of the parent. Also added error codes and possibility + to get native error code (as returned by stream_socket_client() and + curl_errno()) (request #16762) + * An additional 'sentBody' event is now sent to Observers (request #16828) + * setBody() and addUpload() can now accept file pointers (request #16863) + +Bugfixes: + * Incorrect check in Socket Adapter prevented Keep-alive from working in + some cases (bug #17031) + </notes> + </release> + <release> + <version> + <release>0.6.0</release> + <api>0.6.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2011-02-14</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +Additions and changes: + * Added test suite that interacts with a webserver. Please refer to + tests/NetworkConfig.php.dist for instructions. + * Packaging changes: docs/ and tests/ contents are installed without + redundant subdirectories. + * Added a $replace parameter to HTTP_Request2::setHeader() that controls + whether new header value will overwrite previous one or be appended + to it (request #17507) + +Bugfixes: + * Fixed a typo in Curl Adapter that prevented 'strict_redirects' from working + * Curl Adapter will throw an exception if CURLOPT_FOLLOWLOCATION can not be + enabled due to PHP setup (bug #17450) + * Allow parameters in manually set Content-Type headers (bug #17460) + * Properly reset redirect limit if multiple requests are performed with the + same instance of Socket Adapter (bug #17826) + * Response::getBody() no longer tries to decode empty strings (bug #18169) + </notes> + </release> + <release> + <version> + <release>0.5.2</release> + <api>0.5.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2010-04-21</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* magic_quotes_runtime PHP setting could be incorrectly enabled after + performing the request (bug #16440) +* Unit tests fixes (bugs #17079, #17106, #17326) +* Observer_Log now appends to the log file rather than rewrites it (thanks to + troelskn at gmail dot com for reporting) + </notes> + </release> + <release> + <version> + <release>0.5.1</release> + <api>0.5.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-11-21</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* Content-Type request header is no longer removed for POST and PUT requests + with empty request body (request #16799). +* CURLOPT_NOBODY option is now set when doing HEAD requests with Curl adapter. + </notes> + </release> + <release> + <version> + <release>0.5.0</release> + <api>0.5.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-11-18</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* Redirect support added, new configuration parameters 'follow_redirects', + 'max_redirects' and 'strict_redirects' available + +* Implemented workaround for PHP bug #47204, Curl Adapter can now handle + Digest authentication and redirects when doing POST requests, unfortunately + this requires loading the entire request body into memory. +* Config parameter 'use_brackets' is propagated to created instances of Net_URL2 +* Prevent memory leaks due to circular references (request #16646) +* Fixed a misleading error message when timing out due to default_socket_timeout +* HTTP_Request2::setBody() can now accept an instance of HTTP_Request2_MultipartBody + without trying to convert it to string +* Calling HTTP_Request2::setBody() now clears post parameters and uploads + </notes> + </release> + <release> + <version> + <release>0.4.1</release> + <api>0.4.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-09-14</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* Decoding of gzipped responses failed if mbstring.func_overload was enabled + (bug #16555) +* Changed boundary generation in multipart bodies to work correctly with + rapidshare.com, added first usage example: file uploading to rapidshare.com +* Added forgotten optional dependency on OpenSSL PHP extension + </notes> + </release> + <release> + <version> + <release>0.4.0</release> + <api>0.4.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-05-03</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +* Added 'store_body' config parameter, if set to false it will prevent storing + the response body in Response object (request #15881) +* HTTP_Request2::setHeader() method now works as documented, setHeader('name') + will remove the 'name' header, while setHeader('name', '') will set 'name' + header to empty value (bug #15937) +* Custom 'Host' header will not be overwritten by generated one (bug #16146) +* When trying to reuse the connected socket in Socket adapter, make sure that + it is still connected (bug #16149) + </notes> + </release> + <release> + <version> + <release>0.3.0</release> + <api>0.3.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-01-28</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +API changes: + * Removed HTTP_Request2::getConfigValue() method + +Feature additions: + * Added digest authentication (RFC 2617) support to Socket adapter. Thanks + to Tom Snyder (tomsn at inetoffice dot com) who sent me a prototype + implementation for HTTP_Request a couple of years ago. + * Added HTTPS proxy support to Socket adapter, this works through CONNECT + request described in RFC 2817. + * Mock adapter can now throw an Exception instead of returning a response + if Exception object is added via its addResponse() method (request #15629) + +Other changes and fixes: + * Support RFC 3986 by not encoding '~' in POST body (request #15368) + * Prevent an error with particular versions of PHP and Curl (bug #15617) + * Regular expressions used in HTTP_Request2 are now class constants + (request #15630) + * Curl adapter now throws an exception in case of malformed (non-HTTP) + response rather than dies with a fatal error (bug #15716) + * Curl handle wasn't closed in Curl adapter in case of error (bug #15721) + * Curl adapter sent an extra 'sentHeaders' event and returned bogus + response status when server returned 100-Continue response (bug #15785) + </notes> + </release> + <release> + <version> + <release>0.2.0</release> + <api>0.2.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-01-07</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +API changes: + * HTTP_Request2::getConfigValue() is deprecated and will be removed in next + release. Use HTTP_Request2::getConfig(). + * Changed HTTP_Request2::setConfig() to accept a pair of parameter name and + parameter value in addition to array('parameter name' => 'value') + * Added HTTP_Request2::getConfig() method that can return a single + configuration parameter or the whole configuration array + +Other additions and changes: + * Added a debug Observer that can log request progress to a file or an + instance of PEAR::Log (thanks to David Jean Louis, request #15424) + * Added a new 'timeout' parameter that limits total number of seconds + a request can take (see requests #5735 and #8964) + * Added various SSL protocol options: 'ssl_verify_peer', 'ssl_verify_host', + 'ssl_cafile', 'ssl_capath', 'ssl_local_cert', 'ssl_passphrase'. Note that + 'ssl_verify_host' option behaves differently in Socket and Curl Adapters: + http://bugs.php.net/bug.php?id=47030 + +Fixes: + * Fixed 'data error' when processing response encoded by 'deflate' + encoding (bug #15305) + * Curl Adapter now passes full request headers in 'sentHeaders' event + </notes> + </release> + <release> + <version> + <release>0.1.0</release> + <api>0.1.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2008-11-17</date> + <license uri="http://opensource.org/licenses/bsd-license.php">BSD License</license> + <notes> +Initial release. The features supported are mostly the same as those of +HTTP_Request, with the following additional feature requests implemented: + * cURL extension support (request #5463) + * It is now possible to monitor the file upload progress with Observers + (request #7630) + * Added 'sentHeaders' notification providing the request headers to the + Observers (request #7633) + * Added support for 'deflate' encoding (request #11246) + </notes> + </release> + </changelog> +</package> diff --git a/lib/.xmlregistry/packages/pear.php.net/Net_URL2/0.3.1-info.xml b/lib/.xmlregistry/packages/pear.php.net/Net_URL2/0.3.1-info.xml new file mode 100644 index 0000000000000000000000000000000000000000..295977a67919e2d1df1ab71ba2f651bfcef47946 --- /dev/null +++ b/lib/.xmlregistry/packages/pear.php.net/Net_URL2/0.3.1-info.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.7.1" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> + <name>Net_URL2</name> + <channel>pear.php.net</channel> + <summary>Class for parsing and handling URL.</summary> + <description>Provides parsing of URLs into their constituent parts (scheme, host, path etc.), URL generation, and resolving of relative URLs.</description> + <lead> + <name>David Coallier</name> + <user>davidc</user> + <email>davidc@php.net</email> + <active>yes</active> + </lead> + <lead> + <name>Christian Schmidt</name> + <user>schmidt</user> + <email>schmidt@php.net</email> + <active>yes</active> + </lead> + <date>2011-04-27</date> + <time>13:58:16</time> + <version> + <release>0.3.1</release> + <api>0.3.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license> + <notes>* BC break: Removed setOption() to avoid undefined behaviour (bug #16674) +* Fixed Bug #16854: Invalid package.xml making it impossible to install with Pyrus +* Fixed Bug #16651: Port may be an empty string +* Fixed Bug #16653: Don't make OPTION_SEPARATOR_(IN|OUT)PUT default to arg_separator.(in|out)put</notes> + <contents> + <dir name="/"> + <file baseinstalldir="Net" md5sum="f0a637e921da7a47373e2b065107c7a0" name="URL2.php" role="php"/> + <file baseinstalldir="Net" md5sum="587a224d39fbffa47cacb9fb67b51da1" name="docs/example.php" role="doc"/> + <file baseinstalldir="Net" md5sum="ea8b73061588566519fd0e55a230f2a6" name="docs/6470.php" role="doc"/> + </dir> + </contents> + <dependencies> + <required> + <php> + <min>5.0</min> + </php> + <pearinstaller> + <min>1.4.0b1</min> + </pearinstaller> + </required> + </dependencies> + <phprelease> + <changelog> + <release> + <version> + <release>0.3.0</release> + <api>0.3.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2009-09-05</date> + <license>BSD</license> + <notes>* Fixed #14399 (Errors in URL parsing (items #1 and #3)) +* Fixed #14735 (Encode query string values) +* Fixed #15546 (Add adding __toString()) +* Fixed #15367 (Use RFC 3986-compliant version of rawurlencode() in PHP < 5.2) +* Fixed #14289 (Add __get() and __set())</notes> + </release> + <release> + <version> + <release>0.2.0</release> + <api>0.2.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2008-06-18</date> + <license>BSD</license> + <notes>Major rewrite to comply with RFC3986 (bug 11574). + Much better support for resolving relative URLs. + WARNING: Method and property names has changed to reflect the terminology used in the RFC - THIS RELEASE IS NOT BACKWARDS COMPATIBLE WITH VERSION 0.1.0.</notes> + </release> + <release> + <version> + <release>0.1.0</release> + <api>0.1.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2007-05-08</date> + <license>BSD</license> + <notes>Convert to PHP5 only. PHP4 users should continue with version 1.0.15</notes> + </release> + </changelog> + </phprelease> +</package> diff --git a/lib/.xmlregistry/packages/pear.unl.edu/UNL_Auth/0.4.0-info.xml b/lib/.xmlregistry/packages/pear.unl.edu/UNL_Auth/0.4.0-info.xml new file mode 100644 index 0000000000000000000000000000000000000000..f93fca7639b9e4b5a0d5604965737e5d3c661f0a --- /dev/null +++ b/lib/.xmlregistry/packages/pear.unl.edu/UNL_Auth/0.4.0-info.xml @@ -0,0 +1,178 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> + <name>UNL_Auth</name> + <channel>pear.unl.edu</channel> + <summary>An authentication framework for PHP Applications at UNL</summary> + <description>This package provides an authentication framework for web +applications developed at UNL.</description> + <lead> + <name>Brett Bieber</name> + <user>saltybeagle</user> + <email>brett.bieber@gmail.com</email> + <active>yes</active> + </lead> + <date>2011-04-27</date> + <time>13:54:20</time> + <version> + <release>0.4.0</release> + <api>0.4.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Fix E_STRICT warning about static methods. + </notes> + <contents> + <dir name="/"> + <file baseinstalldir="/" md5sum="b7a35f1bfe174ef2725733df1539f212" name="UNL/Auth/SimpleCAS/ZendAuth.php" role="php"/> + <file baseinstalldir="/" md5sum="6def037a77fd9fa49bcc5e752cbb8170" name="UNL/Auth/SimpleCAS.php" role="php"/> + <file baseinstalldir="/" md5sum="ada4bb738641430ba2420da94ec3e749" name="UNL/Auth/CAS/PEARAuth.php" role="php"/> + <file baseinstalldir="/" md5sum="76deaf815ba781ca6f7f6bbcc7095912" name="UNL/Auth/CAS.php" role="php"/> + <file baseinstalldir="/" md5sum="f0bb48dadf42f6511b718032203f9c28" name="UNL/Auth.php" role="php"/> + <file baseinstalldir="/" md5sum="c4ee1094cd276ff40fbd2b16d66068fe" name="docs/examples/Zend_SimpleCAS_example.php" role="doc"/> + <file baseinstalldir="/" md5sum="3a80ba084e5bc5f8b17d188b15e6a7aa" name="docs/examples/SimpleCAS_example.php" role="doc"/> + <file baseinstalldir="/" md5sum="3759e0e8f63d4161653c158b08cdd0ed" name="docs/examples/CAS_example.php" role="doc"/> + <file baseinstalldir="/" md5sum="e89e49dca8497ec59459d17140c6e63a" name="docs/examples/CASPEARAuth_example.php" role="doc"/> + </dir> + </contents> + <dependencies> + <required> + <php> + <min>5.2.0</min> + </php> + <pearinstaller> + <min>1.4.3</min> + </pearinstaller> + </required> + <optional> + <package> + <name>Auth</name> + <channel>pear.php.net</channel> + <min>1.0</min> + </package> + <package> + <name>CAS</name> + <channel>pear.unl.edu</channel> + <min>1.0.0</min> + </package> + <package> + <name>SimpleCAS</name> + <channel>simplecas.googlecode.com/svn</channel> + <min>0.3.0</min> + </package> + </optional> + </dependencies> + <phprelease> + <changelog> + <release> + <version> + <release>0.1.0</release> + <api>0.1.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2007-12-17</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +First Release - only CAS is available. + </notes> + </release> + <release> + <version> + <release>0.1.1</release> + <api>0.1.1</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2008-05-20</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Check if session is already started - kabel +* Improve PHP docs and fix example. - bbieber + </notes> + </release> + <release> + <version> + <release>0.2.0</release> + <api>0.2.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2008-08-22</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Upgrade CAS driver dependency to 1.0.0 + </notes> + </release> + <release> + <version> + <release>0.3.0</release> + <api>0.3.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2008-12-09</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Enable SimpleCAS support. http://code.google.com/p/simplecas/ + </notes> + </release> + <release> + <version> + <release>0.3.1</release> + <api>0.3.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-02-12</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Increase SimpleCAS dependency of 0.2.0 to take advantage of getRequest() so we can ignore the ssl peer verification. + </notes> + </release> + <release> + <version> + <release>0.3.2</release> + <api>0.3.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-03-03</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Increase SimpleCAS dependency of 0.3.0 to use new object names and array options for the constructor. + </notes> + </release> + <release> + <version> + <release>0.4.0</release> + <api>0.4.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-11-18</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Fix E_STRICT warning about static methods. + </notes> + </release> + </changelog> + </phprelease> +</package> diff --git a/lib/.xmlregistry/packages/simplecas.googlecode.com!svn/SimpleCAS/0.5.0-info.xml b/lib/.xmlregistry/packages/simplecas.googlecode.com!svn/SimpleCAS/0.5.0-info.xml new file mode 100644 index 0000000000000000000000000000000000000000..5fc5b35c2b95028c4312ef2030f9468fa60ea644 --- /dev/null +++ b/lib/.xmlregistry/packages/simplecas.googlecode.com!svn/SimpleCAS/0.5.0-info.xml @@ -0,0 +1,225 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.1" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> + <name>SimpleCAS</name> + <channel>simplecas.googlecode.com/svn</channel> + <summary>A PHP5 library for CAS Authentication.</summary> + <description>This package is a PHP5 only library for identifying users +in a JA-SIG CAS secured environment.</description> + <lead> + <name>Brett Bieber</name> + <user>saltybeagle</user> + <email>brett.bieber@gmail.com</email> + <active>yes</active> + </lead> + <helper> + <name>John Thiltges</name> + <user>jthiltges</user> + <email>jthiltges@gmail.com</email> + <active>yes</active> + </helper> + <helper> + <name>Kevin Abel</name> + <user>kevin.abel.0</user> + <email>kevin.abel.0@gmail.com</email> + <active>yes</active> + </helper> + <helper> + <name>Eric Rasmussen</name> + <user>ericrasmussen1</user> + <email>ericrasmussen1@gmail.com</email> + <active>yes</active> + </helper> + <date>2011-04-27</date> + <time>13:58:16</time> + <version> + <release>0.5.0</release> + <api>0.5.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Allow service url to be customized with SimpleCAS::setURL($url); [ericrasmussen1] + </notes> + <contents> + <dir name="/"> + <file baseinstalldir="/" md5sum="ad9c06f186ce8fa739d6e716f6c9b48d" name="SimpleCAS/SingleSignOut.php" role="php"/> + <file baseinstalldir="/" md5sum="7a49aa1c67563ed628202c3e9244dcdc" name="SimpleCAS/ProxyGranting/Storage/File.php" role="php"/> + <file baseinstalldir="/" md5sum="26b6d753c3f9098be6493c818aabd959" name="SimpleCAS/ProxyGranting/Storage.php" role="php"/> + <file baseinstalldir="/" md5sum="e96b4c13218c39664fdeb8dc8d4e7541" name="SimpleCAS/ProxyGranting.php" role="php"/> + <file baseinstalldir="/" md5sum="46feaaf938c6eb0f7f260125a646b3fb" name="SimpleCAS/Protocol/Version2/ValidationResponse.php" role="php"/> + <file baseinstalldir="/" md5sum="f4ed06c8ae5918cfb9790ca72c6256d0" name="SimpleCAS/Protocol/Version2.php" role="php"/> + <file baseinstalldir="/" md5sum="80862315b001dcfa1fde9348341ff432" name="SimpleCAS/Protocol/Version1.php" role="php"/> + <file baseinstalldir="/" md5sum="daf05e7a073e2ac47ddb0c7ea6a83e33" name="SimpleCAS/Protocol.php" role="php"/> + <file baseinstalldir="/" md5sum="d93480efa786598948935145b06352a6" name="SimpleCAS/Autoload.php" role="php"/> + <file baseinstalldir="/" md5sum="a9d96c7d2032b5432306ffba5438d0f1" name="SimpleCAS.php" role="php"/> + <file baseinstalldir="/" md5sum="9d686c3553b6c8be0af988ddfb363f0c" name="docs/examples/Zend_Auth_Adapter_SimpleCAS.php" role="doc"/> + <file baseinstalldir="/" md5sum="2254593f10efc23cd0eadab309506e15" name="docs/examples/simple.php" role="doc"/> + </dir> + </contents> + <dependencies> + <required> + <php> + <min>5.2.5</min> + </php> + <pearinstaller> + <min>1.5.4</min> + </pearinstaller> + <package> + <name>HTTP_Request2</name> + <channel>pear.php.net</channel> + <min>0.1.0</min> + </package> + </required> + </dependencies> + <phprelease> + <changelog> + <release> + <version> + <release>0.1.0</release> + <api>0.1.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2008-12-08</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +First Release. + </notes> + </release> + <release> + <version> + <release>0.1.1</release> + <api>0.1.1</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2008-12-08</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Fix Notice: Trying to get property of non-object in SimpleCAS/Server/Version2/ValidationResponse.php on line 23 +* Change PHP dependency to 5.2.5 + </notes> + </release> + <release> + <version> + <release>0.1.2</release> + <api>0.1.1</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-02-05</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Update PEAR dependency to 1.5.4 +* Match case for variables (jthiltges) +* In CAS v2 validateTicket, typecast successful return value to string (jthiltges) + </notes> + </release> + <release> + <version> + <release>0.2.0</release> + <api>0.2.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-02-11</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Allow setting HTTP_Request2 object so configuration can be set. + $server->getRequest()->setConfig('ssl_verify_peer', false); +* Add $server->getRequest(), $server->setRequest(HTTP_Request2 $http_request) +Change interface for server to abstract class. + </notes> + </release> + <release> + <version> + <release>0.2.1</release> + <api>0.2.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-02-12</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Explicitly call __toString() for PHP 5.1 compatibility. [jthiltges] +* Add Zend_Auth_Adapter_SimpleCAS to the examples. [jthiltges] + </notes> + </release> + <release> + <version> + <release>0.3.0</release> + <api>0.3.0</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-03-03</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Issue 1: Rename SimpleCAS_Server to SimpleCAS_Protocol [saltybeagle] +* Issue 2: Switch to arrays for the protocol constructors. [saltybeagle] +* Exit immediately after re-direct. + </notes> + </release> + <release> + <version> + <release>0.3.1</release> + <api>0.3.1</api> + </version> + <stability> + <release>alpha</release> + <api>alpha</api> + </stability> + <date>2009-04-06</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Prefix session variables with __SIMPLECAS [saltybeagle] + </notes> + </release> + <release> + <version> + <release>0.4.0</release> + <api>0.4.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2010-01-08</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Allow request adapter to be customized. [Kevin] + </notes> + </release> + <release> + <version> + <release>0.5.0</release> + <api>0.5.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2010-01-27</date> + <license uri="http://www1.unl.edu/wdn/wiki/Software_License">BSD License</license> + <notes> +* Allow service url to be customized with SimpleCAS::setURL($url); [ericrasmussen1] + </notes> + </release> + </changelog> + </phprelease> +</package> diff --git a/lib/data/HTTP_Request2/HTTP/generate-list.php b/lib/data/HTTP_Request2/HTTP/generate-list.php new file mode 100644 index 0000000000000000000000000000000000000000..839266bf999a42a1102588100a64653aab4ddac8 --- /dev/null +++ b/lib/data/HTTP_Request2/HTTP/generate-list.php @@ -0,0 +1,98 @@ +<?php +/** + * Helper file for downloading Public Suffix List and converting it to PHP array + * + * You can run this script to update PSL to the current version instead of + * waiting for a new release of HTTP_Request2. + * + * @version SVN: $Id: generate-list.php 308480 2011-02-19 11:27:13Z avb $ + */ + +/** URL to download Public Suffix List from */ +define('LIST_URL', 'http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1'); +/** Name of PHP file to write */ +define('OUTPUT_FILE', dirname(__FILE__) . '/public-suffix-list.php'); + +require_once 'HTTP/Request2.php'; + +function buildSubdomain(&$node, $tldParts) +{ + $part = trim(array_pop($tldParts)); + + if (!array_key_exists($part, $node)) { + $node[$part] = array(); + } + + if (0 < count($tldParts)) { + buildSubdomain($node[$part], $tldParts); + } +} + +function writeNode($fp, $valueTree, $key = null, $indent = 0) +{ + if (is_null($key)) { + fwrite($fp, "return "); + + } else { + fwrite($fp, str_repeat(' ', $indent) . "'$key' => "); + } + + if (0 == ($count = count($valueTree))) { + fwrite($fp, 'true'); + } else { + fwrite($fp, "array(\n"); + for ($keys = array_keys($valueTree), $i = 0; $i < $count; $i++) { + writeNode($fp, $valueTree[$keys[$i]], $keys[$i], $indent + 1); + if ($i + 1 != $count) { + fwrite($fp, ",\n"); + } else { + fwrite($fp, "\n"); + } + } + fwrite($fp, str_repeat(' ', $indent) . ")"); + } +} + + +try { + $request = new HTTP_Request2(LIST_URL); + $response = $request->send(); + if (200 != $response->getStatus()) { + throw new Exception("List download URL returned status: " . + $response->getStatus() . ' ' . $response->getReasonPhrase()); + } + $list = $response->getBody(); + if (false === strpos($list, 'The Original Code is the Public Suffix List.')) { + throw new Exception("List download URL does not contain expected phrase"); + } + if (!($fp = @fopen(OUTPUT_FILE, 'wt'))) { + throw new Exception("Unable to open " . OUTPUT_FILE); + } + +} catch (Exception $e) { + die($e->getMessage()); +} + +$tldTree = array(); +$license = true; + +fwrite($fp, "<?php\n"); + +foreach (array_filter(array_map('trim', explode("\n", $list))) as $line) { + if ('//' != substr($line, 0, 2)) { + buildSubdomain($tldTree, explode('.', $line)); + + } elseif ($license) { + fwrite($fp, $line . "\n"); + + if (0 === strpos($line, "// ***** END LICENSE BLOCK")) { + $license = false; + fwrite($fp, "\n"); + } + } +} + +writeNode($fp, $tldTree); +fwrite($fp, ";\n?>"); +fclose($fp); +?> \ No newline at end of file diff --git a/lib/data/HTTP_Request2/HTTP/public-suffix-list.php b/lib/data/HTTP_Request2/HTTP/public-suffix-list.php new file mode 100644 index 0000000000000000000000000000000000000000..eb68660d98f0958f40baabc54c47c61cabce4338 --- /dev/null +++ b/lib/data/HTTP_Request2/HTTP/public-suffix-list.php @@ -0,0 +1,4464 @@ +<?php +// ***** BEGIN LICENSE BLOCK ***** +// Version: MPL 1.1/GPL 2.0/LGPL 2.1 +// +// The contents of this file are subject to the Mozilla Public License Version +// 1.1 (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// http://www.mozilla.org/MPL/ +// +// Software distributed under the License is distributed on an "AS IS" basis, +// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +// for the specific language governing rights and limitations under the +// License. +// +// The Original Code is the Public Suffix List. +// +// The Initial Developer of the Original Code is +// Jo Hermans <jo.hermans@gmail.com>. +// Portions created by the Initial Developer are Copyright (C) 2007 +// the Initial Developer. All Rights Reserved. +// +// Contributor(s): +// Ruben Arakelyan <ruben@wackomenace.co.uk> +// Gervase Markham <gerv@gerv.net> +// Pamela Greene <pamg.bugs@gmail.com> +// David Triendl <david@triendl.name> +// Jothan Frakes <jothan@gmail.com> +// The kind representatives of many TLD registries +// +// Alternatively, the contents of this file may be used under the terms of +// either the GNU General Public License Version 2 or later (the "GPL"), or +// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +// in which case the provisions of the GPL or the LGPL are applicable instead +// of those above. If you wish to allow use of your version of this file only +// under the terms of either the GPL or the LGPL, and not to allow others to +// use your version of this file under the terms of the MPL, indicate your +// decision by deleting the provisions above and replace them with the notice +// and other provisions required by the GPL or the LGPL. If you do not delete +// the provisions above, a recipient may use your version of this file under +// the terms of any one of the MPL, the GPL or the LGPL. +// +// ***** END LICENSE BLOCK ***** + +return array( + 'ac' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'mil' => true, + 'org' => true + ), + 'ad' => array( + 'nom' => true + ), + 'ae' => array( + 'co' => true, + 'net' => true, + 'org' => true, + 'sch' => true, + 'ac' => true, + 'gov' => true, + 'mil' => true + ), + 'aero' => array( + 'accident-investigation' => true, + 'accident-prevention' => true, + 'aerobatic' => true, + 'aeroclub' => true, + 'aerodrome' => true, + 'agents' => true, + 'aircraft' => true, + 'airline' => true, + 'airport' => true, + 'air-surveillance' => true, + 'airtraffic' => true, + 'air-traffic-control' => true, + 'ambulance' => true, + 'amusement' => true, + 'association' => true, + 'author' => true, + 'ballooning' => true, + 'broker' => true, + 'caa' => true, + 'cargo' => true, + 'catering' => true, + 'certification' => true, + 'championship' => true, + 'charter' => true, + 'civilaviation' => true, + 'club' => true, + 'conference' => true, + 'consultant' => true, + 'consulting' => true, + 'control' => true, + 'council' => true, + 'crew' => true, + 'design' => true, + 'dgca' => true, + 'educator' => true, + 'emergency' => true, + 'engine' => true, + 'engineer' => true, + 'entertainment' => true, + 'equipment' => true, + 'exchange' => true, + 'express' => true, + 'federation' => true, + 'flight' => true, + 'freight' => true, + 'fuel' => true, + 'gliding' => true, + 'government' => true, + 'groundhandling' => true, + 'group' => true, + 'hanggliding' => true, + 'homebuilt' => true, + 'insurance' => true, + 'journal' => true, + 'journalist' => true, + 'leasing' => true, + 'logistics' => true, + 'magazine' => true, + 'maintenance' => true, + 'marketplace' => true, + 'media' => true, + 'microlight' => true, + 'modelling' => true, + 'navigation' => true, + 'parachuting' => true, + 'paragliding' => true, + 'passenger-association' => true, + 'pilot' => true, + 'press' => true, + 'production' => true, + 'recreation' => true, + 'repbody' => true, + 'res' => true, + 'research' => true, + 'rotorcraft' => true, + 'safety' => true, + 'scientist' => true, + 'services' => true, + 'show' => true, + 'skydiving' => true, + 'software' => true, + 'student' => true, + 'taxi' => true, + 'trader' => true, + 'trading' => true, + 'trainer' => true, + 'union' => true, + 'workinggroup' => true, + 'works' => true + ), + 'af' => array( + 'gov' => true, + 'com' => true, + 'org' => true, + 'net' => true, + 'edu' => true + ), + 'ag' => array( + 'com' => true, + 'org' => true, + 'net' => true, + 'co' => true, + 'nom' => true + ), + 'ai' => array( + 'off' => true, + 'com' => true, + 'net' => true, + 'org' => true + ), + 'al' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'mil' => true, + 'net' => true, + 'org' => true + ), + 'am' => true, + 'an' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true + ), + 'ao' => array( + 'ed' => true, + 'gv' => true, + 'og' => true, + 'co' => true, + 'pb' => true, + 'it' => true + ), + 'aq' => true, + 'ar' => array( + '*' => true, + '!congresodelalengua3' => true, + '!educ' => true, + '!gobiernoelectronico' => true, + '!mecon' => true, + '!nacion' => true, + '!nic' => true, + '!promocion' => true, + '!retina' => true, + '!uba' => true + ), + 'arpa' => array( + 'e164' => true, + 'in-addr' => true, + 'ip6' => true, + 'iris' => true, + 'uri' => true, + 'urn' => true + ), + 'as' => array( + 'gov' => true + ), + 'asia' => true, + 'at' => array( + 'ac' => true, + 'co' => true, + 'gv' => true, + 'or' => true, + 'biz' => true, + 'info' => true, + 'priv' => true + ), + 'au' => array( + '*' => true, + 'edu' => array( + 'act' => true, + 'nsw' => true, + 'nt' => true, + 'qld' => true, + 'sa' => true, + 'tas' => true, + 'vic' => true, + 'wa' => true + ), + 'gov' => array( + 'act' => true, + 'nt' => true, + 'qld' => true, + 'sa' => true, + 'tas' => true, + 'vic' => true, + 'wa' => true + ), + 'act' => true, + 'nsw' => true, + 'nt' => true, + 'qld' => true, + 'sa' => true, + 'tas' => true, + 'vic' => true, + 'wa' => true + ), + 'aw' => array( + 'com' => true + ), + 'ax' => true, + 'az' => array( + 'com' => true, + 'net' => true, + 'int' => true, + 'gov' => true, + 'org' => true, + 'edu' => true, + 'info' => true, + 'pp' => true, + 'mil' => true, + 'name' => true, + 'pro' => true, + 'biz' => true + ), + 'ba' => array( + 'org' => true, + 'net' => true, + 'edu' => true, + 'gov' => true, + 'mil' => true, + 'unsa' => true, + 'unbi' => true, + 'co' => true, + 'com' => true, + 'rs' => true + ), + 'bb' => array( + 'biz' => true, + 'com' => true, + 'edu' => true, + 'gov' => true, + 'info' => true, + 'net' => true, + 'org' => true, + 'store' => true + ), + 'bd' => array( + '*' => true + ), + 'be' => array( + 'ac' => true + ), + 'bf' => array( + 'gov' => true + ), + 'bg' => array( + 'a' => true, + 'b' => true, + 'c' => true, + 'd' => true, + 'e' => true, + 'f' => true, + 'g' => true, + 'h' => true, + 'i' => true, + 'j' => true, + 'k' => true, + 'l' => true, + 'm' => true, + 'n' => true, + 'o' => true, + 'p' => true, + 'q' => true, + 'r' => true, + 's' => true, + 't' => true, + 'u' => true, + 'v' => true, + 'w' => true, + 'x' => true, + 'y' => true, + 'z' => true, + '0' => true, + '1' => true, + '2' => true, + '3' => true, + '4' => true, + '5' => true, + '6' => true, + '7' => true, + '8' => true, + '9' => true + ), + 'bh' => array( + 'com' => true, + 'edu' => true, + 'net' => true, + 'org' => true, + 'gov' => true + ), + 'bi' => array( + 'co' => true, + 'com' => true, + 'edu' => true, + 'or' => true, + 'org' => true + ), + 'biz' => true, + 'bj' => array( + 'asso' => true, + 'barreau' => true, + 'gouv' => true + ), + 'bm' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'org' => true + ), + 'bn' => array( + '*' => true + ), + 'bo' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'gob' => true, + 'int' => true, + 'org' => true, + 'net' => true, + 'mil' => true, + 'tv' => true + ), + 'br' => array( + 'adm' => true, + 'adv' => true, + 'agr' => true, + 'am' => true, + 'arq' => true, + 'art' => true, + 'ato' => true, + 'bio' => true, + 'blog' => true, + 'bmd' => true, + 'can' => true, + 'cim' => true, + 'cng' => true, + 'cnt' => true, + 'com' => true, + 'coop' => true, + 'ecn' => true, + 'edu' => true, + 'eng' => true, + 'esp' => true, + 'etc' => true, + 'eti' => true, + 'far' => true, + 'flog' => true, + 'fm' => true, + 'fnd' => true, + 'fot' => true, + 'fst' => true, + 'g12' => true, + 'ggf' => true, + 'gov' => true, + 'imb' => true, + 'ind' => true, + 'inf' => true, + 'jor' => true, + 'jus' => true, + 'lel' => true, + 'mat' => true, + 'med' => true, + 'mil' => true, + 'mus' => true, + 'net' => true, + 'nom' => true, + 'not' => true, + 'ntr' => true, + 'odo' => true, + 'org' => true, + 'ppg' => true, + 'pro' => true, + 'psc' => true, + 'psi' => true, + 'qsl' => true, + 'rec' => true, + 'slg' => true, + 'srv' => true, + 'tmp' => true, + 'trd' => true, + 'tur' => true, + 'tv' => true, + 'vet' => true, + 'vlog' => true, + 'wiki' => true, + 'zlg' => true + ), + 'bs' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'gov' => true + ), + 'bt' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'org' => true + ), + 'bw' => array( + 'co' => true, + 'org' => true + ), + 'by' => array( + 'gov' => true, + 'mil' => true, + 'com' => true, + 'of' => true + ), + 'bz' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'gov' => true + ), + 'ca' => array( + 'ab' => true, + 'bc' => true, + 'mb' => true, + 'nb' => true, + 'nf' => true, + 'nl' => true, + 'ns' => true, + 'nt' => true, + 'nu' => true, + 'on' => true, + 'pe' => true, + 'qc' => true, + 'sk' => true, + 'yk' => true, + 'gc' => true + ), + 'cat' => true, + 'cc' => true, + 'cd' => array( + 'gov' => true + ), + 'cf' => true, + 'cg' => true, + 'ch' => true, + 'ci' => array( + 'org' => true, + 'or' => true, + 'com' => true, + 'co' => true, + 'edu' => true, + 'ed' => true, + 'ac' => true, + 'net' => true, + 'go' => true, + 'asso' => true, + 'aéroport' => true, + 'int' => true, + 'presse' => true, + 'md' => true, + 'gouv' => true + ), + 'ck' => array( + '*' => true + ), + 'cl' => array( + 'gov' => true, + 'gob' => true + ), + 'cm' => array( + 'gov' => true + ), + 'cn' => array( + 'ac' => true, + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'org' => true, + 'mil' => true, + '公司' => true, + '网络' => true, + '網絡' => true, + 'ah' => true, + 'bj' => true, + 'cq' => true, + 'fj' => true, + 'gd' => true, + 'gs' => true, + 'gz' => true, + 'gx' => true, + 'ha' => true, + 'hb' => true, + 'he' => true, + 'hi' => true, + 'hl' => true, + 'hn' => true, + 'jl' => true, + 'js' => true, + 'jx' => true, + 'ln' => true, + 'nm' => true, + 'nx' => true, + 'qh' => true, + 'sc' => true, + 'sd' => true, + 'sh' => true, + 'sn' => true, + 'sx' => true, + 'tj' => true, + 'xj' => true, + 'xz' => true, + 'yn' => true, + 'zj' => true, + 'hk' => true, + 'mo' => true, + 'tw' => true + ), + 'co' => array( + 'arts' => true, + 'com' => true, + 'edu' => true, + 'firm' => true, + 'gov' => true, + 'info' => true, + 'int' => true, + 'mil' => true, + 'net' => true, + 'nom' => true, + 'org' => true, + 'rec' => true, + 'web' => true + ), + 'com' => array( + 'ar' => true, + 'br' => true, + 'cn' => true, + 'de' => true, + 'eu' => true, + 'gb' => true, + 'hu' => true, + 'jpn' => true, + 'kr' => true, + 'no' => true, + 'qc' => true, + 'ru' => true, + 'sa' => true, + 'se' => true, + 'uk' => true, + 'us' => true, + 'uy' => true, + 'za' => true, + 'operaunite' => true, + 'appspot' => true + ), + 'coop' => true, + 'cr' => array( + 'ac' => true, + 'co' => true, + 'ed' => true, + 'fi' => true, + 'go' => true, + 'or' => true, + 'sa' => true + ), + 'cu' => array( + 'com' => true, + 'edu' => true, + 'org' => true, + 'net' => true, + 'gov' => true, + 'inf' => true + ), + 'cv' => true, + 'cx' => array( + 'gov' => true + ), + 'cy' => array( + '*' => true + ), + 'cz' => true, + 'de' => true, + 'dj' => true, + 'dk' => true, + 'dm' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'gov' => true + ), + 'do' => array( + '*' => true + ), + 'dz' => array( + 'com' => true, + 'org' => true, + 'net' => true, + 'gov' => true, + 'edu' => true, + 'asso' => true, + 'pol' => true, + 'art' => true + ), + 'ec' => array( + 'com' => true, + 'info' => true, + 'net' => true, + 'fin' => true, + 'k12' => true, + 'med' => true, + 'pro' => true, + 'org' => true, + 'edu' => true, + 'gov' => true, + 'gob' => true, + 'mil' => true + ), + 'edu' => true, + 'ee' => array( + 'edu' => true, + 'gov' => true, + 'riik' => true, + 'lib' => true, + 'med' => true, + 'com' => true, + 'pri' => true, + 'aip' => true, + 'org' => true, + 'fie' => true + ), + 'eg' => array( + '*' => true + ), + 'er' => array( + '*' => true + ), + 'es' => array( + 'com' => true, + 'nom' => true, + 'org' => true, + 'gob' => true, + 'edu' => true + ), + 'et' => array( + '*' => true + ), + 'eu' => true, + 'fi' => array( + 'aland' => true, + 'iki' => true + ), + 'fj' => array( + '*' => true + ), + 'fk' => array( + '*' => true + ), + 'fm' => true, + 'fo' => true, + 'fr' => array( + 'com' => true, + 'asso' => true, + 'nom' => true, + 'prd' => true, + 'presse' => true, + 'tm' => true, + 'aeroport' => true, + 'assedic' => true, + 'avocat' => true, + 'avoues' => true, + 'cci' => true, + 'chambagri' => true, + 'chirurgiens-dentistes' => true, + 'experts-comptables' => true, + 'geometre-expert' => true, + 'gouv' => true, + 'greta' => true, + 'huissier-justice' => true, + 'medecin' => true, + 'notaires' => true, + 'pharmacien' => true, + 'port' => true, + 'veterinaire' => true + ), + 'ga' => true, + 'gd' => true, + 'ge' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'mil' => true, + 'net' => true, + 'pvt' => true + ), + 'gf' => true, + 'gg' => array( + 'co' => true, + 'org' => true, + 'net' => true, + 'sch' => true, + 'gov' => true + ), + 'gh' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'mil' => true + ), + 'gi' => array( + 'com' => true, + 'ltd' => true, + 'gov' => true, + 'mod' => true, + 'edu' => true, + 'org' => true + ), + 'gl' => true, + 'gm' => true, + 'gn' => array( + 'ac' => true, + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'net' => true + ), + 'gov' => true, + 'gp' => array( + 'com' => true, + 'net' => true, + 'mobi' => true, + 'edu' => true, + 'org' => true, + 'asso' => true + ), + 'gq' => true, + 'gr' => array( + 'com' => true, + 'edu' => true, + 'net' => true, + 'org' => true, + 'gov' => true + ), + 'gs' => true, + 'gt' => array( + '*' => true + ), + 'gu' => array( + '*' => true + ), + 'gw' => true, + 'gy' => array( + 'co' => true, + 'com' => true, + 'net' => true + ), + 'hk' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'idv' => true, + 'net' => true, + 'org' => true, + '公司' => true, + '教育' => true, + '敎育' => true, + '政府' => true, + '個人' => true, + '个人' => true, + '箇人' => true, + '網络' => true, + '网络' => true, + '组織' => true, + '網絡' => true, + '网絡' => true, + '组织' => true, + '組織' => true, + '組织' => true + ), + 'hm' => true, + 'hn' => array( + 'com' => true, + 'edu' => true, + 'org' => true, + 'net' => true, + 'mil' => true, + 'gob' => true + ), + 'hr' => array( + 'iz' => true, + 'from' => true, + 'name' => true, + 'com' => true + ), + 'ht' => array( + 'com' => true, + 'shop' => true, + 'firm' => true, + 'info' => true, + 'adult' => true, + 'net' => true, + 'pro' => true, + 'org' => true, + 'med' => true, + 'art' => true, + 'coop' => true, + 'pol' => true, + 'asso' => true, + 'edu' => true, + 'rel' => true, + 'gouv' => true, + 'perso' => true + ), + 'hu' => array( + 'co' => true, + 'info' => true, + 'org' => true, + 'priv' => true, + 'sport' => true, + 'tm' => true, + '2000' => true, + 'agrar' => true, + 'bolt' => true, + 'casino' => true, + 'city' => true, + 'erotica' => true, + 'erotika' => true, + 'film' => true, + 'forum' => true, + 'games' => true, + 'hotel' => true, + 'ingatlan' => true, + 'jogasz' => true, + 'konyvelo' => true, + 'lakas' => true, + 'media' => true, + 'news' => true, + 'reklam' => true, + 'sex' => true, + 'shop' => true, + 'suli' => true, + 'szex' => true, + 'tozsde' => true, + 'utazas' => true, + 'video' => true + ), + 'id' => array( + 'ac' => true, + 'co' => true, + 'go' => true, + 'mil' => true, + 'net' => true, + 'or' => true, + 'sch' => true, + 'web' => true + ), + 'ie' => array( + 'gov' => true + ), + 'il' => array( + '*' => true + ), + 'im' => array( + 'co' => array( + 'ltd' => true, + 'plc' => true + ), + 'net' => true, + 'gov' => true, + 'org' => true, + 'nic' => true, + 'ac' => true + ), + 'in' => array( + 'co' => true, + 'firm' => true, + 'net' => true, + 'org' => true, + 'gen' => true, + 'ind' => true, + 'nic' => true, + 'ac' => true, + 'edu' => true, + 'res' => true, + 'gov' => true, + 'mil' => true + ), + 'info' => true, + 'int' => array( + 'eu' => true + ), + 'io' => array( + 'com' => true + ), + 'iq' => array( + 'gov' => true, + 'edu' => true, + 'mil' => true, + 'com' => true, + 'org' => true, + 'net' => true + ), + 'ir' => array( + 'ac' => true, + 'co' => true, + 'gov' => true, + 'id' => true, + 'net' => true, + 'org' => true, + 'sch' => true, + 'ایران' => true, + 'ايران' => true + ), + 'is' => array( + 'net' => true, + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'int' => true + ), + 'it' => array( + 'gov' => true, + 'edu' => true, + 'agrigento' => true, + 'ag' => true, + 'alessandria' => true, + 'al' => true, + 'ancona' => true, + 'an' => true, + 'aosta' => true, + 'aoste' => true, + 'ao' => true, + 'arezzo' => true, + 'ar' => true, + 'ascoli-piceno' => true, + 'ascolipiceno' => true, + 'ap' => true, + 'asti' => true, + 'at' => true, + 'avellino' => true, + 'av' => true, + 'bari' => true, + 'ba' => true, + 'andria-barletta-trani' => true, + 'andriabarlettatrani' => true, + 'trani-barletta-andria' => true, + 'tranibarlettaandria' => true, + 'barletta-trani-andria' => true, + 'barlettatraniandria' => true, + 'andria-trani-barletta' => true, + 'andriatranibarletta' => true, + 'trani-andria-barletta' => true, + 'traniandriabarletta' => true, + 'bt' => true, + 'belluno' => true, + 'bl' => true, + 'benevento' => true, + 'bn' => true, + 'bergamo' => true, + 'bg' => true, + 'biella' => true, + 'bi' => true, + 'bologna' => true, + 'bo' => true, + 'bolzano' => true, + 'bozen' => true, + 'balsan' => true, + 'alto-adige' => true, + 'altoadige' => true, + 'suedtirol' => true, + 'bz' => true, + 'brescia' => true, + 'bs' => true, + 'brindisi' => true, + 'br' => true, + 'cagliari' => true, + 'ca' => true, + 'caltanissetta' => true, + 'cl' => true, + 'campobasso' => true, + 'cb' => true, + 'carboniaiglesias' => true, + 'carbonia-iglesias' => true, + 'iglesias-carbonia' => true, + 'iglesiascarbonia' => true, + 'ci' => true, + 'caserta' => true, + 'ce' => true, + 'catania' => true, + 'ct' => true, + 'catanzaro' => true, + 'cz' => true, + 'chieti' => true, + 'ch' => true, + 'como' => true, + 'co' => true, + 'cosenza' => true, + 'cs' => true, + 'cremona' => true, + 'cr' => true, + 'crotone' => true, + 'kr' => true, + 'cuneo' => true, + 'cn' => true, + 'dell-ogliastra' => true, + 'dellogliastra' => true, + 'ogliastra' => true, + 'og' => true, + 'enna' => true, + 'en' => true, + 'ferrara' => true, + 'fe' => true, + 'fermo' => true, + 'fm' => true, + 'firenze' => true, + 'florence' => true, + 'fi' => true, + 'foggia' => true, + 'fg' => true, + 'forli-cesena' => true, + 'forlicesena' => true, + 'cesena-forli' => true, + 'cesenaforli' => true, + 'fc' => true, + 'frosinone' => true, + 'fr' => true, + 'genova' => true, + 'genoa' => true, + 'ge' => true, + 'gorizia' => true, + 'go' => true, + 'grosseto' => true, + 'gr' => true, + 'imperia' => true, + 'im' => true, + 'isernia' => true, + 'is' => true, + 'laquila' => true, + 'aquila' => true, + 'aq' => true, + 'la-spezia' => true, + 'laspezia' => true, + 'sp' => true, + 'latina' => true, + 'lt' => true, + 'lecce' => true, + 'le' => true, + 'lecco' => true, + 'lc' => true, + 'livorno' => true, + 'li' => true, + 'lodi' => true, + 'lo' => true, + 'lucca' => true, + 'lu' => true, + 'macerata' => true, + 'mc' => true, + 'mantova' => true, + 'mn' => true, + 'massa-carrara' => true, + 'massacarrara' => true, + 'carrara-massa' => true, + 'carraramassa' => true, + 'ms' => true, + 'matera' => true, + 'mt' => true, + 'medio-campidano' => true, + 'mediocampidano' => true, + 'campidano-medio' => true, + 'campidanomedio' => true, + 'vs' => true, + 'messina' => true, + 'me' => true, + 'milano' => true, + 'milan' => true, + 'mi' => true, + 'modena' => true, + 'mo' => true, + 'monza' => true, + 'monza-brianza' => true, + 'monzabrianza' => true, + 'monzaebrianza' => true, + 'monzaedellabrianza' => true, + 'monza-e-della-brianza' => true, + 'mb' => true, + 'napoli' => true, + 'naples' => true, + 'na' => true, + 'novara' => true, + 'no' => true, + 'nuoro' => true, + 'nu' => true, + 'oristano' => true, + 'or' => true, + 'padova' => true, + 'padua' => true, + 'pd' => true, + 'palermo' => true, + 'pa' => true, + 'parma' => true, + 'pr' => true, + 'pavia' => true, + 'pv' => true, + 'perugia' => true, + 'pg' => true, + 'pescara' => true, + 'pe' => true, + 'pesaro-urbino' => true, + 'pesarourbino' => true, + 'urbino-pesaro' => true, + 'urbinopesaro' => true, + 'pu' => true, + 'piacenza' => true, + 'pc' => true, + 'pisa' => true, + 'pi' => true, + 'pistoia' => true, + 'pt' => true, + 'pordenone' => true, + 'pn' => true, + 'potenza' => true, + 'pz' => true, + 'prato' => true, + 'po' => true, + 'ragusa' => true, + 'rg' => true, + 'ravenna' => true, + 'ra' => true, + 'reggio-calabria' => true, + 'reggiocalabria' => true, + 'rc' => true, + 'reggio-emilia' => true, + 'reggioemilia' => true, + 're' => true, + 'rieti' => true, + 'ri' => true, + 'rimini' => true, + 'rn' => true, + 'roma' => true, + 'rome' => true, + 'rm' => true, + 'rovigo' => true, + 'ro' => true, + 'salerno' => true, + 'sa' => true, + 'sassari' => true, + 'ss' => true, + 'savona' => true, + 'sv' => true, + 'siena' => true, + 'si' => true, + 'siracusa' => true, + 'sr' => true, + 'sondrio' => true, + 'so' => true, + 'taranto' => true, + 'ta' => true, + 'tempio-olbia' => true, + 'tempioolbia' => true, + 'olbia-tempio' => true, + 'olbiatempio' => true, + 'ot' => true, + 'teramo' => true, + 'te' => true, + 'terni' => true, + 'tr' => true, + 'torino' => true, + 'turin' => true, + 'to' => true, + 'trapani' => true, + 'tp' => true, + 'trento' => true, + 'trentino' => true, + 'tn' => true, + 'treviso' => true, + 'tv' => true, + 'trieste' => true, + 'ts' => true, + 'udine' => true, + 'ud' => true, + 'varese' => true, + 'va' => true, + 'venezia' => true, + 'venice' => true, + 've' => true, + 'verbania' => true, + 'vb' => true, + 'vercelli' => true, + 'vc' => true, + 'verona' => true, + 'vr' => true, + 'vibo-valentia' => true, + 'vibovalentia' => true, + 'vv' => true, + 'vicenza' => true, + 'vi' => true, + 'viterbo' => true, + 'vt' => true + ), + 'je' => array( + 'co' => true, + 'org' => true, + 'net' => true, + 'sch' => true, + 'gov' => true + ), + 'jm' => array( + '*' => true + ), + 'jo' => array( + 'com' => true, + 'org' => true, + 'net' => true, + 'edu' => true, + 'sch' => true, + 'gov' => true, + 'mil' => true, + 'name' => true + ), + 'jobs' => true, + 'jp' => array( + 'ac' => true, + 'ad' => true, + 'co' => true, + 'ed' => true, + 'go' => true, + 'gr' => true, + 'lg' => true, + 'ne' => true, + 'or' => true, + 'aichi' => array( + '*' => true, + '!pref' => true + ), + 'akita' => array( + '*' => true, + '!pref' => true + ), + 'aomori' => array( + '*' => true, + '!pref' => true + ), + 'chiba' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'ehime' => array( + '*' => true, + '!pref' => true + ), + 'fukui' => array( + '*' => true, + '!pref' => true + ), + 'fukuoka' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'fukushima' => array( + '*' => true, + '!pref' => true + ), + 'gifu' => array( + '*' => true, + '!pref' => true + ), + 'gunma' => array( + '*' => true, + '!pref' => true + ), + 'hiroshima' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'hokkaido' => array( + '*' => true, + '!pref' => true + ), + 'hyogo' => array( + '*' => true, + '!pref' => true + ), + 'ibaraki' => array( + '*' => true, + '!pref' => true + ), + 'ishikawa' => array( + '*' => true, + '!pref' => true + ), + 'iwate' => array( + '*' => true, + '!pref' => true + ), + 'kagawa' => array( + '*' => true, + '!pref' => true + ), + 'kagoshima' => array( + '*' => true, + '!pref' => true + ), + 'kanagawa' => array( + '*' => true, + '!pref' => true + ), + 'kawasaki' => array( + '*' => true, + '!city' => true + ), + 'kitakyushu' => array( + '*' => true, + '!city' => true + ), + 'kobe' => array( + '*' => true, + '!city' => true + ), + 'kochi' => array( + '*' => true, + '!pref' => true + ), + 'kumamoto' => array( + '*' => true, + '!pref' => true + ), + 'kyoto' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'mie' => array( + '*' => true, + '!pref' => true + ), + 'miyagi' => array( + '*' => true, + '!pref' => true + ), + 'miyazaki' => array( + '*' => true, + '!pref' => true + ), + 'nagano' => array( + '*' => true, + '!pref' => true + ), + 'nagasaki' => array( + '*' => true, + '!pref' => true + ), + 'nagoya' => array( + '*' => true, + '!city' => true + ), + 'nara' => array( + '*' => true, + '!pref' => true + ), + 'niigata' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'oita' => array( + '*' => true, + '!pref' => true + ), + 'okayama' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'okinawa' => array( + '*' => true, + '!pref' => true + ), + 'osaka' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'saga' => array( + '*' => true, + '!pref' => true + ), + 'saitama' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'sapporo' => array( + '*' => true, + '!city' => true + ), + 'sendai' => array( + '*' => true, + '!city' => true + ), + 'shiga' => array( + '*' => true, + '!pref' => true + ), + 'shimane' => array( + '*' => true, + '!pref' => true + ), + 'shizuoka' => array( + '*' => true, + '!pref' => true, + '!city' => true + ), + 'tochigi' => array( + '*' => true, + '!pref' => true + ), + 'tokushima' => array( + '*' => true, + '!pref' => true + ), + 'tokyo' => array( + '*' => true, + '!metro' => true + ), + 'tottori' => array( + '*' => true, + '!pref' => true + ), + 'toyama' => array( + '*' => true, + '!pref' => true + ), + 'wakayama' => array( + '*' => true, + '!pref' => true + ), + 'yamagata' => array( + '*' => true, + '!pref' => true + ), + 'yamaguchi' => array( + '*' => true, + '!pref' => true + ), + 'yamanashi' => array( + '*' => true, + '!pref' => true + ), + 'yokohama' => array( + '*' => true, + '!city' => true + ) + ), + 'ke' => array( + '*' => true + ), + 'kg' => array( + 'org' => true, + 'net' => true, + 'com' => true, + 'edu' => true, + 'gov' => true, + 'mil' => true + ), + 'kh' => array( + '*' => true + ), + 'ki' => array( + 'edu' => true, + 'biz' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'info' => true, + 'com' => true + ), + 'km' => array( + 'org' => true, + 'nom' => true, + 'gov' => true, + 'prd' => true, + 'tm' => true, + 'edu' => true, + 'mil' => true, + 'ass' => true, + 'com' => true, + 'coop' => true, + 'asso' => true, + 'presse' => true, + 'medecin' => true, + 'notaires' => true, + 'pharmaciens' => true, + 'veterinaire' => true, + 'gouv' => true + ), + 'kn' => array( + 'net' => true, + 'org' => true, + 'edu' => true, + 'gov' => true + ), + 'kp' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'rep' => true, + 'tra' => true + ), + 'kr' => array( + 'ac' => true, + 'co' => true, + 'es' => true, + 'go' => true, + 'hs' => true, + 'kg' => true, + 'mil' => true, + 'ms' => true, + 'ne' => true, + 'or' => true, + 'pe' => true, + 're' => true, + 'sc' => true, + 'busan' => true, + 'chungbuk' => true, + 'chungnam' => true, + 'daegu' => true, + 'daejeon' => true, + 'gangwon' => true, + 'gwangju' => true, + 'gyeongbuk' => true, + 'gyeonggi' => true, + 'gyeongnam' => true, + 'incheon' => true, + 'jeju' => true, + 'jeonbuk' => true, + 'jeonnam' => true, + 'seoul' => true, + 'ulsan' => true + ), + 'kw' => array( + '*' => true + ), + 'ky' => array( + 'edu' => true, + 'gov' => true, + 'com' => true, + 'org' => true, + 'net' => true + ), + 'kz' => array( + 'org' => true, + 'edu' => true, + 'net' => true, + 'gov' => true, + 'mil' => true, + 'com' => true + ), + 'la' => array( + 'int' => true, + 'net' => true, + 'info' => true, + 'edu' => true, + 'gov' => true, + 'per' => true, + 'com' => true, + 'org' => true, + 'c' => true + ), + 'lb' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'org' => true + ), + 'lc' => array( + 'com' => true, + 'net' => true, + 'co' => true, + 'org' => true, + 'edu' => true, + 'gov' => true + ), + 'li' => true, + 'lk' => array( + 'gov' => true, + 'sch' => true, + 'net' => true, + 'int' => true, + 'com' => true, + 'org' => true, + 'edu' => true, + 'ngo' => true, + 'soc' => true, + 'web' => true, + 'ltd' => true, + 'assn' => true, + 'grp' => true, + 'hotel' => true + ), + 'local' => true, + 'lr' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'net' => true + ), + 'ls' => array( + 'co' => true, + 'org' => true + ), + 'lt' => array( + 'gov' => true + ), + 'lu' => true, + 'lv' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'org' => true, + 'mil' => true, + 'id' => true, + 'net' => true, + 'asn' => true, + 'conf' => true + ), + 'ly' => array( + 'com' => true, + 'net' => true, + 'gov' => true, + 'plc' => true, + 'edu' => true, + 'sch' => true, + 'med' => true, + 'org' => true, + 'id' => true + ), + 'ma' => array( + 'co' => true, + 'net' => true, + 'gov' => true, + 'org' => true, + 'ac' => true, + 'press' => true + ), + 'mc' => array( + 'tm' => true, + 'asso' => true + ), + 'md' => true, + 'me' => array( + 'co' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'ac' => true, + 'gov' => true, + 'its' => true, + 'priv' => true + ), + 'mg' => array( + 'org' => true, + 'nom' => true, + 'gov' => true, + 'prd' => true, + 'tm' => true, + 'edu' => true, + 'mil' => true, + 'com' => true + ), + 'mh' => true, + 'mil' => true, + 'mk' => array( + 'com' => true, + 'org' => true, + 'net' => true, + 'edu' => true, + 'gov' => true, + 'inf' => true, + 'name' => true + ), + 'ml' => array( + 'com' => true, + 'edu' => true, + 'gouv' => true, + 'gov' => true, + 'net' => true, + 'org' => true, + 'presse' => true + ), + 'mm' => array( + '*' => true + ), + 'mn' => array( + 'gov' => true, + 'edu' => true, + 'org' => true + ), + 'mo' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'gov' => true + ), + 'mobi' => true, + 'mp' => true, + 'mq' => true, + 'mr' => array( + 'gov' => true + ), + 'ms' => true, + 'mt' => array( + '*' => true + ), + 'mu' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'ac' => true, + 'co' => true, + 'or' => true + ), + 'museum' => array( + 'academy' => true, + 'agriculture' => true, + 'air' => true, + 'airguard' => true, + 'alabama' => true, + 'alaska' => true, + 'amber' => true, + 'ambulance' => true, + 'american' => true, + 'americana' => true, + 'americanantiques' => true, + 'americanart' => true, + 'amsterdam' => true, + 'and' => true, + 'annefrank' => true, + 'anthro' => true, + 'anthropology' => true, + 'antiques' => true, + 'aquarium' => true, + 'arboretum' => true, + 'archaeological' => true, + 'archaeology' => true, + 'architecture' => true, + 'art' => true, + 'artanddesign' => true, + 'artcenter' => true, + 'artdeco' => true, + 'arteducation' => true, + 'artgallery' => true, + 'arts' => true, + 'artsandcrafts' => true, + 'asmatart' => true, + 'assassination' => true, + 'assisi' => true, + 'association' => true, + 'astronomy' => true, + 'atlanta' => true, + 'austin' => true, + 'australia' => true, + 'automotive' => true, + 'aviation' => true, + 'axis' => true, + 'badajoz' => true, + 'baghdad' => true, + 'bahn' => true, + 'bale' => true, + 'baltimore' => true, + 'barcelona' => true, + 'baseball' => true, + 'basel' => true, + 'baths' => true, + 'bauern' => true, + 'beauxarts' => true, + 'beeldengeluid' => true, + 'bellevue' => true, + 'bergbau' => true, + 'berkeley' => true, + 'berlin' => true, + 'bern' => true, + 'bible' => true, + 'bilbao' => true, + 'bill' => true, + 'birdart' => true, + 'birthplace' => true, + 'bonn' => true, + 'boston' => true, + 'botanical' => true, + 'botanicalgarden' => true, + 'botanicgarden' => true, + 'botany' => true, + 'brandywinevalley' => true, + 'brasil' => true, + 'bristol' => true, + 'british' => true, + 'britishcolumbia' => true, + 'broadcast' => true, + 'brunel' => true, + 'brussel' => true, + 'brussels' => true, + 'bruxelles' => true, + 'building' => true, + 'burghof' => true, + 'bus' => true, + 'bushey' => true, + 'cadaques' => true, + 'california' => true, + 'cambridge' => true, + 'can' => true, + 'canada' => true, + 'capebreton' => true, + 'carrier' => true, + 'cartoonart' => true, + 'casadelamoneda' => true, + 'castle' => true, + 'castres' => true, + 'celtic' => true, + 'center' => true, + 'chattanooga' => true, + 'cheltenham' => true, + 'chesapeakebay' => true, + 'chicago' => true, + 'children' => true, + 'childrens' => true, + 'childrensgarden' => true, + 'chiropractic' => true, + 'chocolate' => true, + 'christiansburg' => true, + 'cincinnati' => true, + 'cinema' => true, + 'circus' => true, + 'civilisation' => true, + 'civilization' => true, + 'civilwar' => true, + 'clinton' => true, + 'clock' => true, + 'coal' => true, + 'coastaldefence' => true, + 'cody' => true, + 'coldwar' => true, + 'collection' => true, + 'colonialwilliamsburg' => true, + 'coloradoplateau' => true, + 'columbia' => true, + 'columbus' => true, + 'communication' => true, + 'communications' => true, + 'community' => true, + 'computer' => true, + 'computerhistory' => true, + 'comunicações' => true, + 'contemporary' => true, + 'contemporaryart' => true, + 'convent' => true, + 'copenhagen' => true, + 'corporation' => true, + 'correios-e-telecomunicações' => true, + 'corvette' => true, + 'costume' => true, + 'countryestate' => true, + 'county' => true, + 'crafts' => true, + 'cranbrook' => true, + 'creation' => true, + 'cultural' => true, + 'culturalcenter' => true, + 'culture' => true, + 'cyber' => true, + 'cymru' => true, + 'dali' => true, + 'dallas' => true, + 'database' => true, + 'ddr' => true, + 'decorativearts' => true, + 'delaware' => true, + 'delmenhorst' => true, + 'denmark' => true, + 'depot' => true, + 'design' => true, + 'detroit' => true, + 'dinosaur' => true, + 'discovery' => true, + 'dolls' => true, + 'donostia' => true, + 'durham' => true, + 'eastafrica' => true, + 'eastcoast' => true, + 'education' => true, + 'educational' => true, + 'egyptian' => true, + 'eisenbahn' => true, + 'elburg' => true, + 'elvendrell' => true, + 'embroidery' => true, + 'encyclopedic' => true, + 'england' => true, + 'entomology' => true, + 'environment' => true, + 'environmentalconservation' => true, + 'epilepsy' => true, + 'essex' => true, + 'estate' => true, + 'ethnology' => true, + 'exeter' => true, + 'exhibition' => true, + 'family' => true, + 'farm' => true, + 'farmequipment' => true, + 'farmers' => true, + 'farmstead' => true, + 'field' => true, + 'figueres' => true, + 'filatelia' => true, + 'film' => true, + 'fineart' => true, + 'finearts' => true, + 'finland' => true, + 'flanders' => true, + 'florida' => true, + 'force' => true, + 'fortmissoula' => true, + 'fortworth' => true, + 'foundation' => true, + 'francaise' => true, + 'frankfurt' => true, + 'franziskaner' => true, + 'freemasonry' => true, + 'freiburg' => true, + 'fribourg' => true, + 'frog' => true, + 'fundacio' => true, + 'furniture' => true, + 'gallery' => true, + 'garden' => true, + 'gateway' => true, + 'geelvinck' => true, + 'gemological' => true, + 'geology' => true, + 'georgia' => true, + 'giessen' => true, + 'glas' => true, + 'glass' => true, + 'gorge' => true, + 'grandrapids' => true, + 'graz' => true, + 'guernsey' => true, + 'halloffame' => true, + 'hamburg' => true, + 'handson' => true, + 'harvestcelebration' => true, + 'hawaii' => true, + 'health' => true, + 'heimatunduhren' => true, + 'hellas' => true, + 'helsinki' => true, + 'hembygdsforbund' => true, + 'heritage' => true, + 'histoire' => true, + 'historical' => true, + 'historicalsociety' => true, + 'historichouses' => true, + 'historisch' => true, + 'historisches' => true, + 'history' => true, + 'historyofscience' => true, + 'horology' => true, + 'house' => true, + 'humanities' => true, + 'illustration' => true, + 'imageandsound' => true, + 'indian' => true, + 'indiana' => true, + 'indianapolis' => true, + 'indianmarket' => true, + 'intelligence' => true, + 'interactive' => true, + 'iraq' => true, + 'iron' => true, + 'isleofman' => true, + 'jamison' => true, + 'jefferson' => true, + 'jerusalem' => true, + 'jewelry' => true, + 'jewish' => true, + 'jewishart' => true, + 'jfk' => true, + 'journalism' => true, + 'judaica' => true, + 'judygarland' => true, + 'juedisches' => true, + 'juif' => true, + 'karate' => true, + 'karikatur' => true, + 'kids' => true, + 'koebenhavn' => true, + 'koeln' => true, + 'kunst' => true, + 'kunstsammlung' => true, + 'kunstunddesign' => true, + 'labor' => true, + 'labour' => true, + 'lajolla' => true, + 'lancashire' => true, + 'landes' => true, + 'lans' => true, + 'läns' => true, + 'larsson' => true, + 'lewismiller' => true, + 'lincoln' => true, + 'linz' => true, + 'living' => true, + 'livinghistory' => true, + 'localhistory' => true, + 'london' => true, + 'losangeles' => true, + 'louvre' => true, + 'loyalist' => true, + 'lucerne' => true, + 'luxembourg' => true, + 'luzern' => true, + 'mad' => true, + 'madrid' => true, + 'mallorca' => true, + 'manchester' => true, + 'mansion' => true, + 'mansions' => true, + 'manx' => true, + 'marburg' => true, + 'maritime' => true, + 'maritimo' => true, + 'maryland' => true, + 'marylhurst' => true, + 'media' => true, + 'medical' => true, + 'medizinhistorisches' => true, + 'meeres' => true, + 'memorial' => true, + 'mesaverde' => true, + 'michigan' => true, + 'midatlantic' => true, + 'military' => true, + 'mill' => true, + 'miners' => true, + 'mining' => true, + 'minnesota' => true, + 'missile' => true, + 'missoula' => true, + 'modern' => true, + 'moma' => true, + 'money' => true, + 'monmouth' => true, + 'monticello' => true, + 'montreal' => true, + 'moscow' => true, + 'motorcycle' => true, + 'muenchen' => true, + 'muenster' => true, + 'mulhouse' => true, + 'muncie' => true, + 'museet' => true, + 'museumcenter' => true, + 'museumvereniging' => true, + 'music' => true, + 'national' => true, + 'nationalfirearms' => true, + 'nationalheritage' => true, + 'nativeamerican' => true, + 'naturalhistory' => true, + 'naturalhistorymuseum' => true, + 'naturalsciences' => true, + 'nature' => true, + 'naturhistorisches' => true, + 'natuurwetenschappen' => true, + 'naumburg' => true, + 'naval' => true, + 'nebraska' => true, + 'neues' => true, + 'newhampshire' => true, + 'newjersey' => true, + 'newmexico' => true, + 'newport' => true, + 'newspaper' => true, + 'newyork' => true, + 'niepce' => true, + 'norfolk' => true, + 'north' => true, + 'nrw' => true, + 'nuernberg' => true, + 'nuremberg' => true, + 'nyc' => true, + 'nyny' => true, + 'oceanographic' => true, + 'oceanographique' => true, + 'omaha' => true, + 'online' => true, + 'ontario' => true, + 'openair' => true, + 'oregon' => true, + 'oregontrail' => true, + 'otago' => true, + 'oxford' => true, + 'pacific' => true, + 'paderborn' => true, + 'palace' => true, + 'paleo' => true, + 'palmsprings' => true, + 'panama' => true, + 'paris' => true, + 'pasadena' => true, + 'pharmacy' => true, + 'philadelphia' => true, + 'philadelphiaarea' => true, + 'philately' => true, + 'phoenix' => true, + 'photography' => true, + 'pilots' => true, + 'pittsburgh' => true, + 'planetarium' => true, + 'plantation' => true, + 'plants' => true, + 'plaza' => true, + 'portal' => true, + 'portland' => true, + 'portlligat' => true, + 'posts-and-telecommunications' => true, + 'preservation' => true, + 'presidio' => true, + 'press' => true, + 'project' => true, + 'public' => true, + 'pubol' => true, + 'quebec' => true, + 'railroad' => true, + 'railway' => true, + 'research' => true, + 'resistance' => true, + 'riodejaneiro' => true, + 'rochester' => true, + 'rockart' => true, + 'roma' => true, + 'russia' => true, + 'saintlouis' => true, + 'salem' => true, + 'salvadordali' => true, + 'salzburg' => true, + 'sandiego' => true, + 'sanfrancisco' => true, + 'santabarbara' => true, + 'santacruz' => true, + 'santafe' => true, + 'saskatchewan' => true, + 'satx' => true, + 'savannahga' => true, + 'schlesisches' => true, + 'schoenbrunn' => true, + 'schokoladen' => true, + 'school' => true, + 'schweiz' => true, + 'science' => true, + 'scienceandhistory' => true, + 'scienceandindustry' => true, + 'sciencecenter' => true, + 'sciencecenters' => true, + 'science-fiction' => true, + 'sciencehistory' => true, + 'sciences' => true, + 'sciencesnaturelles' => true, + 'scotland' => true, + 'seaport' => true, + 'settlement' => true, + 'settlers' => true, + 'shell' => true, + 'sherbrooke' => true, + 'sibenik' => true, + 'silk' => true, + 'ski' => true, + 'skole' => true, + 'society' => true, + 'sologne' => true, + 'soundandvision' => true, + 'southcarolina' => true, + 'southwest' => true, + 'space' => true, + 'spy' => true, + 'square' => true, + 'stadt' => true, + 'stalbans' => true, + 'starnberg' => true, + 'state' => true, + 'stateofdelaware' => true, + 'station' => true, + 'steam' => true, + 'steiermark' => true, + 'stjohn' => true, + 'stockholm' => true, + 'stpetersburg' => true, + 'stuttgart' => true, + 'suisse' => true, + 'surgeonshall' => true, + 'surrey' => true, + 'svizzera' => true, + 'sweden' => true, + 'sydney' => true, + 'tank' => true, + 'tcm' => true, + 'technology' => true, + 'telekommunikation' => true, + 'television' => true, + 'texas' => true, + 'textile' => true, + 'theater' => true, + 'time' => true, + 'timekeeping' => true, + 'topology' => true, + 'torino' => true, + 'touch' => true, + 'town' => true, + 'transport' => true, + 'tree' => true, + 'trolley' => true, + 'trust' => true, + 'trustee' => true, + 'uhren' => true, + 'ulm' => true, + 'undersea' => true, + 'university' => true, + 'usa' => true, + 'usantiques' => true, + 'usarts' => true, + 'uscountryestate' => true, + 'usculture' => true, + 'usdecorativearts' => true, + 'usgarden' => true, + 'ushistory' => true, + 'ushuaia' => true, + 'uslivinghistory' => true, + 'utah' => true, + 'uvic' => true, + 'valley' => true, + 'vantaa' => true, + 'versailles' => true, + 'viking' => true, + 'village' => true, + 'virginia' => true, + 'virtual' => true, + 'virtuel' => true, + 'vlaanderen' => true, + 'volkenkunde' => true, + 'wales' => true, + 'wallonie' => true, + 'war' => true, + 'washingtondc' => true, + 'watchandclock' => true, + 'watch-and-clock' => true, + 'western' => true, + 'westfalen' => true, + 'whaling' => true, + 'wildlife' => true, + 'williamsburg' => true, + 'windmill' => true, + 'workshop' => true, + 'york' => true, + 'yorkshire' => true, + 'yosemite' => true, + 'youth' => true, + 'zoological' => true, + 'zoology' => true, + 'ירושלים' => true, + 'иком' => true + ), + 'mv' => array( + 'aero' => true, + 'biz' => true, + 'com' => true, + 'coop' => true, + 'edu' => true, + 'gov' => true, + 'info' => true, + 'int' => true, + 'mil' => true, + 'museum' => true, + 'name' => true, + 'net' => true, + 'org' => true, + 'pro' => true + ), + 'mw' => array( + 'ac' => true, + 'biz' => true, + 'co' => true, + 'com' => true, + 'coop' => true, + 'edu' => true, + 'gov' => true, + 'int' => true, + 'museum' => true, + 'net' => true, + 'org' => true + ), + 'mx' => array( + 'com' => true, + 'org' => true, + 'gob' => true, + 'edu' => true, + 'net' => true + ), + 'my' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'edu' => true, + 'mil' => true, + 'name' => true + ), + 'mz' => array( + '*' => true + ), + 'na' => array( + 'info' => true, + 'pro' => true, + 'name' => true, + 'school' => true, + 'or' => true, + 'dr' => true, + 'us' => true, + 'mx' => true, + 'ca' => true, + 'in' => true, + 'cc' => true, + 'tv' => true, + 'ws' => true, + 'mobi' => true, + 'co' => true, + 'com' => true, + 'org' => true + ), + 'name' => true, + 'nc' => array( + 'asso' => true + ), + 'ne' => true, + 'net' => array( + 'gb' => true, + 'se' => true, + 'uk' => true, + 'za' => true + ), + 'nf' => array( + 'com' => true, + 'net' => true, + 'per' => true, + 'rec' => true, + 'web' => true, + 'arts' => true, + 'firm' => true, + 'info' => true, + 'other' => true, + 'store' => true + ), + 'ng' => array( + 'ac' => true, + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'org' => true + ), + 'ni' => array( + '*' => true + ), + 'nl' => array( + 'bv' => true + ), + 'no' => array( + 'fhs' => true, + 'vgs' => true, + 'fylkesbibl' => true, + 'folkebibl' => true, + 'museum' => true, + 'idrett' => true, + 'priv' => true, + 'mil' => true, + 'stat' => true, + 'dep' => true, + 'kommune' => true, + 'herad' => true, + 'aa' => array( + 'gs' => true + ), + 'ah' => array( + 'gs' => true + ), + 'bu' => array( + 'gs' => true + ), + 'fm' => array( + 'gs' => true + ), + 'hl' => array( + 'gs' => true + ), + 'hm' => array( + 'gs' => true + ), + 'jan-mayen' => array( + 'gs' => true + ), + 'mr' => array( + 'gs' => true + ), + 'nl' => array( + 'gs' => true + ), + 'nt' => array( + 'gs' => true + ), + 'of' => array( + 'gs' => true + ), + 'ol' => array( + 'gs' => true + ), + 'oslo' => array( + 'gs' => true + ), + 'rl' => array( + 'gs' => true + ), + 'sf' => array( + 'gs' => true + ), + 'st' => array( + 'gs' => true + ), + 'svalbard' => array( + 'gs' => true + ), + 'tm' => array( + 'gs' => true + ), + 'tr' => array( + 'gs' => true + ), + 'va' => array( + 'gs' => true + ), + 'vf' => array( + 'gs' => true + ), + 'akrehamn' => true, + 'åkrehamn' => true, + 'algard' => true, + 'ålgård' => true, + 'arna' => true, + 'brumunddal' => true, + 'bryne' => true, + 'bronnoysund' => true, + 'brønnøysund' => true, + 'drobak' => true, + 'drøbak' => true, + 'egersund' => true, + 'fetsund' => true, + 'floro' => true, + 'florø' => true, + 'fredrikstad' => true, + 'hokksund' => true, + 'honefoss' => true, + 'hønefoss' => true, + 'jessheim' => true, + 'jorpeland' => true, + 'jørpeland' => true, + 'kirkenes' => true, + 'kopervik' => true, + 'krokstadelva' => true, + 'langevag' => true, + 'langevåg' => true, + 'leirvik' => true, + 'mjondalen' => true, + 'mjøndalen' => true, + 'mo-i-rana' => true, + 'mosjoen' => true, + 'mosjøen' => true, + 'nesoddtangen' => true, + 'orkanger' => true, + 'osoyro' => true, + 'osøyro' => true, + 'raholt' => true, + 'råholt' => true, + 'sandnessjoen' => true, + 'sandnessjøen' => true, + 'skedsmokorset' => true, + 'slattum' => true, + 'spjelkavik' => true, + 'stathelle' => true, + 'stavern' => true, + 'stjordalshalsen' => true, + 'stjørdalshalsen' => true, + 'tananger' => true, + 'tranby' => true, + 'vossevangen' => true, + 'afjord' => true, + 'åfjord' => true, + 'agdenes' => true, + 'al' => true, + 'ål' => true, + 'alesund' => true, + 'ålesund' => true, + 'alstahaug' => true, + 'alta' => true, + 'áltá' => true, + 'alaheadju' => true, + 'álaheadju' => true, + 'alvdal' => true, + 'amli' => true, + 'åmli' => true, + 'amot' => true, + 'åmot' => true, + 'andebu' => true, + 'andoy' => true, + 'andøy' => true, + 'andasuolo' => true, + 'ardal' => true, + 'årdal' => true, + 'aremark' => true, + 'arendal' => true, + 'ås' => true, + 'aseral' => true, + 'åseral' => true, + 'asker' => true, + 'askim' => true, + 'askvoll' => true, + 'askoy' => true, + 'askøy' => true, + 'asnes' => true, + 'åsnes' => true, + 'audnedaln' => true, + 'aukra' => true, + 'aure' => true, + 'aurland' => true, + 'aurskog-holand' => true, + 'aurskog-høland' => true, + 'austevoll' => true, + 'austrheim' => true, + 'averoy' => true, + 'averøy' => true, + 'balestrand' => true, + 'ballangen' => true, + 'balat' => true, + 'bálát' => true, + 'balsfjord' => true, + 'bahccavuotna' => true, + 'báhccavuotna' => true, + 'bamble' => true, + 'bardu' => true, + 'beardu' => true, + 'beiarn' => true, + 'bajddar' => true, + 'bájddar' => true, + 'baidar' => true, + 'báidár' => true, + 'berg' => true, + 'bergen' => true, + 'berlevag' => true, + 'berlevåg' => true, + 'bearalvahki' => true, + 'bearalváhki' => true, + 'bindal' => true, + 'birkenes' => true, + 'bjarkoy' => true, + 'bjarkøy' => true, + 'bjerkreim' => true, + 'bjugn' => true, + 'bodo' => true, + 'bodø' => true, + 'badaddja' => true, + 'bådåddjå' => true, + 'budejju' => true, + 'bokn' => true, + 'bremanger' => true, + 'bronnoy' => true, + 'brønnøy' => true, + 'bygland' => true, + 'bykle' => true, + 'barum' => true, + 'bærum' => true, + 'telemark' => array( + 'bo' => true, + 'bø' => true + ), + 'nordland' => array( + 'bo' => true, + 'bø' => true, + 'heroy' => true, + 'herøy' => true + ), + 'bievat' => true, + 'bievát' => true, + 'bomlo' => true, + 'bømlo' => true, + 'batsfjord' => true, + 'båtsfjord' => true, + 'bahcavuotna' => true, + 'báhcavuotna' => true, + 'dovre' => true, + 'drammen' => true, + 'drangedal' => true, + 'dyroy' => true, + 'dyrøy' => true, + 'donna' => true, + 'dønna' => true, + 'eid' => true, + 'eidfjord' => true, + 'eidsberg' => true, + 'eidskog' => true, + 'eidsvoll' => true, + 'eigersund' => true, + 'elverum' => true, + 'enebakk' => true, + 'engerdal' => true, + 'etne' => true, + 'etnedal' => true, + 'evenes' => true, + 'evenassi' => true, + 'evenášši' => true, + 'evje-og-hornnes' => true, + 'farsund' => true, + 'fauske' => true, + 'fuossko' => true, + 'fuoisku' => true, + 'fedje' => true, + 'fet' => true, + 'finnoy' => true, + 'finnøy' => true, + 'fitjar' => true, + 'fjaler' => true, + 'fjell' => true, + 'flakstad' => true, + 'flatanger' => true, + 'flekkefjord' => true, + 'flesberg' => true, + 'flora' => true, + 'fla' => true, + 'flå' => true, + 'folldal' => true, + 'forsand' => true, + 'fosnes' => true, + 'frei' => true, + 'frogn' => true, + 'froland' => true, + 'frosta' => true, + 'frana' => true, + 'fræna' => true, + 'froya' => true, + 'frøya' => true, + 'fusa' => true, + 'fyresdal' => true, + 'forde' => true, + 'førde' => true, + 'gamvik' => true, + 'gangaviika' => true, + 'gáŋgaviika' => true, + 'gaular' => true, + 'gausdal' => true, + 'gildeskal' => true, + 'gildeskål' => true, + 'giske' => true, + 'gjemnes' => true, + 'gjerdrum' => true, + 'gjerstad' => true, + 'gjesdal' => true, + 'gjovik' => true, + 'gjøvik' => true, + 'gloppen' => true, + 'gol' => true, + 'gran' => true, + 'grane' => true, + 'granvin' => true, + 'gratangen' => true, + 'grimstad' => true, + 'grong' => true, + 'kraanghke' => true, + 'kråanghke' => true, + 'grue' => true, + 'gulen' => true, + 'hadsel' => true, + 'halden' => true, + 'halsa' => true, + 'hamar' => true, + 'hamaroy' => true, + 'habmer' => true, + 'hábmer' => true, + 'hapmir' => true, + 'hápmir' => true, + 'hammerfest' => true, + 'hammarfeasta' => true, + 'hámmárfeasta' => true, + 'haram' => true, + 'hareid' => true, + 'harstad' => true, + 'hasvik' => true, + 'aknoluokta' => true, + 'ákŋoluokta' => true, + 'hattfjelldal' => true, + 'aarborte' => true, + 'haugesund' => true, + 'hemne' => true, + 'hemnes' => true, + 'hemsedal' => true, + 'more-og-romsdal' => array( + 'heroy' => true, + 'sande' => true + ), + 'møre-og-romsdal' => array( + 'herøy' => true, + 'sande' => true + ), + 'hitra' => true, + 'hjartdal' => true, + 'hjelmeland' => true, + 'hobol' => true, + 'hobøl' => true, + 'hof' => true, + 'hol' => true, + 'hole' => true, + 'holmestrand' => true, + 'holtalen' => true, + 'holtålen' => true, + 'hornindal' => true, + 'horten' => true, + 'hurdal' => true, + 'hurum' => true, + 'hvaler' => true, + 'hyllestad' => true, + 'hagebostad' => true, + 'hægebostad' => true, + 'hoyanger' => true, + 'høyanger' => true, + 'hoylandet' => true, + 'høylandet' => true, + 'ha' => true, + 'hå' => true, + 'ibestad' => true, + 'inderoy' => true, + 'inderøy' => true, + 'iveland' => true, + 'jevnaker' => true, + 'jondal' => true, + 'jolster' => true, + 'jølster' => true, + 'karasjok' => true, + 'karasjohka' => true, + 'kárášjohka' => true, + 'karlsoy' => true, + 'galsa' => true, + 'gálsá' => true, + 'karmoy' => true, + 'karmøy' => true, + 'kautokeino' => true, + 'guovdageaidnu' => true, + 'klepp' => true, + 'klabu' => true, + 'klæbu' => true, + 'kongsberg' => true, + 'kongsvinger' => true, + 'kragero' => true, + 'kragerø' => true, + 'kristiansand' => true, + 'kristiansund' => true, + 'krodsherad' => true, + 'krødsherad' => true, + 'kvalsund' => true, + 'rahkkeravju' => true, + 'ráhkkerávju' => true, + 'kvam' => true, + 'kvinesdal' => true, + 'kvinnherad' => true, + 'kviteseid' => true, + 'kvitsoy' => true, + 'kvitsøy' => true, + 'kvafjord' => true, + 'kvæfjord' => true, + 'giehtavuoatna' => true, + 'kvanangen' => true, + 'kvænangen' => true, + 'navuotna' => true, + 'návuotna' => true, + 'kafjord' => true, + 'kåfjord' => true, + 'gaivuotna' => true, + 'gáivuotna' => true, + 'larvik' => true, + 'lavangen' => true, + 'lavagis' => true, + 'loabat' => true, + 'loabát' => true, + 'lebesby' => true, + 'davvesiida' => true, + 'leikanger' => true, + 'leirfjord' => true, + 'leka' => true, + 'leksvik' => true, + 'lenvik' => true, + 'leangaviika' => true, + 'leaŋgaviika' => true, + 'lesja' => true, + 'levanger' => true, + 'lier' => true, + 'lierne' => true, + 'lillehammer' => true, + 'lillesand' => true, + 'lindesnes' => true, + 'lindas' => true, + 'lindås' => true, + 'lom' => true, + 'loppa' => true, + 'lahppi' => true, + 'láhppi' => true, + 'lund' => true, + 'lunner' => true, + 'luroy' => true, + 'lurøy' => true, + 'luster' => true, + 'lyngdal' => true, + 'lyngen' => true, + 'ivgu' => true, + 'lardal' => true, + 'lerdal' => true, + 'lærdal' => true, + 'lodingen' => true, + 'lødingen' => true, + 'lorenskog' => true, + 'lørenskog' => true, + 'loten' => true, + 'løten' => true, + 'malvik' => true, + 'masoy' => true, + 'måsøy' => true, + 'muosat' => true, + 'muosát' => true, + 'mandal' => true, + 'marker' => true, + 'marnardal' => true, + 'masfjorden' => true, + 'meland' => true, + 'meldal' => true, + 'melhus' => true, + 'meloy' => true, + 'meløy' => true, + 'meraker' => true, + 'meråker' => true, + 'moareke' => true, + 'moåreke' => true, + 'midsund' => true, + 'midtre-gauldal' => true, + 'modalen' => true, + 'modum' => true, + 'molde' => true, + 'moskenes' => true, + 'moss' => true, + 'mosvik' => true, + 'malselv' => true, + 'målselv' => true, + 'malatvuopmi' => true, + 'málatvuopmi' => true, + 'namdalseid' => true, + 'aejrie' => true, + 'namsos' => true, + 'namsskogan' => true, + 'naamesjevuemie' => true, + 'nååmesjevuemie' => true, + 'laakesvuemie' => true, + 'nannestad' => true, + 'narvik' => true, + 'narviika' => true, + 'naustdal' => true, + 'nedre-eiker' => true, + 'akershus' => array( + 'nes' => true + ), + 'buskerud' => array( + 'nes' => true + ), + 'nesna' => true, + 'nesodden' => true, + 'nesseby' => true, + 'unjarga' => true, + 'unjárga' => true, + 'nesset' => true, + 'nissedal' => true, + 'nittedal' => true, + 'nord-aurdal' => true, + 'nord-fron' => true, + 'nord-odal' => true, + 'norddal' => true, + 'nordkapp' => true, + 'davvenjarga' => true, + 'davvenjárga' => true, + 'nordre-land' => true, + 'nordreisa' => true, + 'raisa' => true, + 'ráisa' => true, + 'nore-og-uvdal' => true, + 'notodden' => true, + 'naroy' => true, + 'nærøy' => true, + 'notteroy' => true, + 'nøtterøy' => true, + 'odda' => true, + 'oksnes' => true, + 'øksnes' => true, + 'oppdal' => true, + 'oppegard' => true, + 'oppegård' => true, + 'orkdal' => true, + 'orland' => true, + 'ørland' => true, + 'orskog' => true, + 'ørskog' => true, + 'orsta' => true, + 'ørsta' => true, + 'hedmark' => array( + 'os' => true, + 'valer' => true, + 'våler' => true + ), + 'hordaland' => array( + 'os' => true + ), + 'osen' => true, + 'osteroy' => true, + 'osterøy' => true, + 'ostre-toten' => true, + 'østre-toten' => true, + 'overhalla' => true, + 'ovre-eiker' => true, + 'øvre-eiker' => true, + 'oyer' => true, + 'øyer' => true, + 'oygarden' => true, + 'øygarden' => true, + 'oystre-slidre' => true, + 'øystre-slidre' => true, + 'porsanger' => true, + 'porsangu' => true, + 'porsáŋgu' => true, + 'porsgrunn' => true, + 'radoy' => true, + 'radøy' => true, + 'rakkestad' => true, + 'rana' => true, + 'ruovat' => true, + 'randaberg' => true, + 'rauma' => true, + 'rendalen' => true, + 'rennebu' => true, + 'rennesoy' => true, + 'rennesøy' => true, + 'rindal' => true, + 'ringebu' => true, + 'ringerike' => true, + 'ringsaker' => true, + 'rissa' => true, + 'risor' => true, + 'risør' => true, + 'roan' => true, + 'rollag' => true, + 'rygge' => true, + 'ralingen' => true, + 'rælingen' => true, + 'rodoy' => true, + 'rødøy' => true, + 'romskog' => true, + 'rømskog' => true, + 'roros' => true, + 'røros' => true, + 'rost' => true, + 'røst' => true, + 'royken' => true, + 'røyken' => true, + 'royrvik' => true, + 'røyrvik' => true, + 'rade' => true, + 'råde' => true, + 'salangen' => true, + 'siellak' => true, + 'saltdal' => true, + 'salat' => true, + 'sálát' => true, + 'sálat' => true, + 'samnanger' => true, + 'vestfold' => array( + 'sande' => true + ), + 'sandefjord' => true, + 'sandnes' => true, + 'sandoy' => true, + 'sandøy' => true, + 'sarpsborg' => true, + 'sauda' => true, + 'sauherad' => true, + 'sel' => true, + 'selbu' => true, + 'selje' => true, + 'seljord' => true, + 'sigdal' => true, + 'siljan' => true, + 'sirdal' => true, + 'skaun' => true, + 'skedsmo' => true, + 'ski' => true, + 'skien' => true, + 'skiptvet' => true, + 'skjervoy' => true, + 'skjervøy' => true, + 'skierva' => true, + 'skiervá' => true, + 'skjak' => true, + 'skjåk' => true, + 'skodje' => true, + 'skanland' => true, + 'skånland' => true, + 'skanit' => true, + 'skánit' => true, + 'smola' => true, + 'smøla' => true, + 'snillfjord' => true, + 'snasa' => true, + 'snåsa' => true, + 'snoasa' => true, + 'snaase' => true, + 'snåase' => true, + 'sogndal' => true, + 'sokndal' => true, + 'sola' => true, + 'solund' => true, + 'songdalen' => true, + 'sortland' => true, + 'spydeberg' => true, + 'stange' => true, + 'stavanger' => true, + 'steigen' => true, + 'steinkjer' => true, + 'stjordal' => true, + 'stjørdal' => true, + 'stokke' => true, + 'stor-elvdal' => true, + 'stord' => true, + 'stordal' => true, + 'storfjord' => true, + 'omasvuotna' => true, + 'strand' => true, + 'stranda' => true, + 'stryn' => true, + 'sula' => true, + 'suldal' => true, + 'sund' => true, + 'sunndal' => true, + 'surnadal' => true, + 'sveio' => true, + 'svelvik' => true, + 'sykkylven' => true, + 'sogne' => true, + 'søgne' => true, + 'somna' => true, + 'sømna' => true, + 'sondre-land' => true, + 'søndre-land' => true, + 'sor-aurdal' => true, + 'sør-aurdal' => true, + 'sor-fron' => true, + 'sør-fron' => true, + 'sor-odal' => true, + 'sør-odal' => true, + 'sor-varanger' => true, + 'sør-varanger' => true, + 'matta-varjjat' => true, + 'mátta-várjjat' => true, + 'sorfold' => true, + 'sørfold' => true, + 'sorreisa' => true, + 'sørreisa' => true, + 'sorum' => true, + 'sørum' => true, + 'tana' => true, + 'deatnu' => true, + 'time' => true, + 'tingvoll' => true, + 'tinn' => true, + 'tjeldsund' => true, + 'dielddanuorri' => true, + 'tjome' => true, + 'tjøme' => true, + 'tokke' => true, + 'tolga' => true, + 'torsken' => true, + 'tranoy' => true, + 'tranøy' => true, + 'tromso' => true, + 'tromsø' => true, + 'tromsa' => true, + 'romsa' => true, + 'trondheim' => true, + 'troandin' => true, + 'trysil' => true, + 'trana' => true, + 'træna' => true, + 'trogstad' => true, + 'trøgstad' => true, + 'tvedestrand' => true, + 'tydal' => true, + 'tynset' => true, + 'tysfjord' => true, + 'divtasvuodna' => true, + 'divttasvuotna' => true, + 'tysnes' => true, + 'tysvar' => true, + 'tysvær' => true, + 'tonsberg' => true, + 'tønsberg' => true, + 'ullensaker' => true, + 'ullensvang' => true, + 'ulvik' => true, + 'utsira' => true, + 'vadso' => true, + 'vadsø' => true, + 'cahcesuolo' => true, + 'čáhcesuolo' => true, + 'vaksdal' => true, + 'valle' => true, + 'vang' => true, + 'vanylven' => true, + 'vardo' => true, + 'vardø' => true, + 'varggat' => true, + 'várggát' => true, + 'vefsn' => true, + 'vaapste' => true, + 'vega' => true, + 'vegarshei' => true, + 'vegårshei' => true, + 'vennesla' => true, + 'verdal' => true, + 'verran' => true, + 'vestby' => true, + 'vestnes' => true, + 'vestre-slidre' => true, + 'vestre-toten' => true, + 'vestvagoy' => true, + 'vestvågøy' => true, + 'vevelstad' => true, + 'vik' => true, + 'vikna' => true, + 'vindafjord' => true, + 'volda' => true, + 'voss' => true, + 'varoy' => true, + 'værøy' => true, + 'vagan' => true, + 'vågan' => true, + 'voagat' => true, + 'vagsoy' => true, + 'vågsøy' => true, + 'vaga' => true, + 'vågå' => true, + 'ostfold' => array( + 'valer' => true + ), + 'østfold' => array( + 'våler' => true + ) + ), + 'np' => array( + '*' => true + ), + 'nr' => array( + 'biz' => true, + 'info' => true, + 'gov' => true, + 'edu' => true, + 'org' => true, + 'net' => true, + 'com' => true + ), + 'nu' => true, + 'nz' => array( + '*' => true + ), + 'om' => array( + '*' => true, + '!mediaphone' => true, + '!nawrastelecom' => true, + '!nawras' => true, + '!omanmobile' => true, + '!omanpost' => true, + '!omantel' => true, + '!rakpetroleum' => true, + '!siemens' => true, + '!songfest' => true, + '!statecouncil' => true + ), + 'org' => array( + 'ae' => true, + 'za' => true + ), + 'pa' => array( + 'ac' => true, + 'gob' => true, + 'com' => true, + 'org' => true, + 'sld' => true, + 'edu' => true, + 'net' => true, + 'ing' => true, + 'abo' => true, + 'med' => true, + 'nom' => true + ), + 'pe' => array( + 'edu' => true, + 'gob' => true, + 'nom' => true, + 'mil' => true, + 'org' => true, + 'com' => true, + 'net' => true + ), + 'pf' => array( + 'com' => true, + 'org' => true, + 'edu' => true + ), + 'pg' => array( + '*' => true + ), + 'ph' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'edu' => true, + 'ngo' => true, + 'mil' => true, + 'i' => true + ), + 'pk' => array( + 'com' => true, + 'net' => true, + 'edu' => true, + 'org' => true, + 'fam' => true, + 'biz' => true, + 'web' => true, + 'gov' => true, + 'gob' => true, + 'gok' => true, + 'gon' => true, + 'gop' => true, + 'gos' => true, + 'info' => true + ), + 'pl' => array( + 'aid' => true, + 'agro' => true, + 'atm' => true, + 'auto' => true, + 'biz' => true, + 'com' => true, + 'edu' => true, + 'gmina' => true, + 'gsm' => true, + 'info' => true, + 'mail' => true, + 'miasta' => true, + 'media' => true, + 'mil' => true, + 'net' => true, + 'nieruchomosci' => true, + 'nom' => true, + 'org' => true, + 'pc' => true, + 'powiat' => true, + 'priv' => true, + 'realestate' => true, + 'rel' => true, + 'sex' => true, + 'shop' => true, + 'sklep' => true, + 'sos' => true, + 'szkola' => true, + 'targi' => true, + 'tm' => true, + 'tourism' => true, + 'travel' => true, + 'turystyka' => true, + '6bone' => true, + 'art' => true, + 'mbone' => true, + 'gov' => array( + 'uw' => true, + 'um' => true, + 'ug' => true, + 'upow' => true, + 'starostwo' => true, + 'so' => true, + 'sr' => true, + 'po' => true, + 'pa' => true + ), + 'ngo' => true, + 'irc' => true, + 'usenet' => true, + 'augustow' => true, + 'babia-gora' => true, + 'bedzin' => true, + 'beskidy' => true, + 'bialowieza' => true, + 'bialystok' => true, + 'bielawa' => true, + 'bieszczady' => true, + 'boleslawiec' => true, + 'bydgoszcz' => true, + 'bytom' => true, + 'cieszyn' => true, + 'czeladz' => true, + 'czest' => true, + 'dlugoleka' => true, + 'elblag' => true, + 'elk' => true, + 'glogow' => true, + 'gniezno' => true, + 'gorlice' => true, + 'grajewo' => true, + 'ilawa' => true, + 'jaworzno' => true, + 'jelenia-gora' => true, + 'jgora' => true, + 'kalisz' => true, + 'kazimierz-dolny' => true, + 'karpacz' => true, + 'kartuzy' => true, + 'kaszuby' => true, + 'katowice' => true, + 'kepno' => true, + 'ketrzyn' => true, + 'klodzko' => true, + 'kobierzyce' => true, + 'kolobrzeg' => true, + 'konin' => true, + 'konskowola' => true, + 'kutno' => true, + 'lapy' => true, + 'lebork' => true, + 'legnica' => true, + 'lezajsk' => true, + 'limanowa' => true, + 'lomza' => true, + 'lowicz' => true, + 'lubin' => true, + 'lukow' => true, + 'malbork' => true, + 'malopolska' => true, + 'mazowsze' => true, + 'mazury' => true, + 'mielec' => true, + 'mielno' => true, + 'mragowo' => true, + 'naklo' => true, + 'nowaruda' => true, + 'nysa' => true, + 'olawa' => true, + 'olecko' => true, + 'olkusz' => true, + 'olsztyn' => true, + 'opoczno' => true, + 'opole' => true, + 'ostroda' => true, + 'ostroleka' => true, + 'ostrowiec' => true, + 'ostrowwlkp' => true, + 'pila' => true, + 'pisz' => true, + 'podhale' => true, + 'podlasie' => true, + 'polkowice' => true, + 'pomorze' => true, + 'pomorskie' => true, + 'prochowice' => true, + 'pruszkow' => true, + 'przeworsk' => true, + 'pulawy' => true, + 'radom' => true, + 'rawa-maz' => true, + 'rybnik' => true, + 'rzeszow' => true, + 'sanok' => true, + 'sejny' => true, + 'siedlce' => true, + 'slask' => true, + 'slupsk' => true, + 'sosnowiec' => true, + 'stalowa-wola' => true, + 'skoczow' => true, + 'starachowice' => true, + 'stargard' => true, + 'suwalki' => true, + 'swidnica' => true, + 'swiebodzin' => true, + 'swinoujscie' => true, + 'szczecin' => true, + 'szczytno' => true, + 'tarnobrzeg' => true, + 'tgory' => true, + 'turek' => true, + 'tychy' => true, + 'ustka' => true, + 'walbrzych' => true, + 'warmia' => true, + 'warszawa' => true, + 'waw' => true, + 'wegrow' => true, + 'wielun' => true, + 'wlocl' => true, + 'wloclawek' => true, + 'wodzislaw' => true, + 'wolomin' => true, + 'wroclaw' => true, + 'zachpomor' => true, + 'zagan' => true, + 'zarow' => true, + 'zgora' => true, + 'zgorzelec' => true, + 'gda' => true, + 'gdansk' => true, + 'gdynia' => true, + 'med' => true, + 'sopot' => true, + 'gliwice' => true, + 'krakow' => true, + 'poznan' => true, + 'wroc' => true, + 'zakopane' => true, + 'co' => true + ), + 'pn' => array( + 'gov' => true, + 'co' => true, + 'org' => true, + 'edu' => true, + 'net' => true + ), + 'pr' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'edu' => true, + 'isla' => true, + 'pro' => true, + 'biz' => true, + 'info' => true, + 'name' => true, + 'est' => true, + 'prof' => true, + 'ac' => true + ), + 'pro' => array( + 'aca' => true, + 'bar' => true, + 'cpa' => true, + 'jur' => true, + 'law' => true, + 'med' => true, + 'eng' => true + ), + 'ps' => array( + 'edu' => true, + 'gov' => true, + 'sec' => true, + 'plo' => true, + 'com' => true, + 'org' => true, + 'net' => true + ), + 'pt' => array( + 'net' => true, + 'gov' => true, + 'org' => true, + 'edu' => true, + 'int' => true, + 'publ' => true, + 'com' => true, + 'nome' => true + ), + 'pw' => array( + 'co' => true, + 'ne' => true, + 'or' => true, + 'ed' => true, + 'go' => true, + 'belau' => true + ), + 'py' => array( + '*' => true + ), + 'qa' => array( + '*' => true + ), + 're' => array( + 'com' => true, + 'asso' => true, + 'nom' => true + ), + 'ro' => array( + 'com' => true, + 'org' => true, + 'tm' => true, + 'nt' => true, + 'nom' => true, + 'info' => true, + 'rec' => true, + 'arts' => true, + 'firm' => true, + 'store' => true, + 'www' => true + ), + 'rs' => array( + 'co' => true, + 'org' => true, + 'edu' => true, + 'ac' => true, + 'gov' => true, + 'in' => true + ), + 'ru' => array( + 'ac' => true, + 'com' => true, + 'edu' => true, + 'int' => true, + 'net' => true, + 'org' => true, + 'pp' => true, + 'adygeya' => true, + 'altai' => true, + 'amur' => true, + 'arkhangelsk' => true, + 'astrakhan' => true, + 'bashkiria' => true, + 'belgorod' => true, + 'bir' => true, + 'bryansk' => true, + 'buryatia' => true, + 'cbg' => true, + 'chel' => true, + 'chelyabinsk' => true, + 'chita' => true, + 'chukotka' => true, + 'chuvashia' => true, + 'dagestan' => true, + 'dudinka' => true, + 'e-burg' => true, + 'grozny' => true, + 'irkutsk' => true, + 'ivanovo' => true, + 'izhevsk' => true, + 'jar' => true, + 'joshkar-ola' => true, + 'kalmykia' => true, + 'kaluga' => true, + 'kamchatka' => true, + 'karelia' => true, + 'kazan' => true, + 'kchr' => true, + 'kemerovo' => true, + 'khabarovsk' => true, + 'khakassia' => true, + 'khv' => true, + 'kirov' => true, + 'koenig' => true, + 'komi' => true, + 'kostroma' => true, + 'krasnoyarsk' => true, + 'kuban' => true, + 'kurgan' => true, + 'kursk' => true, + 'lipetsk' => true, + 'magadan' => true, + 'mari' => true, + 'mari-el' => true, + 'marine' => true, + 'mordovia' => true, + 'mosreg' => true, + 'msk' => true, + 'murmansk' => true, + 'nalchik' => true, + 'nnov' => true, + 'nov' => true, + 'novosibirsk' => true, + 'nsk' => true, + 'omsk' => true, + 'orenburg' => true, + 'oryol' => true, + 'palana' => true, + 'penza' => true, + 'perm' => true, + 'pskov' => true, + 'ptz' => true, + 'rnd' => true, + 'ryazan' => true, + 'sakhalin' => true, + 'samara' => true, + 'saratov' => true, + 'simbirsk' => true, + 'smolensk' => true, + 'spb' => true, + 'stavropol' => true, + 'stv' => true, + 'surgut' => true, + 'tambov' => true, + 'tatarstan' => true, + 'tom' => true, + 'tomsk' => true, + 'tsaritsyn' => true, + 'tsk' => true, + 'tula' => true, + 'tuva' => true, + 'tver' => true, + 'tyumen' => true, + 'udm' => true, + 'udmurtia' => true, + 'ulan-ude' => true, + 'vladikavkaz' => true, + 'vladimir' => true, + 'vladivostok' => true, + 'volgograd' => true, + 'vologda' => true, + 'voronezh' => true, + 'vrn' => true, + 'vyatka' => true, + 'yakutia' => true, + 'yamal' => true, + 'yaroslavl' => true, + 'yekaterinburg' => true, + 'yuzhno-sakhalinsk' => true, + 'amursk' => true, + 'baikal' => true, + 'cmw' => true, + 'fareast' => true, + 'jamal' => true, + 'kms' => true, + 'k-uralsk' => true, + 'kustanai' => true, + 'kuzbass' => true, + 'magnitka' => true, + 'mytis' => true, + 'nakhodka' => true, + 'nkz' => true, + 'norilsk' => true, + 'oskol' => true, + 'pyatigorsk' => true, + 'rubtsovsk' => true, + 'snz' => true, + 'syzran' => true, + 'vdonsk' => true, + 'zgrad' => true, + 'gov' => true, + 'mil' => true, + 'test' => true + ), + 'rw' => array( + 'gov' => true, + 'net' => true, + 'edu' => true, + 'ac' => true, + 'com' => true, + 'co' => true, + 'int' => true, + 'mil' => true, + 'gouv' => true + ), + 'sa' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'med' => true, + 'pub' => true, + 'edu' => true, + 'sch' => true + ), + 'sb' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'net' => true, + 'org' => true + ), + 'sc' => array( + 'com' => true, + 'gov' => true, + 'net' => true, + 'org' => true, + 'edu' => true + ), + 'sd' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'med' => true, + 'gov' => true, + 'info' => true + ), + 'se' => array( + 'a' => true, + 'ac' => true, + 'b' => true, + 'bd' => true, + 'brand' => true, + 'c' => true, + 'd' => true, + 'e' => true, + 'f' => true, + 'fh' => true, + 'fhsk' => true, + 'fhv' => true, + 'g' => true, + 'h' => true, + 'i' => true, + 'k' => true, + 'komforb' => true, + 'kommunalforbund' => true, + 'komvux' => true, + 'l' => true, + 'lanbib' => true, + 'm' => true, + 'n' => true, + 'naturbruksgymn' => true, + 'o' => true, + 'org' => true, + 'p' => true, + 'parti' => true, + 'pp' => true, + 'press' => true, + 'r' => true, + 's' => true, + 'sshn' => true, + 't' => true, + 'tm' => true, + 'u' => true, + 'w' => true, + 'x' => true, + 'y' => true, + 'z' => true + ), + 'sg' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'edu' => true, + 'per' => true + ), + 'sh' => true, + 'si' => true, + 'sk' => true, + 'sl' => array( + 'com' => true, + 'net' => true, + 'edu' => true, + 'gov' => true, + 'org' => true + ), + 'sm' => true, + 'sn' => array( + 'art' => true, + 'com' => true, + 'edu' => true, + 'gouv' => true, + 'org' => true, + 'perso' => true, + 'univ' => true + ), + 'so' => array( + 'com' => true, + 'net' => true, + 'org' => true + ), + 'sr' => true, + 'st' => array( + 'co' => true, + 'com' => true, + 'consulado' => true, + 'edu' => true, + 'embaixada' => true, + 'gov' => true, + 'mil' => true, + 'net' => true, + 'org' => true, + 'principe' => true, + 'saotome' => true, + 'store' => true + ), + 'su' => true, + 'sv' => array( + '*' => true + ), + 'sy' => array( + 'edu' => true, + 'gov' => true, + 'net' => true, + 'mil' => true, + 'com' => true, + 'org' => true + ), + 'sz' => array( + 'co' => true, + 'ac' => true, + 'org' => true + ), + 'tc' => true, + 'td' => true, + 'tel' => true, + 'tf' => true, + 'tg' => true, + 'th' => array( + 'ac' => true, + 'co' => true, + 'go' => true, + 'in' => true, + 'mi' => true, + 'net' => true, + 'or' => true + ), + 'tj' => array( + 'ac' => true, + 'biz' => true, + 'co' => true, + 'com' => true, + 'edu' => true, + 'go' => true, + 'gov' => true, + 'int' => true, + 'mil' => true, + 'name' => true, + 'net' => true, + 'nic' => true, + 'org' => true, + 'test' => true, + 'web' => true + ), + 'tk' => true, + 'tl' => array( + 'gov' => true + ), + 'tm' => true, + 'tn' => array( + 'com' => true, + 'ens' => true, + 'fin' => true, + 'gov' => true, + 'ind' => true, + 'intl' => true, + 'nat' => true, + 'net' => true, + 'org' => true, + 'info' => true, + 'perso' => true, + 'tourism' => true, + 'edunet' => true, + 'rnrt' => true, + 'rns' => true, + 'rnu' => true, + 'mincom' => true, + 'agrinet' => true, + 'defense' => true, + 'turen' => true + ), + 'to' => array( + 'com' => true, + 'gov' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'mil' => true + ), + 'tr' => array( + '*' => true, + '!nic' => true, + '!tsk' => true, + 'nc' => array( + 'gov' => true + ) + ), + 'travel' => true, + 'tt' => array( + 'co' => true, + 'com' => true, + 'org' => true, + 'net' => true, + 'biz' => true, + 'info' => true, + 'pro' => true, + 'int' => true, + 'coop' => true, + 'jobs' => true, + 'mobi' => true, + 'travel' => true, + 'museum' => true, + 'aero' => true, + 'name' => true, + 'gov' => true, + 'edu' => true + ), + 'tv' => true, + 'tw' => array( + 'edu' => true, + 'gov' => true, + 'mil' => true, + 'com' => true, + 'net' => true, + 'org' => true, + 'idv' => true, + 'game' => true, + 'ebiz' => true, + 'club' => true, + '網路' => true, + '組織' => true, + '商業' => true + ), + 'tz' => array( + 'ac' => true, + 'co' => true, + 'go' => true, + 'mil' => true, + 'ne' => true, + 'or' => true, + 'sc' => true + ), + 'ua' => array( + 'com' => true, + 'edu' => true, + 'gov' => true, + 'in' => true, + 'net' => true, + 'org' => true, + 'cherkassy' => true, + 'chernigov' => true, + 'chernovtsy' => true, + 'ck' => true, + 'cn' => true, + 'crimea' => true, + 'cv' => true, + 'dn' => true, + 'dnepropetrovsk' => true, + 'donetsk' => true, + 'dp' => true, + 'if' => true, + 'ivano-frankivsk' => true, + 'kh' => true, + 'kharkov' => true, + 'kherson' => true, + 'khmelnitskiy' => true, + 'kiev' => true, + 'kirovograd' => true, + 'km' => true, + 'kr' => true, + 'ks' => true, + 'kv' => true, + 'lg' => true, + 'lugansk' => true, + 'lutsk' => true, + 'lviv' => true, + 'mk' => true, + 'nikolaev' => true, + 'od' => true, + 'odessa' => true, + 'pl' => true, + 'poltava' => true, + 'rovno' => true, + 'rv' => true, + 'sebastopol' => true, + 'sumy' => true, + 'te' => true, + 'ternopil' => true, + 'uzhgorod' => true, + 'vinnica' => true, + 'vn' => true, + 'zaporizhzhe' => true, + 'zp' => true, + 'zhitomir' => true, + 'zt' => true + ), + 'ug' => array( + 'co' => true, + 'ac' => true, + 'sc' => true, + 'go' => true, + 'ne' => true, + 'or' => true + ), + 'uk' => array( + '*' => true, + 'sch' => array( + '*' => true + ), + '!bl' => true, + '!british-library' => true, + '!icnet' => true, + '!jet' => true, + '!nel' => true, + '!nhs' => true, + '!nls' => true, + '!national-library-scotland' => true, + '!parliament' => true + ), + 'us' => array( + 'dni' => true, + 'fed' => true, + 'isa' => true, + 'kids' => true, + 'nsn' => true, + 'ak' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'al' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ar' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'as' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'az' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ca' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'co' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ct' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'dc' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'de' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'fl' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ga' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'gu' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'hi' => array( + 'cc' => true, + 'lib' => true + ), + 'ia' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'id' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'il' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'in' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ks' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ky' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'la' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ma' => array( + 'k12' => array( + 'pvt' => true, + 'chtr' => true, + 'paroch' => true + ), + 'cc' => true, + 'lib' => true + ), + 'md' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'me' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'mi' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'mn' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'mo' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ms' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'mt' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'nc' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'nd' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ne' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'nh' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'nj' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'nm' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'nv' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ny' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'oh' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ok' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'or' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'pa' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'pr' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ri' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'sc' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'sd' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'tn' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'tx' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'ut' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'vi' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'vt' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'va' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'wa' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'wi' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'wv' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ), + 'wy' => array( + 'k12' => true, + 'cc' => true, + 'lib' => true + ) + ), + 'uy' => array( + '*' => true + ), + 'uz' => array( + 'com' => true, + 'co' => true + ), + 'va' => true, + 'vc' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'mil' => true, + 'edu' => true + ), + 've' => array( + '*' => true + ), + 'vg' => true, + 'vi' => array( + 'co' => true, + 'com' => true, + 'k12' => true, + 'net' => true, + 'org' => true + ), + 'vn' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'edu' => true, + 'gov' => true, + 'int' => true, + 'ac' => true, + 'biz' => true, + 'info' => true, + 'name' => true, + 'pro' => true, + 'health' => true + ), + 'vu' => true, + 'ws' => array( + 'com' => true, + 'net' => true, + 'org' => true, + 'gov' => true, + 'edu' => true + ), + 'امارات' => true, + '中国' => true, + '中國' => true, + 'مصر' => true, + '香港' => true, + 'الاردن' => true, + 'ලංකා' => true, + 'இலங்கை' => true, + 'فلسطين' => true, + 'рф' => true, + 'قطر' => true, + 'السعودية' => true, + '新加坡' => true, + 'சிங்கப்பூர்' => true, + 'ไทย' => true, + 'تونس' => true, + '台灣' => true, + '台湾' => true, + 'ye' => array( + '*' => true + ), + 'yu' => array( + '*' => true + ), + 'za' => array( + '*' => true + ), + 'zm' => array( + '*' => true + ), + 'zw' => array( + '*' => true + ) +); +?> \ No newline at end of file diff --git a/lib/docs/HTTP_Request2/HTTP/examples/upload-rapidshare.php b/lib/docs/HTTP_Request2/HTTP/examples/upload-rapidshare.php new file mode 100644 index 0000000000000000000000000000000000000000..9773a94c48fed08bb23ad5beffda73d05ff5cd32 --- /dev/null +++ b/lib/docs/HTTP_Request2/HTTP/examples/upload-rapidshare.php @@ -0,0 +1,60 @@ +<?php +/** + * Usage example for HTTP_Request2 package: uploading a file to rapidshare.com + * + * Inspired by Perl usage example: http://images.rapidshare.com/software/rsapi.pl + * Rapidshare API description: http://rapidshare.com/dev.html + * + * $Id: upload-rapidshare.php 287307 2009-08-14 15:40:22Z avb $ + */ + +require_once 'HTTP/Request2.php'; + +// You'll probably want to change this +$filename = '/etc/passwd'; + +try { + // First step: get an available upload server + $request = new HTTP_Request2( + 'http://rapidshare.com/cgi-bin/rsapi.cgi?sub=nextuploadserver_v1' + ); + $server = $request->send()->getBody(); + if (!preg_match('/^(\\d+)$/', $server)) { + throw new Exception("Invalid upload server: {$server}"); + } + + // Calculate file hash, we'll use it later to check upload + if (false === ($hash = @md5_file($filename))) { + throw new Exception("Cannot calculate MD5 hash of '{$filename}'"); + } + + // Second step: upload a file to the available server + $uploader = new HTTP_Request2( + "http://rs{$server}l3.rapidshare.com/cgi-bin/upload.cgi", + HTTP_Request2::METHOD_POST + ); + // Adding the file + $uploader->addUpload('filecontent', $filename); + // This will tell server to return program-friendly output + $uploader->addPostParameter('rsapi_v1', '1'); + + $response = $uploader->send()->getBody(); + if (!preg_match_all('/^(File[^=]+)=(.+)$/m', $response, $m, PREG_SET_ORDER)) { + throw new Exception("Invalid response: {$response}"); + } + $rspAry = array(); + foreach ($m as $item) { + $rspAry[$item[1]] = $item[2]; + } + // Check that uploaded file has the same hash + if (empty($rspAry['File1.4'])) { + throw new Exception("MD5 hash data not found in response"); + } elseif ($hash != strtolower($rspAry['File1.4'])) { + throw new Exception("Upload failed, local MD5 is {$hash}, uploaded MD5 is {$rspAry['File1.4']}"); + } + echo "Upload succeeded\nDownload link: {$rspAry['File1.1']}\nDelete link: {$rspAry['File1.2']}\n"; + +} catch (Exception $e) { + echo "Error: " . $e->getMessage(); +} +?> diff --git a/lib/docs/Net_URL2/Net/docs/6470.php b/lib/docs/Net_URL2/Net/docs/6470.php new file mode 100644 index 0000000000000000000000000000000000000000..104de8960852ebff997e4d9d17a209c35c19cb5d --- /dev/null +++ b/lib/docs/Net_URL2/Net/docs/6470.php @@ -0,0 +1,75 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Copyright (c) 2002-2003, Richard Heyes | +// | All rights reserved. | +// | | +// | Redistribution and use in source and binary forms, with or without | +// | modification, are permitted provided that the following conditions | +// | are met: | +// | | +// | o Redistributions of source code must retain the above copyright | +// | notice, this list of conditions and the following disclaimer. | +// | o 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.| +// | o The names of the authors may not 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. | +// | | +// +-----------------------------------------------------------------------+ +// | Author: Richard Heyes <richard at php net> | +// +-----------------------------------------------------------------------+ +// $Id: 6470.php 235190 2007-05-08 00:04:54Z davidc $ +/** +* This example will decode the url given and display its +* constituent parts. +*/ + error_reporting(E_ALL | E_STRICT); + + require_once 'Net/URL2.php'; + + //$url = &new Net_URL2('https://www.example.com/foo/bar/index.php?foo=bar'); + Net_URL2::setOption('encode_query_keys', true); + $url = new Net_URL2; + +?> +<html> +<body> + +<pre> +Protocol...: <?php echo $url->protocol; ?> + +Username...: <?php echo $url->user; ?> + +Password...: <?php echo $url->pass; ?> + +Server.....: <?php echo $url->host; ?> + +Port.......: <?php $url->port; ?> + +File/path..: <?php $url->path; ?> + +Querystring: <?php print_r($url->querystring); ?> + +Anchor.....: <?php echo $url->anchor;?> + +Full URL...: <?php echo $url->getUrl(); ?> + + +Resolve path (/.././/foo/bar/joe/./././../jabba): <b><?php Net_URL2::resolvePath('/.././/foo/bar/joe/./././../jabba'); ?></b> +</pre> + +</body> +</html> diff --git a/lib/docs/Net_URL2/Net/docs/example.php b/lib/docs/Net_URL2/Net/docs/example.php new file mode 100644 index 0000000000000000000000000000000000000000..d2725ba5aaa8d854de39d43e1e44fbbd6783c1a9 --- /dev/null +++ b/lib/docs/Net_URL2/Net/docs/example.php @@ -0,0 +1,74 @@ +<?php +// +-----------------------------------------------------------------------+ +// | Copyright (c) 2002-2003, Richard Heyes | +// | All rights reserved. | +// | | +// | Redistribution and use in source and binary forms, with or without | +// | modification, are permitted provided that the following conditions | +// | are met: | +// | | +// | o Redistributions of source code must retain the above copyright | +// | notice, this list of conditions and the following disclaimer. | +// | o 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.| +// | o The names of the authors may not 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. | +// | | +// +-----------------------------------------------------------------------+ +// | Author: Richard Heyes <richard at php net> | +// +-----------------------------------------------------------------------+ +// $Id: example.php 235190 2007-05-08 00:04:54Z davidc $ +/** +* This example will decode the url given and display its +* constituent parts. +*/ + error_reporting(E_ALL | E_STRICT); + + //include('../URL2.php'); + include('Net/URL2.php'); + + //$url = &new Net_URL2('https://www.example.com/foo/bar/index.php?foo=bar'); + $url = new Net_URL2('https://example.com/pls/portal30/PORTAL30.wwpob_page.changetabs?p_back_url=http%3A%2F%2Fexample.com%2Fservlet%2Fpage%3F_pageid%3D360%2C366%2C368%2C382%26_dad%3Dportal30%26_schema%3DPORTAL30&foo=bar'); +?> +<html> +<body> + +<pre> +Protocol...: <?php echo $url->protocol; ?> + +Username...: <?php echo $url->user; ?> + +Password...: <?php echo $url->pass; ?> + +Server.....: <?php echo $url->host; ?> + +Port.......: <?php $url->port; ?> + +File/path..: <?php $url->path; ?> + +Querystring: <?php print_r($url->querystring); ?> + +Anchor.....: <?php echo $url->anchor;?> + +Full URL...: <?php echo $url->getUrl(); ?> + + +Resolve path (/.././/foo/bar/joe/./././../jabba): <b><?php Net_URL2::resolvePath('/.././/foo/bar/joe/./././../jabba'); ?></b> +</pre> + +</body> +</html> diff --git a/lib/docs/SimpleCAS/docs/examples/Zend_Auth_Adapter_SimpleCAS.php b/lib/docs/SimpleCAS/docs/examples/Zend_Auth_Adapter_SimpleCAS.php new file mode 100644 index 0000000000000000000000000000000000000000..921d1a08b32da6b6876985491e175012331bb279 --- /dev/null +++ b/lib/docs/SimpleCAS/docs/examples/Zend_Auth_Adapter_SimpleCAS.php @@ -0,0 +1,134 @@ +<?php +/** + * This is a Zend_Auth adapter library for CAS. + * It uses SimpleCAS. + * + * <code> + * public function casAction() + * { + * $auth = Zend_Auth::getInstance(); + * $authAdapter = new UNL_CasZendAuthAdapter( + * Zend_Registry::get('config')->auth->cas + * ); + * + * # User has not been identified, and there's a ticket in the URL + * if (!$auth->hasIdentity() && isset($_GET['ticket'])) { + * $authAdapter->setTicket($_GET['ticket']); + * $result = $auth->authenticate($authAdapter); + * + * if ($result->isValid()) { + * Zend_Session::regenerateId(); + * } + * } + * + * # No ticket or ticket was invalid. Redirect to CAS. + * if (!$auth->hasIdentity()) { + * $this->_redirect($authAdapter->getLoginURL()); + * } + * } + * </code> + */ + + +/** + * @see Zend_Auth_Adapter_Interface + */ +require_once 'Zend/Auth/Adapter/Interface.php'; + +require_once('SimpleCAS/Server/Version2.php'); + +class Zend_Auth_Adapter_SimpleCAS implements Zend_Auth_Adapter_Interface +{ + /** + * CAS client + */ + private $_protocol; + + /** + * Service ticket + */ + private $_ticket; + + /** + * Constructor + * + * @param string $server_hostname + * @param string $server_port + * @param string $server_uri + * @return void + */ + public function __construct($options) + { + $this->_protocol = new SimpleCAS_Protocol_Version2($options); + } + + public function setTicket($ticket) + { + $this->_ticket = $ticket; + return $this; + } + + /** + * Authenticates ticket + * + * The ticket is provided with setTicket + * + * @param return boolean + */ + public function authenticate() + { + if ($id = $this->_protocol->validateTicket($this->_ticket, self::getURL())) { + return new Zend_Auth_Result( + Zend_Auth_Result::SUCCESS, + $id, + array("Authentication successful")); + } else { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + null, + array("Authentication failed")); + } + } + + /** + * Returns the current URL without CAS affecting parameters. + * Copied directly from SimpleCAS.php 0.1.0 + * + * @return string url + */ + static public function getURL() + { + if (isset($_SERVER['HTTPS']) + && !empty($_SERVER['HTTPS']) + && $_SERVER['HTTPS'] == 'on') { + $protocol = 'https'; + } else { + $protocol = 'http'; + } + + $url = $protocol.'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']; + + $replacements = array('/\?logout/' => '', + '/&ticket=[^&]*/' => '', + '/\?ticket=[^&;]*/' => '?', + '/\?%26/' => '?', + '/\?&/' => '?', + '/\?$/' => ''); + + $url = preg_replace(array_keys($replacements), + array_values($replacements), $url); + + return $url; + } + + /** + * Returns the URL to login form on the CAS server. + * + * @return string + */ + public function getLoginURL() + { + return $this->_protocol->getLoginURL(self::getURL()); + } + +} diff --git a/lib/docs/SimpleCAS/docs/examples/simple.php b/lib/docs/SimpleCAS/docs/examples/simple.php new file mode 100644 index 0000000000000000000000000000000000000000..02f768b8868624426092901051a478966f1680a2 --- /dev/null +++ b/lib/docs/SimpleCAS/docs/examples/simple.php @@ -0,0 +1,27 @@ +<?php +ini_set('display_errors', true); +chdir(dirname(dirname(dirname(__FILE__)))); + +require_once 'SimpleCAS/Autoload.php'; +require_once 'HTTP/Request2.php'; + +$options = array('hostname' =>'login.unl.edu', + 'port' => 443, + 'uri' => 'cas'); +$protocol = new SimpleCAS_Protocol_Version2($options); + +$protocol->getRequest()->setConfig('ssl_verify_peer', false); + +$client = SimpleCAS::client($protocol); +$client->forceAuthentication(); + +if (isset($_GET['logout'])) { + $client->logout(); +} + +if ($client->isAuthenticated()) { + echo '<h1>Authentication Successful!</h1>'; + echo '<p>The user\'s login is '.$client->getUsername().'</p>'; +} +?> +<a href="?logout">Logout</a> \ No newline at end of file diff --git a/lib/docs/UNL_Auth/docs/examples/CASPEARAuth_example.php b/lib/docs/UNL_Auth/docs/examples/CASPEARAuth_example.php new file mode 100644 index 0000000000000000000000000000000000000000..8b2442757e45a7bf08ad841e36e340859292363a --- /dev/null +++ b/lib/docs/UNL_Auth/docs/examples/CASPEARAuth_example.php @@ -0,0 +1,26 @@ +<?php +ini_set('display_errors', true); +error_reporting(E_ALL); +chdir(dirname(__FILE__).'/../../'); +require_once 'UNL/Auth.php'; + +//$auth = UNL_Auth::PEARFactory('CAS', $options=null, $loginfunction=null, false); +$auth = UNL_Auth::PEARFactory('CAS'); +$auth->start(); + +if (isset($_GET['logout']) && $auth->checkAuth()) { + $auth->logout(); + $auth->start(); +} + +if ($auth->checkAuth()) { + /* + * The output of your site goes here. + */ + echo 'You are authenticated, '.$auth->getUsername().'<br />'; + echo '<a href="?logout">Logout</a>'; +} else { + echo 'You need to log in bro!'; +} + +?> \ No newline at end of file diff --git a/lib/docs/UNL_Auth/docs/examples/CAS_example.php b/lib/docs/UNL_Auth/docs/examples/CAS_example.php new file mode 100644 index 0000000000000000000000000000000000000000..42acfc6dff5776434521d352519b0d0cc3c91160 --- /dev/null +++ b/lib/docs/UNL_Auth/docs/examples/CAS_example.php @@ -0,0 +1,22 @@ +<?php +ini_set('display_errors', true); +error_reporting(E_ALL); +chdir(dirname(__FILE__).'/../../'); +require_once 'UNL/Auth.php'; + +$auth = UNL_Auth::factory('CAS'); + +if (isset($_GET['login'])) { + $auth->login(); +} elseif (isset($_GET['logout'])) { + $auth->logout(); +} + +if (!$auth->isLoggedIn()) { + // Could call $auth->login() here to get the party started. + echo "You are not logged in.\n<br />\n"; + echo '<a href="?login=true">Click here to log in!</a>'; +} else { + echo "You are logged in as {$auth->getUser()}<br />"; + echo "<a href='?logout'>logout</a>"; +} diff --git a/lib/docs/UNL_Auth/docs/examples/SimpleCAS_example.php b/lib/docs/UNL_Auth/docs/examples/SimpleCAS_example.php new file mode 100644 index 0000000000000000000000000000000000000000..d514e53980d381ea273aaa4aa3fec1be787c2636 --- /dev/null +++ b/lib/docs/UNL_Auth/docs/examples/SimpleCAS_example.php @@ -0,0 +1,22 @@ +<?php +ini_set('display_errors', true); +error_reporting(E_ALL); +chdir(dirname(__FILE__).'/../../'); +require_once 'UNL/Auth.php'; + +$auth = UNL_Auth::factory('SimpleCAS'); + +if (isset($_GET['login'])) { + $auth->login(); +} elseif (isset($_GET['logout'])) { + $auth->logout(); +} + +if (!$auth->isLoggedIn()) { + // Could call $auth->login() here to get the party started. + echo "You are not logged in.\n<br />\n"; + echo '<a href="?login=true">Click here to log in!</a>'; +} else { + echo "You are logged in as {$auth->getUser()}<br />"; + echo "<a href='?logout'>logout</a>"; +} \ No newline at end of file diff --git a/lib/docs/UNL_Auth/docs/examples/Zend_SimpleCAS_example.php b/lib/docs/UNL_Auth/docs/examples/Zend_SimpleCAS_example.php new file mode 100644 index 0000000000000000000000000000000000000000..adac078d28164bb35aa64c3727a047378b9eac59 --- /dev/null +++ b/lib/docs/UNL_Auth/docs/examples/Zend_SimpleCAS_example.php @@ -0,0 +1,26 @@ +<?php +ini_set('display_errors', true); +error_reporting(E_ALL); +chdir(dirname(__FILE__).'/../../'); +require_once 'UNL/Auth.php'; +require_once 'Zend/Auth.php'; + +$auth = Zend_Auth::getInstance(); +$authAdapter = UNL_Auth::ZendFactory('SimpleCAS'); +if (!$auth->hasIdentity()) { + $result = $auth->authenticate($authAdapter); + if (!$result->isValid()) { + // Authentication failed; print the reasons why + foreach ($result->getMessages() as $message) { + echo "$message\n"; + } + } else { + // Authentication succeeded; the identity ($username) is stored + // in the session + // $result->getIdentity() === $auth->getIdentity() + echo 'Hello '.$result->getIdentity(); + } +} else { + echo 'Hello@'; +} + diff --git a/lib/downloads/HTTP_Request2-2.0.0beta3.tgz b/lib/downloads/HTTP_Request2-2.0.0beta3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..790e40fe830bbdada2733edba199d2d0a53c55b0 Binary files /dev/null and b/lib/downloads/HTTP_Request2-2.0.0beta3.tgz differ diff --git a/lib/downloads/Net_URL2-0.3.1.tgz b/lib/downloads/Net_URL2-0.3.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..a0abb1f560641c79d54f17fa9dcf7273be1b329d Binary files /dev/null and b/lib/downloads/Net_URL2-0.3.1.tgz differ diff --git a/lib/downloads/SimpleCAS-0.5.0.tgz b/lib/downloads/SimpleCAS-0.5.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..fef74eda21e32fe1e04758b9e59425a9e467992c Binary files /dev/null and b/lib/downloads/SimpleCAS-0.5.0.tgz differ diff --git a/lib/downloads/UNL_Auth-0.4.0.tgz b/lib/downloads/UNL_Auth-0.4.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..006d5ec47141da50dad49eeaf0492e0393d7feae Binary files /dev/null and b/lib/downloads/UNL_Auth-0.4.0.tgz differ diff --git a/lib/php/HTTP/Request2.php b/lib/php/HTTP/Request2.php new file mode 100644 index 0000000000000000000000000000000000000000..97903f78e0a49b52f535951432e45a156267d56e --- /dev/null +++ b/lib/php/HTTP/Request2.php @@ -0,0 +1,1015 @@ +<?php +/** + * Class representing a HTTP request message + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Request2.php 308735 2011-02-27 20:31:28Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * A class representing an URL as per RFC 3986. + */ +require_once 'Net/URL2.php'; + +/** + * Exception class for HTTP_Request2 package + */ +require_once 'HTTP/Request2/Exception.php'; + +/** + * Class representing a HTTP request message + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + * @link http://tools.ietf.org/html/rfc2616#section-5 + */ +class HTTP_Request2 implements SplSubject +{ + /**#@+ + * Constants for HTTP request methods + * + * @link http://tools.ietf.org/html/rfc2616#section-5.1.1 + */ + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_GET = 'GET'; + const METHOD_HEAD = 'HEAD'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_DELETE = 'DELETE'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + /**#@-*/ + + /**#@+ + * Constants for HTTP authentication schemes + * + * @link http://tools.ietf.org/html/rfc2617 + */ + const AUTH_BASIC = 'basic'; + const AUTH_DIGEST = 'digest'; + /**#@-*/ + + /** + * Regular expression used to check for invalid symbols in RFC 2616 tokens + * @link http://pear.php.net/bugs/bug.php?id=15630 + */ + const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!'; + + /** + * Regular expression used to check for invalid symbols in cookie strings + * @link http://pear.php.net/bugs/bug.php?id=15630 + * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html + */ + const REGEXP_INVALID_COOKIE = '/[\s,;]/'; + + /** + * Fileinfo magic database resource + * @var resource + * @see detectMimeType() + */ + private static $_fileinfoDb; + + /** + * Observers attached to the request (instances of SplObserver) + * @var array + */ + protected $observers = array(); + + /** + * Request URL + * @var Net_URL2 + */ + protected $url; + + /** + * Request method + * @var string + */ + protected $method = self::METHOD_GET; + + /** + * Authentication data + * @var array + * @see getAuth() + */ + protected $auth; + + /** + * Request headers + * @var array + */ + protected $headers = array(); + + /** + * Configuration parameters + * @var array + * @see setConfig() + */ + protected $config = array( + 'adapter' => 'HTTP_Request2_Adapter_Socket', + 'connect_timeout' => 10, + 'timeout' => 0, + 'use_brackets' => true, + 'protocol_version' => '1.1', + 'buffer_size' => 16384, + 'store_body' => true, + + 'proxy_host' => '', + 'proxy_port' => '', + 'proxy_user' => '', + 'proxy_password' => '', + 'proxy_auth_scheme' => self::AUTH_BASIC, + + 'ssl_verify_peer' => true, + 'ssl_verify_host' => true, + 'ssl_cafile' => null, + 'ssl_capath' => null, + 'ssl_local_cert' => null, + 'ssl_passphrase' => null, + + 'digest_compat_ie' => false, + + 'follow_redirects' => false, + 'max_redirects' => 5, + 'strict_redirects' => false + ); + + /** + * Last event in request / response handling, intended for observers + * @var array + * @see getLastEvent() + */ + protected $lastEvent = array( + 'name' => 'start', + 'data' => null + ); + + /** + * Request body + * @var string|resource + * @see setBody() + */ + protected $body = ''; + + /** + * Array of POST parameters + * @var array + */ + protected $postParams = array(); + + /** + * Array of file uploads (for multipart/form-data POST requests) + * @var array + */ + protected $uploads = array(); + + /** + * Adapter used to perform actual HTTP request + * @var HTTP_Request2_Adapter + */ + protected $adapter; + + /** + * Cookie jar to persist cookies between requests + * @var HTTP_Request2_CookieJar + */ + protected $cookieJar = null; + + /** + * Constructor. Can set request URL, method and configuration array. + * + * Also sets a default value for User-Agent header. + * + * @param string|Net_Url2 Request URL + * @param string Request method + * @param array Configuration for this Request instance + */ + public function __construct($url = null, $method = self::METHOD_GET, array $config = array()) + { + $this->setConfig($config); + if (!empty($url)) { + $this->setUrl($url); + } + if (!empty($method)) { + $this->setMethod($method); + } + $this->setHeader('user-agent', 'HTTP_Request2/2.0.0beta3 ' . + '(http://pear.php.net/package/http_request2) ' . + 'PHP/' . phpversion()); + } + + /** + * Sets the URL for this request + * + * If the URL has userinfo part (username & password) these will be removed + * and converted to auth data. If the URL does not have a path component, + * that will be set to '/'. + * + * @param string|Net_URL2 Request URL + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException + */ + public function setUrl($url) + { + if (is_string($url)) { + $url = new Net_URL2( + $url, array(Net_URL2::OPTION_USE_BRACKETS => $this->config['use_brackets']) + ); + } + if (!$url instanceof Net_URL2) { + throw new HTTP_Request2_LogicException( + 'Parameter is not a valid HTTP URL', + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + // URL contains username / password? + if ($url->getUserinfo()) { + $username = $url->getUser(); + $password = $url->getPassword(); + $this->setAuth(rawurldecode($username), $password? rawurldecode($password): ''); + $url->setUserinfo(''); + } + if ('' == $url->getPath()) { + $url->setPath('/'); + } + $this->url = $url; + + return $this; + } + + /** + * Returns the request URL + * + * @return Net_URL2 + */ + public function getUrl() + { + return $this->url; + } + + /** + * Sets the request method + * + * @param string + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException if the method name is invalid + */ + public function setMethod($method) + { + // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1 + if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) { + throw new HTTP_Request2_LogicException( + "Invalid request method '{$method}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + $this->method = $method; + + return $this; + } + + /** + * Returns the request method + * + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the configuration parameter(s) + * + * The following parameters are available: + * <ul> + * <li> 'adapter' - adapter to use (string)</li> + * <li> 'connect_timeout' - Connection timeout in seconds (integer)</li> + * <li> 'timeout' - Total number of seconds a request can take. + * Use 0 for no limit, should be greater than + * 'connect_timeout' if set (integer)</li> + * <li> 'use_brackets' - Whether to append [] to array variable names (bool)</li> + * <li> 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)</li> + * <li> 'buffer_size' - Buffer size to use for reading and writing (int)</li> + * <li> 'store_body' - Whether to store response body in response object. + * Set to false if receiving a huge response and + * using an Observer to save it (boolean)</li> + * <li> 'proxy_host' - Proxy server host (string)</li> + * <li> 'proxy_port' - Proxy server port (integer)</li> + * <li> 'proxy_user' - Proxy auth username (string)</li> + * <li> 'proxy_password' - Proxy auth password (string)</li> + * <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li> + * <li> 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)</li> + * <li> 'ssl_verify_host' - Whether to check that Common Name in SSL + * certificate matches host name (bool)</li> + * <li> 'ssl_cafile' - Cerificate Authority file to verify the peer + * with (use with 'ssl_verify_peer') (string)</li> + * <li> 'ssl_capath' - Directory holding multiple Certificate + * Authority files (string)</li> + * <li> 'ssl_local_cert' - Name of a file containing local cerificate (string)</li> + * <li> 'ssl_passphrase' - Passphrase with which local certificate + * was encoded (string)</li> + * <li> 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6 + * in using URL without query string in digest + * authentication (boolean)</li> + * <li> 'follow_redirects' - Whether to automatically follow HTTP Redirects (boolean)</li> + * <li> 'max_redirects' - Maximum number of redirects to follow (integer)</li> + * <li> 'strict_redirects' - Whether to keep request method on redirects via status 301 and + * 302 (true, needed for compatibility with RFC 2616) + * or switch to GET (false, needed for compatibility with most + * browsers) (boolean)</li> + * </ul> + * + * @param string|array configuration parameter name or array + * ('parameter name' => 'parameter value') + * @param mixed parameter value if $nameOrConfig is not an array + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException If the parameter is unknown + */ + public function setConfig($nameOrConfig, $value = null) + { + if (is_array($nameOrConfig)) { + foreach ($nameOrConfig as $name => $value) { + $this->setConfig($name, $value); + } + + } else { + if (!array_key_exists($nameOrConfig, $this->config)) { + throw new HTTP_Request2_LogicException( + "Unknown configuration parameter '{$nameOrConfig}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + $this->config[$nameOrConfig] = $value; + } + + return $this; + } + + /** + * Returns the value(s) of the configuration parameter(s) + * + * @param string parameter name + * @return mixed value of $name parameter, array of all configuration + * parameters if $name is not given + * @throws HTTP_Request2_LogicException If the parameter is unknown + */ + public function getConfig($name = null) + { + if (null === $name) { + return $this->config; + } elseif (!array_key_exists($name, $this->config)) { + throw new HTTP_Request2_LogicException( + "Unknown configuration parameter '{$name}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + return $this->config[$name]; + } + + /** + * Sets the autentification data + * + * @param string user name + * @param string password + * @param string authentication scheme + * @return HTTP_Request2 + */ + public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC) + { + if (empty($user)) { + $this->auth = null; + } else { + $this->auth = array( + 'user' => (string)$user, + 'password' => (string)$password, + 'scheme' => $scheme + ); + } + + return $this; + } + + /** + * Returns the authentication data + * + * The array has the keys 'user', 'password' and 'scheme', where 'scheme' + * is one of the HTTP_Request2::AUTH_* constants. + * + * @return array + */ + public function getAuth() + { + return $this->auth; + } + + /** + * Sets request header(s) + * + * The first parameter may be either a full header string 'header: value' or + * header name. In the former case $value parameter is ignored, in the latter + * the header's value will either be set to $value or the header will be + * removed if $value is null. The first parameter can also be an array of + * headers, in that case method will be called recursively. + * + * Note that headers are treated case insensitively as per RFC 2616. + * + * <code> + * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar' + * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz' + * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux' + * $req->setHeader('FOO'); // removes 'Foo' header from request + * </code> + * + * @param string|array header name, header string ('Header: value') + * or an array of headers + * @param string|array|null header value if $name is not an array, + * header will be removed if value is null + * @param bool whether to replace previous header with the + * same name or append to its value + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException + */ + public function setHeader($name, $value = null, $replace = true) + { + if (is_array($name)) { + foreach ($name as $k => $v) { + if (is_string($k)) { + $this->setHeader($k, $v, $replace); + } else { + $this->setHeader($v, null, $replace); + } + } + } else { + if (null === $value && strpos($name, ':')) { + list($name, $value) = array_map('trim', explode(':', $name, 2)); + } + // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2 + if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) { + throw new HTTP_Request2_LogicException( + "Invalid header name '{$name}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + // Header names are case insensitive anyway + $name = strtolower($name); + if (null === $value) { + unset($this->headers[$name]); + + } else { + if (is_array($value)) { + $value = implode(', ', array_map('trim', $value)); + } elseif (is_string($value)) { + $value = trim($value); + } + if (!isset($this->headers[$name]) || $replace) { + $this->headers[$name] = $value; + } else { + $this->headers[$name] .= ', ' . $value; + } + } + } + + return $this; + } + + /** + * Returns the request headers + * + * The array is of the form ('header name' => 'header value'), header names + * are lowercased + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Adds a cookie to the request + * + * If the request does not have a CookieJar object set, this method simply + * appends a cookie to "Cookie:" header. + * + * If a CookieJar object is available, the cookie is stored in that object. + * Data from request URL will be used for setting its 'domain' and 'path' + * parameters, 'expires' and 'secure' will be set to null and false, + * respectively. If you need further control, use CookieJar's methods. + * + * @param string cookie name + * @param string cookie value + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException + * @see setCookieJar() + */ + public function addCookie($name, $value) + { + if (!empty($this->cookieJar)) { + $this->cookieJar->store(array('name' => $name, 'value' => $value), + $this->url); + + } else { + $cookie = $name . '=' . $value; + if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) { + throw new HTTP_Request2_LogicException( + "Invalid cookie: '{$cookie}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; '; + $this->setHeader('cookie', $cookies . $cookie); + } + + return $this; + } + + /** + * Sets the request body + * + * If you provide file pointer rather than file name, it should support + * fstat() and rewind() operations. + * + * @param string|resource|HTTP_Request2_MultipartBody Either a string + * with the body or filename containing body or pointer to + * an open file or object with multipart body data + * @param bool Whether first parameter is a filename + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException + */ + public function setBody($body, $isFilename = false) + { + if (!$isFilename && !is_resource($body)) { + if (!$body instanceof HTTP_Request2_MultipartBody) { + $this->body = (string)$body; + } else { + $this->body = $body; + } + } else { + $fileData = $this->fopenWrapper($body, empty($this->headers['content-type'])); + $this->body = $fileData['fp']; + if (empty($this->headers['content-type'])) { + $this->setHeader('content-type', $fileData['type']); + } + } + $this->postParams = $this->uploads = array(); + + return $this; + } + + /** + * Returns the request body + * + * @return string|resource|HTTP_Request2_MultipartBody + */ + public function getBody() + { + if (self::METHOD_POST == $this->method && + (!empty($this->postParams) || !empty($this->uploads)) + ) { + if (0 === strpos($this->headers['content-type'], 'application/x-www-form-urlencoded')) { + $body = http_build_query($this->postParams, '', '&'); + if (!$this->getConfig('use_brackets')) { + $body = preg_replace('/%5B\d+%5D=/', '=', $body); + } + // support RFC 3986 by not encoding '~' symbol (request #15368) + return str_replace('%7E', '~', $body); + + } elseif (0 === strpos($this->headers['content-type'], 'multipart/form-data')) { + require_once 'HTTP/Request2/MultipartBody.php'; + return new HTTP_Request2_MultipartBody( + $this->postParams, $this->uploads, $this->getConfig('use_brackets') + ); + } + } + return $this->body; + } + + /** + * Adds a file to form-based file upload + * + * Used to emulate file upload via a HTML form. The method also sets + * Content-Type of HTTP request to 'multipart/form-data'. + * + * If you just want to send the contents of a file as the body of HTTP + * request you should use setBody() method. + * + * If you provide file pointers rather than file names, they should support + * fstat() and rewind() operations. + * + * @param string name of file-upload field + * @param string|resource|array full name of local file, pointer to + * open file or an array of files + * @param string filename to send in the request + * @param string content-type of file being uploaded + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException + */ + public function addUpload($fieldName, $filename, $sendFilename = null, + $contentType = null) + { + if (!is_array($filename)) { + $fileData = $this->fopenWrapper($filename, empty($contentType)); + $this->uploads[$fieldName] = array( + 'fp' => $fileData['fp'], + 'filename' => !empty($sendFilename)? $sendFilename + :(is_string($filename)? basename($filename): 'anonymous.blob') , + 'size' => $fileData['size'], + 'type' => empty($contentType)? $fileData['type']: $contentType + ); + } else { + $fps = $names = $sizes = $types = array(); + foreach ($filename as $f) { + if (!is_array($f)) { + $f = array($f); + } + $fileData = $this->fopenWrapper($f[0], empty($f[2])); + $fps[] = $fileData['fp']; + $names[] = !empty($f[1])? $f[1] + :(is_string($f[0])? basename($f[0]): 'anonymous.blob'); + $sizes[] = $fileData['size']; + $types[] = empty($f[2])? $fileData['type']: $f[2]; + } + $this->uploads[$fieldName] = array( + 'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types + ); + } + if (empty($this->headers['content-type']) || + 'application/x-www-form-urlencoded' == $this->headers['content-type'] + ) { + $this->setHeader('content-type', 'multipart/form-data'); + } + + return $this; + } + + /** + * Adds POST parameter(s) to the request. + * + * @param string|array parameter name or array ('name' => 'value') + * @param mixed parameter value (can be an array) + * @return HTTP_Request2 + */ + public function addPostParameter($name, $value = null) + { + if (!is_array($name)) { + $this->postParams[$name] = $value; + } else { + foreach ($name as $k => $v) { + $this->addPostParameter($k, $v); + } + } + if (empty($this->headers['content-type'])) { + $this->setHeader('content-type', 'application/x-www-form-urlencoded'); + } + + return $this; + } + + /** + * Attaches a new observer + * + * @param SplObserver + */ + public function attach(SplObserver $observer) + { + foreach ($this->observers as $attached) { + if ($attached === $observer) { + return; + } + } + $this->observers[] = $observer; + } + + /** + * Detaches an existing observer + * + * @param SplObserver + */ + public function detach(SplObserver $observer) + { + foreach ($this->observers as $key => $attached) { + if ($attached === $observer) { + unset($this->observers[$key]); + return; + } + } + } + + /** + * Notifies all observers + */ + public function notify() + { + foreach ($this->observers as $observer) { + $observer->update($this); + } + } + + /** + * Sets the last event + * + * Adapters should use this method to set the current state of the request + * and notify the observers. + * + * @param string event name + * @param mixed event data + */ + public function setLastEvent($name, $data = null) + { + $this->lastEvent = array( + 'name' => $name, + 'data' => $data + ); + $this->notify(); + } + + /** + * Returns the last event + * + * Observers should use this method to access the last change in request. + * The following event names are possible: + * <ul> + * <li>'connect' - after connection to remote server, + * data is the destination (string)</li> + * <li>'disconnect' - after disconnection from server</li> + * <li>'sentHeaders' - after sending the request headers, + * data is the headers sent (string)</li> + * <li>'sentBodyPart' - after sending a part of the request body, + * data is the length of that part (int)</li> + * <li>'sentBody' - after sending the whole request body, + * data is request body length (int)</li> + * <li>'receivedHeaders' - after receiving the response headers, + * data is HTTP_Request2_Response object</li> + * <li>'receivedBodyPart' - after receiving a part of the response + * body, data is that part (string)</li> + * <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still + * encoded by Content-Encoding</li> + * <li>'receivedBody' - after receiving the complete response + * body, data is HTTP_Request2_Response object</li> + * </ul> + * Different adapters may not send all the event types. Mock adapter does + * not send any events to the observers. + * + * @return array The array has two keys: 'name' and 'data' + */ + public function getLastEvent() + { + return $this->lastEvent; + } + + /** + * Sets the adapter used to actually perform the request + * + * You can pass either an instance of a class implementing HTTP_Request2_Adapter + * or a class name. The method will only try to include a file if the class + * name starts with HTTP_Request2_Adapter_, it will also try to prepend this + * prefix to the class name if it doesn't contain any underscores, so that + * <code> + * $request->setAdapter('curl'); + * </code> + * will work. + * + * @param string|HTTP_Request2_Adapter + * @return HTTP_Request2 + * @throws HTTP_Request2_LogicException + */ + public function setAdapter($adapter) + { + if (is_string($adapter)) { + if (!class_exists($adapter, false)) { + if (false === strpos($adapter, '_')) { + $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter); + } + if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) { + include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php'; + } + if (!class_exists($adapter, false)) { + throw new HTTP_Request2_LogicException( + "Class {$adapter} not found", + HTTP_Request2_Exception::MISSING_VALUE + ); + } + } + $adapter = new $adapter; + } + if (!$adapter instanceof HTTP_Request2_Adapter) { + throw new HTTP_Request2_LogicException( + 'Parameter is not a HTTP request adapter', + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + $this->adapter = $adapter; + + return $this; + } + + /** + * Sets the cookie jar + * + * A cookie jar is used to maintain cookies across HTTP requests and + * responses. Cookies from jar will be automatically added to the request + * headers based on request URL. + * + * @param HTTP_Request2_CookieJar|bool Existing CookieJar object, true to + * create a new one, false to remove + */ + public function setCookieJar($jar = true) + { + if (!class_exists('HTTP_Request2_CookieJar', false)) { + require_once 'HTTP/Request2/CookieJar.php'; + } + + if ($jar instanceof HTTP_Request2_CookieJar) { + $this->cookieJar = $jar; + } elseif (true === $jar) { + $this->cookieJar = new HTTP_Request2_CookieJar(); + } elseif (!$jar) { + $this->cookieJar = null; + } else { + throw new HTTP_Request2_LogicException( + 'Invalid parameter passed to setCookieJar()', + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + + return $this; + } + + /** + * Returns current CookieJar object or null if none + * + * @return HTTP_Request2_CookieJar|null + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Sends the request and returns the response + * + * @throws HTTP_Request2_Exception + * @return HTTP_Request2_Response + */ + public function send() + { + // Sanity check for URL + if (!$this->url instanceof Net_URL2 + || !$this->url->isAbsolute() + || !in_array(strtolower($this->url->getScheme()), array('https', 'http')) + ) { + throw new HTTP_Request2_LogicException( + 'HTTP_Request2 needs an absolute HTTP(S) request URL, ' + . ($this->url instanceof Net_URL2 + ? 'none' : "'" . $this->url->__toString() . "'") + . ' given', + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + if (empty($this->adapter)) { + $this->setAdapter($this->getConfig('adapter')); + } + // magic_quotes_runtime may break file uploads and chunked response + // processing; see bug #4543. Don't use ini_get() here; see bug #16440. + if ($magicQuotes = get_magic_quotes_runtime()) { + set_magic_quotes_runtime(false); + } + // force using single byte encoding if mbstring extension overloads + // strlen() and substr(); see bug #1781, bug #10605 + if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) { + $oldEncoding = mb_internal_encoding(); + mb_internal_encoding('iso-8859-1'); + } + + try { + $response = $this->adapter->sendRequest($this); + } catch (Exception $e) { + } + // cleanup in either case (poor man's "finally" clause) + if ($magicQuotes) { + set_magic_quotes_runtime(true); + } + if (!empty($oldEncoding)) { + mb_internal_encoding($oldEncoding); + } + // rethrow the exception + if (!empty($e)) { + throw $e; + } + return $response; + } + + /** + * Wrapper around fopen()/fstat() used by setBody() and addUpload() + * + * @param string|resource file name or pointer to open file + * @param bool whether to try autodetecting MIME type of file, + * will only work if $file is a filename, not pointer + * @return array array('fp' => file pointer, 'size' => file size, 'type' => MIME type) + * @throws HTTP_Request2_LogicException + */ + protected function fopenWrapper($file, $detectType = false) + { + if (!is_string($file) && !is_resource($file)) { + throw new HTTP_Request2_LogicException( + "Filename or file pointer resource expected", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + $fileData = array( + 'fp' => is_string($file)? null: $file, + 'type' => 'application/octet-stream', + 'size' => 0 + ); + if (is_string($file)) { + $track = @ini_set('track_errors', 1); + if (!($fileData['fp'] = @fopen($file, 'rb'))) { + $e = new HTTP_Request2_LogicException( + $php_errormsg, HTTP_Request2_Exception::READ_ERROR + ); + } + @ini_set('track_errors', $track); + if (isset($e)) { + throw $e; + } + if ($detectType) { + $fileData['type'] = self::detectMimeType($file); + } + } + if (!($stat = fstat($fileData['fp']))) { + throw new HTTP_Request2_LogicException( + "fstat() call failed", HTTP_Request2_Exception::READ_ERROR + ); + } + $fileData['size'] = $stat['size']; + + return $fileData; + } + + /** + * Tries to detect MIME type of a file + * + * The method will try to use fileinfo extension if it is available, + * deprecated mime_content_type() function in the other case. If neither + * works, default 'application/octet-stream' MIME type is returned + * + * @param string filename + * @return string file MIME type + */ + protected static function detectMimeType($filename) + { + // finfo extension from PECL available + if (function_exists('finfo_open')) { + if (!isset(self::$_fileinfoDb)) { + self::$_fileinfoDb = @finfo_open(FILEINFO_MIME); + } + if (self::$_fileinfoDb) { + $info = finfo_file(self::$_fileinfoDb, $filename); + } + } + // (deprecated) mime_content_type function available + if (empty($info) && function_exists('mime_content_type')) { + return mime_content_type($filename); + } + return empty($info)? 'application/octet-stream': $info; + } +} +?> \ No newline at end of file diff --git a/lib/php/HTTP/Request2/Adapter.php b/lib/php/HTTP/Request2/Adapter.php new file mode 100644 index 0000000000000000000000000000000000000000..ca25abfc27420a498aefcef3b7601754bcb06d75 --- /dev/null +++ b/lib/php/HTTP/Request2/Adapter.php @@ -0,0 +1,154 @@ +<?php +/** + * Base class for HTTP_Request2 adapters + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Adapter.php 308322 2011-02-14 13:58:03Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class representing a HTTP response + */ +require_once 'HTTP/Request2/Response.php'; + +/** + * Base class for HTTP_Request2 adapters + * + * HTTP_Request2 class itself only defines methods for aggregating the request + * data, all actual work of sending the request to the remote server and + * receiving its response is performed by adapters. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + */ +abstract class HTTP_Request2_Adapter +{ + /** + * A list of methods that MUST NOT have a request body, per RFC 2616 + * @var array + */ + protected static $bodyDisallowed = array('TRACE'); + + /** + * Methods having defined semantics for request body + * + * Content-Length header (indicating that the body follows, section 4.3 of + * RFC 2616) will be sent for these methods even if no body was added + * + * @var array + * @link http://pear.php.net/bugs/bug.php?id=12900 + * @link http://pear.php.net/bugs/bug.php?id=14740 + */ + protected static $bodyRequired = array('POST', 'PUT'); + + /** + * Request being sent + * @var HTTP_Request2 + */ + protected $request; + + /** + * Request body + * @var string|resource|HTTP_Request2_MultipartBody + * @see HTTP_Request2::getBody() + */ + protected $requestBody; + + /** + * Length of the request body + * @var integer + */ + protected $contentLength; + + /** + * Sends request to the remote server and returns its response + * + * @param HTTP_Request2 + * @return HTTP_Request2_Response + * @throws HTTP_Request2_Exception + */ + abstract public function sendRequest(HTTP_Request2 $request); + + /** + * Calculates length of the request body, adds proper headers + * + * @param array associative array of request headers, this method will + * add proper 'Content-Length' and 'Content-Type' headers + * to this array (or remove them if not needed) + */ + protected function calculateRequestLength(&$headers) + { + $this->requestBody = $this->request->getBody(); + + if (is_string($this->requestBody)) { + $this->contentLength = strlen($this->requestBody); + } elseif (is_resource($this->requestBody)) { + $stat = fstat($this->requestBody); + $this->contentLength = $stat['size']; + rewind($this->requestBody); + } else { + $this->contentLength = $this->requestBody->getLength(); + $headers['content-type'] = 'multipart/form-data; boundary=' . + $this->requestBody->getBoundary(); + $this->requestBody->rewind(); + } + + if (in_array($this->request->getMethod(), self::$bodyDisallowed) || + 0 == $this->contentLength + ) { + // No body: send a Content-Length header nonetheless (request #12900), + // but do that only for methods that require a body (bug #14740) + if (in_array($this->request->getMethod(), self::$bodyRequired)) { + $headers['content-length'] = 0; + } else { + unset($headers['content-length']); + // if the method doesn't require a body and doesn't have a + // body, don't send a Content-Type header. (request #16799) + unset($headers['content-type']); + } + } else { + if (empty($headers['content-type'])) { + $headers['content-type'] = 'application/x-www-form-urlencoded'; + } + $headers['content-length'] = $this->contentLength; + } + } +} +?> diff --git a/lib/php/HTTP/Request2/Adapter/Curl.php b/lib/php/HTTP/Request2/Adapter/Curl.php new file mode 100644 index 0000000000000000000000000000000000000000..eeec8cb2fcf48c032dcef28949e5b851e1ae4822 --- /dev/null +++ b/lib/php/HTTP/Request2/Adapter/Curl.php @@ -0,0 +1,562 @@ +<?php +/** + * Adapter for HTTP_Request2 wrapping around cURL extension + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Curl.php 309921 2011-04-03 16:43:02Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Base class for HTTP_Request2 adapters + */ +require_once 'HTTP/Request2/Adapter.php'; + +/** + * Adapter for HTTP_Request2 wrapping around cURL extension + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter +{ + /** + * Mapping of header names to cURL options + * @var array + */ + protected static $headerMap = array( + 'accept-encoding' => CURLOPT_ENCODING, + 'cookie' => CURLOPT_COOKIE, + 'referer' => CURLOPT_REFERER, + 'user-agent' => CURLOPT_USERAGENT + ); + + /** + * Mapping of SSL context options to cURL options + * @var array + */ + protected static $sslContextMap = array( + 'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER, + 'ssl_cafile' => CURLOPT_CAINFO, + 'ssl_capath' => CURLOPT_CAPATH, + 'ssl_local_cert' => CURLOPT_SSLCERT, + 'ssl_passphrase' => CURLOPT_SSLCERTPASSWD + ); + + /** + * Mapping of CURLE_* constants to Exception subclasses and error codes + * @var array + */ + protected static $errorMap = array( + CURLE_UNSUPPORTED_PROTOCOL => array('HTTP_Request2_MessageException', + HTTP_Request2_Exception::NON_HTTP_REDIRECT), + CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'), + CURLE_COULDNT_RESOLVE_HOST => array('HTTP_Request2_ConnectionException'), + CURLE_COULDNT_CONNECT => array('HTTP_Request2_ConnectionException'), + // error returned from write callback + CURLE_WRITE_ERROR => array('HTTP_Request2_MessageException', + HTTP_Request2_Exception::NON_HTTP_REDIRECT), + CURLE_OPERATION_TIMEOUTED => array('HTTP_Request2_MessageException', + HTTP_Request2_Exception::TIMEOUT), + CURLE_HTTP_RANGE_ERROR => array('HTTP_Request2_MessageException'), + CURLE_SSL_CONNECT_ERROR => array('HTTP_Request2_ConnectionException'), + CURLE_LIBRARY_NOT_FOUND => array('HTTP_Request2_LogicException', + HTTP_Request2_Exception::MISCONFIGURATION), + CURLE_FUNCTION_NOT_FOUND => array('HTTP_Request2_LogicException', + HTTP_Request2_Exception::MISCONFIGURATION), + CURLE_ABORTED_BY_CALLBACK => array('HTTP_Request2_MessageException', + HTTP_Request2_Exception::NON_HTTP_REDIRECT), + CURLE_TOO_MANY_REDIRECTS => array('HTTP_Request2_MessageException', + HTTP_Request2_Exception::TOO_MANY_REDIRECTS), + CURLE_SSL_PEER_CERTIFICATE => array('HTTP_Request2_ConnectionException'), + CURLE_GOT_NOTHING => array('HTTP_Request2_MessageException'), + CURLE_SSL_ENGINE_NOTFOUND => array('HTTP_Request2_LogicException', + HTTP_Request2_Exception::MISCONFIGURATION), + CURLE_SSL_ENGINE_SETFAILED => array('HTTP_Request2_LogicException', + HTTP_Request2_Exception::MISCONFIGURATION), + CURLE_SEND_ERROR => array('HTTP_Request2_MessageException'), + CURLE_RECV_ERROR => array('HTTP_Request2_MessageException'), + CURLE_SSL_CERTPROBLEM => array('HTTP_Request2_LogicException', + HTTP_Request2_Exception::INVALID_ARGUMENT), + CURLE_SSL_CIPHER => array('HTTP_Request2_ConnectionException'), + CURLE_SSL_CACERT => array('HTTP_Request2_ConnectionException'), + CURLE_BAD_CONTENT_ENCODING => array('HTTP_Request2_MessageException'), + ); + + /** + * Response being received + * @var HTTP_Request2_Response + */ + protected $response; + + /** + * Whether 'sentHeaders' event was sent to observers + * @var boolean + */ + protected $eventSentHeaders = false; + + /** + * Whether 'receivedHeaders' event was sent to observers + * @var boolean + */ + protected $eventReceivedHeaders = false; + + /** + * Position within request body + * @var integer + * @see callbackReadBody() + */ + protected $position = 0; + + /** + * Information about last transfer, as returned by curl_getinfo() + * @var array + */ + protected $lastInfo; + + /** + * Creates a subclass of HTTP_Request2_Exception from curl error data + * + * @param resource curl handle + * @return HTTP_Request2_Exception + */ + protected static function wrapCurlError($ch) + { + $nativeCode = curl_errno($ch); + $message = 'Curl error: ' . curl_error($ch); + if (!isset(self::$errorMap[$nativeCode])) { + return new HTTP_Request2_Exception($message, 0, $nativeCode); + } else { + $class = self::$errorMap[$nativeCode][0]; + $code = empty(self::$errorMap[$nativeCode][1]) + ? 0 : self::$errorMap[$nativeCode][1]; + return new $class($message, $code, $nativeCode); + } + } + + /** + * Sends request to the remote server and returns its response + * + * @param HTTP_Request2 + * @return HTTP_Request2_Response + * @throws HTTP_Request2_Exception + */ + public function sendRequest(HTTP_Request2 $request) + { + if (!extension_loaded('curl')) { + throw new HTTP_Request2_LogicException( + 'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION + ); + } + + $this->request = $request; + $this->response = null; + $this->position = 0; + $this->eventSentHeaders = false; + $this->eventReceivedHeaders = false; + + try { + if (false === curl_exec($ch = $this->createCurlHandle())) { + $e = self::wrapCurlError($ch); + } + } catch (Exception $e) { + } + if (isset($ch)) { + $this->lastInfo = curl_getinfo($ch); + curl_close($ch); + } + + $response = $this->response; + unset($this->request, $this->requestBody, $this->response); + + if (!empty($e)) { + throw $e; + } + + if ($jar = $request->getCookieJar()) { + $jar->addCookiesFromResponse($response, $request->getUrl()); + } + + if (0 < $this->lastInfo['size_download']) { + $request->setLastEvent('receivedBody', $response); + } + return $response; + } + + /** + * Returns information about last transfer + * + * @return array associative array as returned by curl_getinfo() + */ + public function getInfo() + { + return $this->lastInfo; + } + + /** + * Creates a new cURL handle and populates it with data from the request + * + * @return resource a cURL handle, as created by curl_init() + * @throws HTTP_Request2_LogicException + */ + protected function createCurlHandle() + { + $ch = curl_init(); + + curl_setopt_array($ch, array( + // setup write callbacks + CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'), + CURLOPT_WRITEFUNCTION => array($this, 'callbackWriteBody'), + // buffer size + CURLOPT_BUFFERSIZE => $this->request->getConfig('buffer_size'), + // connection timeout + CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'), + // save full outgoing headers, in case someone is interested + CURLINFO_HEADER_OUT => true, + // request url + CURLOPT_URL => $this->request->getUrl()->getUrl() + )); + + // set up redirects + if (!$this->request->getConfig('follow_redirects')) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); + } else { + if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) { + throw new HTTP_Request2_LogicException( + 'Redirect support in curl is unavailable due to open_basedir or safe_mode setting', + HTTP_Request2_Exception::MISCONFIGURATION + ); + } + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects')); + // limit redirects to http(s), works in 5.2.10+ + if (defined('CURLOPT_REDIR_PROTOCOLS')) { + curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + } + // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571 + if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) { + curl_setopt($ch, CURLOPT_POSTREDIR, 3); + } + } + + // request timeout + if ($timeout = $this->request->getConfig('timeout')) { + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + } + + // set HTTP version + switch ($this->request->getConfig('protocol_version')) { + case '1.0': + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + break; + case '1.1': + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + } + + // set request method + switch ($this->request->getMethod()) { + case HTTP_Request2::METHOD_GET: + curl_setopt($ch, CURLOPT_HTTPGET, true); + break; + case HTTP_Request2::METHOD_POST: + curl_setopt($ch, CURLOPT_POST, true); + break; + case HTTP_Request2::METHOD_HEAD: + curl_setopt($ch, CURLOPT_NOBODY, true); + break; + case HTTP_Request2::METHOD_PUT: + curl_setopt($ch, CURLOPT_UPLOAD, true); + break; + default: + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod()); + } + + // set proxy, if needed + if ($host = $this->request->getConfig('proxy_host')) { + if (!($port = $this->request->getConfig('proxy_port'))) { + throw new HTTP_Request2_LogicException( + 'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE + ); + } + curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port); + if ($user = $this->request->getConfig('proxy_user')) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $user . ':' . + $this->request->getConfig('proxy_password')); + switch ($this->request->getConfig('proxy_auth_scheme')) { + case HTTP_Request2::AUTH_BASIC: + curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); + break; + case HTTP_Request2::AUTH_DIGEST: + curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST); + } + } + } + + // set authentication data + if ($auth = $this->request->getAuth()) { + curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']); + switch ($auth['scheme']) { + case HTTP_Request2::AUTH_BASIC: + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + break; + case HTTP_Request2::AUTH_DIGEST: + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + } + } + + // set SSL options + if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) { + foreach ($this->request->getConfig() as $name => $value) { + if ('ssl_verify_host' == $name && null !== $value) { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0); + } elseif (isset(self::$sslContextMap[$name]) && null !== $value) { + curl_setopt($ch, self::$sslContextMap[$name], $value); + } + } + } + + $headers = $this->request->getHeaders(); + // make cURL automagically send proper header + if (!isset($headers['accept-encoding'])) { + $headers['accept-encoding'] = ''; + } + + if (($jar = $this->request->getCookieJar()) + && ($cookies = $jar->getMatching($this->request->getUrl(), true)) + ) { + $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies; + } + + // set headers having special cURL keys + foreach (self::$headerMap as $name => $option) { + if (isset($headers[$name])) { + curl_setopt($ch, $option, $headers[$name]); + unset($headers[$name]); + } + } + + $this->calculateRequestLength($headers); + if (isset($headers['content-length'])) { + $this->workaroundPhpBug47204($ch, $headers); + } + + // set headers not having special keys + $headersFmt = array(); + foreach ($headers as $name => $value) { + $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); + $headersFmt[] = $canonicalName . ': ' . $value; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt); + + return $ch; + } + + /** + * Workaround for PHP bug #47204 that prevents rewinding request body + * + * The workaround consists of reading the entire request body into memory + * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large + * file uploads, use Socket adapter instead. + * + * @param resource cURL handle + * @param array Request headers + */ + protected function workaroundPhpBug47204($ch, &$headers) + { + // no redirects, no digest auth -> probably no rewind needed + if (!$this->request->getConfig('follow_redirects') + && (!($auth = $this->request->getAuth()) + || HTTP_Request2::AUTH_DIGEST != $auth['scheme']) + ) { + curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody')); + + // rewind may be needed, read the whole body into memory + } else { + if ($this->requestBody instanceof HTTP_Request2_MultipartBody) { + $this->requestBody = $this->requestBody->__toString(); + + } elseif (is_resource($this->requestBody)) { + $fp = $this->requestBody; + $this->requestBody = ''; + while (!feof($fp)) { + $this->requestBody .= fread($fp, 16384); + } + } + // curl hangs up if content-length is present + unset($headers['content-length']); + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody); + } + } + + /** + * Callback function called by cURL for reading the request body + * + * @param resource cURL handle + * @param resource file descriptor (not used) + * @param integer maximum length of data to return + * @return string part of the request body, up to $length bytes + */ + protected function callbackReadBody($ch, $fd, $length) + { + if (!$this->eventSentHeaders) { + $this->request->setLastEvent( + 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT) + ); + $this->eventSentHeaders = true; + } + if (in_array($this->request->getMethod(), self::$bodyDisallowed) || + 0 == $this->contentLength || $this->position >= $this->contentLength + ) { + return ''; + } + if (is_string($this->requestBody)) { + $string = substr($this->requestBody, $this->position, $length); + } elseif (is_resource($this->requestBody)) { + $string = fread($this->requestBody, $length); + } else { + $string = $this->requestBody->read($length); + } + $this->request->setLastEvent('sentBodyPart', strlen($string)); + $this->position += strlen($string); + return $string; + } + + /** + * Callback function called by cURL for saving the response headers + * + * @param resource cURL handle + * @param string response header (with trailing CRLF) + * @return integer number of bytes saved + * @see HTTP_Request2_Response::parseHeaderLine() + */ + protected function callbackWriteHeader($ch, $string) + { + // we may receive a second set of headers if doing e.g. digest auth + if ($this->eventReceivedHeaders || !$this->eventSentHeaders) { + // don't bother with 100-Continue responses (bug #15785) + if (!$this->eventSentHeaders || + $this->response->getStatus() >= 200 + ) { + $this->request->setLastEvent( + 'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT) + ); + } + $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD); + // if body wasn't read by a callback, send event with total body size + if ($upload > $this->position) { + $this->request->setLastEvent( + 'sentBodyPart', $upload - $this->position + ); + $this->position = $upload; + } + if ($upload && (!$this->eventSentHeaders + || $this->response->getStatus() >= 200) + ) { + $this->request->setLastEvent('sentBody', $upload); + } + $this->eventSentHeaders = true; + // we'll need a new response object + if ($this->eventReceivedHeaders) { + $this->eventReceivedHeaders = false; + $this->response = null; + } + } + if (empty($this->response)) { + $this->response = new HTTP_Request2_Response( + $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) + ); + } else { + $this->response->parseHeaderLine($string); + if ('' == trim($string)) { + // don't bother with 100-Continue responses (bug #15785) + if (200 <= $this->response->getStatus()) { + $this->request->setLastEvent('receivedHeaders', $this->response); + } + + if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) { + $redirectUrl = new Net_URL2($this->response->getHeader('location')); + + // for versions lower than 5.2.10, check the redirection URL protocol + if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute() + && !in_array($redirectUrl->getScheme(), array('http', 'https')) + ) { + return -1; + } + + if ($jar = $this->request->getCookieJar()) { + $jar->addCookiesFromResponse($this->response, $this->request->getUrl()); + if (!$redirectUrl->isAbsolute()) { + $redirectUrl = $this->request->getUrl()->resolve($redirectUrl); + } + if ($cookies = $jar->getMatching($redirectUrl, true)) { + curl_setopt($ch, CURLOPT_COOKIE, $cookies); + } + } + } + $this->eventReceivedHeaders = true; + } + } + return strlen($string); + } + + /** + * Callback function called by cURL for saving the response body + * + * @param resource cURL handle (not used) + * @param string part of the response body + * @return integer number of bytes saved + * @see HTTP_Request2_Response::appendBody() + */ + protected function callbackWriteBody($ch, $string) + { + // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if + // response doesn't start with proper HTTP status line (see bug #15716) + if (empty($this->response)) { + throw new HTTP_Request2_MessageException( + "Malformed response: {$string}", + HTTP_Request2_Exception::MALFORMED_RESPONSE + ); + } + if ($this->request->getConfig('store_body')) { + $this->response->appendBody($string); + } + $this->request->setLastEvent('receivedBodyPart', $string); + return strlen($string); + } +} +?> diff --git a/lib/php/HTTP/Request2/Adapter/Mock.php b/lib/php/HTTP/Request2/Adapter/Mock.php new file mode 100644 index 0000000000000000000000000000000000000000..8e761c87353f42574c708d6f56460a28255f8627 --- /dev/null +++ b/lib/php/HTTP/Request2/Adapter/Mock.php @@ -0,0 +1,171 @@ +<?php +/** + * Mock adapter intended for testing + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Mock.php 308322 2011-02-14 13:58:03Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Base class for HTTP_Request2 adapters + */ +require_once 'HTTP/Request2/Adapter.php'; + +/** + * Mock adapter intended for testing + * + * Can be used to test applications depending on HTTP_Request2 package without + * actually performing any HTTP requests. This adapter will return responses + * previously added via addResponse() + * <code> + * $mock = new HTTP_Request2_Adapter_Mock(); + * $mock->addResponse("HTTP/1.1 ... "); + * + * $request = new HTTP_Request2(); + * $request->setAdapter($mock); + * + * // This will return the response set above + * $response = $req->send(); + * </code> + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter +{ + /** + * A queue of responses to be returned by sendRequest() + * @var array + */ + protected $responses = array(); + + /** + * Returns the next response from the queue built by addResponse() + * + * If the queue is empty it will return default empty response with status 400, + * if an Exception object was added to the queue it will be thrown. + * + * @param HTTP_Request2 + * @return HTTP_Request2_Response + * @throws Exception + */ + public function sendRequest(HTTP_Request2 $request) + { + if (count($this->responses) > 0) { + $response = array_shift($this->responses); + if ($response instanceof HTTP_Request2_Response) { + return $response; + } else { + // rethrow the exception + $class = get_class($response); + $message = $response->getMessage(); + $code = $response->getCode(); + throw new $class($message, $code); + } + } else { + return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n"); + } + } + + /** + * Adds response to the queue + * + * @param mixed either a string, a pointer to an open file, + * an instance of HTTP_Request2_Response or Exception + * @throws HTTP_Request2_Exception + */ + public function addResponse($response) + { + if (is_string($response)) { + $response = self::createResponseFromString($response); + } elseif (is_resource($response)) { + $response = self::createResponseFromFile($response); + } elseif (!$response instanceof HTTP_Request2_Response && + !$response instanceof Exception + ) { + throw new HTTP_Request2_Exception('Parameter is not a valid response'); + } + $this->responses[] = $response; + } + + /** + * Creates a new HTTP_Request2_Response object from a string + * + * @param string + * @return HTTP_Request2_Response + * @throws HTTP_Request2_Exception + */ + public static function createResponseFromString($str) + { + $parts = preg_split('!(\r?\n){2}!m', $str, 2); + $headerLines = explode("\n", $parts[0]); + $response = new HTTP_Request2_Response(array_shift($headerLines)); + foreach ($headerLines as $headerLine) { + $response->parseHeaderLine($headerLine); + } + $response->parseHeaderLine(''); + if (isset($parts[1])) { + $response->appendBody($parts[1]); + } + return $response; + } + + /** + * Creates a new HTTP_Request2_Response object from a file + * + * @param resource file pointer returned by fopen() + * @return HTTP_Request2_Response + * @throws HTTP_Request2_Exception + */ + public static function createResponseFromFile($fp) + { + $response = new HTTP_Request2_Response(fgets($fp)); + do { + $headerLine = fgets($fp); + $response->parseHeaderLine($headerLine); + } while ('' != trim($headerLine)); + + while (!feof($fp)) { + $response->appendBody(fread($fp, 8192)); + } + return $response; + } +} +?> \ No newline at end of file diff --git a/lib/php/HTTP/Request2/Adapter/Socket.php b/lib/php/HTTP/Request2/Adapter/Socket.php new file mode 100644 index 0000000000000000000000000000000000000000..5b4dc54234f1dad6a487f1f06d26c3f3e8285b76 --- /dev/null +++ b/lib/php/HTTP/Request2/Adapter/Socket.php @@ -0,0 +1,1084 @@ +<?php +/** + * Socket-based adapter for HTTP_Request2 + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Socket.php 309921 2011-04-03 16:43:02Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Base class for HTTP_Request2 adapters + */ +require_once 'HTTP/Request2/Adapter.php'; + +/** + * Socket-based adapter for HTTP_Request2 + * + * This adapter uses only PHP sockets and will work on almost any PHP + * environment. Code is based on original HTTP_Request PEAR package. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter +{ + /** + * Regular expression for 'token' rule from RFC 2616 + */ + const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+'; + + /** + * Regular expression for 'quoted-string' rule from RFC 2616 + */ + const REGEXP_QUOTED_STRING = '"(?:\\\\.|[^\\\\"])*"'; + + /** + * Connected sockets, needed for Keep-Alive support + * @var array + * @see connect() + */ + protected static $sockets = array(); + + /** + * Data for digest authentication scheme + * + * The keys for the array are URL prefixes. + * + * The values are associative arrays with data (realm, nonce, nonce-count, + * opaque...) needed for digest authentication. Stored here to prevent making + * duplicate requests to digest-protected resources after we have already + * received the challenge. + * + * @var array + */ + protected static $challenges = array(); + + /** + * Connected socket + * @var resource + * @see connect() + */ + protected $socket; + + /** + * Challenge used for server digest authentication + * @var array + */ + protected $serverChallenge; + + /** + * Challenge used for proxy digest authentication + * @var array + */ + protected $proxyChallenge; + + /** + * Sum of start time and global timeout, exception will be thrown if request continues past this time + * @var integer + */ + protected $deadline = null; + + /** + * Remaining length of the current chunk, when reading chunked response + * @var integer + * @see readChunked() + */ + protected $chunkLength = 0; + + /** + * Remaining amount of redirections to follow + * + * Starts at 'max_redirects' configuration parameter and is reduced on each + * subsequent redirect. An Exception will be thrown once it reaches zero. + * + * @var integer + */ + protected $redirectCountdown = null; + + /** + * Sends request to the remote server and returns its response + * + * @param HTTP_Request2 + * @return HTTP_Request2_Response + * @throws HTTP_Request2_Exception + */ + public function sendRequest(HTTP_Request2 $request) + { + $this->request = $request; + + // Use global request timeout if given, see feature requests #5735, #8964 + if ($timeout = $request->getConfig('timeout')) { + $this->deadline = time() + $timeout; + } else { + $this->deadline = null; + } + + try { + $keepAlive = $this->connect(); + $headers = $this->prepareHeaders(); + if (false === @fwrite($this->socket, $headers, strlen($headers))) { + throw new HTTP_Request2_MessageException('Error writing request'); + } + // provide request headers to the observer, see request #7633 + $this->request->setLastEvent('sentHeaders', $headers); + $this->writeBody(); + + if ($this->deadline && time() > $this->deadline) { + throw new HTTP_Request2_MessageException( + 'Request timed out after ' . + $request->getConfig('timeout') . ' second(s)', + HTTP_Request2_Exception::TIMEOUT + ); + } + + $response = $this->readResponse(); + + if ($jar = $request->getCookieJar()) { + $jar->addCookiesFromResponse($response, $request->getUrl()); + } + + if (!$this->canKeepAlive($keepAlive, $response)) { + $this->disconnect(); + } + + if ($this->shouldUseProxyDigestAuth($response)) { + return $this->sendRequest($request); + } + if ($this->shouldUseServerDigestAuth($response)) { + return $this->sendRequest($request); + } + if ($authInfo = $response->getHeader('authentication-info')) { + $this->updateChallenge($this->serverChallenge, $authInfo); + } + if ($proxyInfo = $response->getHeader('proxy-authentication-info')) { + $this->updateChallenge($this->proxyChallenge, $proxyInfo); + } + + } catch (Exception $e) { + $this->disconnect(); + } + + unset($this->request, $this->requestBody); + + if (!empty($e)) { + $this->redirectCountdown = null; + throw $e; + } + + if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) { + $this->redirectCountdown = null; + return $response; + } else { + return $this->handleRedirect($request, $response); + } + } + + /** + * Connects to the remote server + * + * @return bool whether the connection can be persistent + * @throws HTTP_Request2_Exception + */ + protected function connect() + { + $secure = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https'); + $tunnel = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod(); + $headers = $this->request->getHeaders(); + $reqHost = $this->request->getUrl()->getHost(); + if (!($reqPort = $this->request->getUrl()->getPort())) { + $reqPort = $secure? 443: 80; + } + + if ($host = $this->request->getConfig('proxy_host')) { + if (!($port = $this->request->getConfig('proxy_port'))) { + throw new HTTP_Request2_LogicException( + 'Proxy port not provided', + HTTP_Request2_Exception::MISSING_VALUE + ); + } + $proxy = true; + } else { + $host = $reqHost; + $port = $reqPort; + $proxy = false; + } + + if ($tunnel && !$proxy) { + throw new HTTP_Request2_LogicException( + "Trying to perform CONNECT request without proxy", + HTTP_Request2_Exception::MISSING_VALUE + ); + } + if ($secure && !in_array('ssl', stream_get_transports())) { + throw new HTTP_Request2_LogicException( + 'Need OpenSSL support for https:// requests', + HTTP_Request2_Exception::MISCONFIGURATION + ); + } + + // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive + // connection token to a proxy server... + if ($proxy && !$secure && + !empty($headers['connection']) && 'Keep-Alive' == $headers['connection'] + ) { + $this->request->setHeader('connection'); + } + + $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') && + empty($headers['connection'])) || + (!empty($headers['connection']) && + 'Keep-Alive' == $headers['connection']); + $host = ((!$secure || $proxy)? 'tcp://': 'ssl://') . $host; + + $options = array(); + if ($secure || $tunnel) { + foreach ($this->request->getConfig() as $name => $value) { + if ('ssl_' == substr($name, 0, 4) && null !== $value) { + if ('ssl_verify_host' == $name) { + if ($value) { + $options['CN_match'] = $reqHost; + } + } else { + $options[substr($name, 4)] = $value; + } + } + } + ksort($options); + } + + // Changing SSL context options after connection is established does *not* + // work, we need a new connection if options change + $remote = $host . ':' . $port; + $socketKey = $remote . (($secure && $proxy)? "->{$reqHost}:{$reqPort}": '') . + (empty($options)? '': ':' . serialize($options)); + unset($this->socket); + + // We use persistent connections and have a connected socket? + // Ensure that the socket is still connected, see bug #16149 + if ($keepAlive && !empty(self::$sockets[$socketKey]) && + !feof(self::$sockets[$socketKey]) + ) { + $this->socket =& self::$sockets[$socketKey]; + + } elseif ($secure && $proxy && !$tunnel) { + $this->establishTunnel(); + $this->request->setLastEvent( + 'connect', "ssl://{$reqHost}:{$reqPort} via {$host}:{$port}" + ); + self::$sockets[$socketKey] =& $this->socket; + + } else { + // Set SSL context options if doing HTTPS request or creating a tunnel + $context = stream_context_create(); + foreach ($options as $name => $value) { + if (!stream_context_set_option($context, 'ssl', $name, $value)) { + throw new HTTP_Request2_LogicException( + "Error setting SSL context option '{$name}'" + ); + } + } + $track = @ini_set('track_errors', 1); + $this->socket = @stream_socket_client( + $remote, $errno, $errstr, + $this->request->getConfig('connect_timeout'), + STREAM_CLIENT_CONNECT, $context + ); + if (!$this->socket) { + $e = new HTTP_Request2_ConnectionException( + "Unable to connect to {$remote}. Error: " + . (empty($errstr)? $php_errormsg: $errstr), 0, $errno + ); + } + @ini_set('track_errors', $track); + if (isset($e)) { + throw $e; + } + $this->request->setLastEvent('connect', $remote); + self::$sockets[$socketKey] =& $this->socket; + } + return $keepAlive; + } + + /** + * Establishes a tunnel to a secure remote server via HTTP CONNECT request + * + * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP + * sees that we are connected to a proxy server (duh!) rather than the server + * that presents its certificate. + * + * @link http://tools.ietf.org/html/rfc2817#section-5.2 + * @throws HTTP_Request2_Exception + */ + protected function establishTunnel() + { + $donor = new self; + $connect = new HTTP_Request2( + $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT, + array_merge($this->request->getConfig(), + array('adapter' => $donor)) + ); + $response = $connect->send(); + // Need any successful (2XX) response + if (200 > $response->getStatus() || 300 <= $response->getStatus()) { + throw new HTTP_Request2_ConnectionException( + 'Failed to connect via HTTPS proxy. Proxy response: ' . + $response->getStatus() . ' ' . $response->getReasonPhrase() + ); + } + $this->socket = $donor->socket; + + $modes = array( + STREAM_CRYPTO_METHOD_TLS_CLIENT, + STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + STREAM_CRYPTO_METHOD_SSLv2_CLIENT + ); + + foreach ($modes as $mode) { + if (stream_socket_enable_crypto($this->socket, true, $mode)) { + return; + } + } + throw new HTTP_Request2_ConnectionException( + 'Failed to enable secure connection when connecting through proxy' + ); + } + + /** + * Checks whether current connection may be reused or should be closed + * + * @param boolean whether connection could be persistent + * in the first place + * @param HTTP_Request2_Response response object to check + * @return boolean + */ + protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response) + { + // Do not close socket on successful CONNECT request + if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() && + 200 <= $response->getStatus() && 300 > $response->getStatus() + ) { + return true; + } + + $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding')) + || null !== $response->getHeader('content-length') + // no body possible for such responses, see also request #17031 + || HTTP_Request2::METHOD_HEAD == $this->request->getMethod() + || in_array($response->getStatus(), array(204, 304)); + $persistent = 'keep-alive' == strtolower($response->getHeader('connection')) || + (null === $response->getHeader('connection') && + '1.1' == $response->getVersion()); + return $requestKeepAlive && $lengthKnown && $persistent; + } + + /** + * Disconnects from the remote server + */ + protected function disconnect() + { + if (is_resource($this->socket)) { + fclose($this->socket); + $this->socket = null; + $this->request->setLastEvent('disconnect'); + } + } + + /** + * Handles HTTP redirection + * + * This method will throw an Exception if redirect to a non-HTTP(S) location + * is attempted, also if number of redirects performed already is equal to + * 'max_redirects' configuration parameter. + * + * @param HTTP_Request2 Original request + * @param HTTP_Request2_Response Response containing redirect + * @return HTTP_Request2_Response Response from a new location + * @throws HTTP_Request2_Exception + */ + protected function handleRedirect(HTTP_Request2 $request, + HTTP_Request2_Response $response) + { + if (is_null($this->redirectCountdown)) { + $this->redirectCountdown = $request->getConfig('max_redirects'); + } + if (0 == $this->redirectCountdown) { + $this->redirectCountdown = null; + // Copying cURL behaviour + throw new HTTP_Request2_MessageException ( + 'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed', + HTTP_Request2_Exception::TOO_MANY_REDIRECTS + ); + } + $redirectUrl = new Net_URL2( + $response->getHeader('location'), + array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets')) + ); + // refuse non-HTTP redirect + if ($redirectUrl->isAbsolute() + && !in_array($redirectUrl->getScheme(), array('http', 'https')) + ) { + $this->redirectCountdown = null; + throw new HTTP_Request2_MessageException( + 'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(), + HTTP_Request2_Exception::NON_HTTP_REDIRECT + ); + } + // Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30), + // but in practice it is often not + if (!$redirectUrl->isAbsolute()) { + $redirectUrl = $request->getUrl()->resolve($redirectUrl); + } + $redirect = clone $request; + $redirect->setUrl($redirectUrl); + if (303 == $response->getStatus() || (!$request->getConfig('strict_redirects') + && in_array($response->getStatus(), array(301, 302))) + ) { + $redirect->setMethod(HTTP_Request2::METHOD_GET); + $redirect->setBody(''); + } + + if (0 < $this->redirectCountdown) { + $this->redirectCountdown--; + } + return $this->sendRequest($redirect); + } + + /** + * Checks whether another request should be performed with server digest auth + * + * Several conditions should be satisfied for it to return true: + * - response status should be 401 + * - auth credentials should be set in the request object + * - response should contain WWW-Authenticate header with digest challenge + * - there is either no challenge stored for this URL or new challenge + * contains stale=true parameter (in other case we probably just failed + * due to invalid username / password) + * + * The method stores challenge values in $challenges static property + * + * @param HTTP_Request2_Response response to check + * @return boolean whether another request should be performed + * @throws HTTP_Request2_Exception in case of unsupported challenge parameters + */ + protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response) + { + // no sense repeating a request if we don't have credentials + if (401 != $response->getStatus() || !$this->request->getAuth()) { + return false; + } + if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) { + return false; + } + + $url = $this->request->getUrl(); + $scheme = $url->getScheme(); + $host = $scheme . '://' . $url->getHost(); + if ($port = $url->getPort()) { + if ((0 == strcasecmp($scheme, 'http') && 80 != $port) || + (0 == strcasecmp($scheme, 'https') && 443 != $port) + ) { + $host .= ':' . $port; + } + } + + if (!empty($challenge['domain'])) { + $prefixes = array(); + foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) { + // don't bother with different servers + if ('/' == substr($prefix, 0, 1)) { + $prefixes[] = $host . $prefix; + } + } + } + if (empty($prefixes)) { + $prefixes = array($host . '/'); + } + + $ret = true; + foreach ($prefixes as $prefix) { + if (!empty(self::$challenges[$prefix]) && + (empty($challenge['stale']) || strcasecmp('true', $challenge['stale'])) + ) { + // probably credentials are invalid + $ret = false; + } + self::$challenges[$prefix] =& $challenge; + } + return $ret; + } + + /** + * Checks whether another request should be performed with proxy digest auth + * + * Several conditions should be satisfied for it to return true: + * - response status should be 407 + * - proxy auth credentials should be set in the request object + * - response should contain Proxy-Authenticate header with digest challenge + * - there is either no challenge stored for this proxy or new challenge + * contains stale=true parameter (in other case we probably just failed + * due to invalid username / password) + * + * The method stores challenge values in $challenges static property + * + * @param HTTP_Request2_Response response to check + * @return boolean whether another request should be performed + * @throws HTTP_Request2_Exception in case of unsupported challenge parameters + */ + protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response) + { + if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) { + return false; + } + if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) { + return false; + } + + $key = 'proxy://' . $this->request->getConfig('proxy_host') . + ':' . $this->request->getConfig('proxy_port'); + + if (!empty(self::$challenges[$key]) && + (empty($challenge['stale']) || strcasecmp('true', $challenge['stale'])) + ) { + $ret = false; + } else { + $ret = true; + } + self::$challenges[$key] = $challenge; + return $ret; + } + + /** + * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value + * + * There is a problem with implementation of RFC 2617: several of the parameters + * are defined as quoted-string there and thus may contain backslash escaped + * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as + * just value of quoted-string X without surrounding quotes, it doesn't speak + * about removing backslash escaping. + * + * Now realm parameter is user-defined and human-readable, strange things + * happen when it contains quotes: + * - Apache allows quotes in realm, but apparently uses realm value without + * backslashes for digest computation + * - Squid allows (manually escaped) quotes there, but it is impossible to + * authorize with either escaped or unescaped quotes used in digest, + * probably it can't parse the response (?) + * - Both IE and Firefox display realm value with backslashes in + * the password popup and apparently use the same value for digest + * + * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in + * quoted-string handling, unfortunately that means failure to authorize + * sometimes + * + * @param string value of WWW-Authenticate or Proxy-Authenticate header + * @return mixed associative array with challenge parameters, false if + * no challenge is present in header value + * @throws HTTP_Request2_NotImplementedException in case of unsupported challenge parameters + */ + protected function parseDigestChallenge($headerValue) + { + $authParam = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' . + self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')'; + $challenge = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!"; + if (!preg_match($challenge, $headerValue, $matches)) { + return false; + } + + preg_match_all('!' . $authParam . '!', $matches[0], $params); + $paramsAry = array(); + $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale', + 'algorithm', 'qop'); + for ($i = 0; $i < count($params[0]); $i++) { + // section 3.2.1: Any unrecognized directive MUST be ignored. + if (in_array($params[1][$i], $knownParams)) { + if ('"' == substr($params[2][$i], 0, 1)) { + $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1); + } else { + $paramsAry[$params[1][$i]] = $params[2][$i]; + } + } + } + // we only support qop=auth + if (!empty($paramsAry['qop']) && + !in_array('auth', array_map('trim', explode(',', $paramsAry['qop']))) + ) { + throw new HTTP_Request2_NotImplementedException( + "Only 'auth' qop is currently supported in digest authentication, " . + "server requested '{$paramsAry['qop']}'" + ); + } + // we only support algorithm=MD5 + if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) { + throw new HTTP_Request2_NotImplementedException( + "Only 'MD5' algorithm is currently supported in digest authentication, " . + "server requested '{$paramsAry['algorithm']}'" + ); + } + + return $paramsAry; + } + + /** + * Parses [Proxy-]Authentication-Info header value and updates challenge + * + * @param array challenge to update + * @param string value of [Proxy-]Authentication-Info header + * @todo validate server rspauth response + */ + protected function updateChallenge(&$challenge, $headerValue) + { + $authParam = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' . + self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!'; + $paramsAry = array(); + + preg_match_all($authParam, $headerValue, $params); + for ($i = 0; $i < count($params[0]); $i++) { + if ('"' == substr($params[2][$i], 0, 1)) { + $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1); + } else { + $paramsAry[$params[1][$i]] = $params[2][$i]; + } + } + // for now, just update the nonce value + if (!empty($paramsAry['nextnonce'])) { + $challenge['nonce'] = $paramsAry['nextnonce']; + $challenge['nc'] = 1; + } + } + + /** + * Creates a value for [Proxy-]Authorization header when using digest authentication + * + * @param string user name + * @param string password + * @param string request URL + * @param array digest challenge parameters + * @return string value of [Proxy-]Authorization request header + * @link http://tools.ietf.org/html/rfc2617#section-3.2.2 + */ + protected function createDigestResponse($user, $password, $url, &$challenge) + { + if (false !== ($q = strpos($url, '?')) && + $this->request->getConfig('digest_compat_ie') + ) { + $url = substr($url, 0, $q); + } + + $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password); + $a2 = md5($this->request->getMethod() . ':' . $url); + + if (empty($challenge['qop'])) { + $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2); + } else { + $challenge['cnonce'] = 'Req2.' . rand(); + if (empty($challenge['nc'])) { + $challenge['nc'] = 1; + } + $nc = sprintf('%08x', $challenge['nc']++); + $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' . + $challenge['cnonce'] . ':auth:' . $a2); + } + return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' . + 'realm="' . $challenge['realm'] . '", ' . + 'nonce="' . $challenge['nonce'] . '", ' . + 'uri="' . $url . '", ' . + 'response="' . $digest . '"' . + (!empty($challenge['opaque'])? + ', opaque="' . $challenge['opaque'] . '"': + '') . + (!empty($challenge['qop'])? + ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"': + ''); + } + + /** + * Adds 'Authorization' header (if needed) to request headers array + * + * @param array request headers + * @param string request host (needed for digest authentication) + * @param string request URL (needed for digest authentication) + * @throws HTTP_Request2_NotImplementedException + */ + protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl) + { + if (!($auth = $this->request->getAuth())) { + return; + } + switch ($auth['scheme']) { + case HTTP_Request2::AUTH_BASIC: + $headers['authorization'] = + 'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']); + break; + + case HTTP_Request2::AUTH_DIGEST: + unset($this->serverChallenge); + $fullUrl = ('/' == $requestUrl[0])? + $this->request->getUrl()->getScheme() . '://' . + $requestHost . $requestUrl: + $requestUrl; + foreach (array_keys(self::$challenges) as $key) { + if ($key == substr($fullUrl, 0, strlen($key))) { + $headers['authorization'] = $this->createDigestResponse( + $auth['user'], $auth['password'], + $requestUrl, self::$challenges[$key] + ); + $this->serverChallenge =& self::$challenges[$key]; + break; + } + } + break; + + default: + throw new HTTP_Request2_NotImplementedException( + "Unknown HTTP authentication scheme '{$auth['scheme']}'" + ); + } + } + + /** + * Adds 'Proxy-Authorization' header (if needed) to request headers array + * + * @param array request headers + * @param string request URL (needed for digest authentication) + * @throws HTTP_Request2_NotImplementedException + */ + protected function addProxyAuthorizationHeader(&$headers, $requestUrl) + { + if (!$this->request->getConfig('proxy_host') || + !($user = $this->request->getConfig('proxy_user')) || + (0 == strcasecmp('https', $this->request->getUrl()->getScheme()) && + HTTP_Request2::METHOD_CONNECT != $this->request->getMethod()) + ) { + return; + } + + $password = $this->request->getConfig('proxy_password'); + switch ($this->request->getConfig('proxy_auth_scheme')) { + case HTTP_Request2::AUTH_BASIC: + $headers['proxy-authorization'] = + 'Basic ' . base64_encode($user . ':' . $password); + break; + + case HTTP_Request2::AUTH_DIGEST: + unset($this->proxyChallenge); + $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') . + ':' . $this->request->getConfig('proxy_port'); + if (!empty(self::$challenges[$proxyUrl])) { + $headers['proxy-authorization'] = $this->createDigestResponse( + $user, $password, + $requestUrl, self::$challenges[$proxyUrl] + ); + $this->proxyChallenge =& self::$challenges[$proxyUrl]; + } + break; + + default: + throw new HTTP_Request2_NotImplementedException( + "Unknown HTTP authentication scheme '" . + $this->request->getConfig('proxy_auth_scheme') . "'" + ); + } + } + + + /** + * Creates the string with the Request-Line and request headers + * + * @return string + * @throws HTTP_Request2_Exception + */ + protected function prepareHeaders() + { + $headers = $this->request->getHeaders(); + $url = $this->request->getUrl(); + $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod(); + $host = $url->getHost(); + + $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80; + if (($port = $url->getPort()) && $port != $defaultPort || $connect) { + $host .= ':' . (empty($port)? $defaultPort: $port); + } + // Do not overwrite explicitly set 'Host' header, see bug #16146 + if (!isset($headers['host'])) { + $headers['host'] = $host; + } + + if ($connect) { + $requestUrl = $host; + + } else { + if (!$this->request->getConfig('proxy_host') || + 0 == strcasecmp($url->getScheme(), 'https') + ) { + $requestUrl = ''; + } else { + $requestUrl = $url->getScheme() . '://' . $host; + } + $path = $url->getPath(); + $query = $url->getQuery(); + $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query); + } + + if ('1.1' == $this->request->getConfig('protocol_version') && + extension_loaded('zlib') && !isset($headers['accept-encoding']) + ) { + $headers['accept-encoding'] = 'gzip, deflate'; + } + if (($jar = $this->request->getCookieJar()) + && ($cookies = $jar->getMatching($this->request->getUrl(), true)) + ) { + $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies; + } + + $this->addAuthorizationHeader($headers, $host, $requestUrl); + $this->addProxyAuthorizationHeader($headers, $requestUrl); + $this->calculateRequestLength($headers); + + $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' . + $this->request->getConfig('protocol_version') . "\r\n"; + foreach ($headers as $name => $value) { + $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); + $headersStr .= $canonicalName . ': ' . $value . "\r\n"; + } + return $headersStr . "\r\n"; + } + + /** + * Sends the request body + * + * @throws HTTP_Request2_MessageException + */ + protected function writeBody() + { + if (in_array($this->request->getMethod(), self::$bodyDisallowed) || + 0 == $this->contentLength + ) { + return; + } + + $position = 0; + $bufferSize = $this->request->getConfig('buffer_size'); + while ($position < $this->contentLength) { + if (is_string($this->requestBody)) { + $str = substr($this->requestBody, $position, $bufferSize); + } elseif (is_resource($this->requestBody)) { + $str = fread($this->requestBody, $bufferSize); + } else { + $str = $this->requestBody->read($bufferSize); + } + if (false === @fwrite($this->socket, $str, strlen($str))) { + throw new HTTP_Request2_MessageException('Error writing request'); + } + // Provide the length of written string to the observer, request #7630 + $this->request->setLastEvent('sentBodyPart', strlen($str)); + $position += strlen($str); + } + $this->request->setLastEvent('sentBody', $this->contentLength); + } + + /** + * Reads the remote server's response + * + * @return HTTP_Request2_Response + * @throws HTTP_Request2_Exception + */ + protected function readResponse() + { + $bufferSize = $this->request->getConfig('buffer_size'); + + do { + $response = new HTTP_Request2_Response( + $this->readLine($bufferSize), true, $this->request->getUrl() + ); + do { + $headerLine = $this->readLine($bufferSize); + $response->parseHeaderLine($headerLine); + } while ('' != $headerLine); + } while (in_array($response->getStatus(), array(100, 101))); + + $this->request->setLastEvent('receivedHeaders', $response); + + // No body possible in such responses + if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod() || + (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() && + 200 <= $response->getStatus() && 300 > $response->getStatus()) || + in_array($response->getStatus(), array(204, 304)) + ) { + return $response; + } + + $chunked = 'chunked' == $response->getHeader('transfer-encoding'); + $length = $response->getHeader('content-length'); + $hasBody = false; + if ($chunked || null === $length || 0 < intval($length)) { + // RFC 2616, section 4.4: + // 3. ... If a message is received with both a + // Transfer-Encoding header field and a Content-Length header field, + // the latter MUST be ignored. + $toRead = ($chunked || null === $length)? null: $length; + $this->chunkLength = 0; + + while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) { + if ($chunked) { + $data = $this->readChunked($bufferSize); + } elseif (is_null($toRead)) { + $data = $this->fread($bufferSize); + } else { + $data = $this->fread(min($toRead, $bufferSize)); + $toRead -= strlen($data); + } + if ('' == $data && (!$this->chunkLength || feof($this->socket))) { + break; + } + + $hasBody = true; + if ($this->request->getConfig('store_body')) { + $response->appendBody($data); + } + if (!in_array($response->getHeader('content-encoding'), array('identity', null))) { + $this->request->setLastEvent('receivedEncodedBodyPart', $data); + } else { + $this->request->setLastEvent('receivedBodyPart', $data); + } + } + } + + if ($hasBody) { + $this->request->setLastEvent('receivedBody', $response); + } + return $response; + } + + /** + * Reads until either the end of the socket or a newline, whichever comes first + * + * Strips the trailing newline from the returned data, handles global + * request timeout. Method idea borrowed from Net_Socket PEAR package. + * + * @param int buffer size to use for reading + * @return Available data up to the newline (not including newline) + * @throws HTTP_Request2_MessageException In case of timeout + */ + protected function readLine($bufferSize) + { + $line = ''; + while (!feof($this->socket)) { + if ($this->deadline) { + stream_set_timeout($this->socket, max($this->deadline - time(), 1)); + } + $line .= @fgets($this->socket, $bufferSize); + $info = stream_get_meta_data($this->socket); + if ($info['timed_out'] || $this->deadline && time() > $this->deadline) { + $reason = $this->deadline + ? 'after ' . $this->request->getConfig('timeout') . ' second(s)' + : 'due to default_socket_timeout php.ini setting'; + throw new HTTP_Request2_MessageException( + "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT + ); + } + if (substr($line, -1) == "\n") { + return rtrim($line, "\r\n"); + } + } + return $line; + } + + /** + * Wrapper around fread(), handles global request timeout + * + * @param int Reads up to this number of bytes + * @return Data read from socket + * @throws HTTP_Request2_MessageException In case of timeout + */ + protected function fread($length) + { + if ($this->deadline) { + stream_set_timeout($this->socket, max($this->deadline - time(), 1)); + } + $data = fread($this->socket, $length); + $info = stream_get_meta_data($this->socket); + if ($info['timed_out'] || $this->deadline && time() > $this->deadline) { + $reason = $this->deadline + ? 'after ' . $this->request->getConfig('timeout') . ' second(s)' + : 'due to default_socket_timeout php.ini setting'; + throw new HTTP_Request2_MessageException( + "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT + ); + } + return $data; + } + + /** + * Reads a part of response body encoded with chunked Transfer-Encoding + * + * @param int buffer size to use for reading + * @return string + * @throws HTTP_Request2_MessageException + */ + protected function readChunked($bufferSize) + { + // at start of the next chunk? + if (0 == $this->chunkLength) { + $line = $this->readLine($bufferSize); + if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) { + throw new HTTP_Request2_MessageException( + "Cannot decode chunked response, invalid chunk length '{$line}'", + HTTP_Request2_Exception::DECODE_ERROR + ); + } else { + $this->chunkLength = hexdec($matches[1]); + // Chunk with zero length indicates the end + if (0 == $this->chunkLength) { + $this->readLine($bufferSize); + return ''; + } + } + } + $data = $this->fread(min($this->chunkLength, $bufferSize)); + $this->chunkLength -= strlen($data); + if (0 == $this->chunkLength) { + $this->readLine($bufferSize); // Trailing CRLF + } + return $data; + } +} + +?> \ No newline at end of file diff --git a/lib/php/HTTP/Request2/CookieJar.php b/lib/php/HTTP/Request2/CookieJar.php new file mode 100644 index 0000000000000000000000000000000000000000..3d5cf97806868ff2d3a46ca30594ebb035ef4aac --- /dev/null +++ b/lib/php/HTTP/Request2/CookieJar.php @@ -0,0 +1,499 @@ +<?php +/** + * Stores cookies and passes them between HTTP requests + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: CookieJar.php 308629 2011-02-24 17:34:24Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Class representing a HTTP request message */ +require_once 'HTTP/Request2.php'; + +/** + * Stores cookies and passes them between HTTP requests + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_CookieJar implements Serializable +{ + /** + * Array of stored cookies + * + * The array is indexed by domain, path and cookie name + * .example.com + * / + * some_cookie => cookie data + * /subdir + * other_cookie => cookie data + * .example.org + * ... + * + * @var array + */ + protected $cookies = array(); + + /** + * Whether session cookies should be serialized when serializing the jar + * @var bool + */ + protected $serializeSession = false; + + /** + * Whether Public Suffix List should be used for domain matching + * @var bool + */ + protected $useList = true; + + /** + * Array with Public Suffix List data + * @var array + * @link http://publicsuffix.org/ + */ + protected static $psl = array(); + + /** + * Class constructor, sets various options + * + * @param bool Controls serializing session cookies, see {@link serializeSessionCookies()} + * @param bool Controls using Public Suffix List, see {@link usePublicSuffixList()} + */ + public function __construct($serializeSessionCookies = false, $usePublicSuffixList = true) + { + $this->serializeSessionCookies($serializeSessionCookies); + $this->usePublicSuffixList($usePublicSuffixList); + } + + /** + * Returns current time formatted in ISO-8601 at UTC timezone + * + * @return string + */ + protected function now() + { + $dt = new DateTime(); + $dt->setTimezone(new DateTimeZone('UTC')); + return $dt->format(DateTime::ISO8601); + } + + /** + * Checks cookie array for correctness, possibly updating its 'domain', 'path' and 'expires' fields + * + * The checks are as follows: + * - cookie array should contain 'name' and 'value' fields; + * - name and value should not contain disallowed symbols; + * - 'expires' should be either empty parseable by DateTime; + * - 'domain' and 'path' should be either not empty or an URL where + * cookie was set should be provided. + * - if $setter is provided, then document at that URL should be allowed + * to set a cookie for that 'domain'. If $setter is not provided, + * then no domain checks will be made. + * + * 'expires' field will be converted to ISO8601 format from COOKIE format, + * 'domain' and 'path' will be set from setter URL if empty. + * + * @param array cookie data, as returned by {@link HTTP_Request2_Response::getCookies()} + * @param Net_URL2 URL of the document that sent Set-Cookie header + * @return array Updated cookie array + * @throws HTTP_Request2_LogicException + * @throws HTTP_Request2_MessageException + */ + protected function checkAndUpdateFields(array $cookie, Net_URL2 $setter = null) + { + if ($missing = array_diff(array('name', 'value'), array_keys($cookie))) { + throw new HTTP_Request2_LogicException( + "Cookie array should contain 'name' and 'value' fields", + HTTP_Request2_Exception::MISSING_VALUE + ); + } + if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['name'])) { + throw new HTTP_Request2_LogicException( + "Invalid cookie name: '{$cookie['name']}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['value'])) { + throw new HTTP_Request2_LogicException( + "Invalid cookie value: '{$cookie['value']}'", + HTTP_Request2_Exception::INVALID_ARGUMENT + ); + } + $cookie += array('domain' => '', 'path' => '', 'expires' => null, 'secure' => false); + + // Need ISO-8601 date @ UTC timezone + if (!empty($cookie['expires']) + && !preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+0000$/', $cookie['expires']) + ) { + try { + $dt = new DateTime($cookie['expires']); + $dt->setTimezone(new DateTimeZone('UTC')); + $cookie['expires'] = $dt->format(DateTime::ISO8601); + } catch (Exception $e) { + throw new HTTP_Request2_LogicException($e->getMessage()); + } + } + + if (empty($cookie['domain']) || empty($cookie['path'])) { + if (!$setter) { + throw new HTTP_Request2_LogicException( + 'Cookie misses domain and/or path component, cookie setter URL needed', + HTTP_Request2_Exception::MISSING_VALUE + ); + } + if (empty($cookie['domain'])) { + if ($host = $setter->getHost()) { + $cookie['domain'] = $host; + } else { + throw new HTTP_Request2_LogicException( + 'Setter URL does not contain host part, can\'t set cookie domain', + HTTP_Request2_Exception::MISSING_VALUE + ); + } + } + if (empty($cookie['path'])) { + $path = $setter->getPath(); + $cookie['path'] = empty($path)? '/': substr($path, 0, strrpos($path, '/') + 1); + } + } + + if ($setter && !$this->domainMatch($setter->getHost(), $cookie['domain'])) { + throw new HTTP_Request2_MessageException( + "Domain " . $setter->getHost() . " cannot set cookies for " + . $cookie['domain'] + ); + } + + return $cookie; + } + + /** + * Stores a cookie in the jar + * + * @param array cookie data, as returned by {@link HTTP_Request2_Response::getCookies()} + * @param Net_URL2 URL of the document that sent Set-Cookie header + * @throws HTTP_Request2_Exception + */ + public function store(array $cookie, Net_URL2 $setter = null) + { + $cookie = $this->checkAndUpdateFields($cookie, $setter); + + if (strlen($cookie['value']) + && (is_null($cookie['expires']) || $cookie['expires'] > $this->now()) + ) { + if (!isset($this->cookies[$cookie['domain']])) { + $this->cookies[$cookie['domain']] = array(); + } + if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) { + $this->cookies[$cookie['domain']][$cookie['path']] = array(); + } + $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie; + + } elseif (isset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']])) { + unset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']]); + } + } + + /** + * Adds cookies set in HTTP response to the jar + * + * @param HTTP_Request2_Response response + * @param Net_URL2 original request URL, needed for setting + * default domain/path + */ + public function addCookiesFromResponse(HTTP_Request2_Response $response, Net_URL2 $setter) + { + foreach ($response->getCookies() as $cookie) { + $this->store($cookie, $setter); + } + } + + /** + * Returns all cookies matching a given request URL + * + * The following checks are made: + * - cookie domain should match request host + * - cookie path should be a prefix for request path + * - 'secure' cookies will only be sent for HTTPS requests + * + * @param Net_URL2 + * @param bool Whether to return cookies as string for "Cookie: " header + * @return array + */ + public function getMatching(Net_URL2 $url, $asString = false) + { + $host = $url->getHost(); + $path = $url->getPath(); + $secure = 0 == strcasecmp($url->getScheme(), 'https'); + + $matched = $ret = array(); + foreach (array_keys($this->cookies) as $domain) { + if ($this->domainMatch($host, $domain)) { + foreach (array_keys($this->cookies[$domain]) as $cPath) { + if (0 === strpos($path, $cPath)) { + foreach ($this->cookies[$domain][$cPath] as $name => $cookie) { + if (!$cookie['secure'] || $secure) { + $matched[$name][strlen($cookie['path'])] = $cookie; + } + } + } + } + } + } + foreach ($matched as $cookies) { + krsort($cookies); + $ret = array_merge($ret, $cookies); + } + if (!$asString) { + return $ret; + } else { + $str = ''; + foreach ($ret as $c) { + $str .= (empty($str)? '': '; ') . $c['name'] . '=' . $c['value']; + } + return $str; + } + } + + /** + * Returns all cookies stored in a jar + * + * @return array + */ + public function getAll() + { + $cookies = array(); + foreach (array_keys($this->cookies) as $domain) { + foreach (array_keys($this->cookies[$domain]) as $path) { + foreach ($this->cookies[$domain][$path] as $name => $cookie) { + $cookies[] = $cookie; + } + } + } + return $cookies; + } + + /** + * Sets whether session cookies should be serialized when serializing the jar + * + * @param boolean + */ + public function serializeSessionCookies($serialize) + { + $this->serializeSession = (bool)$serialize; + } + + /** + * Sets whether Public Suffix List should be used for restricting cookie-setting + * + * Without PSL {@link domainMatch()} will only prevent setting cookies for + * top-level domains like '.com' or '.org'. However, it will not prevent + * setting a cookie for '.co.uk' even though only third-level registrations + * are possible in .uk domain. + * + * With the List it is possible to find the highest level at which a domain + * may be registered for a particular top-level domain and consequently + * prevent cookies set for '.co.uk' or '.msk.ru'. The same list is used by + * Firefox, Chrome and Opera browsers to restrict cookie setting. + * + * Note that PSL is licensed differently to HTTP_Request2 package (refer to + * the license information in public-suffix-list.php), so you can disable + * its use if this is an issue for you. + * + * @param boolean + * @link http://publicsuffix.org/learn/ + */ + public function usePublicSuffixList($useList) + { + $this->useList = (bool)$useList; + } + + /** + * Returns string representation of object + * + * @return string + * @see Serializable::serialize() + */ + public function serialize() + { + $cookies = $this->getAll(); + if (!$this->serializeSession) { + for ($i = count($cookies) - 1; $i >= 0; $i--) { + if (empty($cookies[$i]['expires'])) { + unset($cookies[$i]); + } + } + } + return serialize(array( + 'cookies' => $cookies, + 'serializeSession' => $this->serializeSession, + 'useList' => $this->useList + )); + } + + /** + * Constructs the object from serialized string + * + * @param string string representation + * @see Serializable::unserialize() + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $now = $this->now(); + $this->serializeSessionCookies($data['serializeSession']); + $this->usePublicSuffixList($data['useList']); + foreach ($data['cookies'] as $cookie) { + if (!empty($cookie['expires']) && $cookie['expires'] <= $now) { + continue; + } + if (!isset($this->cookies[$cookie['domain']])) { + $this->cookies[$cookie['domain']] = array(); + } + if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) { + $this->cookies[$cookie['domain']][$cookie['path']] = array(); + } + $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie; + } + } + + /** + * Checks whether a cookie domain matches a request host. + * + * The method is used by {@link store()} to check for whether a document + * at given URL can set a cookie with a given domain attribute and by + * {@link getMatching()} to find cookies matching the request URL. + * + * @param string request host + * @param string cookie domain + * @return bool match success + */ + public function domainMatch($requestHost, $cookieDomain) + { + if ($requestHost == $cookieDomain) { + return true; + } + // IP address, we require exact match + if (preg_match('/^(?:\d{1,3}\.){3}\d{1,3}$/', $requestHost)) { + return false; + } + if ('.' != $cookieDomain[0]) { + $cookieDomain = '.' . $cookieDomain; + } + // prevents setting cookies for '.com' and similar domains + if (!$this->useList && substr_count($cookieDomain, '.') < 2 + || $this->useList && !self::getRegisteredDomain($cookieDomain) + ) { + return false; + } + return substr('.' . $requestHost, -strlen($cookieDomain)) == $cookieDomain; + } + + /** + * Removes subdomains to get the registered domain (the first after top-level) + * + * The method will check Public Suffix List to find out where top-level + * domain ends and registered domain starts. It will remove domain parts + * to the left of registered one. + * + * @param string domain name + * @return string|bool registered domain, will return false if $domain is + * either invalid or a TLD itself + */ + public static function getRegisteredDomain($domain) + { + $domainParts = explode('.', ltrim($domain, '.')); + + // load the list if needed + if (empty(self::$psl)) { + $path = '/Library/WebServer/Documents/workspace/UCOMM_Webforms/lib/data' . DIRECTORY_SEPARATOR . 'HTTP_Request2'; + if (0 === strpos($path, '@' . 'data_dir@')) { + $path = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' + . DIRECTORY_SEPARATOR . 'data'); + } + self::$psl = include_once $path . DIRECTORY_SEPARATOR . 'public-suffix-list.php'; + } + + if (!($result = self::checkDomainsList($domainParts, self::$psl))) { + // known TLD, invalid domain name + return false; + } + + // unknown TLD + if (!strpos($result, '.')) { + // fallback to checking that domain "has at least two dots" + if (2 > ($count = count($domainParts))) { + return false; + } + return $domainParts[$count - 2] . '.' . $domainParts[$count - 1]; + } + return $result; + } + + /** + * Recursive helper method for {@link getRegisteredDomain()} + * + * @param array remaining domain parts + * @param mixed node in {@link HTTP_Request2_CookieJar::$psl} to check + * @return string|null concatenated domain parts, null in case of error + */ + protected static function checkDomainsList(array $domainParts, $listNode) + { + $sub = array_pop($domainParts); + $result = null; + + if (!is_array($listNode) || is_null($sub) + || array_key_exists('!' . $sub, $listNode) + ) { + return $sub; + + } elseif (array_key_exists($sub, $listNode)) { + $result = self::checkDomainsList($domainParts, $listNode[$sub]); + + } elseif (array_key_exists('*', $listNode)) { + $result = self::checkDomainsList($domainParts, $listNode['*']); + + } else { + return $sub; + } + + return (strlen($result) > 0) ? ($result . '.' . $sub) : null; + } +} +?> \ No newline at end of file diff --git a/lib/php/HTTP/Request2/Exception.php b/lib/php/HTTP/Request2/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..1bae2f1f33fabcfe74a07d7465343762822a1752 --- /dev/null +++ b/lib/php/HTTP/Request2/Exception.php @@ -0,0 +1,160 @@ +<?php +/** + * Exception classes for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Exception.php 308629 2011-02-24 17:34:24Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Base class for exceptions in PEAR + */ +require_once 'PEAR/Exception.php'; + +/** + * Base exception class for HTTP_Request2 package + * + * @category HTTP + * @package HTTP_Request2 + * @version Release: 2.0.0beta3 + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=132 + */ +class HTTP_Request2_Exception extends PEAR_Exception +{ + /** An invalid argument was passed to a method */ + const INVALID_ARGUMENT = 1; + /** Some required value was not available */ + const MISSING_VALUE = 2; + /** Request cannot be processed due to errors in PHP configuration */ + const MISCONFIGURATION = 3; + /** Error reading the local file */ + const READ_ERROR = 4; + + /** Server returned a response that does not conform to HTTP protocol */ + const MALFORMED_RESPONSE = 10; + /** Failure decoding Content-Encoding or Transfer-Encoding of response */ + const DECODE_ERROR = 20; + /** Operation timed out */ + const TIMEOUT = 30; + /** Number of redirects exceeded 'max_redirects' configuration parameter */ + const TOO_MANY_REDIRECTS = 40; + /** Redirect to a protocol other than http(s):// */ + const NON_HTTP_REDIRECT = 50; + + /** + * Native error code + * @var int + */ + private $_nativeCode; + + /** + * Constructor, can set package error code and native error code + * + * @param string exception message + * @param int package error code, one of class constants + * @param int error code from underlying PHP extension + */ + public function __construct($message = null, $code = null, $nativeCode = null) + { + parent::__construct($message, $code); + $this->_nativeCode = $nativeCode; + } + + /** + * Returns error code produced by underlying PHP extension + * + * For Socket Adapter this may contain error number returned by + * stream_socket_client(), for Curl Adapter this will contain error number + * returned by curl_errno() + * + * @return integer + */ + public function getNativeCode() + { + return $this->_nativeCode; + } +} + +/** + * Exception thrown in case of missing features + * + * @category HTTP + * @package HTTP_Request2 + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception {} + +/** + * Exception that represents error in the program logic + * + * This exception usually implies a programmer's error, like passing invalid + * data to methods or trying to use PHP extensions that weren't installed or + * enabled. Usually exceptions of this kind will be thrown before request even + * starts. + * + * The exception will usually contain a package error code. + * + * @category HTTP + * @package HTTP_Request2 + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_LogicException extends HTTP_Request2_Exception {} + +/** + * Exception thrown when connection to a web or proxy server fails + * + * The exception will not contain a package error code, but will contain + * native error code, as returned by stream_socket_client() or curl_errno(). + * + * @category HTTP + * @package HTTP_Request2 + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception {} + +/** + * Exception thrown when sending or receiving HTTP message fails + * + * The exception may contain both package error code and native error code. + * + * @category HTTP + * @package HTTP_Request2 + * @version Release: 2.0.0beta3 + */ +class HTTP_Request2_MessageException extends HTTP_Request2_Exception {} +?> \ No newline at end of file diff --git a/lib/php/HTTP/Request2/MultipartBody.php b/lib/php/HTTP/Request2/MultipartBody.php new file mode 100644 index 0000000000000000000000000000000000000000..57bc5d6b59bf03affdf72183ee92aa1af3c33d71 --- /dev/null +++ b/lib/php/HTTP/Request2/MultipartBody.php @@ -0,0 +1,274 @@ +<?php +/** + * Helper class for building multipart/form-data request body + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: MultipartBody.php 308322 2011-02-14 13:58:03Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class for building multipart/form-data request body + * + * The class helps to reduce memory consumption by streaming large file uploads + * from disk, it also allows monitoring of upload progress (see request #7630) + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + * @link http://tools.ietf.org/html/rfc1867 + */ +class HTTP_Request2_MultipartBody +{ + /** + * MIME boundary + * @var string + */ + private $_boundary; + + /** + * Form parameters added via {@link HTTP_Request2::addPostParameter()} + * @var array + */ + private $_params = array(); + + /** + * File uploads added via {@link HTTP_Request2::addUpload()} + * @var array + */ + private $_uploads = array(); + + /** + * Header for parts with parameters + * @var string + */ + private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n"; + + /** + * Header for parts with uploads + * @var string + */ + private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"; + + /** + * Current position in parameter and upload arrays + * + * First number is index of "current" part, second number is position within + * "current" part + * + * @var array + */ + private $_pos = array(0, 0); + + + /** + * Constructor. Sets the arrays with POST data. + * + * @param array values of form fields set via {@link HTTP_Request2::addPostParameter()} + * @param array file uploads set via {@link HTTP_Request2::addUpload()} + * @param bool whether to append brackets to array variable names + */ + public function __construct(array $params, array $uploads, $useBrackets = true) + { + $this->_params = self::_flattenArray('', $params, $useBrackets); + foreach ($uploads as $fieldName => $f) { + if (!is_array($f['fp'])) { + $this->_uploads[] = $f + array('name' => $fieldName); + } else { + for ($i = 0; $i < count($f['fp']); $i++) { + $upload = array( + 'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName) + ); + foreach (array('fp', 'filename', 'size', 'type') as $key) { + $upload[$key] = $f[$key][$i]; + } + $this->_uploads[] = $upload; + } + } + } + } + + /** + * Returns the length of the body to use in Content-Length header + * + * @return integer + */ + public function getLength() + { + $boundaryLength = strlen($this->getBoundary()); + $headerParamLength = strlen($this->_headerParam) - 4 + $boundaryLength; + $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength; + $length = $boundaryLength + 6; + foreach ($this->_params as $p) { + $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2; + } + foreach ($this->_uploads as $u) { + $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) + + strlen($u['filename']) + $u['size'] + 2; + } + return $length; + } + + /** + * Returns the boundary to use in Content-Type header + * + * @return string + */ + public function getBoundary() + { + if (empty($this->_boundary)) { + $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime()); + } + return $this->_boundary; + } + + /** + * Returns next chunk of request body + * + * @param integer Amount of bytes to read + * @return string Up to $length bytes of data, empty string if at end + */ + public function read($length) + { + $ret = ''; + $boundary = $this->getBoundary(); + $paramCount = count($this->_params); + $uploadCount = count($this->_uploads); + while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) { + $oldLength = $length; + if ($this->_pos[0] < $paramCount) { + $param = sprintf($this->_headerParam, $boundary, + $this->_params[$this->_pos[0]][0]) . + $this->_params[$this->_pos[0]][1] . "\r\n"; + $ret .= substr($param, $this->_pos[1], $length); + $length -= min(strlen($param) - $this->_pos[1], $length); + + } elseif ($this->_pos[0] < $paramCount + $uploadCount) { + $pos = $this->_pos[0] - $paramCount; + $header = sprintf($this->_headerUpload, $boundary, + $this->_uploads[$pos]['name'], + $this->_uploads[$pos]['filename'], + $this->_uploads[$pos]['type']); + if ($this->_pos[1] < strlen($header)) { + $ret .= substr($header, $this->_pos[1], $length); + $length -= min(strlen($header) - $this->_pos[1], $length); + } + $filePos = max(0, $this->_pos[1] - strlen($header)); + if ($length > 0 && $filePos < $this->_uploads[$pos]['size']) { + $ret .= fread($this->_uploads[$pos]['fp'], $length); + $length -= min($length, $this->_uploads[$pos]['size'] - $filePos); + } + if ($length > 0) { + $start = $this->_pos[1] + ($oldLength - $length) - + strlen($header) - $this->_uploads[$pos]['size']; + $ret .= substr("\r\n", $start, $length); + $length -= min(2 - $start, $length); + } + + } else { + $closing = '--' . $boundary . "--\r\n"; + $ret .= substr($closing, $this->_pos[1], $length); + $length -= min(strlen($closing) - $this->_pos[1], $length); + } + if ($length > 0) { + $this->_pos = array($this->_pos[0] + 1, 0); + } else { + $this->_pos[1] += $oldLength; + } + } + return $ret; + } + + /** + * Sets the current position to the start of the body + * + * This allows reusing the same body in another request + */ + public function rewind() + { + $this->_pos = array(0, 0); + foreach ($this->_uploads as $u) { + rewind($u['fp']); + } + } + + /** + * Returns the body as string + * + * Note that it reads all file uploads into memory so it is a good idea not + * to use this method with large file uploads and rely on read() instead. + * + * @return string + */ + public function __toString() + { + $this->rewind(); + return $this->read($this->getLength()); + } + + + /** + * Helper function to change the (probably multidimensional) associative array + * into the simple one. + * + * @param string name for item + * @param mixed item's values + * @param bool whether to append [] to array variables' names + * @return array array with the following items: array('item name', 'item value'); + */ + private static function _flattenArray($name, $values, $useBrackets) + { + if (!is_array($values)) { + return array(array($name, $values)); + } else { + $ret = array(); + foreach ($values as $k => $v) { + if (empty($name)) { + $newName = $k; + } elseif ($useBrackets) { + $newName = $name . '[' . $k . ']'; + } else { + $newName = $name; + } + $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets)); + } + return $ret; + } + } +} +?> diff --git a/lib/php/HTTP/Request2/Observer/Log.php b/lib/php/HTTP/Request2/Observer/Log.php new file mode 100644 index 0000000000000000000000000000000000000000..237563d569e93a02942a97a29a68b3c5de4f8bdc --- /dev/null +++ b/lib/php/HTTP/Request2/Observer/Log.php @@ -0,0 +1,215 @@ +<?php +/** + * An observer useful for debugging / testing. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author David Jean Louis <izi@php.net> + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Log.php 308680 2011-02-25 17:40:17Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Exception class for HTTP_Request2 package + */ +require_once 'HTTP/Request2/Exception.php'; + +/** + * A debug observer useful for debugging / testing. + * + * This observer logs to a log target data corresponding to the various request + * and response events, it logs by default to php://output but can be configured + * to log to a file or via the PEAR Log package. + * + * A simple example: + * <code> + * require_once 'HTTP/Request2.php'; + * require_once 'HTTP/Request2/Observer/Log.php'; + * + * $request = new HTTP_Request2('http://www.example.com'); + * $observer = new HTTP_Request2_Observer_Log(); + * $request->attach($observer); + * $request->send(); + * </code> + * + * A more complex example with PEAR Log: + * <code> + * require_once 'HTTP/Request2.php'; + * require_once 'HTTP/Request2/Observer/Log.php'; + * require_once 'Log.php'; + * + * $request = new HTTP_Request2('http://www.example.com'); + * // we want to log with PEAR log + * $observer = new HTTP_Request2_Observer_Log(Log::factory('console')); + * + * // we only want to log received headers + * $observer->events = array('receivedHeaders'); + * + * $request->attach($observer); + * $request->send(); + * </code> + * + * @category HTTP + * @package HTTP_Request2 + * @author David Jean Louis <izi@php.net> + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 2.0.0beta3 + * @link http://pear.php.net/package/HTTP_Request2 + */ +class HTTP_Request2_Observer_Log implements SplObserver +{ + // properties {{{ + + /** + * The log target, it can be a a resource or a PEAR Log instance. + * + * @var resource|Log $target + */ + protected $target = null; + + /** + * The events to log. + * + * @var array $events + */ + public $events = array( + 'connect', + 'sentHeaders', + 'sentBody', + 'receivedHeaders', + 'receivedBody', + 'disconnect', + ); + + // }}} + // __construct() {{{ + + /** + * Constructor. + * + * @param mixed $target Can be a file path (default: php://output), a resource, + * or an instance of the PEAR Log class. + * @param array $events Array of events to listen to (default: all events) + * + * @return void + */ + public function __construct($target = 'php://output', array $events = array()) + { + if (!empty($events)) { + $this->events = $events; + } + if (is_resource($target) || $target instanceof Log) { + $this->target = $target; + } elseif (false === ($this->target = @fopen($target, 'ab'))) { + throw new HTTP_Request2_Exception("Unable to open '{$target}'"); + } + } + + // }}} + // update() {{{ + + /** + * Called when the request notifies us of an event. + * + * @param HTTP_Request2 $subject The HTTP_Request2 instance + * + * @return void + */ + public function update(SplSubject $subject) + { + $event = $subject->getLastEvent(); + if (!in_array($event['name'], $this->events)) { + return; + } + + switch ($event['name']) { + case 'connect': + $this->log('* Connected to ' . $event['data']); + break; + case 'sentHeaders': + $headers = explode("\r\n", $event['data']); + array_pop($headers); + foreach ($headers as $header) { + $this->log('> ' . $header); + } + break; + case 'sentBody': + $this->log('> ' . $event['data'] . ' byte(s) sent'); + break; + case 'receivedHeaders': + $this->log(sprintf('< HTTP/%s %s %s', + $event['data']->getVersion(), + $event['data']->getStatus(), + $event['data']->getReasonPhrase())); + $headers = $event['data']->getHeader(); + foreach ($headers as $key => $val) { + $this->log('< ' . $key . ': ' . $val); + } + $this->log('< '); + break; + case 'receivedBody': + $this->log($event['data']->getBody()); + break; + case 'disconnect': + $this->log('* Disconnected'); + break; + } + } + + // }}} + // log() {{{ + + /** + * Logs the given message to the configured target. + * + * @param string $message Message to display + * + * @return void + */ + protected function log($message) + { + if ($this->target instanceof Log) { + $this->target->debug($message); + } elseif (is_resource($this->target)) { + fwrite($this->target, $message . "\r\n"); + } + } + + // }}} +} + +?> \ No newline at end of file diff --git a/lib/php/HTTP/Request2/Response.php b/lib/php/HTTP/Request2/Response.php new file mode 100644 index 0000000000000000000000000000000000000000..35a5ad87c3ddd6e30a87cc80cf97c81ef512fbf4 --- /dev/null +++ b/lib/php/HTTP/Request2/Response.php @@ -0,0 +1,629 @@ +<?php +/** + * Class representing a HTTP response + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Response.php 309921 2011-04-03 16:43:02Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Exception class for HTTP_Request2 package + */ +require_once 'HTTP/Request2/Exception.php'; + +/** + * Class representing a HTTP response + * + * The class is designed to be used in "streaming" scenario, building the + * response as it is being received: + * <code> + * $statusLine = read_status_line(); + * $response = new HTTP_Request2_Response($statusLine); + * do { + * $headerLine = read_header_line(); + * $response->parseHeaderLine($headerLine); + * } while ($headerLine != ''); + * + * while ($chunk = read_body()) { + * $response->appendBody($chunk); + * } + * + * var_dump($response->getHeader(), $response->getCookies(), $response->getBody()); + * </code> + * + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @version Release: 2.0.0beta3 + * @link http://tools.ietf.org/html/rfc2616#section-6 + */ +class HTTP_Request2_Response +{ + /** + * HTTP protocol version (e.g. 1.0, 1.1) + * @var string + */ + protected $version; + + /** + * Status code + * @var integer + * @link http://tools.ietf.org/html/rfc2616#section-6.1.1 + */ + protected $code; + + /** + * Reason phrase + * @var string + * @link http://tools.ietf.org/html/rfc2616#section-6.1.1 + */ + protected $reasonPhrase; + + /** + * Effective URL (may be different from original request URL in case of redirects) + * @var string + */ + protected $effectiveUrl; + + /** + * Associative array of response headers + * @var array + */ + protected $headers = array(); + + /** + * Cookies set in the response + * @var array + */ + protected $cookies = array(); + + /** + * Name of last header processed by parseHederLine() + * + * Used to handle the headers that span multiple lines + * + * @var string + */ + protected $lastHeader = null; + + /** + * Response body + * @var string + */ + protected $body = ''; + + /** + * Whether the body is still encoded by Content-Encoding + * + * cURL provides the decoded body to the callback; if we are reading from + * socket the body is still gzipped / deflated + * + * @var bool + */ + protected $bodyEncoded; + + /** + * Associative array of HTTP status code / reason phrase. + * + * @var array + * @link http://tools.ietf.org/html/rfc2616#section-10 + */ + protected static $phrases = array( + + // 1xx: Informational - Request received, continuing process + 100 => 'Continue', + 101 => 'Switching Protocols', + + // 2xx: Success - The action was successfully received, understood and + // accepted + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + // 3xx: Redirection - Further action must be taken in order to complete + // the request + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + + // 4xx: Client Error - The request contains bad syntax or cannot be + // fulfilled + 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 Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + + // 5xx: Server Error - The server failed to fulfill an apparently + // valid request + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded', + + ); + + /** + * Constructor, parses the response status line + * + * @param string Response status line (e.g. "HTTP/1.1 200 OK") + * @param bool Whether body is still encoded by Content-Encoding + * @param string Effective URL of the response + * @throws HTTP_Request2_MessageException if status line is invalid according to spec + */ + public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null) + { + if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) { + throw new HTTP_Request2_MessageException( + "Malformed response: {$statusLine}", + HTTP_Request2_Exception::MALFORMED_RESPONSE + ); + } + $this->version = $m[1]; + $this->code = intval($m[2]); + if (!empty($m[3])) { + $this->reasonPhrase = trim($m[3]); + } elseif (!empty(self::$phrases[$this->code])) { + $this->reasonPhrase = self::$phrases[$this->code]; + } + $this->bodyEncoded = (bool)$bodyEncoded; + $this->effectiveUrl = (string)$effectiveUrl; + } + + /** + * Parses the line from HTTP response filling $headers array + * + * The method should be called after reading the line from socket or receiving + * it into cURL callback. Passing an empty string here indicates the end of + * response headers and triggers additional processing, so be sure to pass an + * empty string in the end. + * + * @param string Line from HTTP response + */ + public function parseHeaderLine($headerLine) + { + $headerLine = trim($headerLine, "\r\n"); + + // empty string signals the end of headers, process the received ones + if ('' == $headerLine) { + if (!empty($this->headers['set-cookie'])) { + $cookies = is_array($this->headers['set-cookie'])? + $this->headers['set-cookie']: + array($this->headers['set-cookie']); + foreach ($cookies as $cookieString) { + $this->parseCookie($cookieString); + } + unset($this->headers['set-cookie']); + } + foreach (array_keys($this->headers) as $k) { + if (is_array($this->headers[$k])) { + $this->headers[$k] = implode(', ', $this->headers[$k]); + } + } + + // string of the form header-name: header value + } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) { + $name = strtolower($m[1]); + $value = trim($m[2]); + if (empty($this->headers[$name])) { + $this->headers[$name] = $value; + } else { + if (!is_array($this->headers[$name])) { + $this->headers[$name] = array($this->headers[$name]); + } + $this->headers[$name][] = $value; + } + $this->lastHeader = $name; + + // continuation of a previous header + } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) { + if (!is_array($this->headers[$this->lastHeader])) { + $this->headers[$this->lastHeader] .= ' ' . trim($m[1]); + } else { + $key = count($this->headers[$this->lastHeader]) - 1; + $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]); + } + } + } + + /** + * Parses a Set-Cookie header to fill $cookies array + * + * @param string value of Set-Cookie header + * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html + */ + protected function parseCookie($cookieString) + { + $cookie = array( + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false + ); + + // Only a name=value pair + if (!strpos($cookieString, ';')) { + $pos = strpos($cookieString, '='); + $cookie['name'] = trim(substr($cookieString, 0, $pos)); + $cookie['value'] = trim(substr($cookieString, $pos + 1)); + + // Some optional parameters are supplied + } else { + $elements = explode(';', $cookieString); + $pos = strpos($elements[0], '='); + $cookie['name'] = trim(substr($elements[0], 0, $pos)); + $cookie['value'] = trim(substr($elements[0], $pos + 1)); + + for ($i = 1; $i < count($elements); $i++) { + if (false === strpos($elements[$i], '=')) { + $elName = trim($elements[$i]); + $elValue = null; + } else { + list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + } + $elName = strtolower($elName); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName || 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->cookies[] = $cookie; + } + + /** + * Appends a string to the response body + * @param string + */ + public function appendBody($bodyChunk) + { + $this->body .= $bodyChunk; + } + + /** + * Returns the effective URL of the response + * + * This may be different from the request URL if redirects were followed. + * + * @return string + * @link http://pear.php.net/bugs/bug.php?id=18412 + */ + public function getEffectiveUrl() + { + return $this->effectiveUrl; + } + + /** + * Returns the status code + * @return integer + */ + public function getStatus() + { + return $this->code; + } + + /** + * Returns the reason phrase + * @return string + */ + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + /** + * Whether response is a redirect that can be automatically handled by HTTP_Request2 + * @return bool + */ + public function isRedirect() + { + return in_array($this->code, array(300, 301, 302, 303, 307)) + && isset($this->headers['location']); + } + + /** + * Returns either the named header or all response headers + * + * @param string Name of header to return + * @return string|array Value of $headerName header (null if header is + * not present), array of all response headers if + * $headerName is null + */ + public function getHeader($headerName = null) + { + if (null === $headerName) { + return $this->headers; + } else { + $headerName = strtolower($headerName); + return isset($this->headers[$headerName])? $this->headers[$headerName]: null; + } + } + + /** + * Returns cookies set in response + * + * @return array + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Returns the body of the response + * + * @return string + * @throws HTTP_Request2_Exception if body cannot be decoded + */ + public function getBody() + { + if (0 == strlen($this->body) || !$this->bodyEncoded || + !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate')) + ) { + return $this->body; + + } else { + if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) { + $oldEncoding = mb_internal_encoding(); + mb_internal_encoding('iso-8859-1'); + } + + try { + switch (strtolower($this->getHeader('content-encoding'))) { + case 'gzip': + $decoded = self::decodeGzip($this->body); + break; + case 'deflate': + $decoded = self::decodeDeflate($this->body); + } + } catch (Exception $e) { + } + + if (!empty($oldEncoding)) { + mb_internal_encoding($oldEncoding); + } + if (!empty($e)) { + throw $e; + } + return $decoded; + } + } + + /** + * Get the HTTP version of the response + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Decodes the message-body encoded by gzip + * + * The real decoding work is done by gzinflate() built-in function, this + * method only parses the header and checks data for compliance with + * RFC 1952 + * + * @param string gzip-encoded data + * @return string decoded data + * @throws HTTP_Request2_LogicException + * @throws HTTP_Request2_MessageException + * @link http://tools.ietf.org/html/rfc1952 + */ + public static function decodeGzip($data) + { + $length = strlen($data); + // If it doesn't look like gzip-encoded data, don't bother + if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) { + return $data; + } + if (!function_exists('gzinflate')) { + throw new HTTP_Request2_LogicException( + 'Unable to decode body: gzip extension not available', + HTTP_Request2_Exception::MISCONFIGURATION + ); + } + $method = ord(substr($data, 2, 1)); + if (8 != $method) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: unknown compression method', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $flags = ord(substr($data, 3, 1)); + if ($flags & 224) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: reserved bits are set', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + + // header is 10 bytes minimum. may be longer, though. + $headerLength = 10; + // extra fields, need to skip 'em + if ($flags & 4) { + if ($length - $headerLength - 2 < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $extraLength = unpack('v', substr($data, 10, 2)); + if ($length - $headerLength - 2 - $extraLength[1] < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $headerLength += $extraLength[1] + 2; + } + // file name, need to skip that + if ($flags & 8) { + if ($length - $headerLength - 1 < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $filenameLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $headerLength += $filenameLength + 1; + } + // comment, need to skip that also + if ($flags & 16) { + if ($length - $headerLength - 1 < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $commentLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $headerLength += $commentLength + 1; + } + // have a CRC for header. let's check + if ($flags & 2) { + if ($length - $headerLength - 2 < 8) { + throw new HTTP_Request2_MessageException( + 'Error parsing gzip header: data too short', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); + $crcStored = unpack('v', substr($data, $headerLength, 2)); + if ($crcReal != $crcStored[1]) { + throw new HTTP_Request2_MessageException( + 'Header CRC check failed', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + $headerLength += 2; + } + // unpacked data CRC and size at the end of encoded data + $tmp = unpack('V2', substr($data, -8)); + $dataCrc = $tmp[1]; + $dataSize = $tmp[2]; + + // finally, call the gzinflate() function + // don't pass $dataSize to gzinflate, see bugs #13135, #14370 + $unpacked = gzinflate(substr($data, $headerLength, -8)); + if (false === $unpacked) { + throw new HTTP_Request2_MessageException( + 'gzinflate() call failed', + HTTP_Request2_Exception::DECODE_ERROR + ); + } elseif ($dataSize != strlen($unpacked)) { + throw new HTTP_Request2_MessageException( + 'Data size check failed', + HTTP_Request2_Exception::DECODE_ERROR + ); + } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) { + throw new HTTP_Request2_Exception( + 'Data CRC check failed', + HTTP_Request2_Exception::DECODE_ERROR + ); + } + return $unpacked; + } + + /** + * Decodes the message-body encoded by deflate + * + * @param string deflate-encoded data + * @return string decoded data + * @throws HTTP_Request2_LogicException + */ + public static function decodeDeflate($data) + { + if (!function_exists('gzuncompress')) { + throw new HTTP_Request2_LogicException( + 'Unable to decode body: gzip extension not available', + HTTP_Request2_Exception::MISCONFIGURATION + ); + } + // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950, + // while many applications send raw deflate stream from RFC 1951. + // We should check for presence of zlib header and use gzuncompress() or + // gzinflate() as needed. See bug #15305 + $header = unpack('n', substr($data, 0, 2)); + return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data); + } +} +?> \ No newline at end of file diff --git a/lib/php/Net/URL2.php b/lib/php/Net/URL2.php new file mode 100644 index 0000000000000000000000000000000000000000..bbc9f124d3e3797bfde922d8627f8a252897b17e --- /dev/null +++ b/lib/php/Net/URL2.php @@ -0,0 +1,894 @@ +<?php +/** + * Net_URL2, a class representing a URL as per RFC 3986. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2007-2009, Peytz & Co. A/S + * 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 the Net_URL2 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. + * + * @category Networking + * @package Net_URL2 + * @author Christian Schmidt <schmidt@php.net> + * @copyright 2007-2009 Peytz & Co. A/S + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: URL2.php 290036 2009-10-28 19:52:49Z schmidt $ + * @link http://www.rfc-editor.org/rfc/rfc3986.txt + */ + +/** + * Represents a URL as per RFC 3986. + * + * @category Networking + * @package Net_URL2 + * @author Christian Schmidt <schmidt@php.net> + * @copyright 2007-2009 Peytz & Co. A/S + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Net_URL2 + */ +class Net_URL2 +{ + /** + * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default + * is true. + */ + const OPTION_STRICT = 'strict'; + + /** + * Represent arrays in query using PHP's [] notation. Default is true. + */ + const OPTION_USE_BRACKETS = 'use_brackets'; + + /** + * URL-encode query variable keys. Default is true. + */ + const OPTION_ENCODE_KEYS = 'encode_keys'; + + /** + * Query variable separators when parsing the query string. Every character + * is considered a separator. Default is "&". + */ + const OPTION_SEPARATOR_INPUT = 'input_separator'; + + /** + * Query variable separator used when generating the query string. Default + * is "&". + */ + const OPTION_SEPARATOR_OUTPUT = 'output_separator'; + + /** + * Default options corresponds to how PHP handles $_GET. + */ + private $_options = array( + self::OPTION_STRICT => true, + self::OPTION_USE_BRACKETS => true, + self::OPTION_ENCODE_KEYS => true, + self::OPTION_SEPARATOR_INPUT => '&', + self::OPTION_SEPARATOR_OUTPUT => '&', + ); + + /** + * @var string|bool + */ + private $_scheme = false; + + /** + * @var string|bool + */ + private $_userinfo = false; + + /** + * @var string|bool + */ + private $_host = false; + + /** + * @var string|bool + */ + private $_port = false; + + /** + * @var string + */ + private $_path = ''; + + /** + * @var string|bool + */ + private $_query = false; + + /** + * @var string|bool + */ + private $_fragment = false; + + /** + * Constructor. + * + * @param string $url an absolute or relative URL + * @param array $options an array of OPTION_xxx constants + */ + public function __construct($url, array $options = array()) + { + foreach ($options as $optionName => $value) { + if (array_key_exists($optionName, $this->_options)) { + $this->_options[$optionName] = $value; + } + } + + // The regular expression is copied verbatim from RFC 3986, appendix B. + // The expression does not validate the URL but matches any string. + preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', + $url, + $matches); + + // "path" is always present (possibly as an empty string); the rest + // are optional. + $this->_scheme = !empty($matches[1]) ? $matches[2] : false; + $this->setAuthority(!empty($matches[3]) ? $matches[4] : false); + $this->_path = $matches[5]; + $this->_query = !empty($matches[6]) ? $matches[7] : false; + $this->_fragment = !empty($matches[8]) ? $matches[9] : false; + } + + /** + * Magic Setter. + * + * This method will magically set the value of a private variable ($var) + * with the value passed as the args + * + * @param string $var The private variable to set. + * @param mixed $arg An argument of any type. + * @return void + */ + public function __set($var, $arg) + { + $method = 'set' . $var; + if (method_exists($this, $method)) { + $this->$method($arg); + } + } + + /** + * Magic Getter. + * + * This is the magic get method to retrieve the private variable + * that was set by either __set() or it's setter... + * + * @param string $var The property name to retrieve. + * @return mixed $this->$var Either a boolean false if the + * property is not set or the value + * of the private property. + */ + public function __get($var) + { + $method = 'get' . $var; + if (method_exists($this, $method)) { + return $this->$method(); + } + + return false; + } + + /** + * Returns the scheme, e.g. "http" or "urn", or false if there is no + * scheme specified, i.e. if this is a relative URL. + * + * @return string|bool + */ + public function getScheme() + { + return $this->_scheme; + } + + /** + * Sets the scheme, e.g. "http" or "urn". Specify false if there is no + * scheme specified, i.e. if this is a relative URL. + * + * @param string|bool $scheme e.g. "http" or "urn", or false if there is no + * scheme specified, i.e. if this is a relative + * URL + * + * @return void + * @see getScheme() + */ + public function setScheme($scheme) + { + $this->_scheme = $scheme; + } + + /** + * Returns the user part of the userinfo part (the part preceding the first + * ":"), or false if there is no userinfo part. + * + * @return string|bool + */ + public function getUser() + { + return $this->_userinfo !== false + ? preg_replace('@:.*$@', '', $this->_userinfo) + : false; + } + + /** + * Returns the password part of the userinfo part (the part after the first + * ":"), or false if there is no userinfo part (i.e. the URL does not + * contain "@" in front of the hostname) or the userinfo part does not + * contain ":". + * + * @return string|bool + */ + public function getPassword() + { + return $this->_userinfo !== false + ? substr(strstr($this->_userinfo, ':'), 1) + : false; + } + + /** + * Returns the userinfo part, or false if there is none, i.e. if the + * authority part does not contain "@". + * + * @return string|bool + */ + public function getUserinfo() + { + return $this->_userinfo; + } + + /** + * Sets the userinfo part. If two arguments are passed, they are combined + * in the userinfo part as username ":" password. + * + * @param string|bool $userinfo userinfo or username + * @param string|bool $password optional password, or false + * + * @return void + */ + public function setUserinfo($userinfo, $password = false) + { + $this->_userinfo = $userinfo; + if ($password !== false) { + $this->_userinfo .= ':' . $password; + } + } + + /** + * Returns the host part, or false if there is no authority part, e.g. + * relative URLs. + * + * @return string|bool a hostname, an IP address, or false + */ + public function getHost() + { + return $this->_host; + } + + /** + * Sets the host part. Specify false if there is no authority part, e.g. + * relative URLs. + * + * @param string|bool $host a hostname, an IP address, or false + * + * @return void + */ + public function setHost($host) + { + $this->_host = $host; + } + + /** + * Returns the port number, or false if there is no port number specified, + * i.e. if the default port is to be used. + * + * @return string|bool + */ + public function getPort() + { + return $this->_port; + } + + /** + * Sets the port number. Specify false if there is no port number specified, + * i.e. if the default port is to be used. + * + * @param string|bool $port a port number, or false + * + * @return void + */ + public function setPort($port) + { + $this->_port = $port; + } + + /** + * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or + * false if there is no authority. + * + * @return string|bool + */ + public function getAuthority() + { + if (!$this->_host) { + return false; + } + + $authority = ''; + + if ($this->_userinfo !== false) { + $authority .= $this->_userinfo . '@'; + } + + $authority .= $this->_host; + + if ($this->_port !== false) { + $authority .= ':' . $this->_port; + } + + return $authority; + } + + /** + * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify + * false if there is no authority. + * + * @param string|false $authority a hostname or an IP addresse, possibly + * with userinfo prefixed and port number + * appended, e.g. "foo:bar@example.org:81". + * + * @return void + */ + public function setAuthority($authority) + { + $this->_userinfo = false; + $this->_host = false; + $this->_port = false; + if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { + if ($reg[1]) { + $this->_userinfo = $reg[2]; + } + + $this->_host = $reg[3]; + if (isset($reg[5])) { + $this->_port = $reg[5]; + } + } + } + + /** + * Returns the path part (possibly an empty string). + * + * @return string + */ + public function getPath() + { + return $this->_path; + } + + /** + * Sets the path part (possibly an empty string). + * + * @param string $path a path + * + * @return void + */ + public function setPath($path) + { + $this->_path = $path; + } + + /** + * Returns the query string (excluding the leading "?"), or false if "?" + * is not present in the URL. + * + * @return string|bool + * @see self::getQueryVariables() + */ + public function getQuery() + { + return $this->_query; + } + + /** + * Sets the query string (excluding the leading "?"). Specify false if "?" + * is not present in the URL. + * + * @param string|bool $query a query string, e.g. "foo=1&bar=2" + * + * @return void + * @see self::setQueryVariables() + */ + public function setQuery($query) + { + $this->_query = $query; + } + + /** + * Returns the fragment name, or false if "#" is not present in the URL. + * + * @return string|bool + */ + public function getFragment() + { + return $this->_fragment; + } + + /** + * Sets the fragment name. Specify false if "#" is not present in the URL. + * + * @param string|bool $fragment a fragment excluding the leading "#", or + * false + * + * @return void + */ + public function setFragment($fragment) + { + $this->_fragment = $fragment; + } + + /** + * Returns the query string like an array as the variables would appear in + * $_GET in a PHP script. If the URL does not contain a "?", an empty array + * is returned. + * + * @return array + */ + public function getQueryVariables() + { + $pattern = '/[' . + preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . + ']/'; + $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY); + $return = array(); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + list($key, $value) = explode('=', $part, 2); + } else { + $key = $part; + $value = null; + } + + if ($this->getOption(self::OPTION_ENCODE_KEYS)) { + $key = rawurldecode($key); + } + $value = rawurldecode($value); + + if ($this->getOption(self::OPTION_USE_BRACKETS) && + preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) { + + $key = $matches[1]; + $idx = $matches[2]; + + // Ensure is an array + if (empty($return[$key]) || !is_array($return[$key])) { + $return[$key] = array(); + } + + // Add data + if ($idx === '') { + $return[$key][] = $value; + } else { + $return[$key][$idx] = $value; + } + } elseif (!$this->getOption(self::OPTION_USE_BRACKETS) + && !empty($return[$key]) + ) { + $return[$key] = (array) $return[$key]; + $return[$key][] = $value; + } else { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Sets the query string to the specified variable in the query string. + * + * @param array $array (name => value) array + * + * @return void + */ + public function setQueryVariables(array $array) + { + if (!$array) { + $this->_query = false; + } else { + foreach ($array as $name => $value) { + if ($this->getOption(self::OPTION_ENCODE_KEYS)) { + $name = self::urlencode($name); + } + + if (is_array($value)) { + foreach ($value as $k => $v) { + $parts[] = $this->getOption(self::OPTION_USE_BRACKETS) + ? sprintf('%s[%s]=%s', $name, $k, $v) + : ($name . '=' . $v); + } + } elseif (!is_null($value)) { + $parts[] = $name . '=' . self::urlencode($value); + } else { + $parts[] = $name; + } + } + $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT), + $parts); + } + } + + /** + * Sets the specified variable in the query string. + * + * @param string $name variable name + * @param mixed $value variable value + * + * @return array + */ + public function setQueryVariable($name, $value) + { + $array = $this->getQueryVariables(); + $array[$name] = $value; + $this->setQueryVariables($array); + } + + /** + * Removes the specifed variable from the query string. + * + * @param string $name a query string variable, e.g. "foo" in "?foo=1" + * + * @return void + */ + public function unsetQueryVariable($name) + { + $array = $this->getQueryVariables(); + unset($array[$name]); + $this->setQueryVariables($array); + } + + /** + * Returns a string representation of this URL. + * + * @return string + */ + public function getURL() + { + // See RFC 3986, section 5.3 + $url = ""; + + if ($this->_scheme !== false) { + $url .= $this->_scheme . ':'; + } + + $authority = $this->getAuthority(); + if ($authority !== false) { + $url .= '//' . $authority; + } + $url .= $this->_path; + + if ($this->_query !== false) { + $url .= '?' . $this->_query; + } + + if ($this->_fragment !== false) { + $url .= '#' . $this->_fragment; + } + + return $url; + } + + /** + * Returns a string representation of this URL. + * + * @return string + * @see toString() + */ + public function __toString() + { + return $this->getURL(); + } + + /** + * Returns a normalized string representation of this URL. This is useful + * for comparison of URLs. + * + * @return string + */ + public function getNormalizedURL() + { + $url = clone $this; + $url->normalize(); + return $url->getUrl(); + } + + /** + * Returns a normalized Net_URL2 instance. + * + * @return Net_URL2 + */ + public function normalize() + { + // See RFC 3886, section 6 + + // Schemes are case-insensitive + if ($this->_scheme) { + $this->_scheme = strtolower($this->_scheme); + } + + // Hostnames are case-insensitive + if ($this->_host) { + $this->_host = strtolower($this->_host); + } + + // Remove default port number for known schemes (RFC 3986, section 6.2.3) + if ($this->_port && + $this->_scheme && + $this->_port == getservbyname($this->_scheme, 'tcp')) { + + $this->_port = false; + } + + // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) + foreach (array('_userinfo', '_host', '_path') as $part) { + if ($this->$part) { + $this->$part = preg_replace('/%[0-9a-f]{2}/ie', + 'strtoupper("\0")', + $this->$part); + } + } + + // Path segment normalization (RFC 3986, section 6.2.2.3) + $this->_path = self::removeDotSegments($this->_path); + + // Scheme based normalization (RFC 3986, section 6.2.3) + if ($this->_host && !$this->_path) { + $this->_path = '/'; + } + } + + /** + * Returns whether this instance represents an absolute URL. + * + * @return bool + */ + public function isAbsolute() + { + return (bool) $this->_scheme; + } + + /** + * Returns an Net_URL2 instance representing an absolute URL relative to + * this URL. + * + * @param Net_URL2|string $reference relative URL + * + * @return Net_URL2 + */ + public function resolve($reference) + { + if (!$reference instanceof Net_URL2) { + $reference = new self($reference); + } + if (!$this->isAbsolute()) { + throw new Exception('Base-URL must be absolute'); + } + + // A non-strict parser may ignore a scheme in the reference if it is + // identical to the base URI's scheme. + if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) { + $reference->_scheme = false; + } + + $target = new self(''); + if ($reference->_scheme !== false) { + $target->_scheme = $reference->_scheme; + $target->setAuthority($reference->getAuthority()); + $target->_path = self::removeDotSegments($reference->_path); + $target->_query = $reference->_query; + } else { + $authority = $reference->getAuthority(); + if ($authority !== false) { + $target->setAuthority($authority); + $target->_path = self::removeDotSegments($reference->_path); + $target->_query = $reference->_query; + } else { + if ($reference->_path == '') { + $target->_path = $this->_path; + if ($reference->_query !== false) { + $target->_query = $reference->_query; + } else { + $target->_query = $this->_query; + } + } else { + if (substr($reference->_path, 0, 1) == '/') { + $target->_path = self::removeDotSegments($reference->_path); + } else { + // Merge paths (RFC 3986, section 5.2.3) + if ($this->_host !== false && $this->_path == '') { + $target->_path = '/' . $this->_path; + } else { + $i = strrpos($this->_path, '/'); + if ($i !== false) { + $target->_path = substr($this->_path, 0, $i + 1); + } + $target->_path .= $reference->_path; + } + $target->_path = self::removeDotSegments($target->_path); + } + $target->_query = $reference->_query; + } + $target->setAuthority($this->getAuthority()); + } + $target->_scheme = $this->_scheme; + } + + $target->_fragment = $reference->_fragment; + + return $target; + } + + /** + * Removes dots as described in RFC 3986, section 5.2.4, e.g. + * "/foo/../bar/baz" => "/bar/baz" + * + * @param string $path a path + * + * @return string a path + */ + public static function removeDotSegments($path) + { + $output = ''; + + // Make sure not to be trapped in an infinite loop due to a bug in this + // method + $j = 0; + while ($path && $j++ < 100) { + if (substr($path, 0, 2) == './') { + // Step 2.A + $path = substr($path, 2); + } elseif (substr($path, 0, 3) == '../') { + // Step 2.A + $path = substr($path, 3); + } elseif (substr($path, 0, 3) == '/./' || $path == '/.') { + // Step 2.B + $path = '/' . substr($path, 3); + } elseif (substr($path, 0, 4) == '/../' || $path == '/..') { + // Step 2.C + $path = '/' . substr($path, 4); + $i = strrpos($output, '/'); + $output = $i === false ? '' : substr($output, 0, $i); + } elseif ($path == '.' || $path == '..') { + // Step 2.D + $path = ''; + } else { + // Step 2.E + $i = strpos($path, '/'); + if ($i === 0) { + $i = strpos($path, '/', 1); + } + if ($i === false) { + $i = strlen($path); + } + $output .= substr($path, 0, $i); + $path = substr($path, $i); + } + } + + return $output; + } + + /** + * Percent-encodes all non-alphanumeric characters except these: _ . - ~ + * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP + * 5.2.x and earlier. + * + * @param $raw the string to encode + * @return string + */ + public static function urlencode($string) + { + $encoded = rawurlencode($string); + // This is only necessary in PHP < 5.3. + $encoded = str_replace('%7E', '~', $encoded); + return $encoded; + } + + /** + * Returns a Net_URL2 instance representing the canonical URL of the + * currently executing PHP script. + * + * @return string + */ + public static function getCanonical() + { + if (!isset($_SERVER['REQUEST_METHOD'])) { + // ALERT - no current URL + throw new Exception('Script was not called through a webserver'); + } + + // Begin with a relative URL + $url = new self($_SERVER['PHP_SELF']); + $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + $url->_host = $_SERVER['SERVER_NAME']; + $port = $_SERVER['SERVER_PORT']; + if ($url->_scheme == 'http' && $port != 80 || + $url->_scheme == 'https' && $port != 443) { + + $url->_port = $port; + } + return $url; + } + + /** + * Returns the URL used to retrieve the current request. + * + * @return string + */ + public static function getRequestedURL() + { + return self::getRequested()->getUrl(); + } + + /** + * Returns a Net_URL2 instance representing the URL used to retrieve the + * current request. + * + * @return Net_URL2 + */ + public static function getRequested() + { + if (!isset($_SERVER['REQUEST_METHOD'])) { + // ALERT - no current URL + throw new Exception('Script was not called through a webserver'); + } + + // Begin with a relative URL + $url = new self($_SERVER['REQUEST_URI']); + $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + // Set host and possibly port + $url->setAuthority($_SERVER['HTTP_HOST']); + return $url; + } + + /** + * Returns the value of the specified option. + * + * @param string $optionName The name of the option to retrieve + * + * @return mixed + */ + function getOption($optionName) + { + return isset($this->_options[$optionName]) + ? $this->_options[$optionName] : false; + } +} diff --git a/lib/php/SimpleCAS.php b/lib/php/SimpleCAS.php new file mode 100644 index 0000000000000000000000000000000000000000..6de5c28dd4eede7019b3026b7fba1bd5ce629e31 --- /dev/null +++ b/lib/php/SimpleCAS.php @@ -0,0 +1,274 @@ +<?php +/** + * This is a CAS client authentication library for PHP 5. + * + * <code> + * <?php + * $protocol = new SimpleCAS_Protocol_Version2('login.unl.edu', 443, 'cas'); + * $client = SimpleCAS::client($protocol); + * $client->forceAuthentication(); + * + * if (isset($_GET['logout'])) { + * $client->logout(); + * } + * + * if ($client->isAuthenticated()) { + * echo '<h1>Authentication Successful!</h1>'; + * echo '<p>The user\'s login is '.$client->getUsername().'</p>'; + * echo '<a href="?logout">Logout</a>'; + * } + * </code> + * + * PHP version 5 + * + * @category Authentication + * @package SimpleCAS + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + */ +class SimpleCAS +{ + /** + * Version of the CAS library. + */ + const VERSION = '0.0.1'; + + /** + * Singleton CAS object + * + * @var CAS + */ + static private $_instance; + + /** + * Is user authenticated? + * + * @var bool + */ + private $_authenticated = false; + + /** + * Protocol for the server running the CAS service. + * + * @var SimpleCAS_Protocol + */ + protected $protocol; + + /** + * User's login name if authenticated. + * + * @var string + */ + protected $username; + + /** + * (Optional) alternative service URL to return to after CAS authentication. + * + * @var string + */ + static protected $url; + + /** + * Construct a CAS client object. + * + * @param SimpleCAS_Protocol $protocol Protocol to use for authentication. + */ + private function __construct(SimpleCAS_Protocol $protocol) + { + $this->protocol = $protocol; + + if ($this->protocol instanceof SimpleCAS_SingleSignOut + && isset($_POST)) { + if ($ticket = $this->protocol->validateLogoutRequest($_POST)) { + $this->logout($ticket); + } + } + + if (session_id() == '') { + session_start(); + } + + if (isset($_SESSION['__SIMPLECAS_TICKET'])) { + $this->_authenticated = true; + } + + + if ($this->_authenticated == false + && isset($_GET['ticket'])) { + $this->validateTicket($_GET['ticket']); + } + } + + /** + * Checks a ticket to see if it is valid. + * + * If the CAS server verifies the ticket, a session is created and the user + * is marked as authenticated. + * + * @param string $ticket Ticket from the CAS Server + * + * @return bool + */ + protected function validateTicket($ticket) + { + if ($uid = $this->protocol->validateTicket($ticket, self::getURL())) { + $this->setAuthenticated($uid); + $this->redirect(self::getURL()); + return true; + } else { + return false; + } + } + + /** + * Marks the current session as authenticated. + * + * @param string $uid User name returned by the CAS server. + * + * @return void + */ + protected function setAuthenticated($uid) + { + $_SESSION['__SIMPLECAS_TICKET'] = true; + $_SESSION['__SIMPLECAS_UID'] = $uid; + $this->_authenticated = true; + } + + /** + * Return the authenticated user's login name. + * + * @return string + */ + public function getUsername() + { + return $_SESSION['__SIMPLECAS_UID']; + } + + /** + * Singleton interface, returns CAS object. + * + * @param CAS_Server $server CAS Server object + * + * @return CAS + */ + static public function client(SimpleCAS_Protocol $protocol) + { + if (!isset(self::$_instance)) { + self::$_instance = new self($protocol); + } + + return self::$_instance; + } + + /** + * If client is not authenticated, this will redirecting to login and exit. + * + * Otherwise, return the CAS object. + * + * @return CAS + */ + function forceAuthentication() + { + if (!$this->isAuthenticated()) { + self::redirect($this->protocol->getLoginURL(self::getURL())); + } + return $this; + } + + /** + * Check if this user has been authenticated or not. + * + * @return bool + */ + function isAuthenticated() + { + return $this->_authenticated; + } + + /** + * Destroys session data for this client, redirects to the server logout + * url. + * + * @param string $url URL to provide the client on logout. + * + * @return void + */ + public function logout($url = '') + { + session_destroy(); + if (empty($url)) { + $url = self::getURL(); + } + $this->redirect($this->protocol->getLogoutURL($url)); + } + + /** + * Returns the current URL without CAS affecting parameters. + * + * @return string url + */ + static public function getURL() + { + if (!empty(self::$url)) { + return self::$url; + } + if (isset($_SERVER['HTTPS']) + && !empty($_SERVER['HTTPS']) + && $_SERVER['HTTPS'] == 'on') { + $protocol = 'https'; + } else { + $protocol = 'http'; + } + + $url = $protocol.'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']; + + $replacements = array('/\?logout/' => '', + '/&ticket=[^&]*/' => '', + '/\?ticket=[^&;]*/' => '?', + '/\?%26/' => '?', + '/\?&/' => '?', + '/\?$/' => ''); + + $url = preg_replace(array_keys($replacements), + array_values($replacements), $url); + + return $url; + } + + /** + * Set an alternative return URL + * + * @param string $url alternative return URL + * + * @return void + */ + public static function setURL($url) + { + self::$url = $url; + } + + /** + * Send a header to redirect the client to another URL. + * + * @param string $url URL to redirect the client to. + * + * @return void + */ + public static function redirect($url) + { + header("Location: $url"); + exit(); + } + + /** + * Get the version of the CAS library + * + * @return string + */ + static public function getVersion() + { + return self::VERSION; + } +} diff --git a/lib/php/SimpleCAS/Autoload.php b/lib/php/SimpleCAS/Autoload.php new file mode 100644 index 0000000000000000000000000000000000000000..b26f5a07131625c54f2c59ae6d65bfa9391284de --- /dev/null +++ b/lib/php/SimpleCAS/Autoload.php @@ -0,0 +1,62 @@ +<?php +function SimpleCAS_Autoload($class) +{ + if (substr($class, 0, 9) !== 'SimpleCAS') { + return false; + } + $fp = @fopen(str_replace('_', '/', $class) . '.php', 'r', true); + if ($fp) { + fclose($fp); + require str_replace('_', '/', $class) . '.php'; + if (!class_exists($class, false) && !interface_exists($class, false)) { + die(new Exception('Class ' . $class . ' was not present in ' . + str_replace('_', '/', $class) . '.php (include_path="' . get_include_path() . + '") [SimpleCAS_Autoload version 0.1.0]')); + } + return true; + } + $e = new Exception('Class ' . $class . ' could not be loaded from ' . + str_replace('_', '/', $class) . '.php, file does not exist (include_path="' . get_include_path() . + '") [SimpleCAS_Autoload version 0.1.0]'); + $trace = $e->getTrace(); + if (isset($trace[2]) && isset($trace[2]['function']) && + in_array($trace[2]['function'], array('class_exists', 'interface_exists'))) { + return false; + } + if (isset($trace[1]) && isset($trace[1]['function']) && + in_array($trace[1]['function'], array('class_exists', 'interface_exists'))) { + return false; + } + die ((string) $e); +} + +// set up __autoload +if (function_exists('spl_autoload_register')) { + if (!($_____t = spl_autoload_functions()) || !in_array('SimpleCAS_Autoload', spl_autoload_functions())) { + spl_autoload_register('SimpleCAS_Autoload'); + if (function_exists('__autoload') && ($_____t === false)) { + // __autoload() was being used, but now would be ignored, add + // it to the autoload stack + spl_autoload_register('__autoload'); + } + } + unset($_____t); +} elseif (!function_exists('__autoload')) { + function __autoload($class) { return SimpleCAS_Autoload($class); } +} + +// set up include_path if it doesn't register our current location +$____paths = explode(PATH_SEPARATOR, get_include_path()); +$____found = false; +foreach ($____paths as $____path) { + if ($____path == dirname(dirname(__FILE__))) { + $____found = true; + break; + } +} +if (!$____found) { + set_include_path(get_include_path() . PATH_SEPARATOR . dirname(dirname(__FILE__))); +} +unset($____paths); +unset($____path); +unset($____found); diff --git a/lib/php/SimpleCAS/Protocol.php b/lib/php/SimpleCAS/Protocol.php new file mode 100644 index 0000000000000000000000000000000000000000..d050a6d4b7534e739f57498ac4963501ceebef40 --- /dev/null +++ b/lib/php/SimpleCAS/Protocol.php @@ -0,0 +1,83 @@ +<?php +/** + * Interface all CAS servers must implement. + * + * Each concrete class which implements this server interface must provide + * all the following functions. + * + * PHP version 5 + * + * @category Authentication + * @package SimpleCAS + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + */ +abstract class SimpleCAS_Protocol +{ + const DEFAULT_REQUEST_CLASS = 'HTTP_Request2'; + + protected $requestClass; + protected $request; + + /** + * Returns the login URL for the cas server. + * + * @param string $service The URL to the service requesting authentication. + * + * @return string + */ + abstract function getLoginURL($service); + + /** + * Returns the logout url for the CAS server. + * + * @param string $service A URL to provide the user upon logout. + * + * @return string + */ + abstract function getLogoutURL($service = null); + + /** + * Returns the version of this cas server. + * + * @return string + */ + abstract function getVersion(); + + /** + * Function to validate a ticket and service combination. + * + * @param string $ticket Ticket given by the CAS Server + * @param string $service Service requesting authentication + * + * @return false|string False on failure, user name on success. + */ + abstract function validateTicket($ticket, $service); + + /** + * Get the HTTP_Request2 object. + * + * @return HTTP_Request + */ + function getRequest() + { + $class = empty($this->requestClass) ? self::DEFAULT_REQUEST_CLASS : $this->requestClass; + if (!$this->request instanceof $class) { + $this->request = new $class(); + } + return $this->request; + } + + /** + * Set the HTTP Request object. + * + * @param $request + */ + function setRequest($request) + { + $this->request = $request; + } +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/Protocol/Version1.php b/lib/php/SimpleCAS/Protocol/Version1.php new file mode 100644 index 0000000000000000000000000000000000000000..7879522facf558b73840c2f4453c0dcf7bdeeb00 --- /dev/null +++ b/lib/php/SimpleCAS/Protocol/Version1.php @@ -0,0 +1,133 @@ +<?php +/** + * Class representing a CAS server which supports the CAS1 protocol. + * + * PHP version 5 + * + * @category Authentication + * @package SimpleCAS + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + */ +class SimpleCAS_Protocol_Version1 extends SimpleCAS_Protocol +{ + const VERSION = '1.0'; + + protected $request; + + /** + * Construct a new SimpleCAS server object. + * + * <code> + * $options = array('hostname' => 'login.unl.edu', + * 'port' => 443, + * 'uri' => 'cas'); + * $protocol = new SimpleCAS_Protocol_Version1($options); + * </code> + * + * @param array() + */ + function __construct($options) + { + foreach ($options as $option=>$val) { + $this->$option = $val; + } + } + + /** + * Returns the URL used to validate a ticket. + * + * @param string $ticket Ticket to validate + * @param string $service URL to the service requesting authentication + * + * @return string + */ + function getValidationURL($ticket, $service) + { + return 'https://' . $this->hostname . '/' + . $this->uri . '/validate?' + . 'service=' . urlencode($service) + . '&ticket=' . $ticket; + } + + /** + * Returns the URL to login form for the CAS server. + * + * @param string $service Service url requesting authentication. + * + * @return string + */ + function getLoginURL($service) + { + return 'https://' . $this->hostname + . '/'.$this->uri + . '/login?service=' + . urlencode($service); + } + + /** + * Returns the URL to logout of the CAS server. + * + * @param string $service Service url provided to the user. + * + * @return string + */ + function getLogoutURL($service = '') + { + if (isset($service)) { + $service = '?url='.urlencode($service); + } + + return 'https://' . $this->hostname + . '/'.$this->uri + . '/logout' + . $service; + } + + /** + * Function to validate a ticket and service combination. + * + * @param string $ticket Ticket given by the CAS Server + * @param string $service Service requesting authentication + * + * @return false|string False on failure, user name on success. + */ + function validateTicket($ticket, $service) + { + $validation_url = $this->getValidationURL($ticket, $service); + + $http_request = clone $this->getRequest(); + + $defaultClass = SimpleCAS_Protocol::DEFAULT_REQUEST_CLASS; + if ($http_request instanceof $defaultClass) { + $http_request->setURL($validation_url); + + $response = $http_request->send(); + } else { + $http_request->setUri($validation_url); + + $response = $http_request->request(); + } + + + if ($response->getStatus() == 200 + && substr($response->getBody(), 0, 3) == 'yes') { + list($message, $uid) = explode("\n", $response->getBody()); + return $uid; + } + return false; + } + + /** + * Returns the CAS server protocol this object implements. + * + * @return string + */ + function getVersion() + { + return self::VERSION; + } +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/Protocol/Version2.php b/lib/php/SimpleCAS/Protocol/Version2.php new file mode 100644 index 0000000000000000000000000000000000000000..e32d665aad1533dde18e05d9d664540869648569 --- /dev/null +++ b/lib/php/SimpleCAS/Protocol/Version2.php @@ -0,0 +1,94 @@ +<?php +/** + * Class representing a CAS server which supports the CAS2 protocol. + * + * PHP version 5 + * + * @category Authentication + * @package SimpleCAS + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + */ +class SimpleCAS_Protocol_Version2 extends SimpleCAS_Protocol_Version1 implements SimpleCAS_SingleSignOut, SimpleCAS_ProxyGranting +{ + const VERSION = '2.0'; + + /** + * Returns the URL used to validate a ticket. + * + * @param string $ticket Ticket to validate + * @param string $service URL to the service requesting authentication + * + * @return string + */ + function getValidationURL($ticket, $service, $pgtUrl = null) + { + return 'https://' . $this->hostname . '/' + . $this->uri . '/serviceValidate?' + . 'service=' . urlencode($service) + . '&ticket=' . $ticket + . '&pgtUrl=' . urlencode($pgtUrl); + } + + /** + * Function to validate a ticket and service combination. + * + * @param string $ticket Ticket given by the CAS Server + * @param string $service Service requesting authentication + * + * @return false|string False on failure, user name on success. + */ + function validateTicket($ticket, $service) + { + $validation_url = $this->getValidationURL($ticket, $service); + + $http_request = clone $this->getRequest(); + + $defaultClass = SimpleCAS_Protocol::DEFAULT_REQUEST_CLASS; + if ($http_request instanceof $defaultClass) { + $http_request->setURL($validation_url); + + $response = $http_request->send(); + } else { + $http_request->setUri($validation_url); + + $response = $http_request->request(); + } + + if ($response->getStatus() == 200) { + $validationResponse = new SimpleCAS_Protocol_Version2_ValidationResponse($response->getBody()); + if ($validationResponse->authenticationSuccess()) { + return $validationResponse->__toString(); + } + } + return false; + } + + /** + * Validates a single sign out logout request. + * + * @param mixed $post $_POST data + * + * @return bool + */ + function validateLogoutRequest($post) + { + if (false) { + return $ticket; + } + return false; + } + + function getProxyTicket() + { + throw new Exception('not implemented'); + } + + function validateProxyTicket($ticket) + { + throw new Exception('not implemented'); + } +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/Protocol/Version2/ValidationResponse.php b/lib/php/SimpleCAS/Protocol/Version2/ValidationResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..225a759b32680705d8b2c538e0927c61e0efae33 --- /dev/null +++ b/lib/php/SimpleCAS/Protocol/Version2/ValidationResponse.php @@ -0,0 +1,62 @@ +<?php +class SimpleCAS_Protocol_Version2_ValidationResponse +{ + protected $authenticationSuccess = false; + protected $user = false; + protected $pgtiou = false; + protected $proxies = array(); + + /** + * Construct a validation repsonse object from the CAS server's response. + * + * @param string $response + */ + function __construct($response) + { + $xml = new DOMDocument(); + if ($xml->loadXML($response)) { + if ($success = $xml->getElementsByTagName('authenticationSuccess')) { + if ($success->length > 0 + && $uid = $success->item(0)->getElementsByTagName('user')) { + // We have the user name, check for PGTIOU + if ($iou = $success->item(0)->getElementsByTagName('proxyGrantingTicket')) { + if ($iou->length) { + $this->pgtiou = $iou->item(0)->nodeValue; + } + } + $this->authenticationSuccess = true; + $this->user = $uid->item(0)->nodeValue; + } + } + } + } + + function authenticationSuccess() + { + return $this->authenticationSuccess; + } + + function getPGTIOU() + { + return $this->pgtiou; + } + + function getUser() + { + return $this->userid; + } + + function __toString() + { + if ($this->authenticationSuccess()) { + return $this->user; + } + throw new Exception('Validation was not successful'); + } + + function logout() + { + + } +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/ProxyGranting.php b/lib/php/SimpleCAS/ProxyGranting.php new file mode 100644 index 0000000000000000000000000000000000000000..3ab250ce5855859b82d0cd1a01c3ebd74afb3850 --- /dev/null +++ b/lib/php/SimpleCAS/ProxyGranting.php @@ -0,0 +1,31 @@ +<?php +/** + * Interface for servers that implement proxy granting tickets. + * + * PHP version 5 + * + * @category Authentication + * @package SimpleCAS + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + */ +interface SimpleCAS_ProxyGranting +{ + + /** + * get a proxy ticket + * + * @return string + */ + function getProxyTicket(); + + /** + * try and validate a proxy ticket + * + * @param unknown_type $ticket + */ + function validateProxyTicket($ticket); +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/ProxyGranting/Storage.php b/lib/php/SimpleCAS/ProxyGranting/Storage.php new file mode 100644 index 0000000000000000000000000000000000000000..e5560410893ade9aefe2365605ae2fdee53f44e9 --- /dev/null +++ b/lib/php/SimpleCAS/ProxyGranting/Storage.php @@ -0,0 +1,7 @@ +<?php +interface SimpleCAS_ProxyGranting_Storage +{ + function saveIOU($iou); + function getProxyGrantingTicket($iou); +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/ProxyGranting/Storage/File.php b/lib/php/SimpleCAS/ProxyGranting/Storage/File.php new file mode 100644 index 0000000000000000000000000000000000000000..b345c7768e84a62d4f4c5e8cb4d460f73270942f --- /dev/null +++ b/lib/php/SimpleCAS/ProxyGranting/Storage/File.php @@ -0,0 +1,14 @@ +<?php +class SimpleCAS_ProxyGranting_Storage_File implements SimpleCAS_ProxyGranting_Storage +{ + function saveIOU($iou) + { + throw new Exception('not implemented'); + } + + function getProxyGrantingTicket($iou) + { + throw new Exception('not implemented'); + } +} +?> \ No newline at end of file diff --git a/lib/php/SimpleCAS/SingleSignOut.php b/lib/php/SimpleCAS/SingleSignOut.php new file mode 100644 index 0000000000000000000000000000000000000000..1b0e97f4399580eeb372c631ced1751228df5827 --- /dev/null +++ b/lib/php/SimpleCAS/SingleSignOut.php @@ -0,0 +1,24 @@ +<?php +/** + * Interface for servers that implement single sign out. + * + * PHP version 5 + * + * @category Authentication + * @package SimpleCAS + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + */ +interface SimpleCAS_SingleSignOut +{ + /** + * Determines if the posted request is a valid single sign out request. + * + * @param mixed $post $_POST data sent to the service. + * + * @return bool + */ + function validateLogoutRequest($post); +} \ No newline at end of file diff --git a/lib/php/UNL/Auth.php b/lib/php/UNL/Auth.php new file mode 100644 index 0000000000000000000000000000000000000000..a2a403d51f5dadbde799dc39855782fd1572b77a --- /dev/null +++ b/lib/php/UNL/Auth.php @@ -0,0 +1,118 @@ +<?php +/** + * This is a generic authentication framework for UNL which will return customized + * containers for use at UNL. + * + * <code> + * <?php + * require_once 'UNL/Auth.php'; + * $a = UNL_Auth::factory('CAS'); + * if ($a->isLoggedIn()) { + * echo 'Hello ' . $a->getUser(); + * } else { + * echo 'Sorry, you must log in.'; + * } + * </code> + * + * PHP version 5 + * + * @category Authentication + * @package UNL_Auth + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2009 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://pear.unl.edu/package/UNL_Auth + */ +class UNL_Auth +{ + protected static $_instance = null; + + public static function getInstance() + { + if (null === self::$_instance) { + self::$_instance = new self(); + } + + return self::$_instance; + } + + private function __construct() + {} + + private function __clone() + {} + + /** + * Abstract factory, used to get drivers for any of the authentication methods + * on campus. + * + * @param string $auth_type CAS, LDAP, LotusNotes, etc + * @param mixed $options Options for the specific container + * + * @return mixed + */ + public static function factory($auth_type, $options = null) + { + $auth_class = 'UNL_Auth_'.$auth_type; + $class_file = dirname(__FILE__).'/Auth/'.$auth_type.'.php'; + return self::discoverAndReturn($auth_class, $class_file, $options); + } + + /** + * Returns an auth container for use with systems compatible with PEAR Auth + * + * @param string $auth_type CAS, LDAP, LotusNotes, etc + * @param mixed $options Options for the container + * + * @return mixed + */ + public static function PEARFactory($auth_type, $options = null, $loginFunction = null, $showLogin = true) + { + require_once 'Auth/Auth.php'; + /// Get the class... return the pear auth container. + $auth_class = 'UNL_Auth_'.$auth_type.'_PEARAuth'; + $class_file = dirname(__FILE__).'/Auth/'.$auth_type.'/PEARAuth.php'; + $container = self::discoverAndReturn($auth_class, $class_file, $options); + return $container->getPEARAuth($options, $loginFunction, $showLogin); + } + + public static function ZendFactory($auth_type, $options = null) + { + throw new Exception('not implemented yet!'); + /// Get the class name, return the Zend Auth extended class + $auth_class = 'UNL_Auth_'.$auth_type.'_ZendAuth'; + $class_file = dirname(__FILE__).'/Auth/'.$auth_type.'/ZendAuth.php'; + $container = self::discoverAndReturn($auth_class, $class_file, $options); + return $container; + } + + /** + * This is a class used to discover and return a new class based given a class + * name and file. + * + * @param string $class name of the class to load UNL_Auth_CAS + * @param string $class_file ./Auth/CAS.php + * + * @return object + */ + protected static function discoverAndReturn($class, $class_file, $options = null) + { + if (!class_exists($class)) { + if (file_exists($class_file)) { + require_once $class_file; + } else { + throw new Exception('Cannot find authentication class that matches '. + $auth_type.' I tried '.$class_file); + } + } + if (method_exists($class, 'getInstance')) { + return call_user_func(array($class, 'getInstance'), $options); + } else { + return new $class($options); + } + + } +} + + +?> \ No newline at end of file diff --git a/lib/php/UNL/Auth/CAS.php b/lib/php/UNL/Auth/CAS.php new file mode 100644 index 0000000000000000000000000000000000000000..a73d4d1440d39413c6faf307cb084158cfb23428 --- /dev/null +++ b/lib/php/UNL/Auth/CAS.php @@ -0,0 +1,157 @@ +<?php +/** + * This is a CAS central authentication. + * + * DO NOT MODIFY THIS FILE. + * This file remains part of the UNL Login public API and is subject to change. + * If you require features built into this class, please contact us by email at + * <accounts@answers4families.org>. + * + * based on the Answers4Families [http://www.answers4families.org/] Account Services + * LDAP-CAS API. + * + * + * PHP version 5 + * + * @category Authentication + * @package UNL_Auth + * @author Brett Bieber <brett.bieber@gmail.com> + * @author Ryan Lim <rlim@ccfl.unl.edu> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://pear.unl.edu/package/UNL_Auth + */ + +require_once 'CAS.php'; + + +/** + * UNL_Auth_CAS + * + * This is the CAS UserAccount class. + * This class takes care of user authentication using CAS and obtains the user + * account information via LDAP. + * + * This class does not handle changes to the user account information. All account + * information changes are handled by http://login.unl.edu/ + * + */ +class UNL_Auth_CAS extends UNL_Auth +{ + + /** + * Boolean flag to if the user is authenticated or not. + * + * @var bool + */ + protected $isAuth = false; + + /** + * $uid is the LDAP uid value of the authenticated user. + * + * @var string + */ + protected $uid; + + /** + * Options for the CAS server + * + * @var array + */ + protected $cas_options = array('host' => 'login.unl.edu', + 'port' => 443, + 'path' => 'cas'); + + /** + * The class constructor used to initialize the phpCAS class settings. + */ + private function __construct(array $options = null) + { + if (session_id() != '') { + $start_session = false; + } else { + $start_session = true; + } + phpCAS::setDebug(false); + phpCAS::client(CAS_VERSION_2_0, + $this->cas_options['host'], $this->cas_options['port'], $this->cas_options['path'], + $start_session); + phpCAS::setNoCasServerValidation(); + phpCAS::setCacheTimesForAuthRecheck(-1); + + $this->isAuth = phpCAS::checkAuthentication(); + } + + /** + * get a singleton instance of this class + * + * @return UNL_Auth_CAS + */ + public static function getInstance() + { + if (null === self::$_instance) { + self::$_instance = new self(); + } + + return self::$_instance; + } + + /** + * Log in the user. + */ + function login() + { + phpCAS::forceAuthentication(); + $this->isAuth = true; + $this->uid = phpCAS::getUser(); + } + + /** + * Log out the user. + */ + function logout() + { + $this->isAuth = false; + phpCAS::forceAuthentication(); + if (!empty($_SERVER['HTTP_REFERER'])) { + phpCAS::logoutWithUrl($_SERVER['HTTP_REFERER']); + } else { + phpCAS::logout(); + } + } + + /** + * Checks to see if the user is logged in. + * + * @return bool true if logged in, false otherwise. + */ + function isLoggedIn() + { + return $this->isAuth; + } + + /** + * Get the LDAP-uid. + * + * @return string | bool The LDAP uid of the logged in user. + */ + function getUser() + { + if ($this->isAuth) { + return phpCAS::getUser(); + } else { + return false; + } + } + + /** + * Stores the LDAP-uid internally in this instance of the class. + * + * @return string The LDAP uid of the logged in user. If the user is not logged in, return false. + */ + function getUid() + { + $this->uid = $this->getUser(); + return $this->uid; + } +} diff --git a/lib/php/UNL/Auth/CAS/PEARAuth.php b/lib/php/UNL/Auth/CAS/PEARAuth.php new file mode 100644 index 0000000000000000000000000000000000000000..c2f441bf5b64d9670e95fd6f8160b47519df4079 --- /dev/null +++ b/lib/php/UNL/Auth/CAS/PEARAuth.php @@ -0,0 +1,65 @@ +<?php +/** + * PEAR Auth compatible container for CAS + * + * PHP version 5 + * + * @category Default + * @package UNL_Auth + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://pear.unl.edu/package/UNL_Auth + */ + +include_once 'Auth/Container.php'; +require_once 'UNL/Auth/CAS.php'; + +class UNL_Auth_CAS_PEARAuth extends Auth_Container +{ + protected $cas; + + public function __construct($options) + { + $this->cas = UNL_Auth_CAS::getInstance(); + } + + public function getPEARAuth($options = null, $loginFunction = null, $showLogin = true) + { + if (!isset($loginFunction)) { + $loginFunction = array('UNL_Auth_CAS_PEARAuth', 'login'); + } + $auth = new Auth($this, $options, $loginFunction, $showLogin); + if ($this->checkAuth()) { + $auth->setAuth($this->getUsername()); + } + $auth->setLogoutCallback(array('UNL_Auth_CAS_PEARAuth','logout')); + return $auth; + } + + public function login() + { + UNL_Auth_CAS::getInstance()->login(); + } + + public function logout() + { + return UNL_Auth_CAS::getInstance()->logout(); + } + + public function getAuth() + { + return UNL_Auth_CAS::getInstance()->isLoggedIn(); + } + + public function checkAuth() + { + return UNL_Auth_CAS::getInstance()->isLoggedIn(); + } + + public function getUsername() + { + return UNL_Auth_CAS::getInstance()->getUser(); + } + +} diff --git a/lib/php/UNL/Auth/SimpleCAS.php b/lib/php/UNL/Auth/SimpleCAS.php new file mode 100644 index 0000000000000000000000000000000000000000..6f2cd0cfb1be2d0c7281e752d9723b671ab2c268 --- /dev/null +++ b/lib/php/UNL/Auth/SimpleCAS.php @@ -0,0 +1,107 @@ +<?php +/** + * This is a CAS central authentication. + * + * PHP version 5 + * + * @category Authentication + * @package UNL_Auth + * @author Brett Bieber <brett.bieber@gmail.com> + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://pear.unl.edu/package/UNL_Auth + */ + +require_once 'SimpleCAS/Autoload.php'; +require_once 'HTTP/Request2.php'; + +/** + * UNL_Auth_SimpleCAS + * + * This is the CAS UserAccount class. + * This class takes care of user authentication using CAS and obtains the user + * account information via LDAP. + * + * This class does not handle changes to the user account information. All account + * information changes are handled by http://login.unl.edu/ + * + */ +class UNL_Auth_SimpleCAS extends UNL_Auth +{ + /** + * Boolean flag to if the user is authenticated or not. + * + * @var bool + */ + protected $isAuth = false; + + /** + * $uid is the LDAP uid value of the authenticated user. + * + * @var string + */ + protected $uid; + + /** + * Options for the CAS server + * + * @var array + */ + protected $options = array('hostname' => 'login.unl.edu', + 'port' => 443, + 'uri' => 'cas'); + + protected $client; + + /** + * The class constructor used to initialize the SimpleCAS class settings. + */ + private function __construct(array $options = array()) + { + $options = array_merge($this->options, $options); + $protocol = new SimpleCAS_Protocol_Version2($this->options); + + $protocol->getRequest()->setConfig('ssl_verify_peer', false); + + $this->client = SimpleCAS::client($protocol); + if ($this->client->isAuthenticated()) { + $this->isAuth = true; + $this->uid = $this->client->getUsername(); + } + } + + /** + * get a singleton instance of this class + * + * @return UNL_Auth_SimpleCAS + */ + public static function getInstance() + { + if (null === self::$_instance) { + self::$_instance = new self(); + } + + return self::$_instance; + } + + function isLoggedIn() + { + return $this->isAuth; + } + + function getUser() + { + return $this->client->getUsername(); + } + + function login() + { + return $this->client->forceAuthentication(); + } + + function logout() + { + return $this->client->logout(); + } +} +?> \ No newline at end of file diff --git a/lib/php/UNL/Auth/SimpleCAS/ZendAuth.php b/lib/php/UNL/Auth/SimpleCAS/ZendAuth.php new file mode 100644 index 0000000000000000000000000000000000000000..8a759b39286f1178b7da0078874314594b9b4b5b --- /dev/null +++ b/lib/php/UNL/Auth/SimpleCAS/ZendAuth.php @@ -0,0 +1,78 @@ +<?php +/** + * This is a Zend_Auth adapter library for CAS. + * It uses SimpleCAS. + * + * <code> + * public function casAction() + * { + * $auth = Zend_Auth::getInstance(); + * $authAdapter = UNL_Auth::factory('SimpleCAS', Zend_Registry::get('config')->auth->cas); + * + * # User has not been identified, and there's a ticket in the URL + * if (!$auth->hasIdentity() && isset($_GET['ticket'])) { + * $authAdapter->setTicket($_GET['ticket']); + * $result = $auth->authenticate($authAdapter); + * + * if ($result->isValid()) { + * Zend_Session::regenerateId(); + * } + * } + * + * # No ticket or ticket was invalid. Redirect to CAS. + * if (!$auth->hasIdentity()) { + * $this->_redirect($authAdapter->getLoginURL()); + * } + * } + * </code> + */ + + +/** + * @see Zend_Auth_Adapter_Interface + */ +require_once 'Zend/Auth/Adapter/Interface.php'; + +require_once 'UNL/Auth/SimpleCAS.php'; + +class UNL_Auth_SimpleCAS_ZendAuth implements Zend_Auth_Adapter_Interface +{ + /** + * CAS client + * + * @var UNL_Auth_SimpleCAS + */ + protected $_simplecas; + + /** + * Constructor + * + * @return void + */ + public function __construct() + { + $this->_simplecas = UNL_Auth::factory('SimpleCAS'); + } + + /** + * Authenticates the user + * + * @return Zend_Auth_Result + */ + public function authenticate() + { + $this->_simplecas->login(); + if ($this->_simplecas->isLoggedIn()) { + return new Zend_Auth_Result( + Zend_Auth_Result::SUCCESS, + $this->_simplecas->getUser(), + array("Authentication successful")); + } else { + return new Zend_Auth_Result( + Zend_Auth_Result::FAILURE, + null, + array("Authentication failed")); + } + } + +} diff --git a/lib/tests/HTTP_Request2/HTTP/AllTests.php b/lib/tests/HTTP_Request2/HTTP/AllTests.php new file mode 100644 index 0000000000000000000000000000000000000000..bd3341ff5a6471285a800520340d5017c9087b38 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/AllTests.php @@ -0,0 +1,77 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: AllTests.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'HTTP_Request2_AllTests::main'); +} + +require_once dirname(__FILE__) . '/Request2Test.php'; +require_once dirname(__FILE__) . '/ObserverTest.php'; +require_once dirname(__FILE__) . '/Request2/AllTests.php'; + +class HTTP_Request2_AllTests +{ + public static function main() + { + if (!function_exists('phpunit_autoload')) { + require_once 'PHPUnit/TextUI/TestRunner.php'; + } + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + public static function suite() + { + $suite = new PHPUnit_Framework_TestSuite('HTTP_Request2 package'); + + $suite->addTest(Request2_AllTests::suite()); + $suite->addTestSuite('HTTP_Request2Test'); + $suite->addTestSuite('HTTP_Request2_ObserverTest'); + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'HTTP_Request2_AllTests::main') { + HTTP_Request2_AllTests::main(); +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/NetworkConfig.php.dist b/lib/tests/HTTP_Request2/HTTP/NetworkConfig.php.dist new file mode 100644 index 0000000000000000000000000000000000000000..0412ca4629e3040847caee0cbdb3a103cf06e28f --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/NetworkConfig.php.dist @@ -0,0 +1,72 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: NetworkConfig.php.dist 308299 2011-02-12 23:20:23Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * This file contains configuration needed for running HTTP_Request2 tests + * that interact with the network. Do not edit this file, copy it to + * NetworkConfig.php and edit the copy instead. + */ + +/** + * Base URL for HTTP_Request2 Adapters tests + * + * To enable the tests that actually perform network interaction, you should + * copy the contents of _network directory to a directory under your web + * server's document root or create a symbolic link to _network directory + * there. Set this constant to point to the URL of that directory. + */ +define('HTTP_REQUEST2_TESTS_BASE_URL', null); + +/**#@+ + * Proxy setup for Socket Adapter tests + * + * Set these constants to run additional tests for Socket Adapter using a HTTP + * proxy. If proxy host is not set then the tests will not be run. + */ +define('HTTP_REQUEST2_TESTS_PROXY_HOST', null); +define('HTTP_REQUEST2_TESTS_PROXY_PORT', 8080); +define('HTTP_REQUEST2_TESTS_PROXY_USER', ''); +define('HTTP_REQUEST2_TESTS_PROXY_PASSWORD', ''); +define('HTTP_REQUEST2_TESTS_PROXY_AUTH_SCHEME', 'basic'); +/**#@-*/ +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/ObserverTest.php b/lib/tests/HTTP_Request2/HTTP/ObserverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..50b22ffd1b2cbc423f03e9e8126a385c571855ac --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/ObserverTest.php @@ -0,0 +1,118 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: ObserverTest.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class representing a HTTP request + */ +require_once 'HTTP/Request2.php'; + +/** Helper for PHPUnit includes */ +require_once dirname(__FILE__) . '/TestHelper.php'; + +/** + * Mock observer + */ +class HTTP_Request2_MockObserver implements SplObserver +{ + public $calls = 0; + + public $event; + + public function update (SplSubject $subject) + { + $this->calls++; + $this->event = $subject->getLastEvent(); + } +} + +/** + * Unit test for subject-observer pattern implementation in HTTP_Request2 + */ +class HTTP_Request2_ObserverTest extends PHPUnit_Framework_TestCase +{ + public function testSetLastEvent() + { + $request = new HTTP_Request2(); + $observer = new HTTP_Request2_MockObserver(); + $request->attach($observer); + + $request->setLastEvent('foo', 'bar'); + $this->assertEquals(1, $observer->calls); + $this->assertEquals(array('name' => 'foo', 'data' => 'bar'), $observer->event); + + $request->setLastEvent('baz'); + $this->assertEquals(2, $observer->calls); + $this->assertEquals(array('name' => 'baz', 'data' => null), $observer->event); + } + + public function testAttachOnlyOnce() + { + $request = new HTTP_Request2(); + $observer = new HTTP_Request2_MockObserver(); + $observer2 = new HTTP_Request2_MockObserver(); + $request->attach($observer); + $request->attach($observer2); + $request->attach($observer); + + $request->setLastEvent('event', 'data'); + $this->assertEquals(1, $observer->calls); + $this->assertEquals(1, $observer2->calls); + } + + public function testDetach() + { + $request = new HTTP_Request2(); + $observer = new HTTP_Request2_MockObserver(); + $observer2 = new HTTP_Request2_MockObserver(); + + $request->attach($observer); + $request->detach($observer2); // should not be a error + $request->setLastEvent('first'); + + $request->detach($observer); + $request->setLastEvent('second'); + $this->assertEquals(1, $observer->calls); + $this->assertEquals(array('name' => 'first', 'data' => null), $observer->event); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/AllTests.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/AllTests.php new file mode 100644 index 0000000000000000000000000000000000000000..ae6aa96eadc725794d814dd60739732ce387145f --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/AllTests.php @@ -0,0 +1,93 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: AllTests.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'Request2_Adapter_AllTests::main'); +} + +require_once dirname(__FILE__) . '/MockTest.php'; +require_once dirname(__FILE__) . '/SkippedTests.php'; +require_once dirname(__FILE__) . '/SocketTest.php'; +require_once dirname(__FILE__) . '/SocketProxyTest.php'; +require_once dirname(__FILE__) . '/CurlTest.php'; + +class Request2_Adapter_AllTests +{ + public static function main() + { + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + public static function suite() + { + $suite = new PHPUnit_Framework_TestSuite('HTTP_Request2 package - Request2 - Adapter'); + + $suite->addTestSuite('HTTP_Request2_Adapter_MockTest'); + if (defined('HTTP_REQUEST2_TESTS_BASE_URL') && HTTP_REQUEST2_TESTS_BASE_URL) { + $suite->addTestSuite('HTTP_Request2_Adapter_SocketTest'); + } else { + $suite->addTestSuite('HTTP_Request2_Adapter_Skip_SocketTest'); + } + if (defined('HTTP_REQUEST2_TESTS_PROXY_HOST') && HTTP_REQUEST2_TESTS_PROXY_HOST + && defined('HTTP_REQUEST2_TESTS_BASE_URL') && HTTP_REQUEST2_TESTS_BASE_URL + ) { + $suite->addTestSuite('HTTP_Request2_Adapter_SocketProxyTest'); + } else { + $suite->addTestSuite('HTTP_Request2_Adapter_Skip_SocketProxyTest'); + } + if (defined('HTTP_REQUEST2_TESTS_BASE_URL') && HTTP_REQUEST2_TESTS_BASE_URL + && extension_loaded('curl') + ) { + $suite->addTestSuite('HTTP_Request2_Adapter_CurlTest'); + } else { + $suite->addTestSuite('HTTP_Request2_Adapter_Skip_CurlTest'); + } + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'Request2_Adapter_AllTests::main') { + Request2_Adapter_AllTests::main(); +} +?> diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CommonNetworkTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CommonNetworkTest.php new file mode 100644 index 0000000000000000000000000000000000000000..976aae358f4fa0e84b1e634585280525ab39b01c --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CommonNetworkTest.php @@ -0,0 +1,309 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: CommonNetworkTest.php 309921 2011-04-03 16:43:02Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Class representing a HTTP request */ +require_once 'HTTP/Request2.php'; + +/** Helper for PHPUnit includes */ +require_once dirname(dirname(dirname(__FILE__))) . '/TestHelper.php'; + +/** + * Tests for HTTP_Request2 package that require a working webserver + * + * The class contains some common tests that should be run for all Adapters, + * it is extended by their unit tests. + * + * You need to properly set up this test suite, refer to NetworkConfig.php.dist + */ +abstract class HTTP_Request2_Adapter_CommonNetworkTest extends PHPUnit_Framework_TestCase +{ + /** + * HTTP Request object + * @var HTTP_Request2 + */ + protected $request; + + /** + * Base URL for remote test files + * @var string + */ + protected $baseUrl; + + /** + * Configuration for HTTP Request object + * @var array + */ + protected $config = array(); + + protected function setUp() + { + if (!defined('HTTP_REQUEST2_TESTS_BASE_URL') || !HTTP_REQUEST2_TESTS_BASE_URL) { + $this->markTestSkipped('Base URL is not configured'); + + } else { + $this->baseUrl = rtrim(HTTP_REQUEST2_TESTS_BASE_URL, '/') . '/'; + $name = strtolower(preg_replace('/^test/i', '', $this->getName())) . '.php'; + + $this->request = new HTTP_Request2( + $this->baseUrl . $name, HTTP_Request2::METHOD_GET, $this->config + ); + } + } + + /** + * Tests possibility to send GET parameters + * + * NB: Currently there are problems with Net_URL2::setQueryVariables(), thus + * array structure is simple: http://pear.php.net/bugs/bug.php?id=18267 + */ + public function testGetParameters() + { + $data = array( + 'bar' => array( + 'key' => 'value' + ), + 'foo' => 'some value', + 'numbered' => array('first', 'second') + ); + + $this->request->getUrl()->setQueryVariables($data); + $response = $this->request->send(); + $this->assertEquals($response->getBody(), serialize($data)); + } + + public function testPostParameters() + { + $data = array( + 'bar' => array( + 'key' => 'some other value' + ), + 'baz' => array( + 'key1' => array( + 'key2' => 'yet another value' + ) + ), + 'foo' => 'some value', + 'indexed' => array('first', 'second') + ); + + $this->request->setMethod(HTTP_Request2::METHOD_POST) + ->addPostParameter($data); + + $response = $this->request->send(); + $this->assertEquals($response->getBody(), serialize($data)); + } + + public function testUploads() + { + $this->request->setMethod(HTTP_Request2::METHOD_POST) + ->addUpload('foo', dirname(dirname(dirname(__FILE__))) . '/_files/empty.gif', 'picture.gif', 'image/gif') + ->addUpload('bar', array( + array(dirname(dirname(dirname(__FILE__))) . '/_files/empty.gif', null, 'image/gif'), + array(dirname(dirname(dirname(__FILE__))) . '/_files/plaintext.txt', 'secret.txt', 'text/x-whatever') + )); + + $response = $this->request->send(); + $this->assertContains("foo picture.gif image/gif 43", $response->getBody()); + $this->assertContains("bar[0] empty.gif image/gif 43", $response->getBody()); + $this->assertContains("bar[1] secret.txt text/x-whatever 15", $response->getBody()); + } + + public function testRawPostData() + { + $data = 'Nothing to see here, move along'; + + $this->request->setMethod(HTTP_Request2::METHOD_POST) + ->setBody($data); + $response = $this->request->send(); + $this->assertEquals($response->getBody(), $data); + } + + public function testCookies() + { + $cookies = array( + 'CUSTOMER' => 'WILE_E_COYOTE', + 'PART_NUMBER' => 'ROCKET_LAUNCHER_0001' + ); + + foreach ($cookies as $k => $v) { + $this->request->addCookie($k, $v); + } + $response = $this->request->send(); + $this->assertEquals($response->getBody(), serialize($cookies)); + } + + public function testTimeout() + { + $this->request->setConfig('timeout', 2); + try { + $this->request->send(); + $this->fail('Expected HTTP_Request2_Exception was not thrown'); + } catch (HTTP_Request2_MessageException $e) { + $this->assertEquals(HTTP_Request2_Exception::TIMEOUT, $e->getCode()); + } + } + + public function testBasicAuth() + { + $this->request->getUrl()->setQueryVariables(array( + 'user' => 'luser', + 'pass' => 'qwerty' + )); + $wrong = clone $this->request; + + $this->request->setAuth('luser', 'qwerty'); + $response = $this->request->send(); + $this->assertEquals(200, $response->getStatus()); + + $wrong->setAuth('luser', 'password'); + $response = $wrong->send(); + $this->assertEquals(401, $response->getStatus()); + } + + public function testDigestAuth() + { + $this->request->getUrl()->setQueryVariables(array( + 'user' => 'luser', + 'pass' => 'qwerty' + )); + $wrong = clone $this->request; + + $this->request->setAuth('luser', 'qwerty', HTTP_Request2::AUTH_DIGEST); + $response = $this->request->send(); + $this->assertEquals(200, $response->getStatus()); + + $wrong->setAuth('luser', 'password', HTTP_Request2::AUTH_DIGEST); + $response = $wrong->send(); + $this->assertEquals(401, $response->getStatus()); + } + + public function testRedirectsDefault() + { + $this->request->setUrl($this->baseUrl . 'redirects.php') + ->setConfig(array('follow_redirects' => true, 'strict_redirects' => false)) + ->setMethod(HTTP_Request2::METHOD_POST) + ->addPostParameter('foo', 'foo value'); + + $response = $this->request->send(); + $this->assertContains('Method=GET', $response->getBody()); + $this->assertNotContains('foo', $response->getBody()); + $this->assertEquals($this->baseUrl . 'redirects.php?redirects=0', $response->getEffectiveUrl()); + } + + public function testRedirectsStrict() + { + $this->request->setUrl($this->baseUrl . 'redirects.php') + ->setConfig(array('follow_redirects' => true, 'strict_redirects' => true)) + ->setMethod(HTTP_Request2::METHOD_POST) + ->addPostParameter('foo', 'foo value'); + + $response = $this->request->send(); + $this->assertContains('Method=POST', $response->getBody()); + $this->assertContains('foo', $response->getBody()); + } + + public function testRedirectsLimit() + { + $this->request->setUrl($this->baseUrl . 'redirects.php?redirects=4') + ->setConfig(array('follow_redirects' => true, 'max_redirects' => 2)); + + try { + $this->request->send(); + $this->fail('Expected HTTP_Request2_Exception was not thrown'); + } catch (HTTP_Request2_MessageException $e) { + $this->assertEquals(HTTP_Request2_Exception::TOO_MANY_REDIRECTS, $e->getCode()); + } + } + + public function testRedirectsRelative() + { + $this->request->setUrl($this->baseUrl . 'redirects.php?special=relative') + ->setConfig(array('follow_redirects' => true)); + + $response = $this->request->send(); + $this->assertContains('did relative', $response->getBody()); + } + + public function testRedirectsNonHTTP() + { + $this->request->setUrl($this->baseUrl . 'redirects.php?special=ftp') + ->setConfig(array('follow_redirects' => true)); + + try { + $this->request->send(); + $this->fail('Expected HTTP_Request2_Exception was not thrown'); + } catch (HTTP_Request2_MessageException $e) { + $this->assertEquals(HTTP_Request2_Exception::NON_HTTP_REDIRECT, $e->getCode()); + } + } + + public function testCookieJar() + { + $this->request->setUrl($this->baseUrl . 'setcookie.php?name=cookie_name&value=cookie_value'); + $req2 = clone $this->request; + + $this->request->setCookieJar()->send(); + $jar = $this->request->getCookieJar(); + $jar->store( + array('name' => 'foo', 'value' => 'bar'), + $this->request->getUrl() + ); + + $response = $req2->setUrl($this->baseUrl . 'cookies.php')->setCookieJar($jar)->send(); + $this->assertEquals( + serialize(array('cookie_name' => 'cookie_value', 'foo' => 'bar')), + $response->getBody() + ); + } + + public function testCookieJarAndRedirect() + { + $this->request->setUrl($this->baseUrl . 'redirects.php?special=cookie') + ->setConfig('follow_redirects', true) + ->setCookieJar(); + + $response = $this->request->send(); + $this->assertEquals(serialize(array('cookie_on_redirect' => 'success')), $response->getBody()); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CurlTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CurlTest.php new file mode 100644 index 0000000000000000000000000000000000000000..05fc5080f2999f43eb6fa71ddeb1227fd35090ea --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CurlTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: CurlTest.php 308629 2011-02-24 17:34:24Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Tests for HTTP_Request2 package that require a working webserver */ +require_once dirname(__FILE__) . '/CommonNetworkTest.php'; + +/** Adapter for HTTP_Request2 wrapping around cURL extension */ + +/** + * Unit test for Curl Adapter of HTTP_Request2 + */ +class HTTP_Request2_Adapter_CurlTest extends HTTP_Request2_Adapter_CommonNetworkTest +{ + /** + * Configuration for HTTP Request object + * @var array + */ + protected $config = array( + 'adapter' => 'HTTP_Request2_Adapter_Curl' + ); + + /** + * Checks whether redirect support in cURL is disabled by safe_mode or open_basedir + * @return bool + */ + protected function isRedirectSupportDisabled() + { + return ini_get('safe_mode') || ini_get('open_basedir'); + } + + public function testRedirectsDefault() + { + if ($this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Redirect support in cURL is disabled by safe_mode or open_basedir setting'); + } else { + parent::testRedirectsDefault(); + } + } + + public function testRedirectsStrict() + { + if ($this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Redirect support in cURL is disabled by safe_mode or open_basedir setting'); + } else { + parent::testRedirectsStrict(); + } + } + + public function testRedirectsLimit() + { + if ($this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Redirect support in cURL is disabled by safe_mode or open_basedir setting'); + } else { + parent::testRedirectsLimit(); + } + } + + public function testRedirectsRelative() + { + if ($this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Redirect support in cURL is disabled by safe_mode or open_basedir setting'); + } else { + parent::testRedirectsRelative(); + } + } + + public function testRedirectsNonHTTP() + { + if ($this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Redirect support in cURL is disabled by safe_mode or open_basedir setting'); + } else { + parent::testRedirectsNonHTTP(); + } + } + + public function testCookieJarAndRedirect() + { + if ($this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Redirect support in cURL is disabled by safe_mode or open_basedir setting'); + } else { + parent::testCookieJarAndRedirect(); + } + } + + public function testBug17450() + { + if (!$this->isRedirectSupportDisabled()) { + $this->markTestSkipped('Neither safe_mode nor open_basedir is enabled'); + } + + $this->request->setUrl($this->baseUrl . 'redirects.php') + ->setConfig(array('follow_redirects' => true)); + + try { + $this->request->send(); + $this->fail('Expected HTTP_Request2_Exception was not thrown'); + + } catch (HTTP_Request2_LogicException $e) { + $this->assertEquals(HTTP_Request2_Exception::MISCONFIGURATION, $e->getCode()); + } + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/MockTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/MockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..06b7a24e262535ab895acfeeb554ae9913debbe0 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/MockTest.php @@ -0,0 +1,145 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: MockTest.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class representing a HTTP request + */ +require_once 'HTTP/Request2.php'; + +/** + * Mock adapter intended for testing + */ +require_once 'HTTP/Request2/Adapter/Mock.php'; + +/** Helper for PHPUnit includes */ +require_once dirname(dirname(dirname(__FILE__))) . '/TestHelper.php'; + +/** + * Unit test for HTTP_Request2_Response class + */ +class HTTP_Request2_Adapter_MockTest extends PHPUnit_Framework_TestCase +{ + public function testDefaultResponse() + { + $req = new HTTP_Request2('http://www.example.com/', HTTP_Request2::METHOD_GET, + array('adapter' => 'mock')); + $response = $req->send(); + $this->assertEquals(400, $response->getStatus()); + $this->assertEquals(0, count($response->getHeader())); + $this->assertEquals('', $response->getBody()); + } + + public function testResponseFromString() + { + $mock = new HTTP_Request2_Adapter_Mock(); + $mock->addResponse( + "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/plain; charset=iso-8859-1\r\n" . + "\r\n" . + "This is a string" + ); + $req = new HTTP_Request2('http://www.example.com/'); + $req->setAdapter($mock); + + $response = $req->send(); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals(1, count($response->getHeader())); + $this->assertEquals('This is a string', $response->getBody()); + } + + public function testResponseFromFile() + { + $mock = new HTTP_Request2_Adapter_Mock(); + $mock->addResponse(fopen(dirname(dirname(dirname(__FILE__))) . + '/_files/response_headers', 'rb')); + + $req = new HTTP_Request2('http://www.example.com/'); + $req->setAdapter($mock); + + $response = $req->send(); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals(7, count($response->getHeader())); + $this->assertEquals('Nothing to see here, move along.', $response->getBody()); + } + + public function testResponsesQueue() + { + $mock = new HTTP_Request2_Adapter_Mock(); + $mock->addResponse( + "HTTP/1.1 301 Over there\r\n" . + "Location: http://www.example.com/newpage.html\r\n" . + "\r\n" . + "The document is over there" + ); + $mock->addResponse( + "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/plain; charset=iso-8859-1\r\n" . + "\r\n" . + "This is a string" + ); + + $req = new HTTP_Request2('http://www.example.com/'); + $req->setAdapter($mock); + $this->assertEquals(301, $req->send()->getStatus()); + $this->assertEquals(200, $req->send()->getStatus()); + $this->assertEquals(400, $req->send()->getStatus()); + } + + public function testResponseException() + { + $mock = new HTTP_Request2_Adapter_Mock(); + $mock->addResponse( + new HTTP_Request2_Exception('Shit happens') + ); + $req = new HTTP_Request2('http://www.example.com/'); + $req->setAdapter($mock); + try { + $req->send(); + } catch (Exception $e) { + $this->assertEquals('Shit happens', $e->getMessage()); + return; + } + $this->fail('Expected HTTP_Request2_Exception was not thrown'); + } +} +?> diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SkippedTests.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SkippedTests.php new file mode 100644 index 0000000000000000000000000000000000000000..3b2c4945c68fadc72b00fea2393b6813462c7e03 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SkippedTests.php @@ -0,0 +1,79 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: SkippedTests.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Helper for PHPUnit includes */ +require_once dirname(dirname(dirname(__FILE__))) . '/TestHelper.php'; + +/** + * Shows a skipped test if networked tests are not configured + */ +class HTTP_Request2_Adapter_Skip_SocketTest extends PHPUnit_Framework_TestCase +{ + public function testSocketAdapter() + { + $this->markTestSkipped('Socket Adapter tests need base URL configured.'); + } +} + +/** + * Shows a skipped test if proxy is not configured + */ +class HTTP_Request2_Adapter_Skip_SocketProxyTest extends PHPUnit_Framework_TestCase +{ + public function testSocketAdapterWithProxy() + { + $this->markTestSkipped('Socket Adapter proxy tests need base URL and proxy configured'); + } +} + +/** + * Shows a skipped test if networked tests are not configured or cURL extension is unavailable + */ +class HTTP_Request2_Adapter_Skip_CurlTest extends PHPUnit_Framework_TestCase +{ + public function testCurlAdapter() + { + $this->markTestSkipped('Curl Adapter tests need base URL configured and curl extension available'); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketProxyTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketProxyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..14db8630e6fd139fb629ff9956ca77d5e47560b0 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketProxyTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: SocketProxyTest.php 308299 2011-02-12 23:20:23Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Tests for HTTP_Request2 package that require a working webserver */ +require_once dirname(__FILE__) . '/CommonNetworkTest.php'; + +/** + * Unit test for Socket Adapter of HTTP_Request2 working through proxy + */ +class HTTP_Request2_Adapter_SocketProxyTest extends HTTP_Request2_Adapter_CommonNetworkTest +{ + /** + * Configuration for HTTP Request object + * @var array + */ + protected $config = array( + 'adapter' => 'HTTP_Request2_Adapter_Socket' + ); + + protected function setUp() + { + if (!defined('HTTP_REQUEST2_TESTS_PROXY_HOST') || !HTTP_REQUEST2_TESTS_PROXY_HOST) { + $this->markTestSkipped('Proxy is not configured'); + + } else { + $this->config += array( + 'proxy_host' => HTTP_REQUEST2_TESTS_PROXY_HOST, + 'proxy_port' => HTTP_REQUEST2_TESTS_PROXY_PORT, + 'proxy_user' => HTTP_REQUEST2_TESTS_PROXY_USER, + 'proxy_password' => HTTP_REQUEST2_TESTS_PROXY_PASSWORD, + 'proxy_auth_scheme' => HTTP_REQUEST2_TESTS_PROXY_AUTH_SCHEME, + ); + parent::setUp(); + } + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketTest.php new file mode 100644 index 0000000000000000000000000000000000000000..71ca6e62c0fe3d67523394c759ce31286650a668 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: SocketTest.php 308301 2011-02-13 13:02:20Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Tests for HTTP_Request2 package that require a working webserver */ +require_once dirname(__FILE__) . '/CommonNetworkTest.php'; + +/** Socket-based adapter for HTTP_Request2 */ +require_once 'HTTP/Request2/Adapter/Socket.php'; + +/** + * Unit test for Socket Adapter of HTTP_Request2 + */ +class HTTP_Request2_Adapter_SocketTest extends HTTP_Request2_Adapter_CommonNetworkTest +{ + /** + * Configuration for HTTP Request object + * @var array + */ + protected $config = array( + 'adapter' => 'HTTP_Request2_Adapter_Socket' + ); + + public function testBug17826() + { + $adapter = new HTTP_Request2_Adapter_Socket(); + + $request1 = new HTTP_Request2($this->baseUrl . 'redirects.php?redirects=2'); + $request1->setConfig(array('follow_redirects' => true, 'max_redirects' => 3)) + ->setAdapter($adapter) + ->send(); + + $request2 = new HTTP_Request2($this->baseUrl . 'redirects.php?redirects=2'); + $request2->setConfig(array('follow_redirects' => true, 'max_redirects' => 3)) + ->setAdapter($adapter) + ->send(); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/AllTests.php b/lib/tests/HTTP_Request2/HTTP/Request2/AllTests.php new file mode 100644 index 0000000000000000000000000000000000000000..0d709074e39160da8cf603cc4db5fa5dd293752b --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/AllTests.php @@ -0,0 +1,79 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: AllTests.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'Request2_AllTests::main'); +} + +require_once dirname(__FILE__) . '/CookieJarTest.php'; +require_once dirname(__FILE__) . '/MultipartBodyTest.php'; +require_once dirname(__FILE__) . '/ResponseTest.php'; +require_once dirname(__FILE__) . '/Adapter/AllTests.php'; + +class Request2_AllTests +{ + public static function main() + { + if (!function_exists('phpunit_autoload')) { + require_once 'PHPUnit/TextUI/TestRunner.php'; + } + PHPUnit_TextUI_TestRunner::run(self::suite()); + } + + public static function suite() + { + $suite = new PHPUnit_Framework_TestSuite('HTTP_Request2 package - Request2'); + + $suite->addTestSuite('HTTP_Request2_CookieJarTest'); + $suite->addTestSuite('HTTP_Request2_MultipartBodyTest'); + $suite->addTestSuite('HTTP_Request2_ResponseTest'); + $suite->addTest(Request2_Adapter_AllTests::suite()); + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'Request2_AllTests::main') { + Request2_AllTests::main(); +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/CookieJarTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/CookieJarTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4529c071da2d300d5a1dd25456610b9a921b7601 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/CookieJarTest.php @@ -0,0 +1,393 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: CookieJarTest.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Stores cookies and passes them between HTTP requests */ +require_once 'HTTP/Request2/CookieJar.php'; +/** Helper for PHPUnit includes */ +require_once dirname(dirname(__FILE__)) . '/TestHelper.php'; + +/** + * Unit test for HTTP_Request2_CookieJar class + */ +class HTTP_Request2_CookieJarTest extends PHPUnit_Framework_TestCase +{ + /** + * Cookie jar instance being tested + * @var HTTP_Request2_CookieJar + */ + protected $jar; + + protected function setUp() + { + $this->jar = new HTTP_Request2_CookieJar(); + } + + /** + * Test that we can't store junk "cookies" in jar + * + * @dataProvider invalidCookieProvider + * @expectedException HTTP_Request2_LogicException + */ + public function testStoreInvalid($cookie) + { + $this->jar->store($cookie); + } + + /** + * + * @dataProvider noPSLDomainsProvider + */ + public function testDomainMatchNoPSL($requestHost, $cookieDomain, $expected) + { + $this->jar->usePublicSuffixList(false); + $this->assertEquals($expected, $this->jar->domainMatch($requestHost, $cookieDomain)); + } + + /** + * + * @dataProvider PSLDomainsProvider + */ + public function testDomainMatchPSL($requestHost, $cookieDomain, $expected) + { + $this->jar->usePublicSuffixList(true); + $this->assertEquals($expected, $this->jar->domainMatch($requestHost, $cookieDomain)); + } + + public function testConvertExpiresToISO8601() + { + $dt = new DateTime(); + $dt->setTimezone(new DateTimeZone('UTC')); + $dt->modify('+1 day'); + + $this->jar->store(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'expires' => $dt->format(DateTime::COOKIE), + 'secure' => false + )); + $cookies = $this->jar->getAll(); + $this->assertEquals($cookies[0]['expires'], $dt->format(DateTime::ISO8601)); + } + + public function testProblem2038() + { + $this->jar->store(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'expires' => 'Sun, 01 Jan 2040 03:04:05 GMT', + 'secure' => false + )); + $cookies = $this->jar->getAll(); + $this->assertEquals(array(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'expires' => '2040-01-01T03:04:05+0000', + 'secure' => false + )), $cookies); + } + + public function testStoreExpired() + { + $base = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'secure' => false + ); + + $dt = new DateTime(); + $dt->setTimezone(new DateTimeZone('UTC')); + $dt->modify('-1 day'); + $yesterday = $dt->format(DateTime::COOKIE); + + $dt->modify('+2 days'); + $tomorrow = $dt->format(DateTime::COOKIE); + + $this->jar->store($base + array('expires' => $yesterday)); + $this->assertEquals(0, count($this->jar->getAll())); + + $this->jar->store($base + array('expires' => $tomorrow)); + $this->assertEquals(1, count($this->jar->getAll())); + $this->jar->store($base + array('expires' => $yesterday)); + $this->assertEquals(0, count($this->jar->getAll())); + } + + /** + * + * @dataProvider cookieAndSetterProvider + */ + public function testGetDomainAndPathFromSetter($cookie, $setter, $expected) + { + $this->jar->store($cookie, $setter); + $expected = array_merge($cookie, $expected); + $cookies = $this->jar->getAll(); + $this->assertEquals($expected, $cookies[0]); + } + + /** + * + * @dataProvider cookieMatchProvider + */ + public function testGetMatchingCookies($url, $expectedCount) + { + $cookies = array( + array('domain' => '.example.com', 'path' => '/', 'secure' => false), + array('domain' => '.example.com', 'path' => '/', 'secure' => true), + array('domain' => '.example.com', 'path' => '/path', 'secure' => false), + array('domain' => '.example.com', 'path' => '/other', 'secure' => false), + array('domain' => 'example.com', 'path' => '/', 'secure' => false), + array('domain' => 'www.example.com', 'path' => '/', 'secure' => false), + array('domain' => 'specific.example.com', 'path' => '/path', 'secure' => false), + array('domain' => 'nowww.example.com', 'path' => '/', 'secure' => false), + ); + + for ($i = 0; $i < count($cookies); $i++) { + $this->jar->store($cookies[$i] + array('expires' => null, 'name' => "cookie{$i}", 'value' => "cookie_{$i}_value")); + } + + $this->assertEquals($expectedCount, count($this->jar->getMatching(new Net_URL2($url)))); + } + + public function testLongestPathFirst() + { + $cookie = array( + 'name' => 'foo', + 'domain' => '.example.com', + ); + foreach (array('/', '/specific/path/', '/specific/') as $path) { + $this->jar->store($cookie + array('path' => $path, 'value' => str_replace('/', '_', $path))); + } + $this->assertEquals( + 'foo=_specific_path_; foo=_specific_; foo=_', + $this->jar->getMatching(new Net_URL2('http://example.com/specific/path/file.php'), true) + ); + } + + public function testSerializable() + { + $dt = new DateTime(); + $dt->setTimezone(new DateTimeZone('UTC')); + $dt->modify('+1 day'); + $cookie = array('domain' => '.example.com', 'path' => '/', 'secure' => false, 'value' => 'foo'); + + $this->jar->store($cookie + array('name' => 'session', 'expires' => null)); + $this->jar->store($cookie + array('name' => 'long', 'expires' => $dt->format(DateTime::COOKIE))); + + $newJar = unserialize(serialize($this->jar)); + $cookies = $newJar->getAll(); + $this->assertEquals(1, count($cookies)); + $this->assertEquals('long', $cookies[0]['name']); + + $this->jar->serializeSessionCookies(true); + $newJar = unserialize(serialize($this->jar)); + $this->assertEquals($this->jar->getAll(), $newJar->getAll()); + } + + public function testRemoveExpiredOnUnserialize() + { + $dt = new DateTime(); + $dt->setTimezone(new DateTimeZone('UTC')); + $dt->modify('+2 seconds'); + + $this->jar->store(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'expires' => $dt->format(DateTime::COOKIE), + )); + + $serialized = serialize($this->jar); + sleep(2); + $newJar = unserialize($serialized); + $this->assertEquals(array(), $newJar->getAll()); + } + + public static function invalidCookieProvider() + { + return array( + array(array()), + array(array('name' => 'foo')), + array(array( + 'name' => 'a name', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + )), + array(array( + 'name' => 'foo', + 'value' => 'a value', + 'domain' => '.example.com', + 'path' => '/', + )), + array(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => null, + )), + array(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => null, + 'path' => '/', + )), + array(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'expires' => 'invalid date', + )), + ); + } + + public static function noPSLdomainsProvider() + { + return array( + array('localhost', 'localhost', true), + array('www.example.com', 'www.example.com', true), + array('127.0.0.1', '127.0.0.1', true), + array('127.0.0.1', '.0.0.1', false), + array('www.example.com', '.example.com', true), + array('deep.within.example.com', '.example.com', true), + array('example.com', '.com', false), + array('anotherexample.com', 'example.com', false), + array('whatever.msk.ru', '.msk.ru', true), + array('whatever.co.uk', '.co.uk', true), + array('whatever.uk', '.whatever.uk', true), + array('whatever.tokyo.jp', '.whatever.tokyo.jp', true), + array('metro.tokyo.jp', '.metro.tokyo.jp', true), + array('foo.bar', '.foo.bar', true) + ); + } + + public static function PSLdomainsProvider() + { + return array( + array('localhost', 'localhost', true), + array('www.example.com', 'www.example.com', true), + array('127.0.0.1', '127.0.0.1', true), + array('127.0.0.1', '.0.0.1', false), + array('www.example.com', '.example.com', true), + array('deep.within.example.com', '.example.com', true), + array('example.com', '.com', false), + array('anotherexample.com', 'example.com', false), + array('whatever.msk.ru', '.msk.ru', false), + array('whatever.co.uk', '.co.uk', false), + array('whatever.uk', '.whatever.uk', false), + array('whatever.tokyo.jp', '.whatever.tokyo.jp', false), + array('metro.tokyo.jp', '.metro.tokyo.jp', true), + array('foo.bar', '.foo.bar', true) + ); + } + + public static function cookieAndSetterProvider() + { + return array( + array( + array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => null, + 'path' => null, + 'expires' => null, + 'secure' => false + ), + new Net_Url2('http://example.com/directory/file.php'), + array( + 'domain' => 'example.com', + 'path' => '/directory/' + ) + ), + array( + array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => null, + 'expires' => null, + 'secure' => false + ), + new Net_Url2('http://example.com/path/to/file.php'), + array( + 'path' => '/path/to/' + ) + ), + array( + array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => null, + 'path' => '/', + 'expires' => null, + 'secure' => false + ), + new Net_Url2('http://example.com/another/file.php'), + array( + 'domain' => 'example.com' + ) + ) + ); + } + + public static function cookieMatchProvider() + { + return array( + array('http://www.example.com/path/file.php', 4), + array('https://www.example.com/path/file.php', 5), + array('http://example.com/path/file.php', 3), + array('http://specific.example.com/path/file.php', 4), + array('http://specific.example.com/other/file.php', 3), + array('http://another.example.com/another', 2) + ); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/MultipartBodyTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/MultipartBodyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..585800fbf54bbdd06f82302033898e5771b90470 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/MultipartBodyTest.php @@ -0,0 +1,125 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: MultipartBodyTest.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class representing a HTTP request + */ +require_once 'HTTP/Request2.php'; + +/** Helper for PHPUnit includes */ +require_once dirname(dirname(__FILE__)) . '/TestHelper.php'; + +/** + * Unit test for HTTP_Request2_MultipartBody class + */ +class HTTP_Request2_MultipartBodyTest extends PHPUnit_Framework_TestCase +{ + public function testUploadSimple() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $body = $req->addPostParameter('foo', 'I am a parameter') + ->addUpload('upload', dirname(dirname(__FILE__)) . '/_files/plaintext.txt') + ->getBody(); + + $this->assertTrue($body instanceof HTTP_Request2_MultipartBody); + $asString = $body->__toString(); + $boundary = $body->getBoundary(); + $this->assertEquals($body->getLength(), strlen($asString)); + $this->assertContains('This is a test.', $asString); + $this->assertContains('I am a parameter', $asString); + $this->assertRegexp("!--{$boundary}--\r\n$!", $asString); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testRequest16863() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $fp = fopen(dirname(dirname(__FILE__)) . '/_files/plaintext.txt', 'rb'); + $body = $req->addUpload('upload', $fp) + ->getBody(); + + $asString = $body->__toString(); + $this->assertContains('name="upload"; filename="anonymous.blob"', $asString); + $this->assertContains('This is a test.', $asString); + + $req->addUpload('bad_upload', fopen('php://input', 'rb')); + } + + public function testStreaming() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $body = $req->addPostParameter('foo', 'I am a parameter') + ->addUpload('upload', dirname(dirname(__FILE__)) . '/_files/plaintext.txt') + ->getBody(); + $asString = ''; + while ($part = $body->read(10)) { + $asString .= $part; + } + $this->assertEquals($body->getLength(), strlen($asString)); + $this->assertContains('This is a test.', $asString); + $this->assertContains('I am a parameter', $asString); + } + + public function testUploadArray() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $body = $req->addUpload('upload', array( + array(dirname(dirname(__FILE__)) . '/_files/plaintext.txt', 'bio.txt', 'text/plain'), + array(fopen(dirname(dirname(__FILE__)) . '/_files/empty.gif', 'rb'), 'photo.gif', 'image/gif') + )) + ->getBody(); + $asString = $body->__toString(); + $this->assertContains(file_get_contents(dirname(dirname(__FILE__)) . '/_files/empty.gif'), $asString); + $this->assertContains('name="upload[0]"; filename="bio.txt"', $asString); + $this->assertContains('name="upload[1]"; filename="photo.gif"', $asString); + + $body2 = $req->setConfig(array('use_brackets' => false))->getBody(); + $asString = $body2->__toString(); + $this->assertContains('name="upload"; filename="bio.txt"', $asString); + $this->assertContains('name="upload"; filename="photo.gif"', $asString); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2/ResponseTest.php b/lib/tests/HTTP_Request2/HTTP/Request2/ResponseTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a4898f193d36c6d9d8dd0f99e75d740d0e5193ce --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2/ResponseTest.php @@ -0,0 +1,151 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: ResponseTest.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class representing a HTTP response + */ +require_once 'HTTP/Request2/Response.php'; + +/** Helper for PHPUnit includes */ +require_once dirname(dirname(__FILE__)) . '/TestHelper.php'; + +/** + * Unit test for HTTP_Request2_Response class + */ +class HTTP_Request2_ResponseTest extends PHPUnit_Framework_TestCase +{ + /** + * + * @expectedException HTTP_Request2_MessageException + */ + public function testParseStatusLine() + { + $response = new HTTP_Request2_Response('HTTP/1.1 200 OK'); + $this->assertEquals('1.1', $response->getVersion()); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('OK', $response->getReasonPhrase()); + + $response2 = new HTTP_Request2_Response('HTTP/1.2 222 Nishtyak!'); + $this->assertEquals('1.2', $response2->getVersion()); + $this->assertEquals(222, $response2->getStatus()); + $this->assertEquals('Nishtyak!', $response2->getReasonPhrase()); + + $response3 = new HTTP_Request2_Response('Invalid status line'); + } + + public function testParseHeaders() + { + $response = $this->readResponseFromFile('response_headers'); + $this->assertEquals(7, count($response->getHeader())); + $this->assertEquals('PHP/6.2.2', $response->getHeader('X-POWERED-BY')); + $this->assertEquals('text/html; charset=windows-1251', $response->getHeader('cOnTeNt-TyPe')); + $this->assertEquals('accept-charset, user-agent', $response->getHeader('vary')); + } + + public function testParseCookies() + { + $response = $this->readResponseFromFile('response_cookies'); + $cookies = $response->getCookies(); + $this->assertEquals(4, count($cookies)); + $expected = array( + array('name' => 'foo', 'value' => 'bar', 'expires' => null, + 'domain' => null, 'path' => null, 'secure' => false), + array('name' => 'PHPSESSID', 'value' => '1234567890abcdef1234567890abcdef', + 'expires' => null, 'domain' => null, 'path' => '/', 'secure' => true), + array('name' => 'A', 'value' => 'B=C', 'expires' => null, + 'domain' => null, 'path' => null, 'secure' => false), + array('name' => 'baz', 'value' => '%20a%20value', 'expires' => 'Sun, 03 Jan 2010 03:04:05 GMT', + 'domain' => 'pear.php.net', 'path' => null, 'secure' => false), + ); + foreach ($cookies as $k => $cookie) { + $this->assertEquals($expected[$k], $cookie); + } + } + + /** + * + * @expectedException HTTP_Request2_MessageException + */ + public function testGzipEncoding() + { + $response = $this->readResponseFromFile('response_gzip'); + $this->assertEquals('0e964e9273c606c46afbd311b5ad4d77', md5($response->getBody())); + + $response = $this->readResponseFromFile('response_gzip_broken'); + $body = $response->getBody(); + } + + public function testDeflateEncoding() + { + $response = $this->readResponseFromFile('response_deflate'); + $this->assertEquals('0e964e9273c606c46afbd311b5ad4d77', md5($response->getBody())); + } + + public function testBug15305() + { + $response = $this->readResponseFromFile('bug_15305'); + $this->assertEquals('c8c5088fc8a7652afef380f086c010a6', md5($response->getBody())); + } + + public function testBug18169() + { + $response = $this->readResponseFromFile('bug_18169'); + $this->assertEquals('', $response->getBody()); + } + + protected function readResponseFromFile($filename) + { + $fp = fopen(dirname(dirname(__FILE__)) . '/_files/' . $filename, 'rb'); + $response = new HTTP_Request2_Response(fgets($fp)); + do { + $headerLine = fgets($fp); + $response->parseHeaderLine($headerLine); + } while ('' != trim($headerLine)); + + while (!feof($fp)) { + $response->appendBody(fread($fp, 1024)); + } + return $response; + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/Request2Test.php b/lib/tests/HTTP_Request2/HTTP/Request2Test.php new file mode 100644 index 0000000000000000000000000000000000000000..10fc934ff1e774b80aa45b5d86179105244445b7 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/Request2Test.php @@ -0,0 +1,381 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: Request2Test.php 309665 2011-03-24 21:03:48Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Class representing a HTTP request + */ +require_once 'HTTP/Request2.php'; + +/** Helper for PHPUnit includes */ +require_once dirname(__FILE__) . '/TestHelper.php'; + +/** + * Unit test for HTTP_Request2 class + */ +class HTTP_Request2Test extends PHPUnit_Framework_TestCase +{ + public function testConstructorSetsDefaults() + { + $url = new Net_URL2('http://www.example.com/foo'); + $req = new HTTP_Request2($url, HTTP_Request2::METHOD_POST, array('connect_timeout' => 666)); + + $this->assertSame($url, $req->getUrl()); + $this->assertEquals(HTTP_Request2::METHOD_POST, $req->getMethod()); + $this->assertEquals(666, $req->getConfig('connect_timeout')); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testSetUrl() + { + $urlString = 'http://www.example.com/foo/bar.php'; + $url = new Net_URL2($urlString); + + $req1 = new HTTP_Request2(); + $req1->setUrl($url); + $this->assertSame($url, $req1->getUrl()); + + $req2 = new HTTP_Request2(); + $req2->setUrl($urlString); + $this->assertType('Net_URL2', $req2->getUrl()); + $this->assertEquals($urlString, $req2->getUrl()->getUrl()); + + $req3 = new HTTP_Request2(); + $req3->setUrl(array('This will cause an error')); + } + + public function testConvertUserinfoToAuth() + { + $req = new HTTP_Request2(); + $req->setUrl('http://foo:b%40r@www.example.com/'); + + $this->assertEquals('', (string)$req->getUrl()->getUserinfo()); + $this->assertEquals( + array('user' => 'foo', 'password' => 'b@r', 'scheme' => HTTP_Request2::AUTH_BASIC), + $req->getAuth() + ); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testSetMethod() + { + $req = new HTTP_Request2(); + $req->setMethod(HTTP_Request2::METHOD_PUT); + $this->assertEquals(HTTP_Request2::METHOD_PUT, $req->getMethod()); + + $req->setMethod('Invalid method'); + } + + public function testSetAndGetConfig() + { + $req = new HTTP_Request2(); + $this->assertArrayHasKey('connect_timeout', $req->getConfig()); + + $req->setConfig(array('connect_timeout' => 123)); + $this->assertEquals(123, $req->getConfig('connect_timeout')); + try { + $req->setConfig(array('foo' => 'unknown parameter')); + $this->fail('Expected HTTP_Request2_LogicException was not thrown'); + } catch (HTTP_Request2_LogicException $e) {} + + try { + $req->getConfig('bar'); + $this->fail('Expected HTTP_Request2_LogicException was not thrown'); + } catch (HTTP_Request2_LogicException $e) {} + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testHeaders() + { + $req = new HTTP_Request2(); + $autoHeaders = $req->getHeaders(); + + $req->setHeader('Foo', 'Bar'); + $req->setHeader('Foo-Bar: value'); + $req->setHeader(array('Another-Header' => 'another value', 'Yet-Another: other_value')); + $this->assertEquals( + array('foo-bar' => 'value', 'another-header' => 'another value', + 'yet-another' => 'other_value', 'foo' => 'Bar') + $autoHeaders, + $req->getHeaders() + ); + + $req->setHeader('FOO-BAR'); + $req->setHeader(array('aNOTHER-hEADER')); + $this->assertEquals( + array('yet-another' => 'other_value', 'foo' => 'Bar') + $autoHeaders, + $req->getHeaders() + ); + + $req->setHeader('Invalid header', 'value'); + } + + public function testBug15937() + { + $req = new HTTP_Request2(); + $autoHeaders = $req->getHeaders(); + + $req->setHeader('Expect: '); + $req->setHeader('Foo', ''); + $this->assertEquals( + array('expect' => '', 'foo' => '') + $autoHeaders, + $req->getHeaders() + ); + } + + public function testRequest17507() + { + $req = new HTTP_Request2(); + + $req->setHeader('accept-charset', 'iso-8859-1'); + $req->setHeader('accept-charset', array('windows-1251', 'utf-8'), false); + + $req->setHeader(array('accept' => 'text/html')); + $req->setHeader(array('accept' => 'image/gif'), null, false); + + $headers = $req->getHeaders(); + + $this->assertEquals('iso-8859-1, windows-1251, utf-8', $headers['accept-charset']); + $this->assertEquals('text/html, image/gif', $headers['accept']); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testCookies() + { + $req = new HTTP_Request2(); + $req->addCookie('name', 'value'); + $req->addCookie('foo', 'bar'); + $headers = $req->getHeaders(); + $this->assertEquals('name=value; foo=bar', $headers['cookie']); + + $req->addCookie('invalid cookie', 'value'); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testPlainBody() + { + $req = new HTTP_Request2(); + $req->setBody('A string'); + $this->assertEquals('A string', $req->getBody()); + + $req->setBody(dirname(__FILE__) . '/_files/plaintext.txt', true); + $headers = $req->getHeaders(); + $this->assertRegexp( + '!^(text/plain|application/octet-stream)!', + $headers['content-type'] + ); + $this->assertEquals('This is a test.', fread($req->getBody(), 1024)); + + $req->setBody('missing file', true); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testRequest16863() + { + $req = new HTTP_Request2(); + $req->setBody(fopen(dirname(__FILE__) . '/_files/plaintext.txt', 'rb')); + $headers = $req->getHeaders(); + $this->assertEquals('application/octet-stream', $headers['content-type']); + + $req->setBody(fopen('php://input', 'rb')); + } + + public function testUrlencodedBody() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $req->addPostParameter('foo', 'bar'); + $req->addPostParameter(array('baz' => 'quux')); + $req->addPostParameter('foobar', array('one', 'two')); + $this->assertEquals( + 'foo=bar&baz=quux&foobar%5B0%5D=one&foobar%5B1%5D=two', + $req->getBody() + ); + + $req->setConfig(array('use_brackets' => false)); + $this->assertEquals( + 'foo=bar&baz=quux&foobar=one&foobar=two', + $req->getBody() + ); + } + + public function testRequest15368() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $req->addPostParameter('foo', 'te~st'); + $this->assertContains('~', $req->getBody()); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testUpload() + { + $req = new HTTP_Request2(null, HTTP_Request2::METHOD_POST); + $req->addUpload('upload', dirname(__FILE__) . '/_files/plaintext.txt'); + + $headers = $req->getHeaders(); + $this->assertEquals('multipart/form-data', $headers['content-type']); + + $req->addUpload('upload_2', 'missing file'); + } + + public function testPropagateUseBracketsToNetURL2() + { + $req = new HTTP_Request2('http://www.example.com/', HTTP_Request2::METHOD_GET, + array('use_brackets' => false)); + $req->getUrl()->setQueryVariable('foo', array('bar', 'baz')); + $this->assertEquals('http://www.example.com/?foo=bar&foo=baz', $req->getUrl()->__toString()); + + $req->setConfig('use_brackets', true)->setUrl('http://php.example.com/'); + $req->getUrl()->setQueryVariable('foo', array('bar', 'baz')); + $this->assertEquals('http://php.example.com/?foo[0]=bar&foo[1]=baz', $req->getUrl()->__toString()); + } + + public function testSetBodyRemovesPostParameters() + { + $req = new HTTP_Request2('http://www.example.com/', HTTP_Request2::METHOD_POST); + $req->addPostParameter('foo', 'bar'); + $req->setBody(''); + $this->assertEquals('', $req->getBody()); + } + + public function testPostParametersPrecedeSetBodyForPost() + { + $req = new HTTP_Request2('http://www.example.com/', HTTP_Request2::METHOD_POST); + $req->setBody('Request body'); + $req->addPostParameter('foo', 'bar'); + + $this->assertEquals('foo=bar', $req->getBody()); + + $req->setMethod(HTTP_Request2::METHOD_PUT); + $this->assertEquals('Request body', $req->getBody()); + } + + public function testSetMultipartBody() + { + require_once 'HTTP/Request2/MultipartBody.php'; + + $req = new HTTP_Request2('http://www.example.com/', HTTP_Request2::METHOD_POST); + $body = new HTTP_Request2_MultipartBody(array('foo' => 'bar'), array()); + $req->setBody($body); + $this->assertSame($body, $req->getBody()); + } + + public function testBug17460() + { + $req = new HTTP_Request2('http://www.example.com/', HTTP_Request2::METHOD_POST); + $req->addPostParameter('foo', 'bar') + ->setHeader('content-type', 'application/x-www-form-urlencoded; charset=UTF-8'); + + $this->assertEquals('foo=bar', $req->getBody()); + } + + /** + * + * @expectedException HTTP_Request2_LogicException + */ + public function testCookieJar() + { + $req = new HTTP_Request2(); + $this->assertNull($req->getCookieJar()); + + $req->setCookieJar(); + $jar = $req->getCookieJar(); + $this->assertType('HTTP_Request2_CookieJar', $jar); + + $req2 = new HTTP_Request2(); + $req2->setCookieJar($jar); + $this->assertSame($jar, $req2->getCookieJar()); + + $req2->setCookieJar(null); + $this->assertNull($req2->getCookieJar()); + + $req2->setCookieJar('foo'); + } + + public function testAddCookieToJar() + { + $req = new HTTP_Request2(); + $req->setCookieJar(); + + try { + $req->addCookie('foo', 'bar'); + $this->fail('Expected HTTP_Request2_Exception was not thrown'); + } catch (HTTP_Request2_LogicException $e) { } + + $req->setUrl('http://example.com/path/file.php'); + $req->addCookie('foo', 'bar'); + + $this->assertArrayNotHasKey('cookie', $req->getHeaders()); + $cookies = $req->getCookieJar()->getAll(); + $this->assertEquals( + array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'example.com', + 'path' => '/path/', + 'expires' => null, + 'secure' => false + ), + $cookies[0] + ); + } +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/TestHelper.php b/lib/tests/HTTP_Request2/HTTP/TestHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..bbd2bf635fe0800bef8a1250c5896ad5be42be98 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/TestHelper.php @@ -0,0 +1,62 @@ +<?php +/** + * Unit tests for HTTP_Request2 package + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: TestHelper.php 309682 2011-03-25 09:53:38Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** Include PHPUnit dependencies based on version */ +require_once 'PHPUnit/Runner/Version.php'; + +$phpunitVersion = PHPUnit_Runner_Version::id(); +if ($phpunitVersion == '@' . 'package_version@' || !version_compare($phpunitVersion, '3.6', '<=')) { + echo "This version of PHPUnit is not supported."; + exit(1); +} elseif (version_compare($phpunitVersion, '3.5.0', '>=')) { + require_once 'PHPUnit/Autoload.php'; +} else { + require_once 'PHPUnit/Framework.php'; +} + +if (!defined('HTTP_REQUEST2_TESTS_BASE_URL') + && is_readable(dirname(__FILE__) . '/NetworkConfig.php') +) { + require_once dirname(__FILE__) . '/NetworkConfig.php'; +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_files/bug_15305 b/lib/tests/HTTP_Request2/HTTP/_files/bug_15305 new file mode 100644 index 0000000000000000000000000000000000000000..bbf6c70006b8062fd656063ee9cbbff16edf7f04 Binary files /dev/null and b/lib/tests/HTTP_Request2/HTTP/_files/bug_15305 differ diff --git a/lib/tests/HTTP_Request2/HTTP/_files/bug_18169 b/lib/tests/HTTP_Request2/HTTP/_files/bug_18169 new file mode 100644 index 0000000000000000000000000000000000000000..c50dae1472683f231d16dd5f70a713425cc259fa --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_files/bug_18169 @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Date: Fri, 01 Jan 2010 02:03:04 GMT +Server: Apache/3.0.1 (Unix) +X-Powered-By: PHP/6.2.2 +Content-Type: text/plain; charset=iso-8859-1 +Content-Encoding: deflate +Content-Length: 0 +Connection: close + diff --git a/lib/tests/HTTP_Request2/HTTP/_files/empty.gif b/lib/tests/HTTP_Request2/HTTP/_files/empty.gif new file mode 100644 index 0000000000000000000000000000000000000000..1d11fa9ada9e93505b3d736acb204083f45d5fbf Binary files /dev/null and b/lib/tests/HTTP_Request2/HTTP/_files/empty.gif differ diff --git a/lib/tests/HTTP_Request2/HTTP/_files/plaintext.txt b/lib/tests/HTTP_Request2/HTTP/_files/plaintext.txt new file mode 100644 index 0000000000000000000000000000000000000000..273c1a9ffdc201dbca7d19b275bf1cbc88aaa4cb --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_files/plaintext.txt @@ -0,0 +1 @@ +This is a test. \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_files/response_cookies b/lib/tests/HTTP_Request2/HTTP/_files/response_cookies new file mode 100644 index 0000000000000000000000000000000000000000..8f0ab6b64d490cd12b0adc653e03211a44822b4b --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_files/response_cookies @@ -0,0 +1,13 @@ +HTTP/1.1 200 OK +Date: Fri, 01 Jan 2010 02:03:04 GMT +Server: Apache/3.0.1 (Unix) +X-Powered-By: PHP/6.2.2 +Set-Cookie: foo=bar +Set-Cookie: PHPSESSID=1234567890abcdef1234567890abcdef; path=/; secure +Set-Cookie: A=B=C +Set-Cookie: baz=%20a%20value; expires=Sun, 03 Jan 2010 03:04:05 GMT; domain=pear.php.net +Content-Type: text/html; charset=windows-1251 +Content-Length: 32 +Connection: close + +Nothing to see here, move along. \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_files/response_deflate b/lib/tests/HTTP_Request2/HTTP/_files/response_deflate new file mode 100644 index 0000000000000000000000000000000000000000..e4e8adfd0d46af40addef96b0e95dad6243e6232 Binary files /dev/null and b/lib/tests/HTTP_Request2/HTTP/_files/response_deflate differ diff --git a/lib/tests/HTTP_Request2/HTTP/_files/response_gzip b/lib/tests/HTTP_Request2/HTTP/_files/response_gzip new file mode 100644 index 0000000000000000000000000000000000000000..79ad3b6eaac6737c9ae4a577347cb52e26ea5209 Binary files /dev/null and b/lib/tests/HTTP_Request2/HTTP/_files/response_gzip differ diff --git a/lib/tests/HTTP_Request2/HTTP/_files/response_gzip_broken b/lib/tests/HTTP_Request2/HTTP/_files/response_gzip_broken new file mode 100644 index 0000000000000000000000000000000000000000..0df0d1522f8a377ac905b20db95eb29dc1428493 Binary files /dev/null and b/lib/tests/HTTP_Request2/HTTP/_files/response_gzip_broken differ diff --git a/lib/tests/HTTP_Request2/HTTP/_files/response_headers b/lib/tests/HTTP_Request2/HTTP/_files/response_headers new file mode 100644 index 0000000000000000000000000000000000000000..f60787bd566db41bc4680d2e6a93aba607e3a34f --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_files/response_headers @@ -0,0 +1,12 @@ +HTTP/1.1 200 OK +Date: Fri, 01 Jan 2010 02:03:04 GMT +Vary: accept-charset +Server: Apache/3.0.1 (Unix) +X-Powered-By: PHP/6.2.2 +Vary: user-agent +Content-Type: text/html; + charset=windows-1251 +Content-Length: 32 +Connection: close + +Nothing to see here, move along. \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/basicauth.php b/lib/tests/HTTP_Request2/HTTP/_network/basicauth.php new file mode 100644 index 0000000000000000000000000000000000000000..383a78525068dcd3879dac61fb13087274191987 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/basicauth.php @@ -0,0 +1,56 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: basicauth.php 308300 2011-02-13 12:24:18Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +$user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null; +$pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null; +$wantedUser = isset($_GET['user']) ? $_GET['user'] : null; +$wantedPass = isset($_GET['pass']) ? $_GET['pass'] : null; + +if (!$user || !$pass || $user != $wantedUser || $pass != $wantedPass) { + header('WWW-Authenticate: Basic realm="HTTP_Request2 tests"', true, 401); + echo "Login required"; +} else { + echo "Username={$user};Password={$pass}"; +} + +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/cookies.php b/lib/tests/HTTP_Request2/HTTP/_network/cookies.php new file mode 100644 index 0000000000000000000000000000000000000000..5ab966c5b5856ccc832f6e7fadc31f9342976e09 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/cookies.php @@ -0,0 +1,47 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: cookies.php 308299 2011-02-12 23:20:23Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +ksort($_COOKIE); +echo serialize($_COOKIE); + +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/digestauth.php b/lib/tests/HTTP_Request2/HTTP/_network/digestauth.php new file mode 100644 index 0000000000000000000000000000000000000000..463f25a42f8a670ffc610363af3fa1614bd2bb14 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/digestauth.php @@ -0,0 +1,106 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: digestauth.php 308300 2011-02-13 12:24:18Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Mostly borrowed from PHP manual and Socket Adapter implementation + * + * @link http://php.net/manual/en/features.http-auth.php + */ + +/** + * Parses the Digest auth header + * + * @param string $txt + */ +function http_digest_parse($txt) +{ + $token = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+'; + $quoted = '"(?:\\\\.|[^\\\\"])*"'; + + // protect against missing data + $needed_parts = array_flip(array('nonce', 'nc', 'cnonce', 'qop', 'username', 'uri', 'response')); + $data = array(); + + preg_match_all("!({$token})\\s*=\\s*({$token}|{$quoted})!", $txt, $matches); + for ($i = 0; $i < count($matches[0]); $i++) { + // ignore unneeded parameters + if (isset($needed_parts[$matches[1][$i]])) { + unset($needed_parts[$matches[1][$i]]); + if ('"' == substr($matches[2][$i], 0, 1)) { + $data[$matches[1][$i]] = substr($matches[2][$i], 1, -1); + } else { + $data[$matches[1][$i]] = $matches[2][$i]; + } + } + } + + return !empty($needed_parts) ? false : $data; +} + +$realm = 'HTTP_Request2 tests'; +$wantedUser = isset($_GET['user']) ? $_GET['user'] : null; +$wantedPass = isset($_GET['pass']) ? $_GET['pass'] : null; +$validAuth = false; + +if (!empty($_SERVER['PHP_AUTH_DIGEST']) + && ($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) + && $wantedUser == $data['username'] +) { + // generate the valid response + $a1 = md5($data['username'] . ':' . $realm . ':' . $wantedPass); + $a2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']); + $response = md5($a1. ':' . $data['nonce'] . ':' . $data['nc'] . ':' + . $data['cnonce'] . ':' . $data['qop'] . ':' . $a2); + + // check valid response against existing one + $validAuth = ($data['response'] == $response); +} + +if (!$validAuth || empty($_SERVER['PHP_AUTH_DIGEST'])) { + header('WWW-Authenticate: Digest realm="' . $realm . + '",qop="auth",nonce="' . uniqid() . '"', true, 401); + echo "Login required"; +} else { + echo "Username={$user}"; +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/getparameters.php b/lib/tests/HTTP_Request2/HTTP/_network/getparameters.php new file mode 100644 index 0000000000000000000000000000000000000000..334a132e57deb6c1dae224cbccd18d57d6ba0e2a --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/getparameters.php @@ -0,0 +1,47 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: getparameters.php 308299 2011-02-12 23:20:23Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +ksort($_GET); +echo serialize($_GET); + +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/postparameters.php b/lib/tests/HTTP_Request2/HTTP/_network/postparameters.php new file mode 100644 index 0000000000000000000000000000000000000000..7037d889854669cf724a7df6d65850979f52b5d4 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/postparameters.php @@ -0,0 +1,47 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: postparameters.php 308300 2011-02-13 12:24:18Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +ksort($_POST); +echo serialize($_POST); + +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/rawpostdata.php b/lib/tests/HTTP_Request2/HTTP/_network/rawpostdata.php new file mode 100644 index 0000000000000000000000000000000000000000..e6a1d26b4fa8cddad1b6cc5053bd056f8b760c78 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/rawpostdata.php @@ -0,0 +1,45 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: rawpostdata.php 308300 2011-02-13 12:24:18Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +readfile('php://input'); +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/redirects.php b/lib/tests/HTTP_Request2/HTTP/_network/redirects.php new file mode 100644 index 0000000000000000000000000000000000000000..f7a7f815e68514c68c6eca438dfb963a0cab30bf --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/redirects.php @@ -0,0 +1,70 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: redirects.php 308480 2011-02-19 11:27:13Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +$redirects = isset($_GET['redirects'])? $_GET['redirects']: 1; +$https = !empty($_SERVER['HTTPS']) && ('off' != strtolower($_SERVER['HTTPS'])); +$special = isset($_GET['special'])? $_GET['special']: null; + +if ('ftp' == $special) { + header('Location: ftp://localhost/pub/exploit.exe', true, 301); + +} elseif ('relative' == $special) { + header('Location: ./getparameters.php?msg=did%20relative%20redirect', true, 302); + +} elseif ('cookie' == $special) { + setcookie('cookie_on_redirect', 'success'); + header('Location: ./cookies.php', true, 302); + +} elseif ($redirects > 0) { + $url = ($https? 'https': 'http') . '://' . $_SERVER['SERVER_NAME'] + . (($https && 443 == $_SERVER['SERVER_PORT'] || !$https && 80 == $_SERVER['SERVER_PORT']) + ? '' : ':' . $_SERVER['SERVER_PORT']) + . $_SERVER['PHP_SELF'] . '?redirects=' . (--$redirects); + header('Location: ' . $url, true, 302); + +} else { + echo "Method=" . $_SERVER['REQUEST_METHOD'] . ';'; + var_dump($_POST); + var_dump($_GET); +} +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/setcookie.php b/lib/tests/HTTP_Request2/HTTP/_network/setcookie.php new file mode 100644 index 0000000000000000000000000000000000000000..bece10339869839d4811ad0a0f997653f46bd608 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/setcookie.php @@ -0,0 +1,50 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: setcookie.php 308480 2011-02-19 11:27:13Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +$name = empty($_GET['name'])? 'foo': $_GET['name']; +$value = empty($_GET['value'])? 'bar': $_GET['value']; + +setcookie($name, $value); + +echo "Cookie set!"; +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/timeout.php b/lib/tests/HTTP_Request2/HTTP/_network/timeout.php new file mode 100644 index 0000000000000000000000000000000000000000..f99d871d006e02a263807521405b8b7757a13cbf --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/timeout.php @@ -0,0 +1,46 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: timeout.php 308299 2011-02-12 23:20:23Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +sleep(5); + +?> \ No newline at end of file diff --git a/lib/tests/HTTP_Request2/HTTP/_network/uploads.php b/lib/tests/HTTP_Request2/HTTP/_network/uploads.php new file mode 100644 index 0000000000000000000000000000000000000000..7a124de73a1b9acc94c26f85a2babdfe6afca540 --- /dev/null +++ b/lib/tests/HTTP_Request2/HTTP/_network/uploads.php @@ -0,0 +1,55 @@ +<?php +/** + * Helper files for HTTP_Request2 unit tests. Should be accessible via HTTP. + * + * PHP version 5 + * + * LICENSE: + * + * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net> + * 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. + * * The names of the authors may not 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. + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov <avb@php.net> + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version SVN: $Id: uploads.php 308300 2011-02-13 12:24:18Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +if (!empty($_FILES)) { + foreach ($_FILES as $name => $file) { + if (is_array($file['name'])) { + foreach($file['name'] as $k => $v) { + echo "{$name}[{$k}] {$v} {$file['type'][$k]} {$file['size'][$k]}\n"; + } + } else { + echo "{$name} {$file['name']} {$file['type']} {$file['size']}\n"; + } + } +} +?> \ No newline at end of file