From 5acc58092025d08753f59c0a19f24e3a785b1ecb Mon Sep 17 00:00:00 2001 From: Eric Rasmussen <erasmussen2@unl.edu> Date: Wed, 27 Apr 2011 19:10:58 +0000 Subject: [PATCH] Add UNL_Auth and SimpleCAS packages to lib --- .../configsnapshot-2011-04-27 13-54-20.xml | 2 + lib/.pear2registry | Bin 199680 -> 252928 bytes .../channel-simplecas.googlecode.com##svn.xml | 15 + .../channels/channelalias-simplecas.txt | 1 + .../HTTP_Request2/2.0.0beta3-info.xml | 456 ++ .../pear.php.net/Net_URL2/0.3.1-info.xml | 100 + .../pear.unl.edu/UNL_Auth/0.4.0-info.xml | 178 + .../SimpleCAS/0.5.0-info.xml | 225 + lib/data/HTTP_Request2/HTTP/generate-list.php | 98 + .../HTTP_Request2/HTTP/public-suffix-list.php | 4464 +++++++++++++++++ .../HTTP/examples/upload-rapidshare.php | 60 + lib/docs/Net_URL2/Net/docs/6470.php | 75 + lib/docs/Net_URL2/Net/docs/example.php | 74 + .../examples/Zend_Auth_Adapter_SimpleCAS.php | 134 + lib/docs/SimpleCAS/docs/examples/simple.php | 27 + .../docs/examples/CASPEARAuth_example.php | 26 + .../UNL_Auth/docs/examples/CAS_example.php | 22 + .../docs/examples/SimpleCAS_example.php | 22 + .../docs/examples/Zend_SimpleCAS_example.php | 26 + lib/downloads/HTTP_Request2-2.0.0beta3.tgz | Bin 0 -> 95266 bytes lib/downloads/Net_URL2-0.3.1.tgz | Bin 0 -> 8488 bytes lib/downloads/SimpleCAS-0.5.0.tgz | Bin 0 -> 7565 bytes lib/downloads/UNL_Auth-0.4.0.tgz | Bin 0 -> 5633 bytes lib/php/HTTP/Request2.php | 1015 ++++ lib/php/HTTP/Request2/Adapter.php | 154 + lib/php/HTTP/Request2/Adapter/Curl.php | 562 +++ lib/php/HTTP/Request2/Adapter/Mock.php | 171 + lib/php/HTTP/Request2/Adapter/Socket.php | 1084 ++++ lib/php/HTTP/Request2/CookieJar.php | 499 ++ lib/php/HTTP/Request2/Exception.php | 160 + lib/php/HTTP/Request2/MultipartBody.php | 274 + lib/php/HTTP/Request2/Observer/Log.php | 215 + lib/php/HTTP/Request2/Response.php | 629 +++ lib/php/Net/URL2.php | 894 ++++ lib/php/SimpleCAS.php | 274 + lib/php/SimpleCAS/Autoload.php | 62 + lib/php/SimpleCAS/Protocol.php | 83 + lib/php/SimpleCAS/Protocol/Version1.php | 133 + lib/php/SimpleCAS/Protocol/Version2.php | 94 + .../Protocol/Version2/ValidationResponse.php | 62 + lib/php/SimpleCAS/ProxyGranting.php | 31 + lib/php/SimpleCAS/ProxyGranting/Storage.php | 7 + .../SimpleCAS/ProxyGranting/Storage/File.php | 14 + lib/php/SimpleCAS/SingleSignOut.php | 24 + lib/php/UNL/Auth.php | 118 + lib/php/UNL/Auth/CAS.php | 157 + lib/php/UNL/Auth/CAS/PEARAuth.php | 65 + lib/php/UNL/Auth/SimpleCAS.php | 107 + lib/php/UNL/Auth/SimpleCAS/ZendAuth.php | 78 + lib/tests/HTTP_Request2/HTTP/AllTests.php | 77 + .../HTTP_Request2/HTTP/NetworkConfig.php.dist | 72 + lib/tests/HTTP_Request2/HTTP/ObserverTest.php | 118 + .../HTTP/Request2/Adapter/AllTests.php | 93 + .../Request2/Adapter/CommonNetworkTest.php | 309 ++ .../HTTP/Request2/Adapter/CurlTest.php | 143 + .../HTTP/Request2/Adapter/MockTest.php | 145 + .../HTTP/Request2/Adapter/SkippedTests.php | 79 + .../HTTP/Request2/Adapter/SocketProxyTest.php | 77 + .../HTTP/Request2/Adapter/SocketTest.php | 78 + .../HTTP_Request2/HTTP/Request2/AllTests.php | 79 + .../HTTP/Request2/CookieJarTest.php | 393 ++ .../HTTP/Request2/MultipartBodyTest.php | 125 + .../HTTP/Request2/ResponseTest.php | 151 + lib/tests/HTTP_Request2/HTTP/Request2Test.php | 381 ++ lib/tests/HTTP_Request2/HTTP/TestHelper.php | 62 + lib/tests/HTTP_Request2/HTTP/_files/bug_15305 | Bin 0 -> 16338 bytes lib/tests/HTTP_Request2/HTTP/_files/bug_18169 | 9 + lib/tests/HTTP_Request2/HTTP/_files/empty.gif | Bin 0 -> 43 bytes .../HTTP_Request2/HTTP/_files/plaintext.txt | 1 + .../HTTP/_files/response_cookies | 13 + .../HTTP/_files/response_deflate | Bin 0 -> 1654 bytes .../HTTP_Request2/HTTP/_files/response_gzip | Bin 0 -> 1672 bytes .../HTTP/_files/response_gzip_broken | Bin 0 -> 221 bytes .../HTTP/_files/response_headers | 12 + .../HTTP_Request2/HTTP/_network/basicauth.php | 56 + .../HTTP_Request2/HTTP/_network/cookies.php | 47 + .../HTTP/_network/digestauth.php | 106 + .../HTTP/_network/getparameters.php | 47 + .../HTTP/_network/postparameters.php | 47 + .../HTTP/_network/rawpostdata.php | 45 + .../HTTP_Request2/HTTP/_network/redirects.php | 70 + .../HTTP_Request2/HTTP/_network/setcookie.php | 50 + .../HTTP_Request2/HTTP/_network/timeout.php | 46 + .../HTTP_Request2/HTTP/_network/uploads.php | 55 + 84 files changed, 15957 insertions(+) create mode 100644 lib/.configsnapshots/configsnapshot-2011-04-27 13-54-20.xml create mode 100644 lib/.xmlregistry/channels/channel-simplecas.googlecode.com##svn.xml create mode 100644 lib/.xmlregistry/channels/channelalias-simplecas.txt create mode 100644 lib/.xmlregistry/packages/pear.php.net/HTTP_Request2/2.0.0beta3-info.xml create mode 100644 lib/.xmlregistry/packages/pear.php.net/Net_URL2/0.3.1-info.xml create mode 100644 lib/.xmlregistry/packages/pear.unl.edu/UNL_Auth/0.4.0-info.xml create mode 100644 lib/.xmlregistry/packages/simplecas.googlecode.com!svn/SimpleCAS/0.5.0-info.xml create mode 100644 lib/data/HTTP_Request2/HTTP/generate-list.php create mode 100644 lib/data/HTTP_Request2/HTTP/public-suffix-list.php create mode 100644 lib/docs/HTTP_Request2/HTTP/examples/upload-rapidshare.php create mode 100644 lib/docs/Net_URL2/Net/docs/6470.php create mode 100644 lib/docs/Net_URL2/Net/docs/example.php create mode 100644 lib/docs/SimpleCAS/docs/examples/Zend_Auth_Adapter_SimpleCAS.php create mode 100644 lib/docs/SimpleCAS/docs/examples/simple.php create mode 100644 lib/docs/UNL_Auth/docs/examples/CASPEARAuth_example.php create mode 100644 lib/docs/UNL_Auth/docs/examples/CAS_example.php create mode 100644 lib/docs/UNL_Auth/docs/examples/SimpleCAS_example.php create mode 100644 lib/docs/UNL_Auth/docs/examples/Zend_SimpleCAS_example.php create mode 100644 lib/downloads/HTTP_Request2-2.0.0beta3.tgz create mode 100644 lib/downloads/Net_URL2-0.3.1.tgz create mode 100644 lib/downloads/SimpleCAS-0.5.0.tgz create mode 100644 lib/downloads/UNL_Auth-0.4.0.tgz create mode 100644 lib/php/HTTP/Request2.php create mode 100644 lib/php/HTTP/Request2/Adapter.php create mode 100644 lib/php/HTTP/Request2/Adapter/Curl.php create mode 100644 lib/php/HTTP/Request2/Adapter/Mock.php create mode 100644 lib/php/HTTP/Request2/Adapter/Socket.php create mode 100644 lib/php/HTTP/Request2/CookieJar.php create mode 100644 lib/php/HTTP/Request2/Exception.php create mode 100644 lib/php/HTTP/Request2/MultipartBody.php create mode 100644 lib/php/HTTP/Request2/Observer/Log.php create mode 100644 lib/php/HTTP/Request2/Response.php create mode 100644 lib/php/Net/URL2.php create mode 100644 lib/php/SimpleCAS.php create mode 100644 lib/php/SimpleCAS/Autoload.php create mode 100644 lib/php/SimpleCAS/Protocol.php create mode 100644 lib/php/SimpleCAS/Protocol/Version1.php create mode 100644 lib/php/SimpleCAS/Protocol/Version2.php create mode 100644 lib/php/SimpleCAS/Protocol/Version2/ValidationResponse.php create mode 100644 lib/php/SimpleCAS/ProxyGranting.php create mode 100644 lib/php/SimpleCAS/ProxyGranting/Storage.php create mode 100644 lib/php/SimpleCAS/ProxyGranting/Storage/File.php create mode 100644 lib/php/SimpleCAS/SingleSignOut.php create mode 100644 lib/php/UNL/Auth.php create mode 100644 lib/php/UNL/Auth/CAS.php create mode 100644 lib/php/UNL/Auth/CAS/PEARAuth.php create mode 100644 lib/php/UNL/Auth/SimpleCAS.php create mode 100644 lib/php/UNL/Auth/SimpleCAS/ZendAuth.php create mode 100644 lib/tests/HTTP_Request2/HTTP/AllTests.php create mode 100644 lib/tests/HTTP_Request2/HTTP/NetworkConfig.php.dist create mode 100644 lib/tests/HTTP_Request2/HTTP/ObserverTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/AllTests.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CommonNetworkTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/CurlTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/MockTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SkippedTests.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketProxyTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/Adapter/SocketTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/AllTests.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/CookieJarTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/MultipartBodyTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2/ResponseTest.php create mode 100644 lib/tests/HTTP_Request2/HTTP/Request2Test.php create mode 100644 lib/tests/HTTP_Request2/HTTP/TestHelper.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/bug_15305 create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/bug_18169 create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/empty.gif create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/plaintext.txt create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/response_cookies create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/response_deflate create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/response_gzip create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/response_gzip_broken create mode 100644 lib/tests/HTTP_Request2/HTTP/_files/response_headers create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/basicauth.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/cookies.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/digestauth.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/getparameters.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/postparameters.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/rawpostdata.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/redirects.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/setcookie.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/timeout.php create mode 100644 lib/tests/HTTP_Request2/HTTP/_network/uploads.php 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 0000000..69268a5 --- /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 GIT binary patch delta 37736 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 delta 2308 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 a84f1KKMYL&82@Y*<oUq3{SPZM3mX7QCwkog 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 0000000..dc7b714 --- /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 0000000..fdae489 --- /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 0000000..6a1f8a8 --- /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 0000000..295977a --- /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 0000000..f93fca7 --- /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 0000000..5fc5b35 --- /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 0000000..839266b --- /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 0000000..eb68660 --- /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 0000000..9773a94 --- /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 0000000..104de89 --- /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 0000000..d2725ba --- /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 0000000..921d1a0 --- /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 0000000..02f768b --- /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 0000000..8b24427 --- /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 0000000..42acfc6 --- /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 0000000..d514e53 --- /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 0000000..adac078 --- /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 GIT binary patch literal 95266 zcmb2|=3oE=?l*gXm*0xmq+zrF-Wt>Yhx?9iZ_MkdY@Tb8nKZe@y6<iBn{!`0iz1zb zw)rs_ay;6x`}5zo@u6?qw=L0S+&1}Lao(OZD>IL+@mpX2oe}xX{q-r`+Ic<yziq8M z_2F`k*~|DJoqunyFqb;JKUAx?YID%qEw8rjpQ`=TYw4MPVgGKM|A=Jc&OUeJ{y+J@ zmA_>C=G7KGZ+!00zfEiDC$Zw?+um1PUfsMl?tjVhyLoTkhHRT>U;U-z*u>V(`scT@ ztCzkC<5<>}!0^Gu;>NBxowVP(cN<@yH9hV6=1W&%-&k>{hcn#doPRey;^6Tf^B1q@ zdj0tG{6Mh({X2K2<YlSW*KLi~ovyw6e^v3F?d$J$Zhh?guw(f)?tIpJbq?}!zqf5& zuyUPD&)S+rUn~o5NS%&(F4?m6>l?vptF3D9HeVMPPj4>%e&5?XB4qx%OX(lx*5>*y zfA?c1-;0;){)osG*z4a}DeQ8uewVg=D|^$^l%%uy^XH!Z>p$-?+nHHL*SYd;_LiO& zvzxv1^F;gk1}q1j$v6a5?b5!_Ebw8P?f0}D-RIB6%{P6parVyb1vBlFe(ey7j`(*h zI_7ZvXV(SuEM=<Sl>}-ST26HTc4O<ZcXLY5RT*fupFMVZwRW~eS>*AJY1{`k{B`g7 zd*au=zZYW-y6YoSoV5#NpZU+UFI)7O*=V<G)$;;N`S$!fXVyyU)V$MAl!!RUf9(wa zJeD*2Z0ysvgm;|zbJpA->W5m+1)~DVHv-WII0{0aC^0|VVSFPcLgbukQQcDazfW_X zJ^pmw@3(kXwB+0FWL=%84=0+p>ZK>&FJg+CmVWtpUwP~5$`oDk>kRiEbHubf{V8uN zE;lzK_%+vaCA-9Fw%?l_dV^Co@BBYgZtt(NvlTD6_gPCt7o>eUb-29v`p0MMde8g+ zS-Jhga}MESQyK)$T)o44<cNPe<8qdzLJA#yq75nqGx+{!U)X6}X1{y;n^yb(Xa6s` zFw?g6>>T^ecD(c7UCRF<cqcHwXa3(cA7<CL#7BKQ^!th3?$YMvyC0QHWQX&76WMmD zHjGtnQ}DX?Q?d;-|1j)}s1W{>v_J0t`Zu<Bm)bu}cHGAC<qoR`qYZ2LMw=3Gu|0bB zix;NL{pWLQi!Z$K;p=jTjJA6>qVKpRD9%t3`L~i~(f*$$Z*Df!G_Zc2u<vI>SmXXV zxAiVseDv60%`->#Bm1PLwdwl5+~3To{}5v5#&VQBruN$l_Z{whccy5UUzF{QxwZH7 zPmA~j;qt<_)*W}A=NWu1Wcv4E|AS7Y&te5Xx}Vla2`C+4Z45p0G}v5BY35rd6^Hvf z?HPTTk9=->9G%wt;8=^r(N991{{l0wYhOL2T&R8YDVt9DvOh|C5e(v;ytU7#<=wiu zv@DfLK8^c0J45?vZKklbA_dAvjT$|E1@Ac2-C!ourq1QKC&8x8o`2hiqC7*FL(OYr zm+br?6KnUmS$H|)mG=p&<MOX-T@BnFSp4d1)#|X^8`Zi$HoP>O^K3(!_k>Tk-ghm1 z`^f5L$BjGkhnY*FEVLBVHck4w(^@)un|E}@hOe6yG?^RY?eh;_l*#Gb|DgZ2B$MEp z!?XMT=x#e5zOnmsM786z(0tzL)a`N7mSHz@-)zlEU;pS@+tgbpazmq5ayu>h5gv14 z#jUKc*}U7IUTc55>uTPOw^>UchPkiZmAf<SSCfVRPi?)Ea;9^_F6T~v{nR!>V9vy? z;e~g)c871Ay)N2Q$1~{Z-Q@qT*6&!b>2gkVS?&3i(FGp5yMu~PFLv9SHhp%r-h;SX zl7B_7U&`apU43`Wwp)kU?ko=5{3fLBt75>BW!5r$r#rXG2sQt+zqUQnm&-=Pf^CBo zqsPCM3XM5CnN3Qb#NI88UUhDTtq%We2lsDT=LAX)WTifd;@uszbxz{ei8rRbOTVpp z^5Oc56KA(&A5Lm`#Vne+`*2qF`n&6P@m{}M{ASxzm9WogmT?stZ}&0qeVe|gY2Do| zr$QgZEzUYD%UHQ_&Z&DFa;%+~-!01O6!qNrVEv96SywZAwWcQSerWO3NGD<aru3_; zuP(c~;ndc$Xg#*4UlYn31RTqv;&`=sqjoBWH{V~yxyCSSspYC`Zc~57ZA}W*T^*Ts z^R3(F$a41s!E>urYp;|o-C2G;(EVFnEz9$e;<?+d%DtWXea^zWS=Ua5Rj$7))!Vap z*W{azwv{~XeYfXY*3GTA#1?oRUb#K`aG>ysyDLKPWnX)}py0ivJ8R4b2cF-1G<5Cs z-&t)6E?nhYej{u9t*e2|I}$brt(=m$_37S=8;r7~+^1c7>5$=E9>49fp7r6fYW^F+ z)$60SW=%~ztash*?B1x&_fE;hya+Fsy|a$-pyuthw!352_cz?%{;D@|Yuxti+~wES z^=x8y*L}ZEdHbrXQCn?gcD}XEK7UVotBkmw(1M2Gt*?8(-Fr2W{qWBJ7w6uqyzhS4 z?Xcv|sb(Fw7k@jE8+JSO$Nbw&?*iAZvG|g`Ak`uHomIu&OA=Sxzg}DsyPkLUw+*bz z4en%b(V7-5{nm8##)FQwdp|yxFZYae{~NP>g7o`6!FM;$eYWCR&#T^9q5Az<XIF7Y zm}_lkxF4;V%x$v&{E6yw_g3_X@Scc}Tr$&ZYg^B{z}4Fto`+s`d)C(ZTW@z%;Tox! zLWy;3`>peCuCrddckd|<J%t4cryeXg#?;@Mf5E6^{>Qlw<8}tIC*9gMd+pwZ#x1(} z4_(=p#h0#0JoRJ2O1ldz-?Xl}?Tux#U2`vM+o`i&ck{wd7w}D;wwc}7W9ET7yAE0| z_)#|hqT^TQRZKVH)@^tHYI}W~cAoIN+1xFBY7@%7d2QT({zlo}mtn7SSKnE)b?q|l zkY$0ne0S5NHP&pN{@`xWn**^0OZK0?u|TzN&aBG?2~Sg(-zz%()NO6*_3Q%CHC3v) z*Drp1pfCPiQZmDGPU+4^Hx>&{6k~U3ddP7^Y;|1z?Mx1Ro#yY7J9)RP3EQsiyZlz3 zse5iY%aLuIUusjf>zJQADI8UlZnICJ_(Sb72J>B^>$c8u+iJLJcG+q!DW|PBSw!z{ zl}+E0#MS;U<&nStk7*B5-j#m7eYNfC3cmKYQJb%3T+dQ?(SPcy7Dr)kk#S$0+van% z%LC*O%`~4ImTR$Q>vyT%u&uG{uVt0(wq0fBC=+kUYI5XGIhXJ0bylVm*1k#H&2%U% zmw(C0UmHSYLmvc93s-dgzu0{C&U1SO7sq8X_2<6n&D!w6jhp4H7x$BfWzCOrO|LAy zyv$K-&gPx}e}3C1eJRguZCGlA?9Ihqo5N(Ux+y#OOt5)<bs}@qg(%~l-&NZBau1$u z*`3v>bveuQ_S7iJcjw}mT^EJiscF!*Vq#*<mNnBl+{OB-Z12Szt3xJ)^}RY<cGycY z>ZaMd-6gx@a(VAsaoFW@FR9(pmzsVhhxat^>TTh<roqShSKR$R_wc<7Yp*t_ZE^RN zUh!05+ufDh=6LN+v=*&iKg)=HrN;g8ADf!hAH2DFI8ZuoDeorx6|bw`uDY5vW%X3I zk{|6BU(!UE%y(OTXIq1)$+uVEIK-k?Zq<>VvC+88P}Q-4|D=~opT}|TcWa*YesjAP zSH0eBRru?v3$L!QeygY<*YKj0d&{pISxXx~-^<u`ecjuw+6qq@Uv9kX#T@c8kyS!S zrs%Bf(gip7u3Vek{Eee&Y4G}6i^DFjGLI@NHjy%I%S{jIooFfZcB}O~*R%7bwZ#-J zG-{X^xurU+S{S_kdd~LD?T**h_rFR#nj&+buk7ymYbkA7qSMXGo?Sar+?1UyH*wp8 zi@s?Ld)6{3J&Rk-_Bd=#SvG%1&F+}Zx&llw;fdm0n-y;Q-?e;k&N^9t*YzK7-E>$b zvNy+VcbHc`=UKrPbC!*}Vp+>Jr8lMt%a(4F>9eg?kLYu|_q>`vc3W0ycK-T6J@dN@ zQ+GcNJKbuWzdhm2s;hEGeRJ;L*z`@LWV_`~dDHZp!7mSPvo#l}J5#Ukc&hxzZ3BJ7 zwVmd**EB*eig)cVy7B4t@(c$t>%+CX9`$;y3|!@Y&%;K8+4o5F6Lq#I;~h5ke2W!6 z%nof7C^}QLQfzjr1w&QZ;jgw|Pcr3x+&+Jm0Yj~pnMchn6PJY6s#AZ2-dWB&bWH!B z*|#?7{Vw+&DI`zxFW=g_=(b(avx*bD_DiYvmp3{KwI5ycAR$|H?u~{eZ`2k_XE>O( z8XWN0eK9+xig}C1WZ(4tZQ+-9JhhU!%Aj|WVe`$!j4qRAr+&6#?y$%{l@#4}eRA86 z9OJ)S0(Vx;%Psb2UpVu`X5JSs6_UQayDIjvB*2sTTU6LPQwFWYU!tbHHf1Ps{+t#3 zW!8czPb+eTKT9=Cb*<3dypc1ZYihZycY)ZBj)!GiGtId}?g;Ikw$qGz!NFa-)`^;N zuQ{2qYg?%)cflcvUGq+w2&Y^*w6jmtggfR;#?HP(WA2zEd^>Li&1BU|iipXrI+LKj z`=yW3(}_9>Y|V#uwYBSP&@5~6&EblCFj=j^RF`$;@2o8c@)Fj>vR8EFe%1SSV{2RY z41e<tTh=k#WJ%mT(6)>3!;+K5&fG7fnrHCLnmya$x_s8+z$E5^vq2hMOSsy+SYBp@ zzq_%&>&UT7TedL71ast>3fW|H9ZH>O5F^HY<Sf@wQ<le@A}sW}T|};zt>P68JhRVl zjo#%&rTWY7H7z>BoE9By_BH>;h1Abar|3E+&JKR?wxsdn>n}@gv^G4=UV6-5BCgu) z6z}WFMa+v?xwp-E_($`Kc*#x6efswfKaZckQ99dh>56YV-|OU76jhvVI(lcTT5|(m zT|)4Kx_t-sWa@~_uVg$cG%Yc3iDAzorQJ7<@G^O^2_){`D%JMZNVj0RtMgKG$0?`c zIRjT&U%j^7c}m~o3T~^4Gola5eN()?1iYSiAoE~k!Oo_?(@oR6v$@aZ*nG=pvRRby z@O<pN!bDa_n|6k?QCm2*n5^Y@B)T!kq-?#VaiPFwk4-`1?6rp6Y3trbCciiTlViE% zIKv&@N0v%g8xKuTa+`7ADEr-|iVn~2*E{QtQ$6-L*c~r7<dFSgxMt-MtH%e#XFNOi z{NJ2Uwm-~1Xp~sVF|nOy@f78dX4{+XzuCrhLvlm4+B#()rWUaUjC<_Aer|1&yL>Ol zbaj&SG7HWA+YvKP=rS5;IDObJkt2A=rZDz{^_&G>%R3r!!*8>?6*yYB2qc*q98vX5 z{jgnye_C(!$<H2Xc3QIw6V66Gytr8Ww6+2Vm*=u=_U@8m1;@AAXI+nSd&aE$|H#A_ z^VeD5ZZ!UTBzGduOuHcNe6QKf8-E(iRNy(6KUMz0wCUcydyb#f*4%gFLsRtQn_EA& zb?aZ-{b=g1_(xrzt=nUm^dz4pTztB0@%EgVi76YU!xh3i=U&{z_|m;#OGDY8x|ACW zG}xKfKd8yPtm9?!Nm4RM^R<Uh$Ha~O&rUov%#1naeRj#x>kB;_)09qhF~2!8>(QU~ zn3<oRulSsGdh?BKCXL#21(wSxs{H<4;21Yqia)$*8)N(Y-;W#EcI_-{tT=P?%HG77 z+v)Sye7w|pvmxfm;d>uc%#M_uk7rssYZp%#pUw4I6ML^rT*9(rZQ1Im-XiV7)p3W` zTYNnF_RMi@QP0waCtJ6BP3Sm!XR|o-(m<}Dh4(ibO!akIv{QKM5*xdtIeP`I_t@Cm z+3uFuV`FP)S6f|Ebz|p?x$`)pHa4VQ%(|nQcEj>aV{gOMg&YjGcg$JI9g{zWHPX3F z@Lhew>RAz&Rb^i4?mNu)Gx2@QopUi84=c*9FlUtz@K1jy6E}1Bk4Z6l($&H@!e)GL z30b#M+DOJlZsIhJ3G6o+mRlG|s$I63Xn(|Gy3D@V+)ode-rBmqe(8yqSJIQ^4J7#3 z4EetwEbO$N@RYxjN$rH@$)%U>R5Q%FrV)D9WbMcF3tNoR&V*{;585}E;p+zW|8bA* zGYe#2j8haWIJjAIdNTX_`Rb7(3wm85y!4qHm<%7y?0U4uz-gzz)TJ*cUHZ;wxZ+xz z;QzH#W`D`&HGJxRw1@rpKhfuFU6?s63uG>&S<Igs?GpOH%Rf~v$AN{({%6Pe!zztj zJ0Cw#=3wJ4YdzWHpf%CPw__Vq?&s$_9<d)>bt;~H^{Hy>13LS4!(3NPIKt-m-t)nv z^4fQPHD1w2c%09ia^1IdxfD~(BGWk>9syIPN=*Fy?HiY>OvHuTlP`-eci(RmFOa19 zSa$-W@54KS!SUtR?i+OyY@K(05vU0GxB8*u(!aVNAH*0<Ygisv?7Gn7&3tup-RD!> z*KRgBSL|-LX_tliv66y+w~zdqlW@7iW@gQuUo6+<Jg+Zy4?N0Kx!c24J%okHW6SD$ ztERm^s<C@|lidWd^d6ZH9n0I?jSpuUB|ci)RzC5~XQgPr#3M(AwkVbNR9$*>>#^tK z4`~Mic1}nUZ9ISXg(`#MW%jjokIv_N|9Nz9Sr(I2{12l7cIOGMjoPQzHaMm!uttQR z5@ECw|EDEsnb~>iZGRi5o>u2Ui3CM~FvX2G?%j#&TWcwv5nC0#f8JTEi^ZD#zIzxZ zA1#cH;hvJlc}q3H@dxjTWkP!%_vr|DEOFAvdBtF$S<RKHw5%ucaz~BO)mr6esgC&t zACCT+R(8)lq*Z&DL!Z+sD=(!THx)_+6+LIOo@3zFVhCD%+}XY82w&ob{q}QLF8?_D z*L>&n30JP{SDkBFRQZ?1CgzLJxfu7S$D21WPL~suK5|RZe$S;0%dPAy@*Z#ou<aGd zWLP&PN~)Lb>G8=)tusHYP-1-6y@NeTCs<LF`P+k9ktXdRVKFc7j(`~(<k@0w%vf^1 zS4}y;PtDP(UU1RI_2LT!G$d*xrwTG$DtxNnaNS7Kf00(gjqMRkk465y_}<^SXeGy# z_5(XU#dANFs#@Ba-%wcdTXplt=MM4CrzgoSKID4Dc8c8t1@~(b%UGwhY;jvyKj~-x z>uE7{VoPONI{U+0E@WmezPnDOXGy@yhl@6`1Suc!QLtl-;+!_;yJ+MOo%!}vazFA@ zzAWl2a_mhLulL|6b6s$+W82FMU5`RJXEQwBc0|6#v2_E7^f6WYJXL}4%^SnBKVA~u zkT3l4SN4Jp6FCci#GDOV#=1w#xwL&{N-xvxH3ep0r|p=q=wGq;>a!0{J&akt;ctfb zQ>nZ|zxq-W><-E4Jio)iqW$s2F$tXlBgKY-6YT-X*_qcjep8Qi53ty`a6#wME~jRh zuDLO*JMMOBmmhq(I9_1pm7^73UZ{H5RU}<XI<xSD_B)Bz-G6?bb`Hx8i(*y1#k9<& zaN!X>kKZ*%-iC@dto-{@S1C<ygPDTumD#(v>rI+WJyS*29o>5V?8OD0(Hcv1YkJgN z4d#hN{?rKw>Ae!QDInH;Gw+9k(UYv#Xzv%%W0QXuEW7CTmrC)S9H%^4XKkGpzCd#B zyg4QO`f{yy9LKVGc|B87w#dKr>Snuq+Eg>pNAiSI{*-4y51!TUDV(NMzva=yZ+jNh z8CagPwyzY6(f{QgbYjAtmnNGcTcf|dn9{?-6zH;X`FW;Pmt2h~_uTb&U6dM^IESSP zxjg$n`O@bp-5(2Gw*+Qy<L-~p&|T~}!6hcN_VnXPn?8vgdNk4Uaf!#-2_}3KyOlqS z?tM@<FY&5+Rl4Md*nS0znJ;#nI2r4q^6}8O2X<~{i^J4T?Aj(R7bswy<acsr!{s>< z$%+3?`~Q8($LGhR^dx+jY+H+B;!n9q?g>6W+)jMms&%$NEM~fsqt8_NO~>~|q^8Wf zRg?5#1yf*7XroQ@`R<)dcQ#tIb1BVJShvAbe@gvUo<-fwcVl!~)sDXndiZ#1Q6+n? z+`W>O8}m)KmOW<ds4?w55&qvqDL(ISrAwV?;rA61G93?&Jo{MSDd+HhZvuaVSFi(5 z;i)Zxs%4zq9?Wy~1u7>?IR-tM)UOygb(MNdRes+KDXpg(F9ZIXOIZAw(rpp<Vuy;x z=bfe6Dl0V@8h5kX$LP-EIHShlw5v;~_H$OqS;J44C%hH;DEUl=;bimg4q=~-mluSy zr7ArLOE&)|;2U)*TKj0e*srNF;_o|6zO`+3{G7Mwh3q@d>-`B#9|R0JeYe}cE<5-A z`^5wqsrs7}zn(0<@xSol&*b`-firDg`CebGa-7d6wVXxpQ5)OF%d@U?r+e%ZYUx{& zyK4?ltkqM7kc*n(uXIhh)}=KzEqu3)|AxKP+NkDjPvzh4U1rzqe{Ig4_!YY*vLyUY zUigUh_oP17<TG|3-L^N+e0^@Q(Y9|-w+i)KtXfu^AHAdh<oBFxwi274KW}W;{c&xs z{-pbfR}Q_Y;tTJn4@*-2=XCNw<m5?iw-#3C)*j;OyVsH9vBEO@)7F&!DoL-%i^6v~ zRxMKh)41RM`<&*g)iK)570Y+nm^*wdT=__Oqktj5(whx8&5CZm4D~qX$@X9(=c7=$ zIcuj(4e**EvOg@Z<;;r38*fPRACS=XTz-l7@src1p9FkQSDb1h7WC6N!echirTY_F zzbtd;TvO=rM!%t<cgF{Lk4A}1296+MhkyePDuqw(ZwV0l=J}ywN${0LpIVLbHmOOP z@z@GJIPCqtH7)(!(mpYp(4tC>ji#?844o2{wBlStDpWqNSRs7sxj<CkC9YPxU-61B z#HO=Go>?&Qfc*`Je3hy5B}Kb?t$(YC72lUhY}TB?=C;OFY3h-p!0Co}^xkH*s_YJ0 zYrdI9YhJV3BH67uoA<~4ea?}8k8}0Bcgv=&ulexney^8q-2dH?>%;b4|NqbI>;8ux z|NmutKQ90O+)VrDB85lyd0m~9?h>_Qx|Wy8hg7D9SxXe|tyySi`%bpZ|JU8xuLl>} z@19#7e%&GU4U=+Ek7B5+n5H1l6&oFfKRx~eH^ufz1iavg$y>f;!J_T_Z(ko?TGX)c zDbEcl_4{8=UYNZ|P*(J$!v(*Evdv3e6VwXsyRX`}sl$5af|ut6mmC*W%AWFK(Ve5J zzD&+8cVb(*({v2oFYP<}MB`ct(~pjz4Xl&hUR)DcJ8#<^J<fJP$1SmmS_da&w}_l; zI3Dl1jQw#_5}(l1JyYytCBkkiSjq~r|M{AB<VTTn!X=wEF*A1;c-Wo%EWF@s@q<N< z6%t*{uevXWb>@hLPIF-EUu1fgeVf6?yrqrPe55ko&9(Z#>-n9b{!y@jgkHU1#^q+o z6IsV=4mR3H-FYz4`@O8|J|?Myx8;;(Z1^jrHBnUc$nEayd+lD&-?yjYw&i=HAAjHc z`7-@p;P;NbKScMv*>w2w=J@l~>*L}#Twi|N{qASG*K=eRdcJwI=li2ik3{Tly!yf! zwtl~Be3@Te>fQRebIX6%mQ|gr%0Jgv%^=`r@qSLsmtB9AUeA@0i<kTA7bhyokf^>~ zydd!Y7rh4?yDu-A5qbTCmUZpPdnM=j;}6fDE57&fy`$?*o}IpTl(X-<5Zjv#o6r4p zegEOpj^cBghoAnunYsUTL8trPe-8_ne_vJ|(iC~%#G2pU-=8#njW6%(Pu8}pt-fU2 zu3E5bq5e--@n^Ry3-3H-KGf7-_5AK(?wH6Qw-s|#d_M0u@hkoAVYxp}6L$T7b!eB> z^ZjCl)eL$2f6Gt*pkQ;L&nn+)eZtJ2rez#`uReU*^P6os|LsfrYcB485gQX%a&z_N z%gVQY*M0vZuF&vT{mqXp{<F((z86cJ((M<h$8+GoyMxLf{&b(N>;JZ~yy&8>{eGLn z6ZSI6{Efb$IwPAs&VCWEz3sx(!)<ft{r4(huz1Hg*K^|D+Pkgq&lUD(KDXMTT6^Q* zVeY?%62fL*oTBv)TD{z7^=|L0c`{`mJA+%kY`gwm>;Xr$!QpTYvG)4gKc~)(yY%Vf zi-fX2*X53Tblz~aO1V~j(#n1Rmjcd5tPb6E{tE<iA3n@3WO*q1Q*y0F_>1r9M`rwJ z-?X*iM?j(WKi&OLO5(#ePMyE>j{K{VY_Gp>Shke<et7;sg}Y_`KYjJzIlG^~G1he4 z7*(0i&oXC&i|uNY@2|K1E&6IN>C>Bg+S1p)s3`f3jH1~7*%3eA-Is5b`OA7sYp0{x z$t$f>jRf*L7xXvwFYA%9+m*d)|D5VY`~~?F9qpq&-&|*L=j!s+vg`#mlP-Oa)OgeN z^mA<2v9yk>ydM2xye4P*|NOo9zTSOm!+Y+HbIp_f9w=#XpJ1%m^Wwl>fo~!|f1IBm zAf$SG>W_@+|GU{F9)5Ff%h;UD$M;|}bK=6s{*V0or2myIc{C}R{d`D_)SYL+N*T94 z+gw*Zex?80pNB7B+E>T^&6G<tK0kAw-TQrS=9QoSdVJ&J!+didmrV4w;hXYEChzA- z*7|)86;pS$W^^qI_;j9oXEFbV$&LngPaJ~FjMJWWy;1YYWKh}p{nPc$c_lX&R%-8j zbJ*|wlc$-VGEJ&x&a-<Rv!~BqcGjUH9ka!U-~Dv`^YMaJTFsv|m*4NVT~r<>!?f>) z?4K2u<w`GOqq_LJs-v9F$@aC~Gn>yf|JYN{|DWFQScHGiVBq=k=*x#MGo~GvKY#Sl znu?z3vvT}yFWFibWHjyDcYi;Bwm+x!rdx9z{Sm_NuixMA_o3~Je}DbUM<xHiX1@yI z-#J^Zp?z~K?`Kmh7I}_k1>4)g|M(e{m$Pzd*o*q7ED+&#c<@o6qo#%{|GeV?sfGv4 zPVOygf4};D{ldc`;g#OZEs*BO#<cL+`5CJuY%LxCxjlapn&NzYUV@N9kWHhs)REs0 z?-ag2YRU14SztZ8zQ4YIpHZUR@9=qlMZbOEeW9{kUB7z0-p`3@&wqaU^kf2`wb8}> z_iU{7Ej(*JY8&)Fek?xsaQ(kf{R>B5zI?#@Lg3ZJfAfp4PyTGM*lyEL#-cO#PTxyp zpR%WV(ZAKfKB5evoLg2}U$FYDvRi$<L%qumA<zF+7OT!~zQ4EP=1W=Smw&j=ZLThw z^1$`GrGmq{csUV;z?3Nu&-us8l;2;&@MCM)rt(kT&v%rYyqL3pmrJzADVyr_jJ}BE zhp%Jhn{FPwYA<s}e6I470`UzTOLgx)Z15@C(zQr-*PZ>RtCzXDKGvxD={0N39^JV| z+8*BPin%`LncwNo2<0A&>6*{?eVOE{ZuF)0&CwZFMvSl87dZMXzwv0z%oD2lHnX>? z70b2GGhAqF|3FYLW9`>v6Q@jjp_d@^RW$4SBj=v^0gGN+@*D})4N^CotSq}>Q=*cs z&%T))+TR7<9$R3S_^REM*G+;;a<z}cRQ7duSf@=2S-fs>NUm~>%vpm@=Tmz)4c@rD z$d_c5+QE@O*|sI6x+hs+!j&405LR20Lt6#gZme{7S>b<5L%YP;Xa0ld=G)UUX1{zC z^6B=stv4p$Kb*by>}y+@Mb%Fo^zSzK+h3eF+0((pDdJtg+JgB{BRuzNv|Wt*;->Te z2J_PLiTrmyX4J=Lu6w^_qrC{%uQe6dtaBNU{a%ve9Ot*{#3|QW)n7m2XLKw6HGFXU zj{<+hwK?~u+S_#QZ~M)%KJZ5Mv!;8Yb|OJPju(fe{mwah+CQkg`Fw1&%~`El<{#_h zC(XF4UOjiuU8QXC!|%n`@hs$7tjFB-bgsK*9?w$VV<$MQ)-SmKCSCB+scSAZaeLes zT5mfd^8U%<W7l#wo>2KGT&1=7HG{HojQV`%nscg!liWSk1C6vSo=ko|ue){Pf@t5R z*3Y_Lb0-E~*jRjRo%XJ~W%EvM?)@XP;%!Azg`>g47u=VhR?Ml12z>wI@Y5%gIrC1; zpTcwE%NMS)qy5Qs()Nlr{_~C$7bv{EnSR}ERSWlX<_{@eOA^<2zIn!da`CdCJGz`y zJTu=k)m`-L%hgTTQ*%OW+uaik@ryW4oK$A=^>IEE@k!ltvFc=FpO$^gKfL*|J&-5R zy;i`!=Izv{Q_{o^E=%IsJ>j_cV*{><&5ySCWT?GyIkojx+2v<l>+_Q%mkE@X&kJ0# zW<}Nv^BftQ{h!Yi$@~lWASAKb%Q>K1-;FV7{e7ovCa2zH{_(l@l<B>B`GX(k!7B1Q z`A_a#VVGdv|8T2bQbinR&5q`sNfjz=pVoeCczeUL*6`$|rj&H<fI_WxCrmiKf8-1N z*mhjo>Erop6L$oiFt)jNS?;&(&J8d2Ez{Q*IjIpU>$%z^@ImX^w9^|^vwT|K9(FZp z;_CE{-6M2H!y}^E@6$4$Bh$LSFT3Y7*)-T9Vv6IEllfVzC$VU0?htgmyi8zc!52$~ z+4j*qJF?i$iwn<VN^E=fG-pxUbA?}1zH}x;w!PbF@~w52{M=Z<A9E`H>M}jvV!g=i z)rJ$();g?ha4lEi<9ucpljLA@SUW@R>CvaBr{Dct_uhVY(Er{^`i`C&&+Lvb<Q15e zX%MY>(#K`~?BgPC35k_)hRYbVgrEC9W)qtxe0W7l#e=R_A{`Cq7J2PFcq(az-b>a! z+eCSkx2D%ty@(ZFZgF7eN8QLX)ANcVH0>CSW?t_sSI&wySTngJ>qj)lN`a%1eH}ao zfud<0g%f&wd&HL*aOB^b`(XRQn13rzu=ZZHV)1?XeCxtTVzZC-SbI-ZiBwxvq2RQj zimhYA94*D*u<QdTdKt~09a#5%!2-5xCuDsB)(V=oUOE$3E)w`SDXV9Bs@$2`=k}j? zaz{Vy?tG866Jr$ZBo>J3&k>#WyCL_$jEOY?8eJ}4H;i+x1#14EVv~GawlX+`%gNpD zjOWjg-<F?OE?cMd&FX*CKb3Zd-^$+{>yLgr$h6h<ir3ZoUE0C=8<xivEtQ-tqknUf z_6GU7gwC}Gw|Gb0Up9MDoX)1GX&JK05?+<P!O8n%{;kw|s20BL#JgWC&KF9Lc>OC{ zbwVRUYVPWe&7Avr94s776_bmMc6*p@GCV8PS-&nw>-jhH8~MA=?pKI+xz#e?%}y$W z?dcE02j>M?CinZF5nf^QfkkU|S*V=d`N`4Q@%Jw*{y8PK=jo%pFXQ&u*-Gts6_>Ni zpu$z)kMFk&Ha7Qq{_^HuYUb^0I;7V8`BWVL_HU<qr`1)K)!eeYl~WS9H)C=lpZ;(0 z<@x6fRkqb9rucH54NEY1`YZVF#FekDcI|Nc!hQL}rcQCj1tqr3sS1p$`+c1bN(Fq_ z8S^l^Ln9=A?nRpmA5@huhu_$1&{g)q`}vPezYBy|UBwPgym)R}M4o)_^)1eY9VHAB z^S7V7_OMvG*k%qF+t0asS*JX8?vzaZEgLScvf{0RpoM2-M(sN01cQzefg9J~9r2Bf zzV-HPOxzy1JukjgxBq_Wee#|J%Rzgt#+hN0OSgvmeO&Q<Rkq5y@>$C5X|tY8-}+Qi zg==Gy+Cp~Eg{pRmZm0QbCf|3M#QBjwao^&GD|gyIq<!!gb*k-H@24nSACPdG{j={5 z?qw;PZu(A8;dOl1CNtaTbdR|91ct|z%7wcZGJbl%(0%9Op0_7;=C3UH{?g{tf@e<x zlnpYED$ARx&6X3n@NTPbs$jt3<yLIRgpd7vnw0s(a*lqzqzGGy%7)`VJgugj`ODkf z6+d~-;fFTqS8LNmB6hx#le*b@U03MFpT1?^7Z$ANOk<d~c@KN$jeTKx0*5U*<vru% z89sD$#ZLSyl(*b}Ym2MuE6t~`&Ne(Q*x^>bR_1f8Qrf)-YtL3l{5`r;XZEg5Z)Y1A zZ292M-m<x=SG-q(uVtsatA5Ge13OlVJ4Yp%Scm*da`|GvPEr4lc;|br^NF>;mrZZz zu}fT_Ieo^Qmt2(r^FD3;%9&fU$ezXiaR0ltZd_NkidKj}vvGN+rLO39(xB|bBfd52 zhdFk*iq+q3czCFF%HIdmbL`a~to1dkbzIzk_ob+T6{m?@zQFPLz~uULpY@U#r}qns z&9dD+Z*N3gU}NOQl*18`?j22eZzLA0YcTGcqZ7ijpU+0+jL;LQkc!*emi-fX|C7_! z<10_i@w3m%iVpp;pAoUSCNDEM)BDGTziVtAKX7yI44nOXH~$mIzP{#9Oh+3(WX`-B zKGSA$^pfsh9)Yhnd=b2P{bk+4?UAKT%DSu<x=SDX%F1O<<H(=HU}zeqZP!yND;L(e zU{Xf*^nX(?T(DH--d@dZ;}@G=fAh7R{MW{bKlW%mG-(&(yFK4$#YU#J898UyhiqvH z^I_r^ZI%#zZ+e7#$JC%^(-Qyb&z^hPY5Z-pan%V5Oj@e1{n+pC0=>{fds(+<zG_#y zt@LlH=<j6f^95@bn#$b&#QxEwH|k4TT(ipL$~|p+<}RtK|1(iBq^aTHBcWn8>r)$4 z%KMi5Uo+#8&E&v}olCa6=1488<d5B0?&_4Ywrq;z%*}h1PX1sBWzpKbZ?h$rGUwX^ zA0D3Dd@$kR_mxcrXQZXXS=fa>@*4YgvPhooX|{_j-X+NB@$W;|nxDs&Z%no6nXoQJ zI$;{$4UT@Uc%4mNo3E|QSBv+WzO2J;57&jy&w3-KM1JymqIYb*TYT@vuPXhuo7Q={ zEv{{Q9HBkwZGgh=XO7QvOD=f#xAcCWut6xbkjLad-;==l2`h~B_gNm^WM3)5vZ2OF z@#j2_`Ad)bt~q}%#WFGJ_QQDrSC~R~xSpHd6a3)lrk~dWI<o`!ImSl|@{1<qaBnz# zyLI=h6m_MFoh&9yuM`-TvRqo)QoXb@`q>5v=Pz^Q7RUOyIEec8y>+Tu5)+nii!qbk z|C008Wz4<jX06@lC3!^H;uF&=Wp)k*A4Mz9OHY?}oPN(gJ!fL=k>)q^X3C#^Ak7r~ z=u~(}kX(UDRJTwb_Y~^`0U1AR=L>SEJ(peNb7*0mc#@2tV}Si-o=a>#>}g5rGW!_A zqGqJp?C~z!V%>b2GlRRCal;-76Q$(i=YK74{&CrV(j<fb&rbgBNIDQY#Uu9fij;Ex zT`PiDNR&)_o|^e=ntQAi-}(M{u}LO=%O@m?x-Uu+5qazq%pZ}}@$sMC87%=p&*S&5 z_@4LjnBb^8%`?EMP(y9y*JG-UUM=4euQ5!WqjEQ3sujnFPao@)^Ioo6;=Qd<ysXMa z=R(I;PCcJ99|IRld@^#}^7GiH9ga)%)*W0p#m(~Zk+TA!XL2WntT2yvXbRJvo}5y( zm0`!EXd8j!5r@xi(e?T%o5I^LG5wHnCWmjMt}3T=w)W&h=@<WQ5&0y#bEn@Dri*hI z&RX^*%;;6`?zKO*%scw>RP(!aZa1GaSbt;;f4`#hm#jy_&E4zur;2P9IdkUQver3f z+}_;ZU4yr#J}g`7w60WD#I>U6M!><)(4yxTw~4IGn$@`@MYq7U;vbjPgt?Iuwy?eu z78AP>v7~ZW$x5a>ll!tox$<ThEPtoOXE3><B!1tUrf+|}#g!F}RnCcgRPHM;6iZF` zcj~NDo#>A$Auf?+#?=ys^xa!$ADAbX5;w!N^v0He9~07~g}8W5t*x9VJ+o#)g-X~H zR-<)B*`7kDR9{?>Ii2FYZck=^3(v&Ezf5f8%`%r?;NyF0e6@oilp}EWe0MD|SK}nT zT^}nIzPdE}wDrz(6qb&cz3TCM(OLoXrN%seJ{CQE#{DuxG_G1^m8T$s;PfRPl5WRS z{E7o+F)p9is3!P6B&cS8{3WwUf1Q(McB#Dc=Vl2VZBEK+DG4iK7T*(eUa7c@&Bgb- z<l+qbe^1={bhDG<H$9*8cEROiC%F3K^wqkjKMLyGX6^IqwAbsKS!x*xXX7^&Ri25u z=d4v+zOf)xYVD=7ekTR@_o*HKeI{-BwAoWF&d!*+@X$rS2i1(-%T{<qtm;{*Tg~IP zIcZPenR9w&$3DhR`m}M?CV^+I(({;FT)BlCG@=AE_C~x@;aKYW_ULOPiIxRx%~#Fs zY1x0iPJH*b)YB7`C&-<;Zdq4Z^>0~q%hny|HcZP%6MB7%@#Utq(RoW*IF4AlNHzy- z-RA1<e`E44|5c4@U-J(CpJ)I3_u~Dv|9^J>UladdwmPe}_t*QWQLFEHU8&dKzVCYd zf8kgA-KCZ*+6UKF=FH|R|5Ls2`}@{=`^BsN$G!Lq=lE-W(Q{P|)83wab(O}KxVtN_ zL@wQSKjZ!M?Ek#nd)Mnr%R~jwy`XpEqxtM!*44Z&-33~jvhNkO{G*;<p0+n4D8l?g z$dj;;;JT{Qp<XwybIr~SOb)Yiyl67(ib(2z)eH&w&b@CL;%*!@y>xJ@Ty<B@EakJ` zSf=M~+IafYhaKiNTS9Goj~THRc5mAev}!}p$|Ku;H2+DIJeAb2j^Wj+SrUf+i!&M= zIG2BY{krdlj>dbo$XzXx*N!p%;HZ9eLU8`ORY&V5d#}8^X!q3T>UZvJv8iuAsO&wx zeU}$M;{)x|7d+etUYKq-TeU-+>1y-*%Um}fBnFxXlp3v?!#)35!OLFpCzibrSLOY9 z)w{)<LAK&^(apE94_ZAqMB`U|^w=RJHmho9%oKyYfm^aSy=dxjmHx>pRC@Ku0`9BN zr}P+KzI1)vl5bP9m*4zXw5ua}Q4>@3u~W;8JYGe84EXZkmbBzOv!zL^%7c|XpUO$} zp3vMnd6R6(sk-C5nIf-b@A^6WI)2HD+u7Z?;?ne|`-5CX`44q=dKmq($*<|Wu8`6> zeZz<OAA2wGWa!Dxh&**>i~gkpu7P=b!Y1CS`BK{<ac9E1!?lcjZ%$nJ`2F+u;6MMb zRUMowdz_P--)&v+tGhD;)B}&j^qcQYy|grY<4pgng)4&JPFb}u;KZ_B=TFLKOJycz zyIF-yc@+M3-&dZ}AhAaiKT2g4)=1VBzWuk>!n#<~sQAjgzic&}v-fB1I4*ss?%QF0 zweNkBF6Nc9ubVAuzxMgg%vZK=J|2<hzU^AQ@TJZ6Q@3<9?(gRRwKi@$LzI+Tk)KOX z(7lE$(!xJ~X^X$<nZDBZxYLZO3qMI#&1DjGWD<IPRe|AW<;3&0=l0LHueaCz6ZiL1 zg;LLF*RbA}sJPWXxI}DIzFiiZuy_%}-*qybardt|hZ`k(D*5<IFP|{=`qA{Jov{z^ zzcfE7pt5N1w5HUT`p=6rcNW(uYsqs(24CE(^{VyvHN8E|dCH9Or;mFc{5gsFMbaA% zNymL3!VlK_iMSgxe_Aho)r6^1;en;<txAhJuVvbgnA!?fZl8BY`fo~^w_f{AKbrv0 zY7?m+o2-P7^^``j`Rv{luA9?S^Q5bq;XnWRJ-fTvH7@@TUU}_xuiKsfmsgqZ`(6K! z<v+ju6Irc!%Y8ICi!YU{U%)^CUBVhurvFR-`ulExONT}3v<#l_%O6Kil3{8(63IHV zXJa2zRm+->EZx@^cC*h@HhA}CvibMrCse$8qINPw*s*W%U@m5|GwVCkIJeaL`K9?G zzusTXi<q+W#+Ai|@8WciE1YXO#s5j@ETg~L_7C>EF3Zf;KYnO+!M{%vLT67+3#nYZ zY~7nZzquk#K6vzHenCBd#JjuyYya<zxmWprr%~1aou*CkZi{OkzT7;&+yC4B!{@KP z`(6It-p2mN-_ySz9QL<d+<n+)ZM}ZueJkrb2Ady#`S<vfNVSYoCG#zo%Xd!MFMm1Z zUjygkEp{SxcZ|-uZDBl|<|M<|y~u#Y_Z4U<%*!6bje#Mv{mOry$P183s9Lh_m8p(^ z-D%kpm)1WU*9OfnSUL67<D(`#rZF2n@s%HoTkXcCF#kgcXZe%bvgSz-O|{a5FD&l# z<7m7VrNE!{qN2&vYnjH^i}uz}Bm>h@>(dyUrg<%G+Bg4L>XZoi<Y-woA-S8oUC&Cb zdA{`Ki#e*_C0Do97o9KcuzvW>>odDfvdg-7@zX*Fm5!~jUVV7d#I;+TznLDJ=5LwU z5fOUrz5kkol=&Gl_ZKOjJel7S#5CRK@X5$R$>~kcDwiui4$OIU;Yq<2hS$7)Q_bGn zn>C)4+O>bqgeup@=Mh_G{Wx{?hCEZefa{v>g~i&>mbe^VP;&IPQa(=zUzTc<3=d<9 zX`8p-4aPgt#fvAOVDOoAlvAT_g;cAkA&2?&&z5m)zpE_&a(HRKnX&4P<NQp)*V!hk z4~O&r%$EG&(%_oOR=j#w!LDm8`gY%EuU@0GYj(`-?%0+8n%3N0;CP(t=3~G9J*%J2 zxx8)WC&LFPD_4A8EaxR7_x7ZaTv*zH#k1O2-+QlFT)iSW-TM>o)Z^?L1#9;2EWTg! zK3sN{%zFWIhT9L9%zyX&T5_jc?8I#`6aP%~7Kl0;+Iw~P@$O32l<Ka?M+$N~#B{=v zIsRvC$?8@5?Owe;(tmyL{>ac8!`!!Qk2n8h`t3ROaJ>0vQ%}|&$?7(<_}lW1LJyld z;>21vw@!IfdM8z{=<IX72ff)^b7sC#`u4da>hTE+)^jn3f=Uxp`6JKfY?iv!nKMVm zR8rZvGi<)z_FcR!227ed6S>8|dz<k;J)tE3%YAj^>fC^N>S0j_L&O8uuUK(u-K3T4 zqT2cowaC{^c=1K2PxbrlD_7oWK5Q-8u)9xSa?t96^!s9KS~Rrko-`jiwl>5ixpOO5 z;F8_9u6~S+(wI{D-8^U2w}RfQJ>3^xci*`7W&WqW=Z9afEB8FD71Z$Uib&N5Rc8L? z?0U<xh6h0>e<@Eoz2eyYuE52zXG-!Gy!bD5R41wU#pT?;*Csn?hHR0#W0tnw{pb~0 zo$d*zZMvVHIhdCgWE1+jhwIEDHcP&}=S)1GoSma?yYX|t=H9Lwu3~-)!u!L%H^uaB z5o~x}mU3qW<KimY&<5{Icixos_HVzgv(F*4=h~Xx8*Z8yJN+{{eCvpYjcC-_H0wLB zd(3*{gG}!n(0IDcO!3IHdv}b_&2FCjO1NzP=1PH|Jh_<%okY{`#Ppe}__%M)j!j@a z*7itsmBaVdPV$<y+gd&z<G6B%+3m}v%U(}sX1%lB>A$wb=JxfKd&3U3J~002Qk?D7 zyZMTm_QTgddRAUt^Uv=7qvvbl*1z3cX1Q7U)&C#g3}%}z+U<T`!tVkvtHN0+p4@#C z<_hRPdZe%GUen+HTP1Z%#myy847<6TQbT`4^IxA<^*X7}TU5DbrtP68rITMZetCH5 z#O$Ykm&?0{o!YNpG*QdwMo3e5K*?IpYe`Z8+|hR)A1GP;?%wj>EU(k&p1Y%I_F#gn z=*1xSDcSZpmf}y$?sV_o^0DpViCgh`+X6Emv+UXwo;6kL_nO0RHnx||GoN1jZo*Qp z!V{WK*0ozUv|igMGFMK7MK@={9QmnUOK%i@$mjfd<)lmce~X(Ej(n3J`<fO#tf{aT zc)U20S8@{9$|;{rI9~2uSDNiOFY4;j4U@m@tJW+$`eX9We@z<=l>R;me;0GB<8^Mv z6wRWviRV`rA30S%XP!;^&emSN&^3?v!+Gl$uM@pqcScH8%AVy|AM3BLOE*=y?AYgf zT=EuM<W#YEolQj(ubRJlIqB{`wasRNy0+3)HQe`~^7*p~hwv52U;25%s<b<(I9$lt z&3r}Ng2u<X(q^kWMSg!yZgA?8G3eePn{=@-#m*x$_nxu)HX9)~iQc%xZDAWWv?c5B zVB_kYT5_XbLHCb@?!H&&FRDLyl`2rGplsUEyLh*t;DjSZc`82J+ApscGH3XAdGFKX zZttgV==D9Z>&nqvw<^Ewwz;MMd%t()gVSPnO!g#aE^!QC+u6V8D9?O<2gUe%`FTIK z?mI8YI?>-W@aKQsoeNC9I^I^5D>_iy@+tZ0ylOed%X8Uw#EJetKL6lmF0mIW0r|z< z%KGV7)?E3XWTLEc{zUqZOR4s%??mgw^L8~FeA@A_$2@GanBmuE`3S2S>VZe&(w3|A z%`HAH@=E=6yJyP21;;;WKD)eCdr9|NHpR?t;R5ArfoAXDx7_>jvB$u0cR<yzvIl9d zW#>28w^<#jy?9rr?rErN%B#vXPy7Y;YS_(-$?nO|+Vi}(p+tez<>SEy_G{|j%u*b> z?yNbVD|h@@L$hRoL8awHCE-u27Hnop(n{Yk{hLdYO}WI0dqyYkrtZ9-sTW@qt@%{c zJWqBbuaL#l+W&8SZSS<!^w~-7un_;k6B1Mq6=(Ww^9`?m2OGRMFKb#e=hz`PrZ+3k z|37+W-c{2I(}{j}CNyrkDjj!R?#f*A=EINUzCUq_lI!9+yeYpcZ?9O%^^7!=<#{q% zOnY7(*{-+z$_3e$@{MYLzLszFj`!->kT`#-jOiw=OwJ{_EYCLC?w<Ftc{WS<@1lBc z73Yt>_ao-|xpMbeEITW_(miilZ{qFP)mcSdueFv;O4|O;{jE`uuZXv|f5|JYO0|F~ zz1b;E3tj#`^w_m>nX`|x{}Elr-%n*X?p$7<Qki@wE_T!0o$qYijYJnt3QkJjH1pDh zt-4cqD{>V1H46;C%Fq1M>wPd_VaS94g~)r6arSx}cB-D4CdPlSe};9a_PU0s!iDAc z%bs)3%bdVt6|``^(ud-QzaKAnva?&Tb#a`&+T*)FPpsRfy|$i1wQ_mjD!VzJCI{C{ zUKV(Keh`bw{#zYxyN+nQU%9f+)$CteEVuF6#fDL8QhRrP);9M(t+i~kNMgC*#+8;y zJzI`>JU*iS?1;(ExV?F98VnOUSr3OdFU{TUXb{yGx$oEddeJ0BxvGmV(+%bOwa)WZ zrTL01jax5MDz!Pb|KGE?L%EMC^v{RfJ8ie0y=1E0{j5;q-gQr=JYDd6MoHg^>(AGT z{<ysP^-fV0E&cR#G3~h1{O2`R=K9yTcVFsmnlAJ4&-KmSmm3{+bwA$sVB%5Jd2EGG zf^!c)UoE{~VP3z|a?RJ%){Cjy{W+T<+J8!Qj$4yKrFHa=j|u`NV&beTc$UA3GHra{ zH%lt&(@ih2MVk9XG}Zqv(>hpOUs-x~quKO5SJcAA#ANuV_<OZJYHjX_D=MC@BonBn znr>E?*8kTb^rx>|{=&njw=Uy!TEcdDPP|acrN3K$xvNYL{O)Zg9ldS0(j*-ptG@CO z1+9kYq_X=5v#zc1TjOrYA@%pX!41V!HOZ?A4F}|YK5<{jJKf6i*oxLC*EF0w-t6y} z)>}9$d~=7f)~T&LulDWQ9=a)Vy4&@N2QTcG-*KKJd~u=W!(A~yLW1n``Q#M-A38C& zy|#z@rRw`e&Q>!w<sA{_drAWKyjL|XpZU}A!Nb^P8c$UP^h4E~ZU?U^e9)d4Gc{~y z*vVOoe#UGp`P$`tmT8svFY`GI)_jWBz204L<!i=u?X&+{*jtP~_bu(YrG4JR{BQb< zo06w5zm!Rt+s8B|v6S6vbqu?p#r^_yN0UREJoX)NS(}7kuJ9~V-YWd^%6AXfc9z7i z+QBA{Ry!{;`&@c;;>hidoS|l!$JBpKGBHWZ{L`k<o71X0ryy@{M)!4*t%4_Bm1{9n zPn~w8GJnaW>Edn$#kT2ORjh~3e&_bt_8}nkfI&-jW0h%?^~<zFzY`Z$i+)m)U8}+T zS%l}(;eUINIMl>9MH`FNoPCvOsAD&kGcl02h?ng|@wOxJ9+zFt?4I*-SKxI+k3~J~ zUUnaA_wN5B*8SMfJzacrty!kry;6xyJHk4C{oKN^tk-F7&&1Hcnv);aPgg!-XV?GR zHD+P>@%e!+j9&vJcHLl<U2*d6Eg^%+OyAtTc%J1p&3qcRz%TT|JNbS`<M!!G*q_a7 z+IG?M=hX6(jFv0*&l8(=Q9)ztzRrz-6Fd)|_m24ed-)Uz+Z}Jp+4av^xBq_=_r){S z{4IaSvu4Fx5AL{bx$o!WKYzEf35(|{@!e0N68CMjVzG?~U%<{8w<q{O#XFgDmm{ZV zL^3ZG;W~3AY(@F9dn|AL9{Bv6&h+-$A*)qoubK{Z2d<Bh6jK+~N}6V&crQ7n)pu=V zrq=AJrE#mKe*Jma!P&=&v5)0Pi>iLb>#Br`i2ICFCZq(s*rnU^NVoglg*7Ki1UH61 zzA=AS)h6FZij$lQCYYSqF(Yc{v?(m5yF<Kqbkx4w>i#nCb4BpADXX%da(P|7>h$ez zhmJSL*R<3Jq2CV${PL`xZDd*Wa-!Ls!!~>En{VDcxoy34@*T^bue=i1rc4i<Xcbc7 z?xfgzz1H_h$Fa>nVt(9{dTko#l)!l5L&B5JDgOC_W?$9*mfD-dy~z;mtXz3hz-w>J zMfoG13(~I~d{DNAVR7$sy(Kf9D+9NRP59XOquOa!a{m2&cVD&`eqI%?f4=OQ^n#-5 z(gtp?aJR^5;&<o#dRVOUK=P~&tMoj_E|&elHEu!@lQ$o9V9I_Y_rNSuazf<{b$iPm zd$;vRe0J&o<6ixx=Y-U<!^P8{=zm_eM^E{~R?DB&<%Zp+J3c=5J2>ILt_aJlngwg! zp0pm;`C^hj@#h(9E8V>7!e@Q2EuEUS)K{P)*HLP^^KH&sr;4<k<R6|nl)9QZ;;y5e zZTnL{G2H`88?q*yxjVCqbwW4Wtm4OAdecR=ExOyKk=mr)a`eR|&u_`iqAh8$9>xxm zhsqbr=-=6A@KeD1#Yf$XmwbAd9-KYfJMZA5Sxg%)r!U&L?P<-KFDxfmtb183CYrd_ zRwRFlR9ZB_q3gL}LRR_oCGtP#879TG&N;o@C1KA=k@@~9QXdYsWSrO?vh8%q8jfo@ z>`{!aCX8#ZXDiMrmHPL|U}N(Xi;YVb-k;OI*x*U*xsQh3HtDN&X}%7TP}WzL<J+t7 z>fP_}2mN=ycyHG06eqw}@%P!rK69y^Dc_&R7<zP`y3^Z|@+nYb!GcK_147qL5<euG zSn=p`uT#jGxy`CSO237(GQLY=5j%f!jp$~l_I+xsCdrFULj*3eEBaTe$9ZwGol`gL z_@OGN8(_?Pzu4lagwzT5>yD-KV$MXa(JM(XX-HV*CjVS%so@v#<QuJt4@Jrnv#07B zovzlCi~Hgo^5TcF%KmBp6y{&NKI#97>c6}y4*R;^8$I~XY?NR#{a2n@$eXGi8$XEo zCLUHke>ZZ<>HERcyq_pfHYqnRe5cGhzj#8>vnYMu3j#e#N2Y%jVz_*%i&y2(V#A2v z2WL(@`5^I;wPt6Ho7lR!wWkhgoH==5`n4ptyXPfXc5<j#in^#B(f`!(#8Lczv!4G! zj|rVDs-9xY4~k4Pm)R}sw|(zPp1SbRl!tZQMn!9DBNWpU7aa<E)FnJ!{O_Z{Nu43f zghS?sZ;Ab6v}w}xg!NAna#*jQ;myew6H+=JzbJV&1FMd{_N`XSysJT51CrMNm7e}; z>WQ6Qkt_~#{>}X_ermmt(#4uv0x@nSZU4ErFX}xnu0J_D`{Tjx6H`Ou(*pEFk8W-I z!S*YB<;t=g-4B6xewIDDZ=l7pyin7EL&eHE>$}0$B+HG<tY7>(>>+z9>qv}9O7~^$ ztqZcXiq9=B+v9qI=jRsXNzUnQQ)ka;t>gX45L6U6mCttz!x^WUvOAQg&V2Iu$#2nX z)nRLbbg%J#u`1nWKT}s+@V`vo{r}%D6+ih<86orDUG3Z+g>;vWL(31ZJ<>4Q=!oK% znn~r&uRN>vcdqkxnj`QbAT7f0sXUWet8`_s;YAN)aaFMgdQlZ;TsANMyZ_U9?q6MT z4Bn}G^<=Mm^Q#2@H~w`lJ^8iS#?pyrKP~%p;g4D2&8D+|ZWg%-EN(w|I5|+@p~2z5 zeGkL06ra2s;aD@R@{qv<J2!6|=E_2@TW1zcZ@#nmigdbV>q7C<ngJ3OlbuS`6*5>< zymTuyol1?g%x<u|d{@icuxK*}w|@GeLmb?GQ>`X{>VNZg@`@8D{12<F60y_|sJ-c1 zm?W4u^+|A-pW70n3AaN6Umc16qO!7d?t0~jnW=nsr{guQsz}c7VJ}zfV_|8x@mFy^ zC$=)9Yu}~s@0;cMKiL1gqU6z~do2E+GNXA^m&n8CN)s<;F&D%|_kTV=U+&L+`-Cs{ zU99_Drv0~_^H+RUxZwZa$IBLexcqo+g(=&nx`3#qYDp`MLbupTtncKX@rL2GNBlzp z=7#y_ZPh+%-bsvj@;Jug#O_b*_acwYV!8N1{nrZTqh-EfbKmw$$h#OgZ+7W>!N1F` zRg%4Kans@--0O|H_7_@ahDJD_lZroB8W$scp#Jyfm^Hz?r}L{rK2A8e-+$(dZ^zXA za*G~(o?oXWacPf=`ueG>rmV4JO){Umdt1FJ>!*hXf-{+?Prr2CG4|uSWh<1X@pLMl zl2vnCS5vY5?Zb6h)*&AL0{6OREwboY)v)oDk}Chp*pdPfhG2!TuAJpbXI7j#bLFgz zmesLX-^3Y8uRlp$-n5NT=9go}wT|0rhcyMuW)$fkR^sJQ3!ie5U5xXCT++0hO;fa= zb?I;#2hZ^Te0{k<RPe;^;28b+vU=hxcXPz@eRmX>Pil?m&N=PEzP%+lK*NVY;d^yb zZfDExlIgX760TgesVKe@QEBziRZLa<!BUy+Y0GWCZ2dNqEljy~@!4x(+p<niUNx&} z^12Ok6;Jctu-02%^TMUOD^SuX*6YFFuoV*`CK%+Yx~k~yGw_vS2<+a!ugzv_RnWo< zkF4!<=lUds&4@AMPfH2t5smbn?)WJ5Qk_Tgw$-T<kJm3V_++u2&)x6o--l5`N^X-L z-k&@FUVFE#1iSb1xbo*QD|5KMb5&GN)cz)OyFxr+Rrs2dk0f0Lt=Woio_G>?d5za2 zMKRUsw_f`j-P`)m_0aTFRbL;3)b9?se|f#Zs_;7=%5UFgFg++tk8C}3S(7zNVa0R* zU&=w=zV%MCrt6>YzswnN$2wtyYHL@Fnj&|(V$_+k$4f3On<mn=DWkO1k55)0-0sk! zWji`cR{wMpJy{)_B6Zhz_S&7#I%g{Wd|Urnh}GEs#OLkB=P%00imx=%*;v!ep3$*U zjQRZ8)LV|zMXc1{Tl98t-u>_QJv~aaBJq*QkE+8$8dCd;|2}RGlXI=|FY(F>Rb9}N zqR5(b^6={(yXf2uMLo4^niu?dII<1SM&B#^IyY2z%{IPlhBp(u5^g@o;#_!UZA4S{ zuQw9|mP~kIVcB^}D0tTID`|6Huo&-f?GCj2zW?;w^M=WmhnVs{J#pN|?IyB6U32O& z-oxJ~q)C3aRqTJ-a!+5jggyGmaq-Uw?<{TEyXS)Wz5VWMOSQ$_c$2e*|GfEI{cFmE z85eR^XZRd`dg2Vb$H9+V%rkROnr~HIzIW%G>PNPF=Fgn`_=vlWZXa*Ffo)!wy5P44 z{?_Mh8P67Fcuk6Fsp;+CnqP7Kg}c$&hWN<OA68W5DaIXO^Lfk6d%Ek4-_3a0<(HOy zQ`tSWLhn#~(WJA_4CC9QHjC_-Z5QPnn}1;9Ifmr?wQ0*mQYOV;6<W4IEdCzLxwL1e zr`lXDnsZ_u*Zk-FiM=(F+`WQ{591}mb5myb@65>Mix5_<)I7FZ#Qa{bd+7|$H@ju} z{~NtHn6k9j)xBI#KaM}H<k#NAwZS4Y*6dAp{lK-N!fWqk@vsLsr>HjA?wa+L@#&0R zMOjalUi&FhtF<#{%bNLrB$<x>nxXM=-mYnj@2$@Je`v#ujE@^K8uKpixcwsSf#5Hr zN8Z*^S2E_M&r-O)N?L|ZY~6}A%NNd=Cd0z;eY3itT}H&Bu4j+RbaPavmXv)Cu==65 zd51#z?RKG=H*6-IO5x{h2|S^rG^4R#m1vu1OYFu<t52enBf?|y-)CBC>U}QZXzf|m zo|bY&YwOYWlMSltPu}&CT^4V0TsHFb$E5ndr%t?{ebwcp`lgc4Tf&mB8=G{vdd}W> z<9b!(mO1`yemnPbZU1w5rcCb1S#$K}ZeJ_CN|-;jPUp%d(^aLX1D0EKxTtL5XwX~d z-o;zE;^tE&x%4oxh}egUp~0bbUrW8FduuIA-Y>)vwzv0&slfDAA0~Wqk`O4<U#Yli z<?JmV*K4V|NGD6QvQ)KrbMlL9DqH`<G&%Bk4@ZvOPku?iL!s_f?)&B#Sf$$RYWlw_ zbWQyKz~p@wjr|_(&FWD9f7kM5Sk~v|y&0?uPp_%jSey;=VRV`!yz1S%q@UKuZ<us% zKV|&!lF91Vho^>jlm~h5sy53%t~hDxwKHK{-LGvUm<8wET(VqarHk6$XY$8GFYZ#F zn6$;`$+pue%E8tAUS(cqdlpF<{*e6?TpP1yv!2<4gm-}pKTQr1;#p>H+uv|g+|z&M zxy-<zQ|y9staeTGzQ5VF=+!RnY1O`4FZ~QWzRK1-`BJ~e=am_1f|Zja&A+vr;W(8u zZQ?F*#o8+m))@W2=N)x;KJSF2xh<PdC>8DeDqC{#>T&gj>F;9IvoEMiEsbz!Inv*^ zS}ZE{(Xlj%>E*)xc7No56yBSVv+fyZ#cBgFg=Ef>H~t6yU$`)nZ3641`In`BxJOoe za<Elc!D?A&F7dlzwV&Ph+c%^?<s4#}7j1Uw%7z6IWs#r0T>tQ5eck6Y*OY0=KFS{c zQ3105d`|QH{-f$u`oMM3ySg8GrhlA+I`i_^Bu<Q5B69cz_v^LY{0^s2s{c;-@czg~ zsW_&d+CvqSxePCd_%`~R9d%l{GWR;?=_kU%Q$j>9tYK73ym9SD+@qbGen)?EJ^$)9 z%k$mM_$4xn-}xl(TyeA8&Ux94RO{Ps*cv=O*Zr&8mFiO-a_j%AqF1-KnSNa!G5<-6 z+!7YnF2UQKZ4-2JwXUvkpZNB~rB|&^QR~C+=(VY+nDGf&?eo6#Iwzt}abp0pRP{se zsO)6hpM0x-9(}q|WT$pqYueHuOTUJjhR+LHIQiv@#UgBTUQ85^*y9j1Y4OXgi(Oou z`}TYfc>K3vlUk?si%%E66?9e}t3AVXLgn(GosJCrZd-QG&dz@mv}vcr!<B`vjzvi8 zOB7gI=4Y(QNIT0I8<2U^cTXtK^r$P>)A%-dY98JGPRDIVOwzm~t0o#RP<!}MZu3u_ z1?4YS_bmN8)mG;9h5wO%r<`}OPu+7SQM{_9`Dt&;zu-MPH{FZaxhFF@_0Bcp?0qMW zmMPqf^Go>Z&pwgec(vnt33C%Bw|oz)wbt#A`6rcX6u-QCYk!dSH<iSmGJC(QrRA#n ziX6wyH6&NDRSD@E^VmflPYZdWZ0>b+hM(ZBO|yek)K_jinS8I>Hv5bE%%!I_g`aM1 zntJ_#+R1K(6P9w*a_WL?x2CbK@sebgKP{@qxu*L=@%6I3{_7T(8eXs7<+l3j@zo!z zE=L&ND_y0&J}}ihb5;EXr!(JIADJ`f?y=VTaPeZZvRA!xR%twxS^sQL8Rv8L&Q}+D z)<$o#Oj6jyq%xyTB{{D};PRBxg)2X8kl?pF%#?Yrgi$CXt|rPL^@7Ugq}NM-)jir6 z)j8Em^_sL{x39(P!wafAA1k>F3QMg$vRk~UdskBTlIr{?JCo<F%}QiWzQu8lSIJ*Q zSfDdBN%hXpu08YKw)Wp)-nxIyUnX7w`&-lNXRl@#W-zqZb90jyc4__2*}e6JW3Ks{ z^Ins+P6SV?U%pK2<jUo{*<`X4T{GV()~AZj?Dc4xcyq1wjf|-!+uZzw9IyCplzDMv z_NkXHt*1)dne5-q4Ty2P(E7E8HD%Mo@VRO&>ECV?d@IOlQ}(;RVM|4Wej!WO-Cq$~ z8#vsSbPI(zr!?GAnO@kYXt;={pnb}2U-Q?6T$O#o+b5W;lJM%+I;9gJ<+A4VjlExI z=u8y5ev#$&m!m0<A4MHrYkKaN>#ipY*I%AwmTI>1&9sk_JO-hU;tp~4rpDP!iplw- zeaieq8-H<JU^S0Wm>}nZ+TN`q2JP~be_dQAv_6I9*O?yg&j+k*eYmDGe5v|u5q#!; z%U<2CN4FSvOZ|4v)=~82_1F@nZKyZZz(JQgLNFuQTw|58k51y9=f-xg%g)_Av&o)` z-CIBBov-+xS3+kKHKqq|3prq{Il=1XKi#GIC3XiVtuW;Zobc2~E3c@tvZSCQxOz{? zmZ{=qTW?%=QkA%E`;F$UU#7NvnHv&)EGDJ@#y{g7voG#alxsX(kkPq}TRr;0j#Y_q zF~0L7=0BdWfo;n1y;4&Tttxx{#fft*gTwTu7D0u%*}2s-Zi<yvA1QkvT%O4LAz|}x z!3(cGel540v%>g*TZ4SX=0`8Q8V-M0y1--4vATm-kIMNf&Y$9+-l}JlZh7xTnd_gX ztVj>G*a>WZS5D^V+NpEnw~jhj9H;&KoOG3jjW@rpdE#q()70qu6FISIBJsa>y0JId z9bB-#yY|<n&@&p_?p0s5-Mi<;j;9J+^Mu$e{JxcGv5H7-yXU{%XqNFt%V(OW%Xidn z-w?KF){@&l(yY$bS-bo3eVLJUp5t|lqMyy<kYZI<ku?qiMgl(%=LsLa-I{%3hn3T* z_Ql1wP76MY7PL_8<=S{JV@+J?MY(AjFTWWoTDbEp?-J=%PTMenCD*HQAK%xTGd|Vm zt}2f2D(27nykJUNYfoGH9xuDi1|JI#{@%yYtsptE;>0{<?W+ZA%@?-V?|8F0?#%gP z`<9!eUJE&RGb$n1WuJP?t6zH_SbwnDYu~x-mg>d2JsUq7#mTE}|6Tw2{VD6}Yv*1Z z_HT{lXDvO)eChRJ8<uFBEz2~rt@M<dotQf{T|*a6+BdPU{my%n?fN^{PSH3JqGJC_ zZk}8z``yo<FMl?z$l4`+A|-ognf+m_Pphu^9-T3-sW&mhu;s`8oyGBbr)OJ*ywjYJ z)e|{CR$Tj<;fm!E8Y$lr->o|u+H*}jPowj^_1(0j8&7y9pL7#f%l2+DoS<FX{`|PU zxk950>wNp)xBu^%e`4ybmD5fw{UmUsCO5opTea4ny4}bBg>wG5{VaLP!{s+~F75vy z{CoSQ6<4$CSBFS_O<Nk3dGP&Hug!7#oA*!Mf0RvUVw&-{?f>2D=h?L@|NX6e?p}S$ z+?Ag`DV;y+Hfu`DBfjb3;T45tk^-ArHF!_nx2&!`=JJIndxd|0b^G1Pb>;E34Y`}2 z-Mjj=>FHahU0JuRuNEy0N}0Ry_szYj`hR}r|5N{V<(J7H$)hV)?yflY+=T5YW4y|A z?Q>^2*^e(vKk>WWd)|JdrK^rVIk;#C)31~lzH5K#u1gUTv26VIJSy;n{7sf6lP0b@ z-+a`;arVpw!gm}uGQ>XNTQBZ0H*}?e;#Y=X)s37y`#6G%c4R0TyevL)&9SKHh^%_V zKjs&Gs$5ag&hpkaC*Dpf*ia|`O<t?WH{%VfN@j-Z&dSE1+NvAT1v!leJaWGTEo%<- zIm|oha$~5q_wBz`#zJ3rJ16wz>mUBK_QNYZ86o{YJZrAc4dyH;mC*8uiSOCwrIXXk z7}6+myy6(s`g>iQPF{GbYPxdahR4Z!eI)oFOrAbpAF?B|W|En0RKbIOG4qNitG>ND zvsJM7L9V|`a)Jo!!XSYYKLt;8le#A|G5mJ-=X>3!yMuoutiG%svG<=??M=&{Q%nDs zmfd^%{pL%{*Ly9k-!VE!Jls=Ubn*4K?lt8_6*ZMttSduP7+U7ei96t0dwF-maXr88 zq_o(>tKWQ4t-fq)U&C8p7WbvET73Vd8T!@Y>K6CBB{m#BXZ2is@8Q#r&si?#nf^R| z`Cq-ms&Zd{wI7!aaO?9j;Fg$KTK{rh%!Yrj&wVV+Il4UGf7kOjQ<yt-kheruUN#tz z{g8}7Z&bhT(mrr;e`{>h&awwzm2Z8xEF5K)bK~XD^(^cSkLO*ODRT4n_1GWpWWLWR zTh|=jbaerr{+HtkJ~u9K=k8^Bz2x_+9p6_~7u`6j?k9NZcHI8x2IV(jH!cY2=l{R$ zvo`Zuq2tFEH{bjiUoE4onk4>mLgahZ*D`-E9ailSXVCp++vv8XwSE0j!ItnR=_0e% z*DUbXSn{F2<*m&!t`F~ju&PT7OyKxdzoF^R#{Dhbzpnf~+x=<Ehlineo<BDXu+WOp zU-jXK@sG(T%tIdv#<TA}eBh>F{el@s{2VH)F4`v@|6|PO^U3qt|EDSQbHC52RXd^; zroDakTO;RrJA&B)*BsPZ`?soc|D|94Y@3sA7g=3g<N!J<^+-?o(Qj|m%F@MIBThbX zwici8sA0N&U)Vj-f+MptEI&Sb*KjK6sm6^k&vq`cbBf<x<d+>j+IE(e>s8F^ds4r{ z8Q(i_>^Bs+w(bsVdBKf;bF!!Oi7okdlIhlNo-N1L+jl)tOk7%UvG!<plJJX%YfYXh za73`q`&eYiT-8@PC-=ajg{ya64r+Wa|Iu?6uU~e{I?jm8>gN|pPMFN@7h3-1ge>O@ z56fcv=pXtgIp1h{G0NODDvNrT|1Fp^bMAhp^(R+krg7bU<hg0Xm)WytPnr>T@|(Ji z21{Jn^Te*-lNR$OT-j6OEO}?m(L;x#whFmCVtiAm9$&p{^)}}>H;>HU9Wq@yZ`B>k zbN`<D)o-rt+5720^%?Dydl8pgkE}14`r-Pr=MB=E?Kiz)oe)se&i+s0a`oYxS*n&_ z14CncXJjnr-SfHUzQW#^e3NTyCfw<-Ryz1UBdp+EU#xb)_sA8s_a|Q|-ZZaIDsgp7 z!`JlWpI7h8b5?WZx3;{EHL!j5{eJy)MdnjnIxKG{D2cnit$el5YwqioE@!_gSE0R{ zD{dE*rz{EKc$cnl=ha&K4>xo9BJCy`T<Us%SZ>ENORk)!JLVect-rS6tb8y34w*xd zr}RaywW}?jlXHJvq^{<Jh@)}G?4An>-)iqn@I74KIep8%kB=T3Y!K%AaC*A_tM3OH z^8Y9Cary3>P&TjqlVE+`4b{8_&s$$UJtlwZu^OL;W^v`rJ2Ht&Bq|r5+qR-Xs$L*| z8JGW(Ps;>40(Ohc4}bC}qWxc!`PRkks)8nlKXm6TG?nXC=Q{b7%ld(OeCgk=hs{NT zFJ?<GO7V-|@AKVLAy9E?Rhx&H@MiDL#aaoE4t|l@nX!oV?8XCgi)!{2?M^(OF!}Pa z;BMjds$Se~#qys%Uayl=JUB&IW78Z{6Vqy+&$cao?Q2i3JQ|&4(9+Sz%=g(lPkZq% z?>|u=Casn<XA=G|8#gD!zsza%@e_-u$#kSPtlRQ`<#si#=zMnXS0QdkG=ELCY5Tok zafHZ(ulpq2`7$`RF02f$_Ws9V>3&#Z{SMhU?wyOx??1h8$Z6}8k{+F$8H!6!O>t>( zYnY%OCwQw?xr(Xs{PT`JU(05OFAD$HGjv-yZ`u-JV>hXP$>jI7#s6e;X8T-!{88kn zsPf02{WI;mpEydrE^xiX^YeWxPrc~cKa%llDy*z~RBA6CNi+KPeP*Kf$;!Tu5<eF- z_~uNmbbt2oQI39XL@47I>RX+-F?;f95WGm*;L0f4RlC_Vm8w?O)CCf9pIub@@Lx zzuq~dGv`W)uj?{p{qBUTUj-VyR(~wHJoUzhDf5N*33+nFif$5P-FVY--2vIdpDM$i zS1r!?<oE2aebD)x=T5P%c_6@HG}TsSaw1d54^f@ERYJlovPvcr9GsR@Z97zU=W;&q z<?t{`xz!=B(kzkjnf1$=6-%r=m?gT6W3oQ<IyEX@4E9jud~vbGQ&VfjSxw2N>zDXG z)~~z%E&5lzb)RYUj?cSy)?NSj|JtgjJN|FKs1d&Jcm02s|L?g~wq}MUb|*W1xcm7x z8k%am|4hn<^JYKulaALXc<+06?dlP=lDF4Zg+9Ht7QU!C?fSdgIlJ4B?3vgVT<pJO zkJx&al~-3sJiR)#RA!Rj-Lot$FRuSF&2L;<vE#buH>+o77g~C}ewFnuE|U9Vh2^6Y zPbDiec5Hof^pezO>H2>YcdXK$b70m?KaHe`n`BSKv?%kJhSeO6Zdq!xLVHz?X8*jz zos06@XG<9^wP<_rM&}GelWu~VfBylSmuD|#$7%cNn$Ka8K9%HNRGyo-_@+nTa;10e zr!I$o{QAPAGvt6@#(c)2;@L;J^jiI#m-ye^aHqrUw0TmVf%GJ4m-v?l=0-#uHxcM$ zSlPP#%*j8Mf^BCvx^xsz*{E=f=d~$E)&_U2yN+x2GMteU$bT`Zvs;Z(fhqj0>MZks z+4H)Jq|Cz_zHSNi{_y3jaQOX_myX-{KG^>_SbW}cMacJ4Pp$Wa<zGGhTIQ<da;{sB zj0g7}XfsZqE`IFsq)JU^@$F*Y)plL@QETw~(~-)wgZ%zc4$s;q+fTV{Fm2Ta8GV~G zft3sQccfZBb`6=Tn058d2hGDg{h@c4OqrCCv`_BA)2XlCPnA&(n)Jdm>gLwaC%zK5 zn>iA_&u%xId+xec;*u3%chVcLMDLl?Z|Qv`%eSLkqnn2<>glQ<oFX5&*bdE|8)j({ zxYhn<#q3XWl%BS?zB`<6f3fY$x!VTpmaQ>UY@WV2aiv{+yRo@=zSx966JJiK$*Ww{ zE_~%m!cSqpX0c7>2VbQXJWJ|bb!Y#<{3#lf)AS6E=7%nNeKT$8bDyufB4+4MYT2-k z)u=i|rE7*>=l^vQs>#z>p2hZgWgqQsJ0E+Yd+ye-4bSGyICXuYbH9AXtn^27wWe-# zc(eG?YGF^C@<6d$3v(oP_@7>xd^VpyY}-mtU8RN3zBnB?wLB+f4U7ESkhGn*?e}t} zM0jM+=ZMORT)2{7=9g58^@Ka0R!x#K{N}XjmcTIuD?9N>k2JS~%1Zk<9p^>HygBiC z)s+(;xFc(iUB9EIDX=6Y+2_H86{=qq!(JO%zxOMN_F$bhso;5GmA=P@$GZ=jOuxau zWc?>?y$4c>g=r6e{D^q-V}eHaVeOad*K*#z6}=YEvp+U6^7~5GU6YSCD=S>Lnf0M6 z+E^#o@7cDy=dI1n&8<t$PWK4XtTo?$$ahBRn`PC>U#`_SzCUwczP9|(pQV58S9N@w zYwK|H-~6h(6aKT$umAr459h!6cGuo>%~;dZw?)``^M^Z6IbT$COJASsRV|cwqP+Y9 z1`3#Et)b)^xGug_|IF9b$?M|R?u%b*zrtDLvgDDi5|ZIt4=GP6VY%xa^uu_LhR~ij zUp^MQZR)XF>#>ZdQ0~1skFB|7_>#F3vKCksr6+B3d*XH{>$&3L(mUtxTzv9FIN`VJ zJlDqAC*#%6iQZ!Ty2kLX)edP>QMP|aWH}hv&#P#dJaUQSn&y0bwtKv)+3r`dro203 ze!X5Abn?mT6JLZgV=G+zS`J?M{q2_OH~tr{&$aGzHr(!$eK+}GyYl0&Pw!vS{1Jc6 z#%TG%_x-`kmo$7mT9GLDX3NaEg_FzbEu|{wH_9Ej%BAl9h;`}SOPZTIUl?uq*TcYc zFr>8d?2Y4}Stkcy?9X5NyEBgeMed@y)py)BC(Nm=n8R^1u}bjP^apk)ePZj?o~AVi z%9XeB7#7R7*!>Mj-f_Ts)8BK`h3p;b<~P?o^j7<k+R!lhw|FD3`@#7S_#b>%yi;1# z5~QX+agE1vS$U^9HLG;(a*t*%mgF*;DV8?ND{V8+yQ)poKU-RPZVZ#v6c*DAX!y1F z-%G#Uk?Rt+ZaLlUT=mGje%3V?<)CZ2cUuj==KJs(%xOI_$(E_C*zqdce3yO8W*rkx z;;Q_6^8XS3B7?&*DmJlG+nd6F?)vnp_it^4w4QEqJ>UA+--}abHSsINF7PR<Hf&Ya zI%USQ-DawLo5mB1w+{X~pEw?x7ulb0NM^T;TYc|HPL7>QmU@^%?cLvsSMPP6a}&@t zxu>}F(N2pRoh)@#_o_wC3)~`p@VvKBO<0hp`p_bg#i77j@=$6w1IKkiA>Y7XGkxaw z>?#O2C;q`}=i@+ur1~E*KgCoV9!{}vn3vXZc3WfYnb^7Aoqrv^+kUI%Y4n)7c}f54 z-U;ym<_!8*yaI*U^i)hE-M%q(mw)GD?9#o+&aNuLzV(St#SF$hn%CWP1-1wldS+eW zP?>Y!%5)p|2aP?6Ga~#JF_rSnzOuQXL&3PSN2~G4qp)JtA0NJanZI}44-el%B}$1L ztrej<EZ<qnCY@Wh<&y1cZ&g>9?9ffp8=NO(Zhk&%&fk@H80t1enENdAG8STEF6}B2 z5czwtT-;iwr+`N#_IcK(=~E^?cX@YHi~rZg`lmMw&3_%9x>Y6Rg^#S=*6AgSKP$OT z_1`S<uEoV{Ui#cg4o+pf3ZYH{5*GI|ricrwxHxxBYB*u^r@8-KuEK1Cya{Ylccp@t zUOBv5z$EzW$tzWp^Q&q@n%MW3^y%-;o*H)4_tLAPV@d(%cCX*{-%9$6A43BF@B0sG zwN7mgketZB!`(Kr!8pYDn_<71)uNpu>94k(_*s4~K6X*y6X`cR_J`N+G&ONb$v-mV zep2y~88?`GW7IrD9L-O+9%}gG9a!*#AwOGivz4P%@k`-J`jg+zk5S<D(sOP)P-k`` z?c<Y@ozg`o_)m(h-srXE{eA2G*Z049U?2MPJnR2-`%mY8f45(lo>6VSeD0RaIZsxe z&+pxLVTnpc!|x+M9vw4Edh=$B;04QFeNQB;T5`LM79Z~Nz5UMZYgO`%eQ&wk?LT(c zDDGyd`En=uxZZ~k%vS|FYTYe<9=tR2;l>2@?~!emKUCCT*lkh@Sl)6+`3`$Z_(3J7 zg0$d(7f1G9I>IlfH}&?GF9mGc5u4}L-#WIeJY~_c=j@XVv(MhX+|7D?p|n-+`s<6H z#ksp2zrz1r=CP4d71LUt$9-K9KF@5ACh1Jsp>DFE<VLxz>SfPVaaTXfJtk-PXLq^x zpS=-aQ<Na1Bqr@)#x;Ge!iUXD+QIf`Em!n^60TTrpe|{u@4B5+zD=k(o%MUm`zun` z^N;LQie@f-AN=EANBk4rGYS4ZeVhk7v^rcASACH9xK?FDP|>EDH5My>e!gy8rChvw zN6>>7ee*5$|GtN0o8^Q_@qak#yK{4sVynVhwjGYOfd#6~o=4`Mns9N)n;AL0>u+6D zZDrA#J^9I-Rh_Ie7j1G8iStlhAYmw}>B|0HuW?ppOUl)3!QwYuOxxat+&ijqDfaoa zhRyF%%-jFyEjuf4Ir#f^8P%HYdp&xl+>sF!{lT!{vbOw-e+)fU;dw$@uLRCAP5#Ne zvgM>t<|9egPno4NA79<GM>~I2>A%MGE~hI>Qo2%0m$dJ4xBA;<HMeGaSz-I8mwity znzZ)K`7L~Pu0?9*?Zo;ni(D_Er5jwlm;7F!bSPw_tAu&bq3xETCKuOUeXgzhb_Tb4 zf8?&I4L?6jQn|HETIX=ohoB5)x7`a)TInpwO!Qqi@lXp#yy>mW@rs4+#$`qq*e)(! zv^+o4{lwZGLD5sQm6{cg+P>}#uV|>fyEZ3j%_pA6^CzEguUqn&Z(UQu`%`mePi)dr z@%RvYmp$-s&RgS3i{up@S<@}%*L1|5wp{VzcG!jI&79&B8d)bL_}F?q6!rag@vr2T zhZEC<9z8X?AJ22{m_T`ive@;F>YMjeE$6>+pxQHV=Ou+}LNQw$3oC*jwoa^Cy?deb zFQqB{@%()uGkO1%TonGkV8uP7P4llR?-zbwFJLaRT_ZGi(pmS6<t7KC_DuT~H^pJb z@%rP9H48E)u(M`cFAd;HW)3+jd1UFs+HF7NwH05a|Li;V|AU^M0%xw;Df7(?b)NsN zp03s}f8!$jZrzdN6YjBf9<86l{;y_Y_<eiNFXF)+HZyj7))rs+YtacG{Ubip`>(z| z`{L)`h`AS;tL5{nB-4$#_qlDp)B1io-xMdV`bE<^rtY6-@G5^>uD2kQc9ZOrBkoQd z=ces_;>GZ4?#{^^-HwxcJYU@Cm{jIq=6_YVX}#lQGc}#fCc8Gx(mP|O#$ELE{igM= zru$0SaZQk%rN>ry$0i}gFDTbF>}}NQDW|6{<j;2ZO;}wfvsqx@?q|QQcmHP$6!d7_ z`#|uq#o4(l7}gx>a#8pg`9C7-pOv;w%|#Yvfz>N|mNzV!?Vz%w)&92R;oxLO*5obr zyL8;I9=|9)@36=Gn9jZ5@;9^^g%&Rp+)+J~ZNh;k7J~AoO<KD4&nm=h{QkX~<<DPM z^{(UM^LL_bnbB1T`1v<IH{?&zV>C#brFwXsz_|<m4vAcRtaqP9G4DyI!RPGvucVH8 z+pXIA;OxT0V>0Vn+65mtho3n$x7+#ATwdEVopaU8{Ej?p+`+%;d5K}zyqHR^UFSq^ zO8lx7s?j++eZxhK2KI{Gni2Q7E&6ywcTAV#IQ{RrlJy?8v#svobI#q_d9*E~<KUzT zDqD+cE}gx(Xlm2Ge07NveqnM))VnS$uLx~lIQ!b%%T5CGHgdBp+tcge751WK>3-J> zI%dCJ^4adm-H@xZEU)-+gHMmo+1+sIh4k(JCV90a8*m&AD9Vty>$FlzS<bh=@+^Zk z$26I(54K*KC2}ah*jDZ=oB#RcyKjo9>I!u&ntv(7Q(}?2N5_wZ(k)Xin1syGyYEwf zq(<x1ZoiBv8aW4)99RAR-PNU<CB5Y0g`90#)3huL_Sh6~g_;@$=LmB%<knkWePVQh zLEB%V<}=S`_lMKx&JEG`IP!__&E?+<X1@%IT)N?K#(L3rCp8c6?GA4fzxpt9Nz9MH z6Bn9i=qrogw<@~5EGPR<Y-9e$Mx_%gXR2;qbR}htl79ZFjR9@1HPYk`?!R$VL?ZXt z8;MhHhGPBczH_e1^ZwkL+{OGPR{L_?g|ClS)*p6qQBQt1`D?Ro;ezs$&VM%KZBea{ zmwUp#_>Y#{q>QhMr!P!-yWTx``@t!Hzcg$+SkZjJ|GDq<cZY9I-9LNB{&}6%TNkM@ z1uYfHK6<8h(bKhI!d$rqs?COhPad>4^>7_kQ#qYyV??!*=K77M;OX=WBE_Dp}V z(kW9>h|iPp-K9O6s+(B-e+z5Q^J)<izQCJbBc-CI*0ZeYsf2`e?2`#c&ZhkHu@k*{ zA!W(}6St!7tPNXo7f9YL(N7W<o?LO(H@U{(oq;dsmCJDpl*A{72kA&CJwMQ}b@%Qy z<!x!-EL-*N?^yM#>9?fz-wT_9lJ{<~{`&dv_jB*3CcQoVJLqBg5B>km?f){0eP3%X z5Hb!rW4tq{&$dtZbL>y&ODgN_8`@?B>6M6m_75(1==j&@$voLgY*E?+Uz;nMEqrNW z^Oi_(Zkf6JJG-FdOOGd<2mf%r(Om5o?x<_~C_(OXmR!iHI4vs~pL;C^Zp)v(Iy>!< z0KcceZSjNW=B}vx=rODCal{N&gXD>e8Q3RQ>BOY#M4r=MAM}_d^+fQgjppsO8{D|V zFF)gYsnT;L&Tl1;*IpI*6?1~0RB!|z5^4_Ks2A6-ZKt0lEI%c}U*qPYipf93ww_nG za%bnZr+s33*rql;^}lN(rJC9Avs`UMm0Dy-`wwS<g-2Yvi>e+z4BoT5;RWl>JZr%c z{p!7wvuX=#RJ0tVI_I3wyp&t(xui7c#q1xCc)t6;KF7EtBIUn*bv_gS*YAJ(vjc;s zDXlyau_@(#`6S&<>r*|O58iMy{uZRUv^DI~GM&yXCnrtd^7L5hbRufg6jLv)y7jro zc#L2Cy(#zqzj^=h-R8#Sw%_j*pSOK}=lMsrv{%k+xbo*`Hcq%_u|>ORJL~%=c^&g7 zMM=moO?d47VHuNb>ye)vxh$rp>ft(d$~z<<gr8g{c6*La>DethTN>gWopp{*tzPwY z`n3O!99e-GoBqyJ?qfK6QTk7)ga-Gf#toAv&ug5xZt`S~w}qY_9A_CHccztUoII@a z>YR#TqhV5Q>>lQriN}|JIFM=PclU&XiLi}`{*&dbbC19B*ndH+J+iAI<No9~%56&V ziCGc;aSdH>xHoN<=sdVQcgxMC>a71+e@v2^YgF;*)!|@=8`DoMXpq}8_hQ2nC9P<c zE#4_PHF63Xsu>c_&4+Y2YUCdIKbU>4)JTI@L}MlMf<F!?1YO(|1YgOxCoePJA#&o@ z7WZGm?k64WxPI^~jB>bDcr2Jh@rsXnr#HhqhC{zA6ZSYp*tz^@R{yr-o`vt12~Mk* zdBp^Ah#PTjFli}rbX_9+`oPI69rpVam^cEaWX%zOX=`+5;^b2%yp4Qp`Ukh0ept9w z|I4428jYlYC89TEr$n>*Ol>><_`~KsUzXI^IqfT*a6+$(SB2r7w{SvO2#aY!gY!4e zPjft;oN=v-Rm+X;(y31=w^mvpxzav>SIM=2F}Bkuc#?;ojJl6OK(~U<5$=yt0TsUv zq+jVcpk>n3xvbP^>(7_!$LDuy1bKf;INhgIEZcs8ZQ=hj1`Ta1`;yXbofXOR?7G?9 zuEOM@swp@{txM&sXA)CP`V_8-o+kv4{cEcE>-{5ML~`DL<)86Oo-A4Zu>Pd>zoY+u z$v>#~_2liFscQKn{>J+K8{+E|_y0Ro_qX-$FYe!8#lLlPy%BHy^yPlj-$m0_usX|d zsJuT|ax3KWfrj{iteTKZ`x7*#wpgy<_@APg?%!f$y|f{i=a;(-%l+em7w$b`;g1o} z)NEUGhDRb{@+twf8}dfanvN*ef85eNMOb9%Q}0bo3qQX5uvtsMfu+f%`J9Bw#Ul=T z;+#7iR;*lqXkqlmEw55ePfPg1KX1lVX0`~%XtsWX5(TN(ybBivn0bA?CCJI@aB_mF z*O`@#HglRc`Z}B!&QRxQNLs1T%y34hq0aqG8rS@@m#dgndgnFn+uACov2DS<l#^AL zS!Xl#PFNyhd(T3Ai}@V0d7E^(V|8WK9T=DnwTm2Sm=vt)y`SqCcf(9QkNn3MQv9P5 zystl*9ptgl@zE@WYPNH%&uvS8M7bS^WSZmcci><qn;h2zE{}=5@%#&nBuw6SF@7;E z;BrYQh>d^3oKO{3u|@wNZ?pRE3%NxpQ(|Ae$eR7?MbH`H2>DNX3j_2!u02q$xpCLw zW}9filqR7WZ`c=DRZ6Qd+MQ!qxFB&yl4Fe`)0WbO+s&QqUL>erdhQe~Cb20X(V_q3 z6hB|zxjl?O`6qDM<?0@MI&skhogJPto_eaZU$sc6ty$Qs<s-oO?bVcHzCONl9_V(M z@SXFB>*888gLBSI=A=N6_GJgm4o{kMQpH)!Ou)rdpx{%hRu;2qNIFL~qu{BIdFhLK zX1(DK%Bs!mX{eGpv*duw1F_{I%x*lr#ZmD|3#PMQTB&Y$<-)$pKAj%5n+%=2vKsUk zY_4i|n2~lsuI-3K;<fFwL?TjMDm+SAe4QAw=6<=ZVw-7qkHw86M^sca>#Wk-z|OwH zMH!I|Nq5samnko1Dr#>$B$%eiruj~2MRsTLN2Y0VCl|LasbtLKJ>veVfPdeIZ%WmR zy}2fQWlx#gFU**A?Br2tZUKfGf983UZ!uQnpPH6fuRL{G=h=z-naUVfSu?iQ6dqDC zdBn6bU2>M3bH39e&8ai&7q#Zy$_lt~ZN*LA1?`9SJz18jWHLdTjn|}0@lX3!CaL2) zInUj1=y=*$IJa)cWvf5s7w4$osyEuud!p=ws9b_gc|mej^8${^<#q)-)l(fA6dB&W zm1T15amqi}akb&}MuAHUBxF|bt`ZawkXe&Fq3B^_(6kxcEG%4yo&WIGaNKgxxFK^= zREUM`H<zO3zP)=7PG;rb+V+w4D@Qoz6jLKDqgKIomS<IsefdgF;+xK$lQ1yo72#%P zb8>jAsF$~C=j7MD0m?^y3RlYex-xkE$)C|Gbmmd~1Hlfr3!B+h)NQ{QR+z9Q&JnD3 zF5ob%o-Vy0Lm*8_(lUf?)~ia!W!;L?H3GaZ`>oD=!F0t{%)+6l^Rs;vZ?4eGe|x>1 ze55!vmmevaFkzL4&9}5DfltB*B`@SZTch=-e}#o;R<fVUpVziO?ce`jcw+I7{u&44 z-}NaeqEAGZ|Ed>B75(?C{vZ3l`DY7Ejx32iZprYPm(x>A^5L>&yZ*f9`-OoPJ>#3T z%)3!fF;Y@;lD9_`pQ`R^rS(gcRDZuO=gj=iz1)gLmH#R`v)aVt$1ij~X#U73&bs9t z>yHEG?3Y@F-W-zdWzP><u-5(nKhMUtRt*(%#>BU*drjF>Ecx1;*;S0riNEeYtgKeD zgD*#_(P&xIdNxl>3sKROg*was@7`jxdH0e(g%<fuEMkw}vu&K%zQ2FF%DuC$QJ#*4 z%U(;R{`!>pGOdU4rst^>-n#l$y3!UK`9!&Q$W=U;ob%t~TUCgHn6If;s4MG_dpzzR z%$2Vxw9CzzmYQ<QVL@Nox!;T2kC_%r_L_!#Ip5}(Q@5-zr_K2LE=Pw=4_-5Q+?}!H z%KQuRZWEd^*uI=p*V{MmK|}cm)n8LeHn7;sFb2&C2}n6P-SMgG-9~<mGwfpQ{~yo( z?4fRcPo7U$?S;L}a;+_|m-EcAS-FmjD{u0(lr0lYl#cWTL=;^YS#sy_<L0nDExs9Y zUp}xKUKZZ0+o;sa@9BT2UE!>d!Gb=out`_sJ@%Xoub%RCMXJ@T)`x|YW7JgJCN){+ zvYAg|=`node@3!(jaorel;gAeH;q*t{v~-lZ=F&p@k*{J=kMlN+qKL^JMF4>U;eJn zI)VAe{{@~tW?SaV&*-=*+0V9i(uJAK<=km}O?U21aI-Hj7ZX~>$1SJA%jm@*X*;Jp zU3lZ>$WL#5y!0*HZk;?UeWvj7YSzVDni;Ph$vZK7>Ayv@RUUF^FlKc6bnLLt+w#D< z)A4=YH>bQhC*%DgYF!o*vL|>|yOjLTFno7@6Pj|)a<{spjKUJODN-M&auj+zSiWe{ z7Q=3)j!msnIZUjZ7&GS_da~t;iuyDbuM=`z%*UJdTXC(Na6a>0!}-e#=d~O>*|D&t zV-e4vxedIn|M*p}v^|`+$3W58X`SNct?b-~PBZ-HVhUJsPExA(*`h=Be}omfC2dxI zIP9!ZYFm`qCbUZVovMRa@Y#I-!@8ZbHYW?^^-2kF29`ShYEG8x&}Z?upfY<x(3B6~ zm{OkWZTxrT)RT1M4p$}ioT-{~{qhB;WqM3DiE26B;H!1^&XVs>&ZsT6T6Qj4WT``F zPk@&iM@!DH+k8vd152|+1XX$O^H-_({@ZTyWqZ#nor0PvcQ|{M68IJ`Snw!iJL8f& z>Qm&EI^PE_GAWmdOuTfhHHy)`(QJeA+m`}&@85gEZF1UAQBP><U&dEfo8R)Sx^?5r zS;LceLgs&0j*vg0B+#zn#N9Y8gsEnomL4BhfP8pmq6SaEoJ*z*we6(`AOBfslci)V zoagm<bMOUm;Wzz7A2}GVuxTW8KH~C<J?{8~)9W;Ql?X>jG2h4I3gN+9v~`7=IcFX) za*=D%X55;1qtwDyB1l9*^OpP;_H%pm63P#GREHc9>$qdFbGD{}oM*K3&6go95~_!^ z4a##fc1>Pyq`A%3wXi}sE7<PQk~<5YJZ-jdNbR!_{IF8AMZh^n-kl+ZsfEwkj3=-w zZrNwabJam6L3!$08S9tWo!GtL>n6#4_JRywZ(YCNJBvkp_L6lsWab$KU%EK2$fIw9 z#grKfXGX5_Zq|?b*&#kRbE=@&!~pfvFB$wB*T`nN8!GXxT-M`o%{NB&#)^GQrWe;8 zxt!CsY{ko(&1+BRc=PKE_c<KAd1Ts=lhKRJ#4BVo^$coa{ygOJ51xDAp{0=jjQXXm zCxxHIXR=ye3CT>UT^M1PpxeRC6P(g^NB{Cn?scEs@0c>TD>BcRdGy(WyIc(^Cd`&> z8S=6`_a-j)iK@A^BvUZEi${6e8n^Cm8{7=Ec^#E@9egy8O`XAfzUAeK9v)BfH_l{d zza&2MxD2ONtI(EocGqm*+siL4XjAb~J9N}j*tzrkspngEHotlk^8D1E4FaOhY=4*A zXfyXEf18zfB~>X@^7*6Q=BCeTr7KPb$1<7BIji>dWVza<8}>qfg1@U?x+b6FqxB$j z^GE*3(;A!1{|5K-1x_(Z`Z!BDboqycRx`C0{!%wMHu=HrEebV`Pw%F#S}J((ZlgKd z&9~)Jc>>cMzC=GTEclmEue3$GB)Ux@IdQw`qO0eYd9x+f#<r##*aU7=`gb^6;*xlY z`zPU|Kby}8t5h>+hBNtIiCkm9MCowF0}(Oj2N}l&Z9Q0Im|k|f?R@N@V;RmPRdRN+ zqav$Azs-Sy9HlVf12Zqn`f$9NRL;@0VotY%;Q9mkLjNzZo;i6^p1pbR<v4{$Osg*K z5}hb7pYn{4%i-M1v|0_lb`t@P|IBY+^RcIJ{C-$?F2f+z<jMJB*1U{>3;MUDP5cB4 z^dc<SCMtc^ajEJEKgf`B#-}1^a=1Fj1eq;=68{?7UG5fC^gWQ@%F*%lr+?1gZ+(~7 z?qN~nveus}virk-Pep~v4^<ynaA>&{tN#%1$~fHo&%x-@s-W#R{9A1oZu#i+;7x-{ ztGeaUGu~f|{L;3yYF}j8+U&h><rnA5kIUwHHH1a+d2u%GU{iRkn!?OChc`=H<^)gH zGJQ3UZPCoUmwdE2WHp~mndM!)Ped$E|Hh%;$7gac-)7;ybV^trM^;&VlK6un--jF% zcOF#qJt^wgnR4jJ5BrY)D$Cq|9seM|`AGe#)2BRqd;d=fpYpT&|Hu6u|6810JkCcY z%&70$lUV=Q=KrzEf4!goNI(B+e(rProagr~{G|%#J+Tt@3iFDLbd%!zI7N8+Bv()E z6(>%v@;LeDx3f*@f0kLvQoq<ECb4~xR^>S%zk+je<Kl)FB}WA|yxX_oiz-)=z|<)s zx~JNe7`CwL)mdyjsSx_8(9tQF;hoNgr3WOLmrrMqf1en$@U!?*wt4G5L^ZeB{by(r zye7`P_`xo#gYzvIj~qKtzL<A%ru{+lH<yh<CQoXcWZD)|a?~-rm-mSr_osai{55_l z9nx0fbZ(S3WZXP6Byh$W!6i--C$-(L+3CM<ZQ*uk?Ql)_uX)KjW42;i%lvDGHFw#Z zy(dN;dFPSKv^`(_I)j0sifdhP#fE@u`L8Pdm)C9Nlw-TIXO>E4;lKToTeKG|tUci0 zAiuav`;5T8Y{y&dmzByj4|;SQ7Cz>&!+XIJ)g?z7>X*1a6I$ijk}m3SD`4rgUYRF1 zUrI;@Hx&wS{W*C@wIT45%mnVz7GCa#UkbZA*yVf=oH00D|GnTt!2Yy=?ICSTUq53r zV}7wK;T&h8infHNso0udfu~b^`idM51^wK-^svD1ZO5vEC1+g=c`eJjibqF?qnN`{ z^=o^`Ov77|vyF01de+*jzWyw;YtgjcLaRyL{4Tcn0)0{+fBHEITDC6`;Pe!k5bUVG zMlkdq^A7Wh!`sCk@jP7ftu7<}$;4*!j@R$H6$5&2&rO|r;9;&>)smBi9hx(wBSnqO z58jDtU-QBv;l!0NPtC5*Yh@pp9`W0KINo64Y%p=zBSGJpi?~m$I>F1e?Y6UrTSBbL zJpQ6ag&Pl7EjX#q!5o{_Im7n>pTfe~2VQ7gW`82r#p)*-K1F55^4ro!<}CBdy70qV zS+nF}=h;$oheuNkG@m#8`}m~DE$N!WTYGWyOF^3*Qn?S*3ZGaMt-0Xd(&pyS0Jh01 zKQru9@^gBs^vXEtNQw6YA%)#Zn?y4;H8137FX>&9A|#~z-%sk-IiVek4&BT>GvRX^ zgV<t;E*=XJZ;OvS1{?wL&WFF{38e9y^mr`Bpj7I^$K!Na@keu>)Ry(yuGJah0l6yv zvlW_qu9R^9lt`Umn8m!BRb=@eFScJ^+?idUtRr7o3nxf!Q7~nA&0owSX{o7wLQ%Bi zcgv^f$`{cI-<f=Tw>B_^FtF@C#&p^xtR?BzCI!jP7vTzDj|5gRR#==A{KJ@%$T%_h zLY%}4O=iCLT-Nq61q;}Z$va<Eh_qXD@_edbkd(0NtZnRkJ9-69xa)KaT$5nGrF+JJ z!MA21PydwG1fkcB^Rg5TXJ~f1Ys4@t+pNmgY{a_UF><y?aQzg+6z9b;Dv>R#t|{>F zb`&`MT&nbCspzre{O#<#0WO<-l~>7lgyeGcvN%p`mYh<-ru;+tfGY!U&R&5B{RteK zTR5`aFG?IZoEIVRIqg%hWy_q|4X;kTQ<h&T<K&qgAMuc7^U=UM{pJsD94$Z68xMR5 zoHo&wqqqFnu3bqrn)~N0QQjo<B>s?#Nn7pQ1WDZs^USu^vIYr%sAH^6o1&Uf<;>m4 z^qr?7wYhhmN!yy1H=Hw0g+<8o=p}HaPU$>!oOg#(aGg4@aH#xqZ%e++8M8bLIIqrI zD%kYMP^DOjWxx5>89GN9N*jOvXkW0Vz+#Hv3!Y^<+U{{-cWxbG(CheIx?HDy!y0|X z1N;xX17=7w*L1oyUfexJRrEwO%c)3*c@YJyI*hA??rgX`%|R-T>2893!`X|$%UlmG z{WQU%?PJ4<$ay?U9AT9LWo*SunYLS+%+rz9y(H4(Zt2^0LfI#ufy2nA;?sohZgyFf zEq@PoPE1S>6q~4+t5ECE!@kg2W4{`gV2y&pvVa3_N={)xV$Ih%G8?51pH!aBX!SOC zg0-+q;iIg;X<=?{PhU&5l(OzP%kjGTYx@~xg+C8fuDYf%Gqs2WFtrN#TrqPfEi&uh z$vV~QL~K95(RKzWaVrJi?H@WXe3>G=^??0VhUNY3pVl?sII%qQyMUKa;;XyWRxJVx zG$)<su1aZNuwd5?hU3yrZfsH~{xg&mT)Lp-wop(hf720-d|#7^?26N^E4NwRdn<6X zRW?ReMJC2I;DG+_l*2A9rlA3cgcd0&&D!IXq;cwA?Xusy6>kL?iukJXO=b73IhK(h z;C^t&B#(7Xw_L9!OUO_C5h(S;M@#GSL~l*)%N-Zpo%}o6*h>?peG~av;*qHlYgphS ze(kz^L}=oIIX#cJEZOf-(9pkkjq=%YwkP5R8KMVcVgh(Wvn0JMG$-dAT(!ma=7wi? zF7x=F@sKH6H~Y*BpGxbQL8)B+T!*FoQeN@ScMF+%bIFXxZ9*x<3pT8167;+GpiSxS z!;|j~LWFMmKAT_OxbWWtnF<k$JxkV2h?cb!xs_6vXAyRS_1p>0`$DP>f`@0c9Qf({ zF^$QqtDTiE|H3-<^Bg^I4({}Q6d5#2KcQSv?)AIAJ(kCn%$1&;Vs3R7QfM|*WboR- z{!np|+UC08eK$X-ezUB$m@HTtbY5q5n$aykey+s^+=qMYe@>M6q>>P&Wz{9wXjXiI z)5_CJ>E)3Q6Q?5<HMY#oJC~(Q7ZGZ@q%*0vWLd~T183#hhMzruk0vJboo!z>dE2~` z63b^sU7TZBQ?YE7mhrVY&evCk%-^Ks7<i=8=UGVG;lCN}ng?SYZ5VH#ZPC;|r+bRy zq}M<8$9#(gue+Z5<}%}w%qPF8X%ocPwhDikr8xW7mrV-XKNLE}j|Mm;g>;?KeQ0&G z&Y_HH(Rv3)?t<n=3pU5MW>~c(-C8kMV@g)ThQDR)37VRP?FpIY?Fp{t55&#Q69r9s zdy`ga@g3lwzodC@^WtT{))}xjEnd;VwL*@=;TGf1drOze9@v=m!8)K`rXu<<xAXJY zS3D1YEKg})cxH>zWw|dCGdRkOKF`iSwdjE&|5x+pKVEBo>?(d=zs&H=l^ZgPKd!jC zb-AtmDl4W|)9Dgi9tZO}?(*(i!RXW8R`e&SkddV;eU9<R8O)AT)UHpkT|HaobdLVt z<!5TLzFKZNyd~?wV`fDWi3rb>BQN(@InS_D$XI5lJ14Ot;hEdjdIOIqH~+-6Sxpxx zbGhl%{PF*z4Oi5>1T^;R7i^zp!?{8u^0Fu=OD6M^shPP!Q8Q20^f*eSbDG>c%51S& zG1&OSb`=Q)byaO`U0qdmeP!LwDJ6x+o8)|(xNq?@Kbt&_Wt}{W%`x9e58i8W_Fv)L zlj7NMEh}nSrWb45t3=b-TE|P%gV{3$4zc`B{Fu;SR&cXT;o<U{ewB6Z7k|l}O5(q3 zxT4;l;c`jRgI@uDfty6G27h0(a(y|+h9v@bc=kxtW$4UvQwseQ$-I2ZDuJyg`z|<h zOPO9-uDadTOJKh6qK-v#ziet>#P}gs`ocPnvtbV|lpLQK-FQr+Ytp=VrzO0kn?5UN zT}qkZR}s+rjal({Y<jQC@dIY^T{)*#IVG)-44tFdqP#6p|LKqZ7jA+7=TGWgBq$ZK zn8kGBG8R)sGXvpOoF@_sCgptUIlE*X>jn<SjQO)A@`YV$8&qW~V`uT-F}P>eq2)O3 zPtm)?j{$#{8C=>bvC?wl$pz(G=5G+NkV|;z8M8o7rEQVSy%_(3l)foaLVpz3UZ104 z$#5@m!XlGQk0&fk`jnGQXMJ>KnyK)+S*~NM%)^rw4-M<?Wr#Oe)GT<W?c#QNrgi($ z2Px+h+M*`C_|wd&`1HU-mIr$i<=20Bll-GOt+V&9@B&G$(?TWN4pmA2Vb49?Atthl zW!|+DiQPX|e+>EiHrVgk%=}MJ0s|7XZw07`tZk2-@G;a$)-<vt?|{M#%Na46_k@G^ z9TY#aAKIw?q_XzF9zKs9rftup&9e;6t~B$`Wxm7tz<!BOq*u`;qs>W^UrdVJm3CIs z$b7Ser7UMi(#(GnF&umyvr0?!ioHyk^*OiBSZ3c^x>3P6`kd7c&4)fFY@S~vCw$}l zvPsDQ)OnV+;|-C0a~4We%Dk{BZ~RnvV57-|=)|lScc+{T<a|2A$W*8C#qFiG+H3Tm zin4nKFxIMQv(EXkDd^Gzd!q;Tn;zJADP30F+n_Bv&EaeZ(=(~&BqkZxXA^8Vyt}qB zUKI>~w3Q)%`@r_qCQ9qmc8MRbKVq@`WHg&iWfafm*;4}UwN<Ql(3=?G?O}YVK&(7y zb3~Aa#9R3bhJ7t>yE=T`E(Xk&z3}PH+ll<#4-5)iHWyh7xp>8us;Tptcz^Z$t$OwF zgraT*;~9U~svH$KVJfs~UC7FBE>mxxSY_Fju_*0FSn8^6X1}|$#ZT?9k#avV<@v_V zdTg3S<zW*!rUuXbefY!7ek&=XNy|P@JoK7ltNqNq0-foznX9I0>ps!ZoboH=gV_dQ z!2rp}OCO2e+B<uac7<nB&zx?L^?L-Ti5EG_Y?U|?$bE;cW0M~T&jMysca<8yj+j$t z550eILPO8^%1zl#8bZNNmW}yO4c8rVvOD*Ii^H)^Gf`4R!K-$o-+>kC`zJG;e9^*v z<iWwH0~c0A&n~#PZpokaR~qu^>N-Lx4_xMKFfetUlI5tV!>GP)iOPB%JzbZhXN-?( zck?eaQ(zKdX#e%tbm7Wu=ZE_~c%6OFDWJd>A!Iw@!~I_lrd-d0mfmeKE;v=a@|+2W z((FG!ElM8D+RU4}%8UOCw_tIRI+xG%!<WO_?rd~9CU?ctXR4sas<V9&PH%dSMVH>F z*Ke44yE><(p?K2eUmg)UPrOxjUH?B(V2SR*2^XeEF8%ssGJ{m})kZZo?`)O3Q!P|N zPcHIuFkAj8p3UNNwUK0&0N;Doy>A(VLk~@@N&F_%IPb%cZsv5+Laq<91Cx?F7E7^5 zy*u$JM<AE2gnM^ILd+hHf_2vB8uel`bohDqg)zm)PwJDi&7HDk)1EVHeo0s;7e3-S z7?!!1MQA&ptEN=CaO%Dt_PmQ1W-2Uem9e)B_Hvua72&8?dLT1k=dCLLqq?o9RtDV= z;=OY~g27<D-VCb?(ifN?tZBL2p~+^{IKh4jAFuJzFIsC`E-93<&sphEuV+`M!Ymsj z8}KdF#bLR{lNrH9&lY!1U<_R#cZl&MgA(r?gM$%G_Ka3BwVlUb#W+`KOrPrIz%L=m z^(cV(myJ)hhhbo&hVjhmiALd@FUYt4@Hx5GW;=s?O8m-Z!DGQ1JTZ%wxGp>PL8DRs z)P$)2=PJ|{EgA&x+<GvrX$4b$o@hwJ`sw;JI{EKhOh{t4u-3C^vR4wQ)Oxy8kUfE^ zoGJ9yiJ-;2L2&}A%F5sGFX9z^&+xS8tGmuOiU0q3A~npcUP^UNn&wmxSj*shLH&Sx zQ^u4}$A0c=KGFE#ba^>{=gUStmrAEo3w(PrIKLfVEXYu0*pN~npnZ^g#x?;#uMDZP zPghvEdQQj-wi0zex!_8Gm+_om|1*}RMeC{vYV+~zI20VatbRwx6^B#HtF|a^n$#ip zfP3F;5iu^7b%Im&=<;@kWeKtw2L{hs#4J(v>AzU5rU%oa3%(24eK|5e9Ap&p3^ZuH z?|507w=MjabKCTP_S+R)R)iKED7|pr?c_U=5+)NaasE9UL_)vKFS+5HD-{rwAzYXB z@PD_rVQ#+9!cGm>4emFla7fEvDD_B9ci6cgBZoh;b^q_=X|9eOzvXYpP0>BS$6Dj` zzq>M*Pnci6_ran0eRSY0v0E4BisyMUJ>`AT!1BY*$J_PqYQ3}DtUu_9E%?gBvvC4f z8FL-OCPw*Fr+oHz2IWn9DX3l{?p-e<XSD9(wcQ$03s*J?wzI|^_IT?gY5tEnySc@# z=?$x)@TX;O>i5`ZtPV6N6clDQ$+=*}vdC?*K!TZyM%}Jh_HXxo|Cp#dvG>dtQ=#xh zXGECmR`r%QKHO0IXPHeO<NmEI*6#bJ7S55Xe#-X9rJ}vVv0rro<D}b1I;L@UX>GX6 zQgzMXCS!rtB(0{3$r2(jrn`UiKJ9h-R5#=Q>61gJDEWVPP?=&C*>1e)@syeVf<KOY z`?vObTv3q2mxX6*|G#=$CR8xRCFq`~vv_9N|7$N_HrQ}|e3n!F`o5WUV#EZ+Lp>+< zDm{qN+~4$6=H`*-HlJ@Uw-L|$^xpW9KySm5m4C9+@{UY2TK!Q%^ZWfx^5>6k^ZxWC z!2JCsqgJN*3TfN_|BrTNe95(o-Qf>k0jnd&4A1+LT`y9Y<d3&hCC?2sXk(EnYTY5a z<cLO8^Tlm77xpP8Fw8XnG)b=Hbn}YGFBFvKD_9n2&YCoBLB=UD=bEMMZ)7>Tgm}&s zG$z?C5S+Nv*`X)+(^cja4L<*iN;@lDg%{iwJtlfanNwEoz)VGbZB_fTS?*<azx=G_ zmPhspgtf;$^Wa{fn_}w~7UeRzsKFq4)r8*eLx&w)%$EKB{dxb-g`NN3@Ygs@C}CRv zB|u|=@Da=ZjDON2U0o$-uKaQS(<h_)kMsXWzqapmWQfseyezZ0X%o}@o8FoWcrNaH zV1DX=z1F8qT1G}1waMCQZW4x0tf@R|EzBPaj`H&8sEVpE<X-W)=Fhq`ciFk^CvOQY z&E;iW_b8x>O-+MYT2|)Nl_MOfTs2wSDy2^aot9|dbjkG4M@C(y+PsN|n~gSIFukdw zdiKbU&^tUl*AMLI%4g~m(DgRv6pq#YkgVG@U;kLVOkvC6h`LWfT}E9F_bayS=8s~v zNIVjIK~EuTiP;bPLIGLE{?E&PG%4|Jv@fu7d>0yOu$F0F!^d#`59Pw)vvk~h>;q<A z5!2WlziPpfr&Fg+b>LmHeS`Ie!q2t`H}Wo@x4&a!*7Dox-G)8Z544Jyik*FK8Xh_L zolkZ(gVmfo*1J^~f3%xd+&cbQCBE`#{Lixz*Zu^aO<4b`W|z|E89yd`nO?T>+Vv$R z`}XX9{V8bIR=X8Pz8!E~ep~G;)AY~#zexB`nf5L^QzH7}jmZfccU4B#p5~s@GCAbo z+1>M}{>i_$J%I1_w#N$?uRp3d-1qTg`|i0%>bKoJUVm2JYSM*N)p_d6W@iS~@7Z1v zu<c*wtFOoAR(93O2yPQEdHMdQ?1{Vot`#;a>usMHSaGlGyHvGEn3sOuz07ToE_Qpf z?t5`9ww(P|e0b(!iTTkirny3wHDYJVeR}dtNbKKJ$9K({`Q|AhYaHK->b~iVHPO3X zbzRloB{MYZ@^Zte1>$$V7K(ST4%>EQW?#y_8=tiw1kbFS*S6`b@tL{4MQ<;jTormK z=lA8nmS1ZRCY^crdvX2Gr#&ZmL+4yIG3)t~C)xe3Zr-8dr1S4K?wK^*RYv-|(HxPm z9h>%_N-NeqKim4y<Hc{Jr?biI?5`_Y{Uz_K@|x|pw(jN;u4UFY<aN!dT6h2Q-cuJU zlc!x*U)NVDTld0X<HV0go<E2&EW01Gf5XRl6IWf+TE=va{WtG<cZ=f^J={5Yo9n8i z+s~Kgt?u8SyY~BQ*NQ3E7RG<>|5#@JcKX`_!~cm>uXxTdH#&81|KGs#i~sBqcem2Y zeJ``=ZjoPleac#f+MA8X8IuC}f3L02<$1SL^_==EdwY{9j(-w1o}9kq;nKNklJmvw z?j0$far>cTPU4}Bi+?BAb>;}#eS3F#)~6$P75DpV>+5%9NIbZI`rdkNvw)&?4&N_x z+U{!=Pfgktc`x_o$<0Nx`SJ|b9WQ)Sw4q?xm&g21Kk9i`eml2$;qC7SE7D)i>(77j z>Fj%P{cd^Q{(rhX_s@rK@Ot|xG0bYA?V7Yj?>E1?zv;sIUY@?<d#_e)<BXmDiR1X+ zPto@K6TWvTuQ~bpr^Tu~J<X8+{~T3L^pwtT(fchlC!%oKfyUKyFV!_Id}V$~>Tdr! z?fPvuEB=I>J~uJPzFmH5<lnPC?Ogh1;x?NmJ=p*GGyCJS;r3<U-BvE1+w_<__4k)K z?|Y*5t-W8A@z?ohmwNdbSF0r{;*Zw+<^H+otW}JXROF|#$1SVFADR6$%Z&N{^wwvs zgxlZaZI8v~F}AcF@wpwp%_(;G@%+@KRlM7C*1zk}=h3aH-~7V$T(9eEx5wPJn`H{F zHT$tvZo8?q``wlrPT@qpn>!8%J#u~!T5f-QQ>x<Pv~uSc(+s0(Pe0_j_2F}R%-vO2 z_Nvd=zx{;tx4L?hizaabW*=%-cxbEsXujxpHhGn{pQn{<eb~m=%=1d$tXnyC$+z7h zTlYQAn_gP}{H^SdLYq%Nr!UWOl5WdmcRTZc<^OC`@0n|gvu=r}ujYSxHFwXfAlAH7 zZyrv4RQ|jFP1KXzMV|jZKUmy(zh&EGo40=pXTS6EEZCU-^-KRhn+ZAJR9;U#y?n=$ zHLHDNs@}i%e6@=An^yFW&}f$THbLJL9!$wOeB`71CS9BLKCgKyb=UXX_=RorlAZeQ zt!~NRIai*p+&wq@=~@-{bqgxCh3Ku#^{<mVvR$?2E%)u8v#hsz?tjwbTAn@aKxaa$ zd~<5z**pC=cFnpaG*zzpt)#_##p=B|KhD07m*wI1&I*g<x3!jCa{j;a*XIdGo}GQ( zv`gu&(F{qskixk)wtXn6d)-+c`*FX#S(knI=Ke>w_N>fZeC%%Hdf7{#<^4kpF6c(D zmznhU(~XG0m``Qr7M_qVi4S{wUrqgxocgZ^K77{s#Q`UtuKQ#@sXISKrqA-XPqw4Z z?Hi4ar{b>}F@5e6Hrv#3vcEQV-oZQ71$7S&_deCCS$nc}mAYl@+B((t<3Fd^drXu3 zxcAXdh3=mnKWvKEu{uRAYWtoyPjZKa{f)<O4F1piZh0p3q?mifyx0dJQYTAmEe=i> zIJ;6*X~vJdzbkvoXTFqP_2Vw@PKzx&^bGcSdY%&he1%W{_LfI~EFG7#thap@nl{&e z#}YRi-+QdOpK6QCd95BrU(?(yH06NQ-78CHRNgN-yzPcdLC-(0j}iN#%KTX`r(fP> zb8qTW^^&<;INsbWso5mceBG5b>`!0J>W%4~ipM=YH&4%e`RL!9_g42y!<JZWI?8LG zw{KHkvfZm!(#q<p%iGsjuRUA5eaq=9{yAUkRTJ*237E~<vmz{3Gqd2z36uT*|9-!m zeN-sMk9C9Y#q7IH9~15Bf{#6jv?~92Ni6r>{5|exr_a`}$_p=l)}r@Z!s^|&!&8r` zRc~G4Ywptje)_|ttkw1fPn?-wyL+m0@7w)teOae{^tU<M!4n@;Joz_C>iUA$lYV)( z%&VO0t?}dY3)7XVGSXdd&;Q@~<Ms0ExnEYB<$CLV|Fk7y;~9fR4-YJo-{1V{u)l61 zN6zBlUDN)h+~d7|yvaV+X5LOC4y!o%&L!NpDh|9@dHcw*-A(0^Yx!&hdZZpjq|`LI z>Zqly__6z7$Lfmgdxt(gHDBtpZCkCztNJZh9?$)=Hsau>GOObSoVUZmtQ>8{jxB$h zKWm-`r}U=R>KtXQZ`W@#mbUomt**=acWdaiD=U^n{ayKUy72V#*?rYAADnx|L$+`I z`)6Op#(f@-UTA%`6?(g^DJ^`TzFe&7`<sj2eEqae^H{U0D)+R!u4ju^pL_Mx*2M74 zA3ObrZaa1w&pm$jdKbHRj$}w}=FXVRyLpoZ_qdiHZ2r`Hi~H9n*814a_{-ZKZ+McV zIdA@#gXd&#w?}WEYt#Qw&rjq|L{;%;wQD9~j}JZy51$e45t8=ujlcZG-nr+$aJttQ z8~C1V%UnHY%IO1tG!$1as$6LCyrWnCthP^oQS8fmE6<<1FZRNA$;=}%mu>!Y+<jT= zx_z;qK}~Xk)}s4=uAEC)JFk=R``L%>k9wE)PWk?r_pQpLeih3&;S{^N|BFAY_*S=( zcMo@Sly>;l`xh*vuFPJur`W^V?yJ`1&+U7U-<!8qu(0@!&<@A-mCwzdPG0fgqiRg# z%*9ORha=ds&rN-MvubBXxc&R($ukzTN1i!f8W3-M$lK?2nat<yze*DJJv_CECG~Uo z@7^8r?h8!Rm^LM*>d~XOddmzSTAwb@^SXTU-Y3PS?Po8|SzGe2x$V^4=+?VUt#kC3 zzP*!v<fL1CNTccn`JMH@wx0R+f$`YByWwY}Ip>9n-k1DlFPZ=E+p7aBMJL5~Db04- zX_8;_P;5)){`rlI`~{+q*jLP5u;GqJ@q_Jq1M2gmcKCRmZJqx#FIU|>e`l|J-{c>E zpI$s)_xB&C&E3US_uN-szx#OC{+##Ie(LY}smAtIzkF`dPuY(ze7yyf_Z}C2y?XoW zfKTc9_x)~UmYC`}w<!Nl_<gO7Pj))*O*ZA%=iYO8-3*<2eBngff44rb&2B5(BvIQo zzkzLUXThWv_nuifPw#g6=W(h_n@$bbJtNmOZ$Z7>GtH6(+n;mj>)7x5utB!b&dJB? zY2@a*kFCv;*597y?9o=-Sw2JhdxrdaX6scZa|-l-&bgnkHFIm&sS7)H3F-f8*!^?; zwMSRQQ~lRmvHg*_=*^zi``p`)G-+4WNncVDdVO>4q;;;j>tb%)yH(0;D|IB{PbKGX zKIe0#v+VPuTg%+`YKxwnVNsy<!82v8;zIeWzZPk~sp_eDAKYK@vvWm$cICAdpY}^l zP4&IKyR_DzxPs&J#irdnug=FkI~`g0O3&0|@1AeRH(faIyzrFu`F&G7H(1W7*}8vL zuI%5bo1Jg_+U}R?^9tRNJb%;2Q&RIMJpQ+GX`X<<v#UF@Di1!aN>(*nsJ}c%Wpdx# z<U5<Q-@ligZ<ZQcUvK#%ZQa3yZyOdZ%G{ZC^yU4x`qvY~*PExmwYdG@!aco!<ORnv zWj5w>9*oYs5%WCZPHVaQr&SSaPD|h0cI(FTx%;*~&Aooe@%8ro`}cImi_Hx=SHJ3U zT<)ZuAO3M}-W~sY->YTsPMp7GQgL?EYKLitIR+<lzBBFGTKDLF2>&;Blj`}awfY{< zJ|21K+^+S;6HD((E{$ei8kbRhf_4ARa-W{^Ddv5pza77&$gh1go!v;PclM>0fZ1-+ z;@h65JlU7}(<<m`<m7F~6mm=~i}w_EI?u7lz8`*#<Lj5;aFt4NIq^rO1{Hzl8>+T7 z2%ioSG5Wvp|Ea0TLV@xY5=G7uY+{C1rtd?yo$iU(jDKSHE!T0{;ay5=Yts!@IW9Nc zzrK9a{^G#XwYM}rJUX+td;c}fm1%EhC~RRfc$9bj|B9CqE7xb;Q8~L{)2{<tbLA&L zc`o~M%LdVN_h-(!u=bPP+s>>#YicGgN$g(IpI+1H^`AE~FL3P&`S0@I3*+}}>8kel zacRMUn>)g@1An(oxp8WK?B`FD`L77?`u;3z?VX?LI@4=5UlPq`y7%h}tHg25cQsFE ztT}qiyrS;yv}KO7H+u_<=kMF5lKuUtve2?c-B%V}nST3CdhdB18=DX7D(^j>Z5?6# z)44)Ng7wHcxd*qFPrvZFAunLHf8)dNU$qxqjIO+!B|b~8=Au^Ynu~vH&Q|@rd`?Do z+w-7Jej8uD?I<#@U-NFw2j>&tQj!JYLu?K&J^xyITDZ8oUeBA_^~d+0=}f=3`Xjqo zdEi||W&UaFa&9?4c+bDJR43;{Y-z~SAA;ij1xG)>TXVS8^`5v}yvVGZe`R9de)(2g zIBiPHtnO0Y%eN1GcAr<9bn)-8Z+Crjzw_;Wl76~puSq`7l$&=aR_<SP`|S5bkGq2D zb@zVHU;juaMWyn5*|UwB<zAmkT}Al2bh7eicD%m7;F-b^r7qr>LvmMhzWdI3eerPn zbpDw7fBT+QbUB}U%#h0K-*`9t`u@FUPXaB2+$yr}PdgUcHEDO2!INhNdsMEx`0}dy zX-#0ib%dO^mFChrzdX%_)~(w4U$|hcq?X{%gh0^^Tb?=x^54~58hz%>rRw87bKXvo zV=KD7e^XYVz1jTl-B;DRzwHfoJ7t*^a8b_AYW+ri$#DJM`uifg6w}tHNam;f%X|ET z>nrPV+ooc@O}(ew_RNb<HJ<TcTNcCD6pbH26*nfB*Zxueb-CJTYTZfQsXJFCU$60d z9KK3xbMU0Ut0ly9cdpZV?q0OV*?QCco2RQE>_5q8&bfa3D%(E`3JSh1JMYeW|8$7l z!?-+wo8KQ@kyN#5e|+t@`ufBl;>$1ZkC~$SM_2T}rh(&yveJ!TrIuIkFnsxJ>T}nx zY~4DO!e1Y=6Z<@UZ^6AH!^Iy@%=Z4=cdVBuHpB9fm3_rxXRmJ?4!=9B%KT1IFs$10 z%gwlT(gk;~Z8vgPvr%{x(b(Gj^1#J?TNFOOKWoeX>DwQRu-B_K7JSku*lB#dqB>=r zT6%8u(#u_(mA6-Hw0Xbe+w6~@&dfjTWt${cuq(-LUzX+n-%tEbU+6yVB%POIY89}@ z`1$Sj_X{JVlFOD~o!iC!_1l)hecIbn56b?&q!@W$t8Hy)T&_Wv>5b`?dp{leWxn&2 z?aZ*}Ii>O<w=T%vcsZf)kM3=;=|0zs-ad_DzwBRD^C#$YrT4Pj^nVv-+3uA&9xcK7 zEO4vpRGC#tC4oscyOrN_-iiOF9i~?FaMzut+GSTaPJOl3r`#)4tmRW)VEm)W%P;BG z#$PrH)Tn7GZGN)sYiF5YTkgNtHX`#X47VQBS@%pT{;{~a&Q=RQGp8l-r=1OM>fbN* zoal38#-$Cv_w42k{~UjMuKb>x9}^7jxn|$}z4Q%R%{zAS_S>m9YV?8)_L}`q`nB!j zqhe1}?$h&BpS8*#s-FK%)NID}CmT~@w|`CCH92jsqqKNi+_x*f{bxeW_SSxSc356p z{MTE}i)XFs-nR#=iP3kT()E2$%*Lm?cE0@_a-(W3tI>`1Z<oh+#m!1{mfstC!YI4* zap><K(#tQ|#~#f+8NSWL@A>)7iQFglRKDC5ZgIZ0O?dtE<5_DyzVV(eU$EBZ&%yME zN6)=Dm-Bd^Hq+_pV*C}g;`gg(+)O>Ro+<p6+8g<Y>--O#S#6vCr1S%`qvwZvr^Bqa zY}b!Eyz7Ne@XO^-i`L7jfBU4;nq9nY>%1pB6{5w>W=UvOyt=afkn3vm5Usff1?Ju5 z+4pJjQEO$z%K6nVo*!)L7rL7&)}Gq$zB$IC?!;Z+$6vQ?IrGh1=(v2!g4ERLKVK|` zuH9~5b9+_5hJ#n0UY;<=)608S=oe}0=?UlGJbZi7)MDC~SvqT-&TM*dA*GbPE!2mH zFI(k0N5$?q%gK*@0z_VG{8>@`$NIoq?rE#`{4Bix<*eiPgx#Ni8zrB&*<JKOCribl z?EATcipytQ_*^Bwqx@!d<Zai}dOSVbv?qiGul*U>;kWK*%y*+xYy!4tzuvp_-nMyD zj&C(vTjkoz+)YADE%mRb-L_qIHAMI83d@p%F@MAE8s)BkcZKcQkA%%v?{0iOuheYc zt<{gWGpzk%7ia&*cJhPOd2e>dobKe_&cj((wxfEUY2)@M^Et||?<v$S@UNIDB{gl~ z-@T^OTBBEf*E`Ss?vlXC_2nYXOsB7V$cgU1_xZF@($&uk%chq8&6YZ{T6M<lTU=q~ z1vM{roSC+Bn@;^xztww!zwC5=^DE}2(&6hC8(CQ;)_!(hl4qg0);l`Kz^a06dE!Ci z2&p|Uc59secS~BTP{;9|)5}+~m)su8Pv84whtT0Ik!A&<o$Hm?X<Pk0d3O4%WrByz zbMvmU-OyP3xLCSo*OHstvg_-2WZhX9cyUv*`sNq?U3H1IM&AtL^d2<1EPj_>-TbxC zK0Zc%-;>$dvp=Z5_*S>rS=GJl?vqffm(`m5(+hJvoL~R`to$l=`)o1&q>Fhs<5&H4 z_|f<Hp7-^olY0yf{dT{<xn=D(ugkZV?yA|H+jVSR%6;Xn<=T32nY-IA&h^mtT2m># z@v*^jr_84c*)y*#7ZEI0P3?O=F=_v^hr6aeUw`A9YgPR}?zSnVe9I5by|Fp0^#7jJ zV;dEk;y#vtp7h6=X}wIl$YQ=Pz8&{Yez|ZxRd@O=f%VHxzxgg)7gg|AXxH`mv$rj* zj+(+L({3TFCHB5n^=p&=3Zulvn92F;|Gs1|ntS8Dsd{aYj>I<GdsZv9Y}-_E!TYRg zDaT#K_Y)^C2>3nY^4yb~CxowDps4%Xt!}H+(TK{Q{nwm5d9;Hgg@o>PwY`z5+@PJ~ z-*D_*hUUZER$=bjlUMusN__b=e@|)a)oY6To*r~sDt}l#%=pIMV4<pmSGGU;v`*ZP z`>@o;99`L$kNFSP70xm2UTJr-=)Bs>AG>1~-TPLvk5yVLOgpAxU+&`dtE~KwJm2`f z$F03fwf$DgrM!jSbJ+drDtAo0a8|5v*`3{2Za%oX?Xk@2UoKr-Z_SSE&U<%!Q+alY zIk(Eus!$F45~Dd8yCVM1n!B&Me#z07Z@a~2*jweRWLJhsO4mPj{G8SnD|7U#U(C+; z>(3eU1^7x`{3Ws_CHc~)se75sZ*q#B@7kr$Ascmdp4yBp(%)MzwwJGe+3~$nY_i-& zS1~t!{dHYs7k*|X6fCjYUvt0jn^CXoEvBul&;A(}=G2P$&*guV)84=PhTBbxs=K=b zikAf&np|JzruX;X#-n_t(rj~_j^=AFe|k@Y^X>k~;^I<I=5NJiz0<EA?GujL*E92D z%LhU8z8Tvpv-#hyJMd5M{`MJ`Uv6^0+Z7%5eos`;G}gCIEmA(YWb#*aT)wjC;(~wQ zkL2Gu&~eLn-uw3(Vs~9C%010us(a1uVaF8Tn5}pIT-kVAY~HbXe#^>N?>K82xwn7H zHO)O-zdq(U?dV_qkp1_ecJ2+@i_g`?6m7J+d+ywX`2PEO+P}|lf7TuUxvIsmWXoK; zO<z1d&brqoyYJ0?*}12`7U#W-YuhJ&eAcV83#M<=%)amS*jnzQ`|SK%F;_O9OFmOw zcrxMZzKK3p^|MpvS$N1toi9t>cj;=d?eXh1*{3q&QzC8~ESjDgJa4t(0}bE#zxVxG z=@ZkvZ_R{+{>7&b6xW^gSMsg$&zpZE;ilfMeNQqkZDRe^vvvJl|1OP4nUAX<c_)00 zzqQD|O5uy!$L-~1l5K8#=I^_$e}-@C;cJWZT3t=VvhD_cixXb!ej_S<`sHaG=2}-z zbtpR=cF|?d@qEK`hg+3pwfWx&>{0*mazpGTz3--%mNBc#w!b=W`6{A9X6KS;_4{*f zcLv&BQ}5S#Gbc_>mhaCyi)l4oYjw=t`K|c%OL|4KXZYkkiFbbSyL}2*udlN;`~8R` zxz4wDZyclA@22M;o^y9!GRm#bpKKj^Ms4l+>?^xIH-|mg+WVe=&)4e8D}VoO4ch&R z<FxZezHO(Cek+T9S>sY&^ktfRhg6%IUf9#yVdhEKUmPs6nYLHQY5Imro3B^o-ky8Y zy#4IW?^`+koU8b>IPGcol?9(t{_4w!cYmLkKcyl+cDt&_Ba7Oq>spN;HuEl&kK-5B z`t0u2c2^{?=KZw~7d9$-X)k?v+QYv@*KGB_&|kXSr5WcPo)?;X`<s{M-k&?aY>gIQ zWoH}xPj+t0+vLtqyKUb7I-|AS?!*QCbGHPR2n3dwO7`ddK0E#6rSCUaA6;cVJ^khW zy<%IBf4<ID)7kd;@P!RsZ(GAEKh4}Wf4`2+s)wDMEE9}gz1lhVv2FUqo0A{Qy2nSd ziWy3k?LW3|h5eKn(sR$R`1K|4^IB8!XS&s%=eO;9bheJ~-ks7pA1BuNAKCRv;+I*` z-ioQ!F4qc+U(apHKiI~(Qr&*J$3B<%dy5a$y$!kii~ssA{g@3xGI7slL@HbAl}-Ka zdgg6yiGEJ^Uh|hH^dt5h|9EMgmf-83FG_nhT@QJ_afiKL<$c2irK?X&{N9xx?y#v% zp#OW>>1^w$sjvLiU;n&0|Nq|p<i%~V8?HDid(IWozxLKGlxe5t`=E=n%QHVuyB+IW zAuO8l{_CfAub=KV>Mq}up|kZ-$Ia~)pJe|2K6v2HMcw1)N=!F<+TRSXzV)Zq^6%Et z?flDPg&y$q*!0Awd|P^>qWWr5_q!8!jJw-aw!U9C>DtNr_3!u8?)va!naA3VD(yAb zJ=pX+JsAJ*-m!k7Tc6Oer%C$W>!%f|=-b6!H$GvK|1a|PW5(m})Vf2`-Q{m?O22q} zqUocadE2*1$=+M_)}+QO;G3*5yKK4m?hjd)zelcEz0t~<U+iu_cTAG-#V?zE@BHSG zZ=TzH{=>P?tNQ)6bJy8cmFTy#>K#h^{n`JQ_xt|i?@wF*W4k@=vbN>{eXS2gCF<Ai zZu;Z(^4G&`|7`{Dt{iEdZy;KJ>B7qB{g1dmJ8v!DbnbEce32v1?u1vD`P_c{Y}?(> zH%hJ@i8GmAxwm!l+ix3Hb9VHb9a37H7O`H+|M4!~N&4H5{}9U$wq7(z$?C+f&6U+h z=B>@KQGJ%w`rUQP^xgZCqT|(GNr>N6yr5aU|Ip!c%Rd##{}zAUz42$~{pvRb(R(AL z>TX+BZZwhFy=Ql<c<7eLQ)AoKzxM9tPk${cv147?Ew<>bdT;W?YcG^(TcrJu?7Md< z_2vHS@1JLN-s_#*|1&bJ@XNp3TWz?1gcax3#D#iJPrv);bLz`ZpXS>;mN|5DIs1JM zZat)H*!gX3zIkklP)K*>=IhxeC+<|mz27ZT+-|)+-d;cX-A9}M%C47`b*H?Y{&lX& zSFWX>niHdwdLm|=64#Q>sJ~zu9QQ~^GPv4ZV7F%YLl=*{s<TG#KEBbvc>EH7-4CNZ zG3ECs?*6^wbg<rD)0Yb$_m^s9y{&&BwdF2%?A7bHHts(4ecDb7EtAH?$$OLY9N%8& zTBaPn`*P(u&`Fcmwk^u`d0?gVN8|8MhZ(_-<~H*jblYwF*&^Idz%!-bgI)2r*xJ6k zx|5ey)zrG*{P9KPX!N$u*iOCbTVj4IE}Pv-Iq=u(&4uTOt6d^`t5nbabdQ-|@@BgH zz5{<Am7HAp?b(*7)@$uzvexZ;`y$p$hU_iU4_THJd`;o=t8=T0^RIl_D9HTgd)UR( zDo?B2jAf^-;ePfid}WdOvh%Zk+*Mk(>3zq-E0Zd%+4ukTSYfqi`rm3p;r@r^*Nr#j zMSYi^{k?X(MMT)z?FBD<tp4X;vnyRW?aYG?y?w$gj=uU|H)-W<<`&-}w&w23aA~=$ z`#-d7S|?si-Qw|J-{aXA*l*S8M>S-x72G^`^XZ5C*cYj@Jc^FLm%#Ni=dFC&x@xt{ zaT`ib{QNMz^3*vd@mI6upBo5?td+0)RBI*WF~3MV(BEP4tACevttd5HEBi}x>91A( zKb*q7;vZXS9Z<~uexq_~QdIFR)z?QCZvSSoWV*r9+kHDkuYKHOJEb^d_rKllbL8K% z$Yi`d`{Ble1G~;Y+4%Wzo8MjWW%`SkGQZzj^ZMtqrVV?R+=<#K^mlh&;B%H$u^)n8 zRjw2KxzOP15}uQd{}##TIGx)b8?x)zq%XxhVqfMZ%W+$L>3dk@v-;X==85ZXepl;0 z`AUOxo_|fs#bt+;Dn9I)aZx<@;=;2hcLe$WJm|Q~%Xr_i)J4(}vAXm1pP%Nq72IUF zX4mb2x%xZq9D4osw)i5pHurerX_~X%B*#wAllrK&#k4TI%j)%|qc!bb>lay_xm~Jy z<Gf|{t2fpm)t4uqR5(?9x{F_YR<p&?UZn~XkuBR^#XfmnoE*Y?XW@y03;S|*A7`8X zNoMyBw=<{2tT_{pXGhQeutWHCk8-`4$JZCHRn7PJas9nDwfdP>a;a(e#tS<Ns}EoL zE4q>A>fysbE|xhTOkciuW=oQk`^!&{=bFji-S)RO@x^=IldrGTq-4G0@c8MzRp8W? zJNdUhdzg!En!01J3P+7;p#QDtxtYu7rW#D#m-qIp!n42TtL4`&i~g+3b$r+N%c6n* zKi&;xJvD26)^)wVf^zfs-g|hm&G}~H)*TPK7v?XiDw_9e@2cpS#m_&zI(syEnqZuz z>~njAxaz-83J%VdH*EE9-WsrRy4<goE4OW~vCR0D_CEBjHTU!>HeEONF1)c}|MQlk zN1430S8AT=-92sHsy)&1nX7-)JZxNcCYRawcKDlbasRZ`Z~ZD>(Uo~<R@37|8~?+L z@A}Tm=9jMu6N*}!Z1yTHbC2h((}!2S3);H<Puu17pIF|r6wmdm`mka0(~Cj}g>PDg zo6Y~f?v7oVk@j@scpnYxjaI2ki_Y)8tl8Lh{l>gau~)8m)-T!qwD0*r{_10EF0J2i zZ|VxGX93st>DmAO5cF^&$Ip!)53RYjJda!MrbhVDx4CC!7Cr9NdS_Z~Ixnu!ulVuZ z9sSd+#qHPrFgT-VTeS1rmpv<ZzyA$vx~3Oq*SoOxnyY@wm-XM~hK2}Qr&f7izZSB^ zFyLuQ_)+seN4M5wWpTesd9m62x5&|BW?{e9*6o$w`zHA4-lO?%H&<0oewF&^*Wp|) z``Y08HGkeL3A<8v?9+0=U%mQFs&e|B`;*l#Ojz>c=e*8x?!yn3E|Gt<yTanh)tvrg zCApUB4PB@ImAYU1{#j~uobB1}LLN0P>pyvAG6h@jZhF?-R;qcf!Qbg$U7Pypr_Q#^ z7a8nZp);>yUh3YLf#DqIQ~009*c=Jo{m|jjxdRU8Q|;?-OKPjBw6|rh-uE%*Mw|5B z{l~AVzi#VEUH@QSkMf@TuQsl(o@acVU+V9!@GVs~k*0Aky_UBBx_NH?*2W#_+&*rP zXIk@b3p*j6D!=XZx%i^X>t3_XR!xqGzTxiOKJn|jLht=w>^yl*e;+;d`_0k!9p<k5 zYqi?1_~zdge_*!lb>g1L$ZrPgv}a2H=ZpXF>PPRw?N5cW!|lAZ?ykLack}ak!H#n{ zt-tSx*mxrE>g&+R?IGQp{`M5yZWDR8#m-CoWx|2)-mw)m{n>)Ac(P}w8-@K;Nk1Oe zZP%=IQlVQzOi=tPpTFn7*PMzrOXuiG&-s=UxmvQWNmBdMhW{%M+s@2qHP4&rJ^$&7 zGrh@DTX)acZgM8`<qK=Rvv>C{`>6h@FT9{OTEDOF?en)%_b$u)NP65F_T`}S?WFJa zt31{S-nu!pe9_KFAHH`-2aD(5J|gzh(?|99)RK6Sg$E98ax`cS4Srv+^=j#zSJ!P$ z$1Iuld2Pp&7ZSH47Zh2})ibH8>y}vm`+f2)=B)hHwp*K(qaV*cyzNNrwS9NvlIoAD z9!u*i=st5|yRhuB^}nvgtn;`N7j#)HG4s$V3$4;Cf1^3Cf4cM~^(<G<rdEGr|LUvk zM^;r;)-JMMlv@2g>W_bB-qB|LZ7J)!yr+7kzL<Dwbz$lG%6K)g64!*&e?#}`M|{~= zwq@J;o4>tk@9tuo+^@U4%k)Xbl25B|o87;+ac}mHEhfJvr7m0Wy>s5aEaqo3wyu-& z-tti=wd+gzxwQs2+6!-PnOpnjwOj4wudTvcnO+;abk95fPO`*5a{7MDn;8#ZE@eM= z;o{fL(Qod4{?{0kd1dGGU58Zr;^u2jQVrj=QY%{YR>CE>xT;&C4=k>7A7)yAwN@;C z$1=5o?(3o-qhI#T*S+$1&XlqTxBKg@OCOfLdigkqHU0XgPQ4X7{&DLD-4dyCJ^ALq z@n%ln?Uh^4K6~zVs-oWJ_=W#Y-YR7;?p96veRR&#`_JD;ntWN^`2OU#<nv8;?ECJg zc<<SM*ZZ7Za_ghyy?sq#jGx0(+s|`&zj_gW_2CWO%xj95B(^`azIXM5S%2P|_63jL zS@h4_@w02!o5eSo=hkRhuK#URw4aYxK4!zgErx%7t&cK4v)*pug~zvUF85abyE(XO zUlAX7TkeAA>-UEU_SPybDK~!p)_G@gYS7k-X{t+as{2NKx8J9uP<tib`h4TVEq`7b z1x`P0nS4;OJpRtaw`Wo<=O~<7Z}=!~Z^awel%{9gcP=mq{{P<7Ds5Y$%6_WOfb-4% z9cAfH3V-r9PuuzH*4#RAFW=v@mlVI=vyQKyE7s%AJgto6t(L{jN8f*%`u?xPJ);LL zbIOdOZ$7+GX79Gk+-!QXVd$YZ=6W|?aMX1Fm~ddD_9vCPfbK@#rF&-^>U?Q$sd~1r zw)YRqnM>+F<;||&$_`IEC8~FMy_Lb7|8{Dxm~T(hE?M}m`nssbyT2zlth%JT`>jLn znd95PwwtKT{4>|ta@Fg*Kf)q3*<u5_^^&uyqzoUdOxUFMT-i~l_WhA$W&7h-RPAT` z>Hj$U<EYuqu-dn_zYqD}c(QE!;d#f*i|U?jNv!98zvWNZ$BUaJbvyU!$SgiJ^~<|I z-0dd6%&leL`&zgZSZ1wzbW}%rx~!JZe2d0Q$tN;ae!pv>fA|@n*)|2olH6IoYyP=J zWaKUF_;QQ!*Y51>JGb0=>k~eCUAn%+WzC-H9s;$u4Q~AOo><wKRB_qjc(b5%cJiHR z?05HV<L4>+BD*L5%<srok=vqvWc+!$puEJom~{%jhrChQ>pk^<UWo7Ozac8W;otS^ z&vx8RS{1c#W6-s3=l_-Wel8OEUTJto@FtJ&jgBjuZL-#`?)3{(sfmeR`J{C9ijTJY z9th}f=bGQbb9&{Lz(!y1<D2IHoTitM^lZ`Xj~36Ci};=Td)T&GZEA0?aLLQY``5S1 ze%JBb{IBl)-AUKq&AZ(tYWu{7bMIq)?Z-xK?|SU_-rczES?{vob8A&{*Dw6r{E2V7 z()k_h{(8J$P<#4a@7{`^vsL-8nw!0<i?t29xVN{rvb=HfuipG_r$R3|%WwN_xz`rv zAHNfN^qkuq|Io)~aet4!+7UZ{Nu1GYsk+(GH>SpJynVC&$J<g)|D+EyCv?f=i+r=+ zk{l_qu)KBFv8a5*_?$1_%jeDuEIstxZHC|X?qia7q9-Spe<&=x@$}cLPgkRTORbG| zZ?=;CyGq%$HG<bJecxuyGZ`86+V?f%a;9xgY|5XzFn9H`xfQFsVs@lzTuz;KU##fX z+J|YqLZ+6>ukNU{wrQK2BUF3Ih<CdA={BAdp7SHB)56VuoqPLwhsoEcsk>gzxo9Sv zZSt+)>#;sV7L~nW_v@d0?R|Cj;OVpW(sBQ`mU`6hmHlk_Hdsb7Z|QTh7qxp2)*ZN0 zGx5gz&+o35FP`f&zeUVs$x_*AZ#y~fn&#|#dUE>oJ5rzStj+e}mFL>Kcjq##{gbbr z@V*tba^u&?zlp)`c2&Bk#;Zj|uGQ|!T66nD;0b+&?C86PEq*bDtXbXj=~47qQ^Swq zu~O^**nN4Y{4DV1KV|0qE@nq{)@i)Ce$;$TRn=l+`=j2k48s>sP-iS<`+YV2`=00D zq)mTV$7span!Xmzi?|)vVSoPrtm%ypf2|Lwylma5Ren3}8gJb18mEt^*DCLSxi0OO zTKE)h{wKeULpI9%68V!m)AK7+=cXfjZI;!!>h1sL`TGyodB48WFZZKoZp&Gc7`-ql z{BHM!Ic=}6&bo77Pi$Li%e7N8Sa@6B{rZ$#d$7iL>i2Vw=VtFwD%-K$l{+DP&8>Y3 zK3YMwSFN5Nj}NnnSo{30)XiNg?|T=voGyx+|9km*)!E;8uj*8P`uNFZ)r=edhtekd zS*>NvZq63Hx?DW>!nXcB*C$Kq7Uu1quC&_s;^FIDYpUFOzWDs^R^xbgGe&1SpD6!A z`Mr9D;Zx50WUlPHcrPSx;)YFXCjx&zU8eMJT5=uZtj^kRd)}XGH98)@TSjyBr^4jB z`#3goZJvC6?)ttm-N>Jhor>?W+Mc$0xaEyAzw+)~rpHgK`P9cOuD<^-TQHd={=u@V zzU&s|??vu^E9ss7XS(_9@^9{5t|#}_$tTu73Ow^kJ>1X6=2B4HCmZjbTE|>oh8$s+ z%l!GV_pf}|(j{xGs~4O7J+x@S$7Y+?&L#5N3#}!^rQDa^Us)Y0_HX4%xx-sLjvhFs z`@sDrYcJbA|Kn@tUAecaAe=v9|A`M(D|+6n$o+Td%c=81GsWV}{P<?M9n<x_`FXPV zTAOCCEjK@Yta!A(p<U{8ZP3h1OkuqGCD98vY>i!R9a!vNo5>uuczOJrFUQ5#UUy>o zCVTjmi0y_m5vFZkYu8Pb4Gx$hyk}qQN4MfLb5HS{EWK~97o$@C<@(9lN?rC-H?QxI z*A*(*nz1`a;OauLuKcN<k8T}`U2fKNF>3ScfYLh=C)LGl%6;6PhU+^1$@;(g=Th0# zP1!rtOm2R@lYH~>oz~vy629!`(r^E?q*-XEzB#V`u{u1*ZOVlS@i7MXH-6a_TBM&W zrzf=PPvvEgqbl87^&chdNPY8d@_O^{IcHw{Dl2C`+!(HUQ~Qg|m9tr9E-UVDGkW{$ z)E~2ncTatu^*O3<^ZgsQ!`RpGzpl57?~k5vH7ozoHM^agCn~ugb@{Z@TfKxQW}kgg z&4I50uWyR(z8ub-Y-$zx@{m+XZ~l?BwON-E4_Jz1{@GKiUY=`Wcltu+-t=?ls?M9w zohkqQnYg5sZBAG9mLF+LeN%f9XMS&)nGw7AS+N+KB75VuK6hbn+mL2vCpQu0CtUW6 zf4`rhcJJo9zhx@L6EY+0WEY>czjk;^PHeSB?v2Bq&M%rCoSwQz`A786>Hb|a{z~O* z?woyL?&R9z*RJe5Xng#k+l*ZYpZ6W;KE%r<ZuRET`^}qcUiZD(RrmAOyI)(Z|Gay> zT72D}ca{<*48MamH21DM{^i?m=gMqeo=<D`?7hdT7kPQ+tLj_DE7v5&%y~57$k9E& z7Cu#X5|Q-#slOrQ#ji8VZ&bx!II-_vdYOW~TK4bH)k;yTZJzwyqI>53v>hJ%x0{NV z@b5c!e{$`|7vHQ`XTLdA70y0I{K%54rMI-+ZN1_dIWvFm<gX7kPhX9-n#(G=Zsl|R zP}@qiik^M`ujBGn@3oa)mkfEk`|tCRmN`G?s(N$F?vPE|bm^~5$bOw$u|2P<Y+}~x zCM26JTT^P=Usvn5NPGW7mic^lXD#Zx5nE%s_?CC=YyQvkuNFoKZL#s{zaaj}?DIO_ z`y9_xWoFOX`?o=Kn~7CL>!LIK%^`x;wpXHT-`&!^|L5NHk}Tbl=lAOi1JAs=zwh|| zE5}ytx%F_VNXYciRO@fb-(O9Q`*=TWchI|3IWw*DEj)6Qxn-_SEngKJP_pT&_~&O2 z>`H%zwS|8=!n(6W=GrRG=dz!5T_5lIIa%je<3{GB^d_n6-!8>p-Dh-R%7&nXiiZ>B z_!_sD9baV5edG4^hxXnfiw+sq-py%pcD;RV7ysI<JF}|Sg`1p?pAwtLn*7#wbI<0J zYnRHl^!mPbZI;SQU!V9Zq2%%G0*BAHPT#no*1vss(VeJW>uRJef0*un7j^vG!?$N1 z?^ox#Qf0j9On|LtzI4S;=AO#)eY>8Wlv?JOQJS7D7iN*VeDdM@&zH;XxN4uif9{@X z=Qk>Ex9)9cE{LA`?Zb1u-)m2-ywfE&d2^rOj{2>+Uqi2cINi>3bgRMfGx_Ci>91t| zJlW%W{O+nJA5Xn)Q8zt0!}{)Xz4^~>p9;OR?p%plNxfzJ-ovNep3Bz!{m~{nJKX;B zzH_aDH?MuYe5v5(YOy&pOTQel`RMvxU)%1RRiwdwBexw|YW0D~7QgA?&RYF?UwOXY zB=ax#rtQ9IJK^!S^66Xe*0LQwwDOtR?$te)%}ug;U#xkQn4xvQ%*HhTib8wc)guv8 z!#no1xOtu4wZQTA-sV5gxHow1s@{D#qSQ`ey=iRpKNsDj-{0(9Bv`z@J*dpLDmPYM zpZ)9S-SvU{_S`)d-W|OB=EZAa7oXMomh4&|^S$=rMALlx>tTHl7Vm#}S$AJ{?e)@6 znp!CrbFX^rUTnPYanS9S#)qwKmnt^)m;Iiz=C0q_bj>a6cuKeSe3GC2J!bt=*CW%s z^YdP~{3v+6+-2t8P7@2u^Bdn9O>c1h+S~faH9cguZspHS?)kl!qFdfv+`Dm8#!G|C zSzERL_wQ9_%vtn8>VuPr>p|(#m8~ihwpdoNaed*)T`2C$T{LBbr&(^sB;MVg{Cxg4 zf}#OaoE;rqMD{oc9QFUW?`go3FD@dkjQo22^=aQu#92mhas3a=oP2ktdH%gUm7nj; zp80**w|`&eu8(`Q{lUC*%I|A5w`Lz+zgutSufLynpW^>|;r9A@HD|NGuf6u|a-ZDO zU7H`xUg#WV^ZlB9zS{kJcGX8-H=kdr%I9PM_gDJ<nY%)^=QC`3*?j$9<&KBH%4YB8 z(XJ|X+`IbnvX!%bo89^Sb+Y(90k3)0Tc2;OXS-kY?MW+p+0SFTx!?Au$A0*@@8|S0 z7JHVz&TW>j`t*2bf1>FBIp2;<$<KQK=XUiy(dGGb;`hG%8QVKQWZ%{uhCdEo+G%b7 z_RF_Zm*RKr*=Je&_H6i?|9`eWRo=wZ$T_>ZJbvoNZT}|i)?Y9!{{N4&Z5#f-7njqW zReJsRz1MEF+aF$CF1BpVJ=6Nj?b+L>UcGYm)7JlW4;N+1KC5sq`LVpFxqZ))ibv(w z_m_WcnsYFE{*8Hio_*Q>T>n==`l*V;>*@|qKQl9b)|boc`*mi9mSv`XUAHLCIRCB5 zu`JsykI%Nvs!X2r-2C~UUEe;;w7$1z)A4(e>1wuh$ENk!miyi;{%9-tayEaR^_~Cs zs^|Th_gh;|ZvU$Aa}%cUvQA!p|7z;BJKc77|Gt~FH`AU^Pd9eY$Ay)}-}*A9-&Ozp zeedS>7@J?8x%=kL{Z#zpwX?fjM9AdY_>Wq#*Lqgx?R<0MAon-Fbu+Vfm0irOFZ;9Z zy6VIE<u-SdPX%vYo@@R4)I|TfZ|Ud1zu)ublz6c5=Hll4>uwY!7bRc!bzY}+?ftBp zZ2!aWUvGQ+Ho~iJ^B#G#PY&}9dDACM=e@l#>BNR!?|1Usk6GOK{Bp-{_kA0bH`Mp0 zUs=OkTtDO2!R^;utM`5W{<=7QH<$dZzt7j5<F>DP+cr;neq?0iuKXv$`EB#d>vxsB z7JA+0SS|j)aP!F>xgURt`s>Y}`1j7GUEg1)O|6M2-~aFDb+@xOH%^JOIQ#Q=^n0C* zsIaH&^LHpe{(bpb+Lv{|OI}aXJr-@Gm2YG7{wcHM{YokKu=op)&w9QLu3NE>LtE}< zUCs1e$IC;n#7(KcnjIgp{)EM*@|eAa!GGV+KO0y7vuonzIB9FU|6P`wvyYdo^s0N* z7|NY??siS~hiUWYZTs`*O>g*ji|ucW|DL;j`nP4U`ozEA{s&yDxIS66{!Yzbe(mo+ z|Ls_met*}z*>*GS&w5Loi@UM${PERaw(l|jcjw&nH{ajQeP3O6_h|n0dE57{^x0hV zHF(xZdoh)#QeF3V*0tqtn|<%=jl1{1oT_-U==!F5yIo(K*Nf$O*<YXftu$`F{H1uQ z%_X-^OgGp4TbP<!aXC8t%Z<(7S+nPF+<ZO7?^fOMe^>VP+y3i)yYTMq!`qzapSgGQ zf#j;4HZ{L9?dR`XlXxq4;cff%H%&^~mFvqcR@FXi4lnt+>(<_Pi~X6NulwTr_vPg5 zXS>zc+=%#PQ&)W~^7-=VcJZP6Ukc}+eRkbSKW<;q`NfU8d)G&pzTN1!fBnr*hjzH% zExvR;?CSZSMd#-1iu^Te%g)WOF4bgP_eTCt&iv3RbvX5U$-k9#6${I5tWvC=&Ubsp zzO&(TV>4day#MS!EB@A%+Lro*i@4h=zQ^szI=AVi_p9qMt9HNNoc=y~Usm?>t;y4J z@0n#p7W_WzyxNpk-}hDd=gr%LGL6q$ywLEke*Wzn<Ku+?-swE2dCDW7-M6VY%W+!$ ziE_EmcD^|^M}JL}_q+dcb#$2JtFAkrxzc&1zdv59d@S|flbd1@TXPfNbK99rvop5e z{e5{`m+1V``FA(}-@#tZG<SdT^V0OktjngiO`5G2Z<BU-+of;O@9k<YbQ#v3+h?m> ze14+p{M_^2(L1(OJ$>}GRC{}<eBP<Kzm6>Dp7j1rq5Jxoed6iW`)+JcU2yi_vD5nE zynb4Dp5Ci_I_*Q|bF25!g&$wJ+hlUP{k^h!WzvIZzSZ#_@_%mj+?^lewfEH@i~T=S z-#)x3{5d?n{-3|_zJHIz_pg1BSX(*mUcrs;e>Y9^*OUHN+&;0stSa^IHPKe*V>Q)v z*QbA5Z~J=lG4ao`&&uBJ=S?k-tgXIx|NGsKIp@Wn&8h!oI@^2u{(X<{yn61HR=Z*U zyTz&3zy7X$eP#78+nu#P4t2ku|KrfD|Dtt&K3;y$D^c`L(EBp4@co$I-G3P$y>vc% zSL&a~PQAtTf&c59`v32!{&B?o`uaM%&+lYwWBs%L@A|&Sv0tz9=e^I*?#|xwck}t( zcAp;G*4J*?{5U+=?tSpL<=>vq@46p3*ZSk#%zf{@j{V<jy!7DrFH2Xi-}3L*3G3?j zcC}|}qvQ6yTQ1vP#C`w&PmlWN$-7oB)A^I(b8p_o%7@Ee=iU8RbH8)-{ujoN@3Jn9 zb>CC%o%eft7^|5n|N4k2Yv=#GTzvHMx97e0>u+{5$KU(@{dcVKE<S@lAH0qqsQlim zC;P}S{nokf%jSN3x8d(|nRQ>~Hrkud@%sI3miw*k`m?TnZqls&w*F1YmyH`=TlxIH zXL@1J`XBwbZ})QQXUiFH?s?IBwyMzE|8n;ASJu6&*TrxB@;z~TO`6o!zlZm-mnZyw zrO(O#)+FcstE=6IO>bBA#D4j1{j^)EWO4RCsV&E^zMTDc|BGqXVRtGXPl)z6za`#( zo^xMa-2R#2=ik}<3%KrhfBxTF?-(<~?>_sz?f-+To7dXkD}3-Jy5DZ@^`~EU|J(QV zaBA)Dn%^IpKmUIzt5qmp74$vnW90dWP3Ly*c(>y5vv>ag-e0&NIdj^f@Mrb0A$Bi* zum2Kkl_Q(MIluDww~3ee_#&-ewddS#6O?^tdhgfu{(W0kUHG(l`nTORAI=|qes2HG zc|V^jmw&mnqo8Q6@rFMqK1k15zTSFHuFu`86aIZ((|&&EzTb@-<Kurv%s;sP-u@Tg zckjPv@T%k9*9W1S^X(SYUavj4X8QKLyZ1IOcz*wVRouO;``6vSaqE%&^>u$9?=6hJ z_oL`Rzy7@Kck7?XMj!S4|9j7O$=_CdHy=su%&ExRl>h9|tVf~zF%>2@o9mu@SsQ(B z$Ld#mcCP;-vBPx#zT0>G^>6-qbV)o;er9xIY`kf$?@L?rb+fiqUc3GE^0xjRPuF_$ z-MeW1<*xhv^u)6#@5k;rKX-HGz2l9%zTL*t<KMqXh^();z&}m&CG)iV8_RC|Tx}Fq z^VP573GdX+8znE-OTO7v|8SDNz1{2Yk6s2ptJ`Aua{0S@+qC~LuBWnm{geLnecb!` zmtTL<Txx$d^ZTi6TY0I9kHxF6?KJ;?tsw66tur%SwY~DB>(2jKY`^zw>%Xo3>)%wq zWc|fi{9p3=mm3Y8_s!GKNbf!M^J1vD->v%hKicNo+kJ37wxZ_K%?q)y>(_YAd>@@w z{pO>neg7Yw@4az<OTR5!=h>W>v&!=O(hrBuceUO9Sp7}*CI9mGGdJX(Y%5n+`?|OA z{E3Oi-}>HNoVh7(TCg|oj#n=(<onl`K2bQEx_j00-uc^gZhtlWxG4YpyqBl*bEcaw zy{=n4UvhV5@K5ctOLwoICA2H!@6UF%nD4i5<lld>;hcH3=ee6VcHQoOcWJNj`-$)E ze?Q)JpWpXZwAqV;|9vBN=R93wUS=ZlW$~^YnJsqDFYwlXzc%M)>h$8$gU_#uM{Lwz zKE1r^@d2mPn^SW6&(B-()B5)N-Dzjv-tE@=yXO71`|WdozT<zo{!a0OyNR)T?w;Sf zKX`tB@tMWW<~sMQu6{M%Wq!Zvp|AMk@N@O~3*WWQel9z6)yJv-EkB+(zjL|2=g)_W zrpr~Xy#KcPyG7?c<G8BaQ+Jm5>(@Tmm)HLOXZrt!yRl)qdmk^)Ydbyf<=(6IZzfHj zb|-UHMY(6>$4!fN+yD4wnH;-(y8K3oJHhTRHyJ%&WBGXg73=ah*15j>9#1>Ie}2jD z$<pU?D?U8RF8>z&`BhK&wcg4fCmzj|t~d9uwcGJ^C*S1K;P2ngS1kU2c=h>R-wynk z^fZ`n??$fZDVFJHGF7>a?Ig`}Q!1+p9?spZcDL+*YJBah9eW<MH@AuB$NasZZ|(kX z(#p!ulLV9Ftu3-XC-2+hZ5OrU2Yb~wTW<Bem44eF_4D0Q|GvT^{nesV_V3isyuUk5 zw(j76hT4?w%F9!?-}tUGw>;xo{`(*5yxwbfJ?cDl*=YVZU%ADxPc47FzHDE!<@fIO z_N!tm?zx-lUp1@$`;+^(nGEOOXSEMMNxwGxn{QQn`Qhp9rfJvxVq(@Voc6Wq)xG7% zLQ>>ToT)iCvF^#yW8Uklw!hiCkN?|FPVTFBr+q6p_sQ7s(YxpkzTx{lbv12vWCY~< zzS`GW@%YXc<;C;&zF($pUs;&=^WS{?cLgslmj1PuuS$)5cKI*6*_C_$KF;vZ`<--k zgQ9f)!Fziy?0m5>|9t(5yUq8@X5W7u9((WL>wWjDKD=vZ6Z!Md{*iWFcKN|Amw%i3 z>(u1^ob-CX-_KWP9<5v`{@%X)_VLPkxqln>9Y6fPI{V<C>t^S6SDkwGZGQgS^6%!# z@8pZGH?6z7JXPmYF3;S3FW0~73+6t5@57HT&DUi){%xvqz4v#0j74eA|Ec%uZ=W@= z{rgyd{q6S5W7}45>-+osh4}rxlUXxr?|r>*yCXaB?y=?fr9IDSMtyp|PPcdeoS(nW zU5YQWsFIJhh~8Y3{-3eEZrAJI&Eoqe{@s7C_QRB&zpwo-JSXn|>)Y(Sn(6~NZ=dPP zOYGv+h^hEK<K1TduQRUAuxG!0`R(ls();f$|NnWtWIFHPSAVa!m046gTX_GU*8SRh z-&PsBy_fvl@^UZp_W6;rzY0no_VU~RGq3$|$A4?@=le6ZuBiHR`SslWdY3HU8~K0E zv3-7NdiOrH`ij!ujQ^|dzUY2m^KbIryL_Vm{<4&QZ!Lf3v$o>RsabZf-|@X&STAK) z@Y1yW%&w1#&epfz9KU_Pe!Jf9>))H7POtg-;<@yGnLCefMbDm}fBovWL%XKiYu%1D zD?4_EPrh{b2hGF5x9`-4oslZ5{#Sm!{+aIF4gV|W=igdz@9sWL^Sw`R{l3%qZu#1` zCQsLFXVYJ>MfUvNU!Q)No7aE*_ifF-nbXUpe#Z9euiN{5U7!5Ft4se^bK6Pm{q@^A zu26UTd8zNk(<dhD#-1?!tzWev_WvjQG~UzkUrO#W#wPyij&FHawXD4O+?MU@DpC$# z3OFw9&UZ7!ye942>Bg<6|M=_5Oxu+B{oN||eQ&@0^jVW~{nz_n>vZl~W`CK+{*R;l z)yMGt#gqJw%fFfse$R08=TnuoJnQ{8izeRxQgN{G?>WX>OE<qQ-&=Qm@$5<Kw!Dih z|LnW`>XMSnI`3xfufN|>XCLAtv#)7~;o>!qFP1KU8}~W=_ww?eb6VfqH`dxEpLny` zYrgEBcdOTDYuECdp4<Ov=}dW@w`I2$&fB>!VENhm?@k!LdUnt5(uK@(te^k={CKu| z-O0BJu~&aw*tN3q^6`5m=g;ha>ndx0W7`#fpO5<cH~pPI>-X|>SwC+6AD<SkwLG7> zK4Mnc<HoYNa(3AjrAJpx+;u+wf5mI&%4N#)&cDBLWBK`RNz2W@?5$NVl)pG|{~7D~ z_qA~x_8;CzrcU?U+-IU&k@Q4(WqH}@J(~3g57hEqw)=Bv%F5<_Qbr}mHe4$2&XbzE ztIp%4P_667HIZ9icE{Q5y*YD#60_l<A4hw|6a3fR{<vTDby``a!H3^BB|q<eQZ}>j zo>1&|UGqG-qNmn!r`w*_dF&4SAAVSPp(Oh{yYe?{J>SpIz2|rK|Cukps!w|Fn_uwo zqM-BYxI1^AUp;zv_P)sP2M(IMzy0s>{nKuLk+VB0E8bbO|2?)@?Vj=aH(PIStKaaW zGyGm%!HE-Je~LC2J&XN#W%u*AIn#~vuiV*u{!B()!M7`w^Zo3-_J2=VzWH0(*`LK< zrn$NY^Iz{dQ}N`H{HL|)_hx^4^YnG_s``i<OXIIw=8N@I<P?VQQmpvpm~>`-@_OqS zvx|mr-t^9&zyDcJ>i2ygzMpt&x_tZYC-c>J&+A@m{r1F~qx0pz=)C`Y;z{Up`wzdb z@Bh0=?*Hm%A8s8ze7^eq>zU{OOf0_pZg*L0+|~Gw`=3wkeBAtfU)BA0y*KCk7Qc;f zjxV`;mEGXYD(AcTa)sBXX1|>uSN`?&y`}s$nLiRIY>z&izt{TTuGfF>9Pa;bvD10| z@9k6gO$EQ-zPIzJ<(q5I&v1vSzn!^p?Sn~+-7idCH2rgV>5i<$-)F@?i*?v7b!@-+ z@|Qnl*HzZOx5)nIdi|wb#IDybSHJu&%Wv~z_v)-a(zCzm&#KFKQ_GxZmG1ggt?J~h z*5$b}UrtY$7k}<;-2C#}=HdT~zZQ1?pOAX$gdN{U`5w1!)mXXz2NxZyxA^!`zOCS+ zJX>I#ZJNo(kMcflXEL@)>4qiGviRqmC*T(Bv6W$)VdAY<4ck4{d+!7UhD@9_G0D$R z`&bOeuY!C9p%TSO9~Zk$6z_Qv*x-=Zw`Rs^_d_n+bFEiCjJ=idZ*h;5=GM6Lg){#j zE9p-8*X;lD)gOC7`_`k)n>Ky?_V(8H-4ExNt>5=ctK!Pz4=>+7)VBS@dVkC73kw(2 z?%ww*<!8P1|A{l&n5<Q=h-Cf#nSW}^ng2e?yFBOqSAWE=D&sm+@nOUA6~az)|8sqw ze>(1I!eoYRZ~vV4I6vXsf1czvtN;F{fA9OX@c3-=)PAOTQbygcQ@&|a+3dS<F%y!F zqAy)CHrl+YcgJPcFLf`!@00MEHFxqY@#IT2-4CBUd-C*2x4h*?PD|UfZZdzIH6Q&> z%K7$ArrLsGSC(Lj=(o#RKJBq@yA3OM6t}#bDBK};{RQuilWw;dzC=vE6Sr~3VhNqp zlofrxg}jGtuQO`BGPLJBeDLrQYt8?^{-pbEFq`{dEVwP_-}2?Ff9=oue|*FLr^U-N z@106q`ej>>UUt3gzsn0H-$gI~ziVIOxvrU3hqJ}2Cuvu|`t@(k>!M>TCOz6S&-I>f zpMF^4{`ipHiudc5e!iAnU-dNM@W0#g9)F*|u>GqhBVkn2_k8wgFa9Y!@?|AQe@&I2 zcI(9Ed2+^v|9{S%FZVC)rSk6WbBrIZpI`oEZh%i;Wc8B|#`|^7-LOtfcz2=td}Ltg z|0n&IkIni1N&l^0b-~-ME5B^>ZI}NV|Leh}$k}ew%x>F73&!n9UYC1DSeWa4{G3f| zYHpuaUS_}X@J7Srp7LuC|FoB;#@}MTmuf%j<mu~rT)&O^;w%2OH(%x#jSIH;;`o2Y z9Ix|1j}~|O+uE)Bvhl#V&iQYC|848{Q9kqB-{hR!#4mSFoeO@oi+2gzdXqTAwDZgD zWmnFqeYV)$dRz2`hwW>=&yU?=Q1d<V<@x7tt8#uuhNu4fwNUo5nd;uk(Bq4y1e(i~ z+H5}X`M{LRX<t{#2Titn*R9^ZrXu%Ea<}&7>op<0_l=(~{r6+@?RE9}7fsux_SqCi zK27dEzcYLG+6~EbzfXMTXTGQW{in$8`&tnN3x75qeQh2q@Zs!!lbl)qZu8E+_c!C$ zPx*7Rc5K~~d~EyQpv&jC*IK>Z7Q3a;aqr&3BZoEJ!<MY6c(*cc`n9rimJe*7#9M#* zbI&^Og@gPdcDwqk?W^;4<lk9!_q+Ya7YC}>|9mNZ$nd|^Yu4FnI=f#S@89+=YUjtd z^=;qR?X37Q@!hPi*?Udhca_&gZ?9wjXZbJVXYI55&;Nh)-23tU+rw%9f86Q!ERHW0 z<GX!3#W&LK?dr{w^H0~@+Wz4(uY6hb-Q<H4BxjbFd!E~o^7~ft2F~?%{~yPHzvFXW z=6>GidwkOIG2t#j_VMNkZ~j`>&-?bzIe%YV$wlYWb^A}6ZmK-9Fg|a_r+XVVhvoi0 zm!b3P`8WGxzux`a`Z&1F@7Zzn-*$6L{gT@cPY?P3r*U=h-EX>lcdtxZu6DQVqW*0w z-^({%t=?Z*@KjRREW2)w;I6+9Zk!Z<U$gDk*~IGkvv&V^S{QEjW?qO@ebuvY)$ji& z-uZbVefRoT`@Z^luPwWhy6*Gl+UI{h{&+Qgm*u{@rS~uGd3ZbT=a*koBWIuA^+GxL zvG?~osaC%aAD*tVbZ7N@iK}&QHr##t_4c*f*CssI-B7pr^TM*%`{H+Gq-@`Pf9~G= zBSx2JeSf(`=Xrep@A$uO*7qKse)ei){H=M{_y5}-UH-=M`5eZ`K6aTSUk}>^$GYF+ zl&MKg`dpIr_FU(&`|fM}BPIRpetfge&pv)t=R!>3f7SB&UpJ;-wV%D(-0o{@vQNU* zZFgR84&slSXqE7VcjvXp^zSk9^9oK%o4<W+Uixsw8}9Xyo1cB0Q1@li%hmd`BBvJ$ z+WqM4>#qNOuui`$qkHM)UEH&ZXP?^t&#k`jqwnRmX#v-Ey8fNC=k9Wwi*Ngiiob7q zziwyF^MwIbJKwE4W%=GFu;}0#$Mt*metLM|(EH~)e!E`1`x{?lx#NuO!|4Z;@A+$c z#2df3DIF(w@oAw}_P>|Q`1jvlFJH6k<-(V$+t0~Fd`mkXdGhD%Nxg617nJM2G%Y-I zZk@2*0k7A3dylTppI?}M-BkQu!O<6&njXf!*D{I!^GnV5=UR8}=3uG34ezd>-S>E6 z{`p(>U)mnV?z6CZf9cV>-~DCh_m*7@NPd3cK(FxsZ|3XG_S{?Z-fl(SF?Rp+E292n zUsYdvcUkO@i~hEg<96ED|C(QEy!Ekp&5f_CTbuRb_dcBa&3fL>?C+D$dh*{*(c4)1 zf5*4Z-Pd9_8E2n8JDqROn`ZI%*V}hT#=p4q{9OE|()ab5f3qI<W$&N!=U~|D&9Zs_ zH>HdJZ|1-M>qKa#s=dhB6VmxJ=gqvb_wtO)$oKXkpN?6s+%Ek3mi_hgN2=WWZrlCa z-S+J&`|LB?@hji|&8uDRxcZ)<tcB6H<~vIk_xH@d{PXSB__$xs^%t^x+0{LDeY~MK zs5#s=d6Vy6b?v2Rwy!E!^lj1da~Trb^mg9A|9Ahtyu*$2(&M)NnrwIVZ(Y>3_3IM9 zyz{+%=BoZbqlJYhc=_y8t4q9oSE~oyt39|r@5j^2p6i9b**2bf-}Wc}xA=RRS9fZ^ zf0mE^=Xm&QxV`nKU;N&`l56kptGXu}`+v{J<Lk=b7~1~rTAuUr-==l-zQ2vG8{epX zvL<w!eD&(G>z-Nv4+i|pefxjo3}3gE|I(SX+E@Kw`D?z%|Kv-7+FJGD{%>y-E-H3P z+<jSxZ!u@3W@v7}RW>N`VgDN5Z?zRSZ%960RdvI2$$!f^B9Z<qx6b|7xBj1Owru4; z`_TW^X@C9a-pjT9KQkphchCRn)6%B={(t$(&;R%5{y$v6<&%4d)%dgIjgz;PRx11y z<ltm0nW3h^9LN2v{>xc2wdOmX+aH$|o&C=sn5DAvSBSb_b!>%22)nuC?Q8!h=b6k} z^7;OxW%K@9D%$^LYxwx)PUp!Px1_a|Y8;ymO}OHIQ^WO7V=MDL0V@rc<q2hKg-a(p z6g4c{Yw$I_S=>D|VU{<W(u`XX%#&C1dOx2(P2~CQQ*X?)|0~SrF$=zTYnQ;X(w7Si zP8l)mOum%7({52`#<5QxSxh$@z8;pI!+bOM$G;Ux>&~|?>@r~!PG{nL(q|Iqxnr+^ z{A%N#qGd&zCVLrs)DBPScJQBbk70tP#z*zF$EEfzwBjj|`KNkVk>wb_hyefW=fV9C zMH(5Ee(qf;@bFlZ$jt|>m2u9cIc8b>FF#N04Op?`M8oawNzF<I8y3yyaJA|!I@DFT zB=MHNnB^3wtNaooX?Iwvgqr%S7Zwy1K6&&Y!C>vG>#Qx|9P?g$Vw@ll#KL?f)Uawn ztoVife^{mp$;w_>)8HUuwcz?Y1BZ`05?c)K_A(StUbjKMqhyZs$q)k%hl=J!bsJIw z<L<b-voZH7wdlU-Suj2HK`6s_;f^~igdMg_6yZ%UUCgqzVJ+kG9S?UoG_<nn7)qQv z>-FoTK%?PZ{)<ds7}y%V&PZ9m;;?~klCn(Ew8;im9y=7I8*jy(NDxU`z;M{h<m|(C zhcHpm+lJLY7p!`?bE+9n&AJsj@?kLv+6O+lHB2|t*}o^rCABayP&6zgE+}F3mgJqO ztOi$;LQ@OlN<%FxCQQk8-_Z5e^F?ajO^!2Vxvopk<{dw!z46hdu1QG@0$T!RMu&yw zY<|7*(WXC>UNt@1<Ydd%kdXQ`G-1&+vGB&E(5F>m#<QJ5*VG7vWvxD=&lF$$xy!q2 zwc7$ScG2*u2fwj3vK4Rax^rsPqd%`E-SKiW3JDLJaywVzO_JkA+x;rrcU%dZz_fuu zH+fs)woO`R;-*9gDZMQG`N1e!HS8RNV5#MKwhw2|YHu{;ZQ>Ib3BUDeb;5b2`JWgS z8E2_horz04t<NSepmKYw*@g9YP6<|d$T3{claAu$YGR6T%*)JEe6h@ok*jgxsrBo& zOm+Ig82RRE<+oLjR=v`a4vUD3Yskpo9?Hkebm5-3w&r%_N1?ZKt1ftjuABE-f`NGj zdsoA!%B$a!T#i55q!pc-TFUBEw}#o-UM7I^!7T1o;tEaEr!UN7Zkih2=TN{<;2~_H zR+UpRk$2DP78AEAr(0DvGIKo(=j?7YV{}~2^4E34shn2TqpjOE)ky0E@+tD_Z@XQR zz|qav)+(TrQ&;hhW6d6^Qh7ev1ARXu&J?H|zfom!BI2Wh&h~@|!#7OPj2l|_&PY{u zIrK-Vj6*3nMM2=Wi!SFAp~(ibDmjWJn70N_bDdRr+Na1Q{;{q$bFD)OV>kP>wcM>u z*RF&ZamXp0oZ}R<b!85t)m6{kyl=!;$Rwt^C{Ih*Kj1YZoyQ`Nlg0j9VrrPq&Ktb^ zOF5@#ok=>gi9uCHubOS~&4f(}YMZalNi$9DJ;G2IVbmV6U|#6#$RjQ3mxCg?mYmu( zpX=(~lob)1w-y^HlyyunyUF<TVwj~D^R+DZywcE#%smlHm$|j`9$2*R$eIX+H_cHM zMFPuT{xHyPb8qE{=X_xL$f2@4{IZ6=Df{{hVWwWp8=o)WkUQ}z%%Ml6RB_s(U1yp- z?w*?)u&boaWwKhL?To%g|1*o_EN2Lq{*V#vuxUU0>E#v4;K<dIb~QT`#d{1Rb}!&! z^qFB}@g_m=Xzjl_DjS<xOIEA!Mi^_(Xf!{Y@yIAuc%7Ayca!A}<^`{QY!IIj<oV!k zO3w#Iv$+~NtRI_KN1t<P`T1m@fZ>8;yALvVd`bxs{1$O`&)Q%0LYw~IXuiL8!@ozj z>NDNC-~3q~xb=U?Z+}$c{gf^LS-eEI{5x~=rr_)IJu`K~Hebxzwq@?uaR137TiQRJ zKHV;IdqHaB8&)Y#|Mx6TU%X8DLjKw>`oDYc)(4KS%^H$k|5rQkQS9cw^{T;F|A+pK zM<xD?T={?0>ni6>hDTz*f+q3(oMou)8JME1zWqVe+vCiMciK}n&n>wstf+Inm)$u) z>36eTllZ}jjSfEwW^C{<2ngzQSk->OIdOy0@&iI_>((39Xg$!>lj?lL<RYpv@uu$- zk7&m^hyF^4yiC~Y!DreU;V`d&VeT8J3JZx{R!suS#HL*;eRm{pf=c^?o2fejmr9zZ zl}x^vA#pe5Q%~NhFAe;1{bASF|97a3f1UAR@)WD8D=%g-S_-T8FU$Uwaiey|laGl) z>wYshOe$m0nqkD*&?K5wvUjq{iIOJO;NXW=f=V$9^tUGbWUF;*l3Q0?!I6HDm4itq zLg2c<olS={x$?^@YVIu%tG$xNlybl|<I4=S2Tl89|9t<LBJhuM^K9D}#!p-8cRZdY zCA+<P3QzQt{@WYRW|Z2jbL?7C+E98W^!b5WyWAlDw=Qi#lS>!7U*22%K>y#xC7n8g zYOkwQUS2e4xWB^8u~^sXY2fTPthtiElOE*TZ}`;CeBf8g>0YC2FCKK;)&7t*o|bX% z76a$Bvjq)`yluzkFvxhgcO9H-RaWlraUjs9B>3!#lziV_KNgvPEzF!(UTLpmj=rpt zxnYh>51%#Tch{;!H`6aO0{dq^>HWB5x@n^;JL{CVX3wssirM;#KZFhhHB4Op=J-h& zV}@K~AJz?W8n5Qh2r2k|rP<kMjnCmbZb?SB&ON!|y?Ddwl|RL|mra%~n17wOJW;W1 z1@lDS_-(U4<-TOvaP8)5hJ-eqy;Y2DZl5*WJ<l9jA<w9w<iKZLHc>O?@=oFN&IxK) zgczHcPRj^&ui%z2-+D8%clHm>x8=*TQ$K7F@kzegaR1GZ5Am$h*XDE_IGYqv)2_8p zDP?`p6sEfi_c(A&$>sBqtvjbuEK!tDFnNiE@`03qhKl6*Co5(CePE3ZWN@g|sOXqo z>J-S6*0eg#$t6xt*!O@>L(@#2f+@WPK77Y6PMyBmG~>gL^;UU82G7}*Ox1l4Ju(hw zVrRP{I?>ipLB>yTrHN;iL-QP!9Yty{1*guqbRb9meQ9>-1dH6d9SyeL3#_>PGyliy z3U;kItXsmkt>XXv9Fyg%zyA;YKmXPL;J@{&{`-IZzw)2`)&J#Mzy7CqUFEc8Wzb1I zs?6}FICN?F*Z&>=<W*jVNi!6F{ckea=3jFpYK{YEKv1@O>UEXVlKFrnL&KR<VhrD% zOc_J|&cFI!bB|z=_8s=@kBkp?{Mzq1>%XD#<weixL%!CB{trhY{s#yCpRN(A?(%av z#~t43<!jldu!rt#Ot4tecx&IP*Q<Zkm;9Aq!79QyZO^v;|1%OzPJgNQUMBV58N)gL zo`2(4ggCyn*Y#fTH$7Qx^3^$+rduE0oS(AK^QElRzt&}L$L}u|wAqrRRxy#y%yaX0 zmlX5#gxO^kg6wfqFHVr_XJd0`VX|=fmGGgwb<u?n9&VEazjr=zU-a#!xYLv$?vr-( zO8IMa<Yg3INcpbi<&nm^JxR{RO#a8a)y&_*vkcz&sLpD#yCm(!m~-&{gbfDfDm$tL zR!DYzVASAsS(Ll<ME^Pd50|%E=B?ko{N@QqXL-pe?kMxqQRzy@G`91;IJW0mm*t5D zXNFMc_KAy@J<O}sP}Jt|6y_FWl>d6X$A;@<;;Hs`rxna38!|7%_fGw}z)?Ja;ps(Q zv3a61_V;bN6C}NdL3P)s#i@>teO}(17u0!chcS3AU74s<_Nn!tOIKW&`rbqqgR2Q) z@16xHO$|~h^m)5&lH-}Tiy3V)ixf=O9u!avD>=2ejq&TE3o$jU%cn3DotZmX<&e|M zX<_m2u4OU%S>8#k57@9%z4FYwU%~mGR(#t(>F;^&>D#vdzkDXG`_8}c_y1RX+n@D+ zH4^cEWyb&RDXWB&|2r>aKbv!|#NEtPSexm_4Jm$4rflCYGkE6<sx8@i!Fky%;pIwK zZ#+Ly)p=vdid5TU4M{%I$7Y;#f3K)prLuDOg#V#`?ODW9H+0DM|M)-c%o&rF|MHDi zJ^vqqQ5}R_`OoTgm2>A$|KA)+D}2S{-g<`o=Vq@}d1q($bN;2vMyWsN8-@J!zxp4I z@N@m^zaV7pgx&Is3XV5-dFOod_!6mbm-9|&dttu8oMRICg?{xql3FZ_UR?8G$YU3F zm)ZO5?uktrH*_)z4O#ZfG3E#K%syMuuC64b)<0`TjQviw*)#4KNd8HB&@!u;;oOQR zzh<2<zGdB5ZDut=MES|qB#Yd@$u>T0@tM0XnW?^YP0ISe-T%swi~o0;ugno@2w9L9 zs`z(3Yny}2U;E3G6ubY=N(=h>zux)(`e&jwtZ(Ow>aEIJzv>$@A@90X{FSG}_VedX zc%+wBcORV7Hq$q3@zGh=uI<`8`$GkDOEh=?lEX{y1kQ?%IX0K0U*nmN;nSchpTh^s zHh9f%e%*8R6?^v7w)5o&mvt2!@DX;9S-eFcihYy33}49&`>j(-t{&d?!pZS|%c>i% z_a0Pu`0V8L4AUuVS1rw5=p`z0^U9q=vwkL?QeSgvr*Oo*45@A}8#~regEuTTtrrXS z7AMv`)AESYtv>0HFufo-#MPvARky4_h^kMoOXS)Riv>$L=O52ndib8e!l#>UKhCUK zWO7t&`qrsEaS4_Y3?D47e^96>WqrrEVB6ajtK0$&kMPvIKj^;g|11x&X&%CD|90G# z<(2Wb>?=1pl;Sbre5G0XD?@SlGksbgZofNmdh2bOt5a2_N_aKHIA`9xDfC3?jhd7H z%?&w%2?zcKF5KS0moRl_S<B)Tcj83n3QaUhs&V{iwy2OLPdZ@s{N=~{p9Idb(EFpl z*mB0Bgh)o?D~x}gHZY1$Fp`Ru7Mv&?dAzym)P`2$`De9`IlR*@={M+}ekh4$eZkTA zUW+@AMXr3hKHI81==|TU*ZxoT|MfRzLC;OmXV>;8fBHYeSVwH}k^jltW?cDkf7O5Y zum4y4lfU|Z@vr)j|NXE2Tb}%5-&G}4!jd}Mca5^f*@P_#%XqS99nCV?p4pbKBfnE= zfe!oYG=@~O)N`o|-^??ZuA!s-sYv1JyzAeU;&?gwtVP*4&SbrGXxm+rXfCkH$YIWI zmtWJ%=f99(=ThNfj<_PB+?ABAaHi?ZHm)kROrGC2JS-;867ZDZ6K6iA&n%|C<AT$} zx9#3iGIC*;9WP}3vyc|$Td=HfLEVNd{j+E4Z{*L~c<F)Hlbp8K-P48Sx*tc&Ikp+j zu$0s@PVi;C`^EIIw_k+C2_2;kHjg_mHm|E-sCD$5miXyp!|~{DHkoxrZ*Dv?h&Or1 zwb8aK>4~Rg|Iz7vk1m`%t|)J;CgNPTrAsL-u`zi`vCKoR!u4!s52oaB9Wq}TqgOcP zi%f~_%%^7Om{l6)v(Gx(z}utYE12=pRm9Pq-__Uf=Do$+9<oeWr8(b8nn(1o$l~yV zJ0h#YUsyO_nHIw_FX>y!I-es)-C9;PbLp9FoNd<kpP|hpb0#x)(X@s8<<r~deO;1e zYO|H`zN6@gqd{#2-gk-v_b~EAOuN*;AR-~O-%Kp4%HZ(K3jLK*Ck=VNg>)L|33%D_ zADPNuE*RzeO5z0PN{y*|-Og`~^KLn{Kqm32yGw>f!YNP7W0N%>g|2mCD=qvUWbkd> zq80P5p8BEkK-OJOy{#{)W7obw<JH_1lP4B93eB7S_1B(-X%@d5*OVnQ?9+K_8^2bE zUq?5wi^=J3`x5>s^&*#|=Gq<dH@;;!<8J{E!?_0D_BPH{l@fegG6I^ey;^Wd?peA- zQmTcB!*kX@43aJ1el^=2TD9Yg#o?_ce_9VoRNl2*rc>;;Eai}aU3#6`fsHyUPNFr_ zr^uYoi7HJ?>U`O$H~mntlb3#AW7Q%_(S-bYUTx0$jO^=oEV`7Tn6S#@G|Qnnb;fk} zC9%E7y)XJms4_`eDXpJ#<EZW<$0Iu$)^4$KuK$^_ppV<qZH8Se7ssadu#g#=3}Vbp zTsM!kH_l!1lyO1ZXOD7YnIgwB{wqma<}zJe5jx=ur=*#QK+oRk7dWgwauzDiPMY?> zYo;X2tb?0+*cT-?Y{+2V_K9ih!j-){xIPF8vw5x7Sr#xu{pq}9iJ~`MZRUqM|NF+R zN$c~{GEAH;+?MIx-4-%`gWYR~S>9S!aWjKne?8#HTQxD{>AtGJ0lDq*Le30+inTs! z)BYKx_A{nR1pD`_{AaO^N314afkouXlmKCKZjGk@Z1<O#NlG==`I#J8d&DK+;#N`N z=bOcv%{UF1)Fixmmh(+Z^SqSol{1ea#9-Ir9#aYbzh*4is}3qnIiiws#OGz<g5z^K zr&cIWkzq}Ebu2>2B-LS#(`=rVrYvlxqPEZ4-?`jzXUj4@9=zjXPP^ZgS6W%>Y9D7L zh<7FR^zM<!wLH6iTlm!Jx3o+(zNtl|w;kq8Eq*e+Ut|5IlhdSLOemfbI{n!}sSOpW zmHz~yr!H36zDsBSiLjRjQoU-vTaKy~Nz6H{buBYjQ?-%hbb52$mBStKfn`Dc|34j4 znlBL_ZaCdgI66_{a?t9;`~s%LRIw!KMYA3YdR}vOROEm6HPdw4GKJ)jF5cSc$=w`E zYorU0&Emcqn3C+UY4z(v&IJrdOT9T~u^Fi`Db4IKo2BICe(*rnS}nb;Q^TE`J0x3b zF8OvWz4C;`ga1tMdTn!=Ro)FhHg3s0+9}NTH(^-=f9;fWQxslkey+1IaCJU=$W!sr z0XMD_x(8P4DeULqjQhAm@|8?G!~LMyU22;zYD|6gO+b$8a@5PN4y#2iVk)l6o;w#S z9}D+oSZK0XQ_QJw=b{7A+;#u$Q@Ce7SKPj9)rr7>n2c_hyuar+`mCN{H}Or#N!EwD z3N{rtrY~5~?xXsdKhvdgx9LVx!LpSP-q>zYeAU5ntxe}x%b%+b?#s1RDxWjfs+Vk> zoe}Ij!L<5;{I#Ck;?}6cYzBH;LNlXQJZIW!#ME|hi|bd*tBT(ya3xttr|RCYk^6A$ zwu3_BWutqW7c1I%M)2>mcD3YRv@CON=TwIKs>gU$<MwZVJHzq7mdxB^5_Nk#KONGE zl>Ybs#jdAM`!3ztB-rOAUHs@kT~C(&dG@I}Vh!7pS<TEH_~x%&;QM3Y+gNR>?bmxh zdTcv7M@yNxfaUhQDF*wGd|dG8|LmW^VR6qAw)5;1wmxoBD<OY4VUE?bjbg0{vJACT zEptBc{MacT)wV-@qN%3=^Rd~{D}Qre2%n|*xWFqh>T1_3&r{3>ij$O7rtzKbdm(1M zDg8$1w3ob9On=V=+2@-uYrp!}5+LEQ>4?HEgOeS8PCPdRW1G|(B_4M?ZhyNeL1AWH z*ReqT8=D<jCImV-igi}cx9EPxc4tmYR`tmvYOS6g7AKDiO)gZ4OI19-bs?)%?wOXE z*)w8I^~!to-YG3yW?(t*`G3~y_im~EYd*>IZ}QQ4gR~XT|6l$5|JBd>uYdMe{foE# zzkc5T@bmxYr#%19S|!x;c3NWJk#DBU6PNj{Ry~{Lmwh|=|M99EyI+ZQo^P0!p5~qS znrY3t6%8KBt?bP&%$Qu;4hlVNN}u#I`uxOs9i@US970ptc~o`_i6+hKPG@hi=bQiF zV`b~+kH>$;R31sX^dN_aHHFFbz4&hPmBCib@$NfbWv<!S=kQuHJtVQjLPABeqQ6;^ zWy7k*uOjcJFRJD>d8a5-w_?eGr4ncO*ta-2#_o%LQ<|sl`D;;Wk9q4}lk`8gW~Oo9 zSSFH_D7Mc0K`Fyyzts;76VDy~e7@Bq^HvtKz*4i+Ggl-m)N9`C`xLQ~cfH_&+t!?S zZty?)5U}IwsymmjRV`cmE_`m*lt)&#qMTfBpR&CA;>fqp7H>-CWKHQR*}C;e*S@KR zt1foE&&=M=kn}s;U$98?`?jU&Zx3x<c4kXS@|NA1xgN`n4s}oI<B(+vaysyO3L}eo z)q@%JpEHisr>@s~mlw+!`MPxL-MCmygIlLhvx$m_g+FuZHd(mV>gvb3SHJ(ae*AOm z)c^T;Cf<MlPdig`_5b=`|3m-pfAv57@BdZ*<G=o2{qO(e)Bp9aO!o8mlb*-gFa4{1 z?Zk`^@+>0X*pla7l-RSe`_X*|UdC5kJ|)vE99*TI?3ao2*t1!|tVwI9uy2O3Vq-6B z-jZm6boZaTgdeYddMn&Yjg4cW#3h%*o4k{glesRo9R47ysds7qs!q|vDvi6ZtSQyG z)%v0NT~<k8{uc%j;eF?md&=jlM}-DlbZui+Jjr`OiHSYilt(~#`Mzg2{G^P(vN4t( z=yMX*<P}w!d?}*yhMSLa1nb?d2aygoE}j{7GlLTT7$}F?rI?snO=wx_!F6!))r5P} zt7bK~Ox^jbH}B^6qcNox7fPKh<7Szpq%KXD_BkamWqz3BQT9Lmn?KH&^u%2;`uO5Y zx6bGqw|PamOu51Cd;8hC=ryaBC8wHezP;7;m#wn)NMW)ae~gGQ_X~mGDIX5X?U1v6 zCGhaU$za~6S5D}Q&MD%VQn`jlM16*nSG0kMt%*-dr?B2>9XH`HzVmVm62eYxTHcbv zQ(~t!bqYH}mg54crB_=2?3>1E+O*y7A&Y9I%c9QhlcPN3|3`{-ck8}kjjKDfHG{A4 z^7_)PmxWf%y<qcj?-TWmRVNZ%4KCbx-JllT&c5*PqKk8Hs9IiI*0Cw#>36-TJ%Nhv zH1<m$yca0cU;jkJbIK0o+`_`FPnwgw7cBi(pIJ1|?8N^Wi6+ZD|NK`gZmaqSP672O z$wGbR|M*qvb640l2*3B)etlx^iAO0_XMSAWE0>VJ!1&7>VJY2-k8V8Xw79N%@<ZR% z<L4Y6>C9XqzA;t(=8UR8UzwLVYwX%TxqFS=%doB^9v%Lx6nw1I`_JAp`t@Pv>swPi z4j<%dSJ*x;%IE!*47RN-&(^9a)OvS_&p-d*litourVLrW2fPPZTH5)ARkyt1^VHVl zdAMZ`r>l8F(q@H4I%i}xUKrhv?yU-s`<uD-ufG3`>NDT=Yud)n`hPwn<oy4YNWENe zr}zKKwEw5Ssz|UMm?O){oM4ok)_pZ+%PigHNfHdLlgoq-2UK;>KhE-QYissviQ5K( zk9E2jYr0}SrtebU`{e$%$mb0{yUJP?bF>}pomZ|Gr18+YLG;wR-f2tbCTCt(SFbvx zeng|sY7$@Oj`W5(8+R}0=VFg5_u=tlov`HMLj%QU!i~&FGE#YJc1XPb`0?b{+wIfC z#a!B1pDt-GyDFIDbL7Q_fInr`b%GJ^zFaiE!})&2raQ~;@!C9n`+wS9hMw&Iryu`M zjk*-_JAUi`P*lR-?RR{}ufxsn_PoqL=p(lJBF~!t!A499m#dk(J{`I*JtcYSi>Z!m zcMPk<g}y2OsV`D=;TD*ZdM@DB%x7&z*Eq|UX;v<%2syK)LcsRp5wo<LH%nN)&X{~F zLgced=lQ*x=k|w9uzjs}{9H}UubhAUUn94E^soJW|9SSsng7M+{`a4J<iBC6)LFHw z|H9waqZ0oopZ+KQ%3J-(oBb8t)2(O3%!-bAme8~>L#Z)(X40vp4J!o<U*7PGSX;Nj zP0^S|_N3nl#{<V!iCVD>tq7O;*1E@Wj*}oyRK)b%4tEb_&6)0Kk<v52@uTR86N<LC zeGgCdPH=Tja=qR1oXfI9Z9~n)DBcVmp5qJ#1}~Khx4X&3hrdxuWY^u=mBAG%zIe6d z)Q1iVMW)UVPZ^tE@R4<2(s$LXl{sqGhsrf!R&RI(FX|umu0AO?-J0_P_qAuCb_)6K zp{A$ww3z)1GJnOhNW@O<XFH;pWWDk39EV)V&s!y$Ca@-lUORP1ZQ@Qf3HHb+7Pg)z zZ7eA|tt)$;J~=Yu^qmQ=nhR3ZcV}H#{mp*M|I|-=&c3PdR=uoZ`=4JPc^n3uLicC= zpP%yc|B_#qIJp@T+ASYs_MF{#?#{-xjaxD_c>X&4YL((tnBAh-_;QE!$C`2r<<B#m zd0m`%WP*3Z$!*;6vC38XQj_$ZMY^>dOw#R#O?h7QZix3|x*NeT^+N046M1!0t}TA| zo_WPu%UkE)DLc=ZQug%d%-gMHdl$raD0!UBWLa%1ccDR)w@A=Tk7tHqXX;Xw9R>Z5 zs}Gc4>uvwQl~&qTl6jyu&it~@_bK{b8!lc`dU!P{b>qYdUYa3{M>fsK44QrBfn>K! z+rJg@H5XT?1ip)DShHce$b<`a%T#N-8&!fAin#65Zf4kWN32fJv;B)iH+L|X+qUFP zk=^nR%_|*pZyuKG-@cONg;Ny&g~=Ti69p=NR0-OYvz(0h@iNM5Q?km9k1FD)-j^h; z6jkkvT)?>Ph6u}{N$Zrl-#(3$benuBro8=zXB<P=39SV_-Oir7X1<B|aFoT!A){5* z{Sf=CSF36~KjcNQOFEv7@|72Uc5L?2JoB4x0(Z47ZgiTvd%>cQ6PCCYWx6n{WlBtK zV4Y*3s(nBp^stBk+mb0$c^)ma+P603W_Hs;6{{yrzu3=8+bntNk+}8Q^y&PX+g>ga z%{(N!jDwBYWF`xvsjJIlvFkE|rCFT<29b~1gq~&f8(r&pRJit{yW`ZSZAT(F&loQ6 z|1i6Enoh1#(T5Z%&t6-eXS*-9@E&7!==L&Ba;P*~7TNnUA~AF7v-^_XZ4K(8l8P^R z+!i#?T4llXt+8&$w7?0|OG=!2I>oZw4r!|X^>s7MnWF#W?{p5yZk42km2*_LDk`n~ z`TcAF`_co+Cr)iVGEXG<AX7qHLZ;yzftTu<T{$_D5j)Sd9^1h`J;X_6N#&L33BMc@ zH@)g(WnVX2*MnVvx3zvghi>4-8I^OS3vPD`I4Bg_uAPt&Y8tSe$wy|2AoIZo*DMrN zGrlM!y814e|M{vb%R`33Plq0=uZT*N-KVN*c>k1X^a6IbY45KE&I-(lc1jIlo-wm0 zlyw7Vu$`m*XGMLD38tGiX`T$7#%g&}L%es6Ns4Qt%df&i{;TX|g<Y2P$_qPu*?9J% zOWljGrrhEpFQu*9c>~T>*4B7PEA;bbZuDs9Q^?&UzG2HoeYSt!9<wN}?`c?G`^$yf zUqRgP%Zilz-c1gCEwu^LHojv2aqgF+np(BG`pe3-%AXD~-nY2$SS5B(&aqRMSkFGu zI5bz7$s=4t*{Sc<DIsGaR;$>gYmS_fD_Y%{M6Sklg`JvmHfZV9RS{0JrcE$QaNv|# z#*)C!@X=Z8_@s`HD*Yx_Jj*wx{80Ot*w<rlzKWwML*}Kx(*+$1_{#-#l=x<3F>GBj z<&(v&0<R3F7gHWTxKQqp(YJ5L55?cHUgugY8csh_IP*YZnY_>b!%L1_GfoP#6WKVm zd<*OIxeU$j|8~c9X5>`qte(-qDiD19s?t)Mqum?~GP&OmmMvMNGA~U+R>S9`a;rqJ z@bp&)8y;P7<~t$Eci~Nf(5(~8Im`lmli6Gj{R(n%f5uXLN5(xz-}RoLOY<y=#|KLf zp1UwNctXlL+i6U?stuFYgaycbE&8Q0BZ%!;LDJbrpB%O_Y_Ct8HPg_*^hCq#fAudJ zrks<k(^xQf;R#J9r%QHC99d^SM*1<B8D0<auJEpM3NF-lYutQebL+)k7BSJ(Tz|E7 z6F7Q9!xSwhc2pLW)HyE}IiexQzqO}d=)uzDR~?6RCVXc)K4ZOlz5lH`n+aV1;@`9} zB`tp;aH3kVTyt|1+lov@rL_6NQ)4m~U)8;5p40n)$*Yz7`@$UsRX$%-+jcu1jGJoT z6?i}8T+8u{V4n@kK6o!G{o%hcCx=x>*!?2!wACUN6=$V0Yy~o1pXKY!`N;5TnTJ-_ z{$p>7WfL~AHF<r!{8ckVV(Y2;9@9lJw&F8|)}>EOwNnl1UaRzPp2tyv$ooFadbqn* zbI&`*@JQWBM6*qyT4b@+1TjqyiH53)=Q*S5FTGsh=T>U8EyqYLB+yP;c-1lsS>aWZ z<#xxKKE=<P5V$QT%2@kpcJ-F$oU2thSF3ie3hjK++BM5_O_AZZqMr`~R-FEQPh<Y3 z_r07Oor2Cf?T_YOsS|YlQAReGR5S}~uhVjsUo-#NpBF6@y*9baeuB4|iyU)#2m9>i zaxb3JMNAGiwSM~d=<i-9*MC#Kz9oA1l#XvpqL<!rJ*ldDui-*+X5cjUh`D|X>Z*Ev zc?tUlTou~A>XKPT_l15o=R<sjf{BtEu8k`lQa68+*#2!*-lFgJqVJz>Kl#^xvZbzZ zu-yO1v;WuJ`ycYYe${vTtH0;J`s@Go-}0*epH+7MU%5hm?h5M#XTCU=D=f5O&rVlh zNj1>h_4VYnj>5OWGFN`?FSr}*a&1SrU21c|n$GIg%?2)$?{a@FTB<D7z|wWFZsy{a zMd@MM!b^__GG5l?JkMm2c)du#Ktz8<Q%m|cg+(7yOp}X~XC?7`Z)W5z(l@b6%@%4* z_;4-j&<p()a_JQ^7orvhHzyqAYhgGW<m9{X1@9H1jssCgla<fRY}3->;opCFUZHvV z(vLfjE>>N{;dlF>mwdy)^@2Rk_3g?oM(4K(-D+jdTXFG&s$kKX!#|eq`KhJqvcN2a z-9Mf|_OLt478|~c{2WgwGp;ywG0ImoL}-seq-n}5k&0EF>*e-u%SjPl8RvOL$k#D9 zh`%{e$-ng^+u@9?5`~@albAXr+OOUIHA^r0;*^O~6Fbdv`)co1G+i)fX<-w1cI%pf z#f;D?!NSFQr__Wpwl4jwRG1g`jQ_^`IVx<;OZG{;;rz_OKcO+rUUFIR9J@<1uQ+Y% zKD_18Lm|P#ox5di93?&--cXis^AlTB;=Fa7aUzR3?(}Oc><YJ()-;^xCh=wcM!tq) zTTTY_xk>O`xU$w%^65fW<$|ChgAmP+$>-GjxAdGWJoDhGk^3D#f%7LN)TY@-+$_K2 z#rXWoq&x0EW@s*Wv8eA}iMNW*L;ki|!C8Cd{J0J<-z-h(crvf@Z@|>n_>zx>7q(VS z+TwQUkemfWLOZt&qn63tM|Q8Hj-7ToX?H3@S?JxH16xB@bvw*DqVTiOqf7CP;@vYg z?@tB!wPYT8xTIjlr)II32NzW>l%API|GCv*$0_uD%Bix6EB?nzMQ?xm$$o~8<V?v+ zfB0uk^FisKAa`4@PXA}TuAXh}kA3xVkAJ_<Wh$6)^y`0a_JyH>^1|ObrU{ww9$U(4 zcacj*$EIw<v(}eFGkTmaem(FlSaXB&b#pal$5ooFY2v0yPn}H)^KZ?%P+2BtV|2C6 z&}U6=^?y+lrM0KODAk4XI`~CxpL8J8C?L?C@t1e8+y~XOoq-2>W}Z!3bH@96>#UEz z<T?co>A0M@<K#8t?50zkla-p5^0+yvY&@dKDJ>!_`<>faRM#Oxh+C2U=cbMWjn|xy zCwgs5zOeIzDUUM)FO%7pu)|Y}uNZE4)aRyFpBi&Sfk*39<J2Q6tisl&%13qNf)#nr zZf)b*urBNt7f+?K$wx*d_Q=>1?_YQCe5DZSWa%)qbaL;OgDHOIuDQoT<yI)2a%x-k zj7zC8tw3BviftRGZHS*Hvw&J-z!fp6hD-AnILr2`m+SA-JvMpo{Pmquv7Rl4_lsn- zmK^1iQ0V=+q=qfDhCNc|M&sV<i5FfbBwBEqaq-BzH2s~IpfZQUt-<=O!+CAJ2@g_{ zme4;{v$^#C)blx0nXVpj*rj@Lnv%wIuYj|sPyP@0d~rnG!{Ak>*`o*Y)s0bxaZ6fz z#WTBP^@IAhx~-_ljOy5*)FE9r?Z6MyuNoGN((M*rGSW{K92ZY|o&Cc?k}EN@FEM%h z(=4BdEcqA23MRf=5nZM$qQ8|P;m_3D9x3Td1aCg`>DE3x<Du8-1GawKkERqYd;HF% zJWWkPf4_C?rYQ#RUSw!zbsLmeXl|Ov;c{DNn(3^LC3*@rb_qYWXqw*TIrG^6!j8So znMW_{9{ZB~a+-0>YX^r%-;Ymy>SdmmS{V~rk><8b^1FbX_q2=2XHFF`1~#N`@vPtE zBYE@q9eECwy84t=4Be}FLMxwC9Nji~=UhJ4FBcuT)*NluG<m?yANEkkHR)Lo`xjmg zYyW^H3_Cn$cBFjq%CL5{`59HKdQ{1U+s!08rI1shV~NK0_H`E%Y>N(fz5LqU)pe9T zdRu$Df-TQABNt_f2aXQD=2DjYw<fWRewlGH!>?N`uE+cLv4a_(-!8ZKZGU{yH|vXk z?!S~2>rVNvemBW0_<Q}8zw)L3-S7SnL}GrfpTFYI{iL&hBqlW!8$`{Ru=`G@SoNx- zFZbV^yE>zD&(z;b(>~kv-Q1JB{KGXZgPAPvS%mYqYejCKaNZ<$a-Q|KD-SuOS6|`u z+ar7YpSH47!JL`K^F4$d*RIZeyPxO7{4amzTN=lrt~v4tao<1L^nZ$|dID$K%*~r4 zy)Wb#%rxA5G|MdN?6ht6tR3&J9{ekzG2>#X_z%Vf!pkH)r4=|d0v<Z?ziDHba_JGP z{gE5)3}R7?4}Fce%3GC`EDn4zV%1idn8C{J(;+x<L#%C-rNNJmlo>u=B3t}6Oy$^C z(!eKZvH3#xuF34i%R|GbX=teJ%(LMLSgg!p%+kOlr>5(8L2@lyDa#kdp5HZR0~{7F z?SIRD-+2j-$>hWywnulLyIl|{<C%3P*I>s!iygCSPPpz^^Pp+{LxwYdSQ8pk8w->? zdaf>9__LLNv4?}r1zmAhhHLC!U#9J!+)^g>h=n2Oo`AI;i|dkvvL;pQ1HAXvwTm2Y zS~C47!^E~0=huoa?D(6Mjh^!-tl&yry!X_b6-@iz_U(z;&8s%+;tN*84+&Pn?{+*h zW&Ujt)T=amR^qHz1p>Si=eA5gv}5B$9^PgHM`4>|CPzdzD6;98Epd5Z*Qlh$D7H*F zJxXwoLXONDeYt1CY|Pw;t(Q*F%v`p%p@Ca!ufx`qQpO8uN+k?Vzi$clEh?%GZ4r`@ zeRO>_gOYiW{^=R1K5OJAte-luO3Jrs?bBOt51-cNo-wQQ+Nrhcw2E$g?mqCVEwAg* zmg}vn7CD+ZFwJEWzjtN+v>X3}!tWH`_-`tCR&C|q`H82tU@ScV^>$;v?GGvZRWI?& ze~-2F>rL^R8{QdAS{kvYqHckVdjt>b!_UgjcjUfI{k-+)?wu#~5?)-^h~JRHZZb22 zVd`%;>&Mc3hjPMZh#lD0KKF6ML7sBvi|s2U&jd!-{yK5~)1qY$G84p@giSe@JDdG* zT0QxVfmVFelUGG8Our<2{t0#jFr{Dqm+19op-X_`bh+#tm)$GeyBen?EI!qE&n0?$ z4f|9+F78SPZOizcE{R{vn;xvQ(&cxT+t;J%&TaT?9nZmPC(ab}r;VEz&x<f~|H-R5 zL*A~)(NmqNXNQ{MlZRJl?D#Hvn<4uf7hk{Wrv?pW+3zg1dQ!_iHLTw$)6qLa=Q7j0 zOvTFuU(e*8%=zd&^Y)}-!IM7|E&0x9uSuGArL%Y`>(}Q?ByL^fUpIRKuims-r+)QW zPG&yN(WSJK<%xjL*>#<56SwH5_8bwIxn-Za+bYE;(Q1lntLu*bulck7@r(bPXI%1; zH2OdP=#pRmL;hndE!uSSzmT@ys{iXt5AJ((^hXx=`A|EXCa2bi6D6z#7jbj&%YADV zS^Ts`Q$E{wk<5gts?S!;<X_nAo;6ofYPUef8jnXYB?Y_@@yspSPdFMwMb=oI*uzw{ zRnP4nueA)vGDfwAVqL*i%W6I{ha7ckZx8Wm@O<9K&!eEZ;wamMX{GMp^;4E@*!(0w z=A}o=-xtnvUL8pdFBO<xASmLpN2pP2`+MJywu&1>jNU1p69@}A<bCX}id==EN3j#{ zY**gno$F(-%BXCST6f#$P1na~M|SF8YFU%DW?qEQtIpQ<oYw0vIC_?-oN0BIX5u&4 zH;Y9%hp~>$+hK*&gZI1@C$x;rWmaW-YB6dy+0@D0XSCaFRlQ7dWn&QEl_N8LH(gMS znkA{fkte}!W5#iLk)tns3?gQpSSZU<+VG-u^S?)p8n49Ou2PN=pXjn-fpdE0&To%u zGJmMcg)3T4HM%`d!tmh9UCA<GVm=}tqhham?TnH2)p2)k<QIJ5eqXqokAq{Pbb<IM zr#)Mr2z=vNdQaofOU0H$QtI*Mi%PxcOn1m!Ycf-}aHh<Z%#%T^^-d0fb5|VMZM5Wp zh~s4?nHBsKdKnX|BqJY4v03uJV=X#xtaX3)(PI(H=VU5)uLd&gaFe-mjMdmC`PK}E zNr@_(PCC7Edpo<q;*#v<dg<T7I?_qUPWdEg9aw!xhsW+LoBXSZ*W4bK=?OhiaC`O4 zvs_p7l%#{A_OT<Ux<jX>UW-wCx}~x5)Cb+!ZBmb!-}Nduv29y0weY6J4YsJQvl=*) z8s4->J(f<4Hof9{+=a*YGS7uaL1~L0IbHb@k)ouuE3wvPu|TfuBp#FPB1e^~Dt9XC zZ#NRmdYpRVnc0<_Mcg@4*Q(sFUvm4-p+kM*@|z4lS`^-#cBVel&T!MIuA_5(H$=}0 z>lIRNn9(TEduk(}XUwgl${$9Tdv%xP^JH}}-&nN7&m{B2DX;TpGm<U`hD}zTsB&hV zn#?DOO%Gmjs9i|&DcQl<>R{aRl=-LEv(P`Yn%;y|nY~*s#8t}qdG@3xxrJhP7I%D! z={Nj)?K5xBq@Xow9~F*O>0FYuJi@U=ct^=G9`89Tf_it~<&i0qk5bDM>2|YzE1a&9 zJV{Em&nfr&QHDP<YEKGUF8Le@YrA9^@Ojgt$+~i(HS-!6S2f&xIKyM|@r>^Z3bSH* z3|$@{-~Gg!Ys*KE6Q?tHHFnD_esaj!a6*6So11%93;kVuTq0Lx_dOLGi7y+YdseDQ z%$v~Y7O7WiyQHgoLihz$XXh1`92Rbt8#X9Cd~#FdX29emEv`d;ZnC;t%0*YWmRqWR z`PHx5pDQWd)jUh>i1s3uWyf_i78$m<L~&RuTyv03K6N!~#wOuBkKNH$m!qdJ8<*`1 zUA~A>S^J#c#SaonQS*<tANKX?-j!tCW0U4Mfl(#Oxi`goYA&z#?P^AyA_b<s5;trw zf9|op%w5Fr<b<$r4U=@|veTTIa?acRbY3UT{^ES0Q7n1h2Ci9oCr|vE9--?Jqs}X~ zbdK~VtEWewvz)d!3xD%*S?Cp+W1J_BPs==P=(>2mH`9Es^GhTy7#^DD=^7a?Q^4%B zXNKxD%VwqK3SBv+Rhdftb8|vdBn6$KEp;XOGj6-j%4iPBSnZ{vn9tqPc;r1l%c%<n zj;8#FHT8KW95~Axw<oB_XWj&dUz0TEN+0kPfA*fGH?FPK;B$7)M)h~ftGiBY^L(JT zGpG0o%Or<eb}9!ol6l=*1DDNW;hfkZEq7|M*CBs1fh`_qE*_t1d^;udx#$^}!}t38 zB|pYxzUtBlaun1%<h#K|+9l}2*ZmG^`|jsd@~HJ(*wQqoutRNwt+3JDzEXd$JM*vg zO590&>{2gcY7(}|D)8<J#>v74n{qDkw{o*BdgoEzaO1Y!ndI1#IL8l<b3PwdTD#b) zWLw&@REht^M$UVFb=I^g@iM&Q<LFtb#(dyZgGswV{#BPv<)PucO?gL>=Q#fFnw(?c zxAkONw&VrgtxdW%a%ERoXXq=kSn@4zc@;Y)=##QyQqYI13JV!eBwNoGa%^fAO+MEk zbZD`vA*Uki`PRRkz7N6}u5q02Il9mK_)hNCCz2oXJPq_q_-(#V-SlGYgKhU1j;U#X z+8E$3;-x#|ini!2mW+!Tou@6XWwsd^iCA7ZP*wPJ+k_-G+ozF7Wm*{LT%9GWzEkty zy&FyJS9VOc=4!p+k{zK^KhH@~FIh@UWx<-HM735a?uiTcChcSiI`i@6^@?uy7`L)H z9jXQaCq!e#{`==IJi7D0s@>M=IiLQ|(3yGZkG<qHpRfO>SN-?i`+w=Z|3UBTSAO4r z<#+t6zw0Nz{x?0M&}sYrO=l+Fj(<7B@b;{{jZwG0eG?BmbLZ5f{LG!4GYThh>{yWb z@W{d&%rlB4PQPy`e3>z$#%Sf?8BK!iQAV>LwAC`M5q)<~H2CnreH)_o=B2DYkfOQC z%W_7V!WpquiPldN1ey5NeGgS_xG!^{i-Sp1do6#Cfj5`WgH<o5E1y{JyyKOJ4ySDW zgJ0}o4F7gXNpoAs@JtekxDz}n;_Cd2Pi<S&rj)QMH;6I{`mr>A5m9zoxL<%ZHR{=> zoedURMsJU-JA6OkvG&Pgo1;C~&Q!5fePyyOKftVz(eGU8?=Ln@`dZk#kVOo?Rwdf{ z75(PdNHuw`xiEO<+z{qg_Pu$#_|9}ro6%hOX1~eiz)AnF=J=cscwGNS`<uMa|Gtff z<?ga<N#b!fFz*iRW0crydq~!y=+o)S!iKhcG6}t=$)YSznQv`hAgI0l(!$FtOS&XH zw3cTr4>GBkDD=hfn0!3P>9rbct7YCU+H->QMDyeWg~Gg=A4GKeMWdFA-jp&<PmSuC zc_HYm8AI~bDQz<^t<7{>eN!i5O>nB^hedG<{EoU!NaQt0Ys`JfQnW)dJH`3S?pGfi zbQ^oPHf$@}x%px5<q)MOUasXY@9>B;YKt+*usqgDEa~8g+8Cw8vR>yA?~d#3n`904 z9{N$xb*LiROYdQaz0YDFoA9;Q8FXCBcn)l5EV(gLSYeivPq2@7AKT<M#-ls0PEu~% z-Li2dQ{J_OC*Q2x(0cKa*piN@cKKgbTW4J{ymfK$-P)3q1=*dfs(#DcZH}&R-Xk0_ z`^a58fxv@ZzA1`6ev>S3Fxq}DoUi?+nNeQQ`1-?=xpLVm4;Cn{cj9Q)*GxOZ5pK(E z+hqC4f$6Lo`wHs~W-mTD?6;Pcoi3;n?a%PP#)iRERJ=bh)6vR_W0mo&PMb9+rH;<K z5x7BrnrVcT%QB5k?UinVg&e_Qmai5yxK6SVTUgokOGh(MqOFnBQqA*0U++F&jWqcS zvt!IB8J?KRuuH))wB!c+zVz>Giu*sP^fPqd`OGW$K>YNafC9st{|<av!&)`{pmQZ- ze^Vxx&u@iK!Y8Gs=EN@QWc(?g(V}+nU4y~%y$lXb!3P?D*JMcXFx5($sx4pEujf9S zNxfzMP5*?1=KeZc1FhUvzTIn21;#}ttj;-T^YFj{%|o?fzCz2COAMwrN#A|bXZl-e zjQ|UudD^7E5`M`i?+HA--uZoDz|p`LXElO-d6%&K%2_99&X}$^E9%7UV?w9To_9aO zY%-;<K+olc>yPGzX}Z^Y*e0EtGHKaL&jzMHN1B$OnNaya@o>Vs3TB%?rIH7|%xP8M zPc*V$S(tR6Lt;VbS>vAWgLe%Ntvz0&GUHRmCT6|49Z@sa^j~PKSIqV@i|A23(zN`r zp7^QH##R9iW^5OXGt#0`7x*xUXt?}f@ORfta&w&|Q2)8QXcDi5BA;1B@4YTnMs3BJ z3GdY|7kuqc+4<&<XXv5-lYiOI5MH^JWy8yZQYj63_a2M<KR+Yy<dmcTjnh{Bw*TMu z?>y_TkDG4opDij`Ah<5!oZ=18WEH1&qhI}tXXS<u^%FR!yyVX1UKrYReQV<{?iN>u zrR7O7FFaz~TW=cN7X2pVXIt2+$>nzX*&K@;CxZol6(V|fh;L=xczB}6(uN~VI}bKg zsXR(+be`BO`tkfp3+ZKP-lvV+f;Oi@*ULvPiLx)*a<C?fA+gWS>xkoT)<au@RF8gJ z<)NkPcm11rpk`eWQ-F8DlbRHJ>6iu%k$2vye<rOo_%H5wb@?vl13KR;IKofdcCc`= z+s>R@wuHfmZ^4ISGQ!h0acVbrt3GCW{MCMkv17*st@Z0xtyt&uZqADKeKUnM*rZDP zmP@RDu9Cw1u|0fF;`;Ns{MIv*nQz6NWW2;{Dse$@fq!d;to%w%$$4{`Z#i{*`Ze=$ zai4<Z6*n)pD>_Te4P9S#rs+wVGch<E7CUm<u*!sM@A`==O(wSn^hVepc<|7{<HkCH zDNF2~P8^E!WZLujiNoVSwpR?&iX2)Cl*L0u7(JPGF_jjU%&@=7y?4h1nPVKS3Qcjh z1Rd1-95M~l<XC1m&OVa0!&7ip=iUjIxE!_@2DnT(;2g!f?BVV9HxrgL<}O?;=^2>O zwno9ltiizBZeRMY7a3U(cINd)Jng!rns%kZOvy%wc`4H(*0a6ZPdK7(*u3F6#m?~m z+hGZthtut9Q+D23%y(Yn3fqOO?5r6Ro#rb)zQS(o$?vRq_agUwho=)W!>%n@Grh`_ zeJbbTh|rbGlI(uEu*XeTd73;iTB)q1-tWi9eT)k;RSNky&M0oa*m2_MR5!0Bwk_?4 zJ}`vF$Vzi2#d$a{JbQ+PDe8m(N049g{jC9c(p+vlc}X4d;Y~|BctWlOCb$a8in~s@ z;Abf;?i1si#^&~iGr^@v<v~J(xR2nDy|I3k*?SEflk9BV9;D_d|KysoC{wicj>^HD zX&W+~4d1zFi@DsLG)vm!m(#mOJN{>SixL7EHOvn+SaRNQ&RORt)NC-}qC$hRkm<%; z{_Wxhn~&VNP*|-IpmtbNXVUMJccvxq?TR|26M1N7%N3O}r^fI(S5JFLY}oOsT)6S6 z+?A{cvsl&m73T1B@mq*~J@dlZ$-!CjMz~;$BCF}jl~O68ZF3WjY+cbHXZ*^r_zi3O zTfZfSyn8;MJ}}*@TBYg6IyccnE41dkt0`|4I-~K>vm-^w;BNI{`7>c*2j>1i_{r<k z=XRBsDN240*+-rT35t5R_=<VUuPjOPWX$eZ@I_Wu;(|QmryVsay>f^1UwsR)`RyP1 zF+2G8`8jze%RSHkPn9yh6!QDM%dPr5Jm)rxicRl)Zt$6H>cg_{8!vSUotcv4rOQ&! zczMY*19_hfB3hBEo>R`Kx4OAMy%FLz=VbV)88&x>j?G*5Aj<iJL-Gu<X}(GKn|}5f znG`SIwP3d0C62~xtS?SBSO2in=j#=&h~j2!E{HJDJYmu5;GrJ%ra<kYadlabGb{hX zuBO%cPLp{9l&8MmIoDd|*nEFxZS~2lQ5=E^S~?AU`wlk8z6cOMvzL4Ee};m?-K)DE zG)jMS5wkL7=6}Sasi)20bicdrB;!r~E1XjElABL1*wZ5YtVOMbS1BRD)~zn&-JV(e z{8!o@=Edl8=A=wvnqynY>03Sj`ukMBrxz#6DLBY{b$L8@MU>L25E+JVHY<W1Oa$zN ztmGpuc`sZj)$F;ZW5FK_hZXlNjF$HOwAmoGrqyo8=L_<T$F)4V4$ksi(YsB~xvTeJ zqmWF%S$Fmj<4V1Wp&pLQgR~{3Lf!n9ZnABfe*EHuNmHg>i@a#evzo`c(P^^x!>=vt zC3%YNx$GovbnJfRX)kynw&1#oYRvuly73$foci4~{jOerShpr#x57Z+Uy{G!!XVKD z0qm2P3FuTN9DFB!<nLC^a|LTBNadVl+H>&ru~jp2dDqOBu9KBW+PBQ1dcL;B44$iu zf7l#-1X~}5TiRIUeLY~%(K6%MLUShH5(Bxj5uu)p72(}KW-q9G^~A|mv*FaQSzkA$ zyqfru`?+$tkFimyL#lo69|s1p(?35{CPtbnNPc<#V^N2|8J^{An_G{22r8_f)S$^B z{Aq(T@4_hpAM{*f5*)-l4@@=qAYh>2BK3NYgKfz}6JyqkH$!JxGxbO@KbkbR+oQzE zEJc5lqwI{Ym5K!`*gQA47Y6nfYkZg5TOxbPl%M~G(o6TM877~bF8d31O=65UW>QyQ z-Z3R<&leA#1>Tl1-Y;VGe6F2yV1DbCAF@za<(*=}0lN=3OoL?)7BSCkNoY9vs%^Kx zopbXWYR)vK%t^GBKPMCX&HWsU@$*T777-nmE;=jDTdl5WRCuZHA8X!O)q6pqLTsWE z(+OsUU3(n$xBOJRtmeQkC*?0}k$i$dQK?_3=#Nd;qMTxG(=R6#%lYg7x;O8ha9FKK z-h+Aliv#W#?m1R(mO3Om&4#hN=?SNR{Ur6e_5Pn&)^z+4{=g7><l=)oO(sT0rin={ zGUs<_a7d@jyRgpcr0_I`jlb01bTTM*O<B~kJ0|Xhh+ze@!;98ETQfSRn4S=05)x9| z6QtpI;ITqOyy(2$HHpg)C7Zi$N}R%aK6~MxDRy6i^tisaJn3I}Qmvkg-}TgdrlayY z`2lAcUrqi~%CY6a#KrutHs<pL><V5o#j#+&@fwax{LA?Fa46LJU0!l}wMR<HtME@3 zI9WwGOC?`&cdb!hvNYC0uiEMAq)q7`<eGREap%<(Tz6mc{=g~OmQ9P!Nl)3o$f?L@ z4{w}ORqlRp_RTyqX+nbY%tRrXtJfu!tBajv@oGHNdtmYhs8zpz3|wf_#w+qF}Z z-mEKqSj5Pzp(?F!@I>T6V)*7Sl?%eotv=%{`Ig~w!$O8%yP1AFOq36)x_3lo|0G+t zU0a^4Y%e(cSLx@8ikblNDRxUZmM%|p@;Vze|GKU`znf&Z!_H|<>>>=;^5#3~eh501 zxuN+~?iEH)rdrQSY7MUhmx-u7x7e_XF*oL?X22H}{Rh+L><N_TEOlD%?G<Yu*X=)n zf15n3B$r0QWA<5wf=*HmUw9vWdt`L-9n+h&21<1aQ9ndHBpEIT1g<*VaChCV4Tof! z@~3F>`hR5HrmEpw;wbjzmxpGNSI&uhev_CPvN%jir^K-YM)181j{4VfK=bA<&xr|# zv~*QX-YGhr-Bc=`<y4f{kS28D^pf05rAtDlUHE!7Pk}wv<;l55FZuREc|T1!ANt0< zRPO4R)veRW=dxj4qoFI!cOsLUe{b_IFJb<Di$t$*_J_=z@LI9xtk1d!C#EVa{3e#U z&TQYU=^O<+g_f<@Q@M2QL^=OE=Z;=`u5_R_ck#hfU$43d3T$6?kolvamrHo4oW;yb zo;#MtPX5-jrq?{naV6hFUQH9*xJ&+CU0(T|Q{)`#SFnC!<_f;!?$AHUHHu~3-%IIR zmt5w)W+@~Qskfk>Wsgxy^^b=W{azksc=t)mW@_dl+Y593mTnR;(ObIOrGJBPxB|QA zk<{eGQy!CbXS3aTJB^Ds-@y4-%O|g;2{S&On#Up{urRrk&(ZMYr{xEoi<s5qmY<mE zy)3m_{e;JZiH0&yL!As|`WNr8RP2anoG|T-s=?kDZa0-97k;$c{zb6Ydwxqn)UOqe zr~DmObpL4PH9W}gY?I<|lV+szL}c<p)#F}mQ)JXFy@YyhH0JlH8Yv&0rNU#g&~(x@ z?d$VSwuyN@evu>Fvwy_{BWGKWiH6Sv?pH7OxwEC@r+>{$ZuXR9X+Dd+AD1-G`7@<y z-QyB3Yo+OuYoiZ~wy7wt7YIn@WVN|_@o3-@0YT>uH?K;eNf-TQMybiN&fKWKb=}gC z$+Kn!NqMcc*K}Q4Hup*O-YXL(Z<<zOv7}jXE_cvE<)`W|FL<g>n3BEplZRHX%Kh0Y z_m{TyO!4oX^j_^``Q|6PL;g=(#r<;9`yf-r;7_*RpKPNWSH6l`te|d`Kc(`9^!4D` z9>0zDIt6~8#>LlLJST^Z@pjZRjwKDgI^ybyOW*zIpWYm^)4B0~^82^*RCcbjHncT% z`tQux6md7?dkq7N2d@Q#v@!3ZwFl2+RJFzEag^UVwI{T6x6x;@PibMkrYC!2Tf>4p z;<RNHZ!X+wc4z}{*3u6_@s3$4k-PaH*Iu{d?5RE2?D6iOxZU%#w9v4Beqm2bzW2zQ zMT;!^eP{Of&v%N?e}C&#{N`y-=Z`mQ%af;Dv%T;4iSnEv9&$rwD_8o0qkfLkcUYFl zGqQvnmAto+q0=Eq>WHF7jU#LJEM~5NiCQmW9%^ULzT(e*@>=fI3p2|u_=zzr@x0lt z#mcL;=J*E77m1f*7AU@0-L~MHWNiMWqZc!F{4MJjjCFp(;2?Z+&h^Z94w{_br=1F@ z-y!-oIYW})yGciDO~fR|uiS61HCxW@+g3C!Se#)=C#U>Qj;S4ItIe6zv~u5ti}^b? zUyC`c<A2-Jy)yN%&^ZCa=V_Pb9E()B7+(JK)J)&$iQkg)`+r{B9sVzGW9;va8+t2v z+MRBv>z5p5o-iq7au(056FeoVj1H4msVH3Enfs}jNrzA4_v|~r+zZ~!`txlOrv&Sz zS!?>2%Cq$DKkWC&(;%nDEbEVL-k;oU-fJA@F6}P-ttydc{93uIQ8)a7$kogCerpq$ z-R(|Fryh7FQufHO*^_;le#f@?7p<JP|1x;DZKo%D?t`b7*O@)~^tk?K{huG@^GzD; z|L*?(aC!ajcHam5{~s;4|FL6dp|4}vg()%3YL_i?u1OqxXL|m}3QfK~x#qcBIWs0r zD-_N=cD6g`PR#7W$pv$oer~nAbotqVMQPHU0#Bc*%l95!b)BW5O~5X*?Ded@DQ9yl zcp9Fq(x25k&(vPh|G<IUZ_4y!IKO=GJh*t->4g%rOziqUah>!L*ABgOa7w$(-5BpJ zhmY-Gd0@+Yi0?<ENjS#}wmDsCy_YjLU-jhre6Or_*-tZGvk4#ebXp#G^n8JGmf5L) z2fIEWX-vLc@@kpc<ZF8sxxY1kx!G%4pv`x7o3+5YWc3xVoB!Cv<cj+?RcN&^o{{SQ zyyLd$_7_Fx%}l&_pS*2f-n(DDcmHGYQ}5fh9iDS&^J&BHkH0<8VAad&npd~7;mfXO z{Va9f{}&k6GPE*xeb&&if4yu==2aK=OKHKiYcFouzjyl;i3<m3AG~(*(J8-!x7Hl6 z7Zgv)U^(0=S9-v6ZrRE!t}*W$PVG=%RNi`SSI^3g`OXuHLY4#=hF<p9nJqQ1>_z?! zSE~$-XTN%bFUc>vEgodCv$H`hQT(KOH>-Z?$(b|kcrS)lq%j8^5Hw)-->6{FRQ=YW zH>hQ{iNCv>dY-+`oQn%g`7+e_^5-WXHsagC&=!9?B7NmO{j9u6@3iiF&u!$GQ2Z`` z?z{QV-h9@u(0aw75s_q^dciv1DlBu&joo}1dOZ2d_O3TFZ{{$`vS>GHng7otzE5p+ z_nUT+S}Wmq-EF6K*h|??{<eCpevhDl{=JJoTy;*YvfjLRw+8q2+rM&u_{_PL#Soah zRazwd%bqXnv%lw<?mO20_u?0uo`{@-XBTp7?{>2ePtMwLruUL~R#)x|?O%)auHV*p zAC!47qq2~1@%kOTk54|8(H5TP&aav^x9-#L`VaASZ}k~|+~+=U|2Mz=zuC*`ULF7c zr~Kc?{C~?B{~TAZZ#po)?!P(f1OER`7b-iuHthfVb^gDv?@KTCeSFTb<oy3{=CcpT zd*uF0So<vNy_8Dg92<R0!S_6YUoG1{v-P>n>B-*RJ?%xG*}H^0g~<l5xK1fJt_n=# zQ<ONg_l9Gd<1_iQkDJ~`3f!7tf8?&W;S|=LN{{BM6j`3z9weB!l*KvY?(Qvj*c;`U z<i1|bzP@hZZ$_;>I&XXqIO;ll<T>IfAnYSEf6@YB)(WA<t#_AMY6w}LmOk<!WdpC8 z<Vqc;_pQO!LF+0t)XX9aVyY+a4-hW+Fy&B|knM4eNuDfy(th^$>SAK#O!ZUB+2qr{ zooSq9c>c=F2NR{T7{B{e@TF%|uajt6`)ApTF4J9;`%b&auFDtRF0*CVYNLY5B^wqw z6sf;kq#?Azs;jo??Io4y%qE8b>y17N#tW@a9y8oF*`a*jj3k9TZ)S&BO_dkAJ#U}b z=i1%Mb2{>}#OP1?<BPsk^PJn(++FHYSO3fX|Ihuuznr)KyIB4&o5-IR+of4oOEx%p zadqvIF<hSaH<DMu`n=pGyOzTT8&5M>sy#ZA;W>jTuwB4^DvMZB>Vvw#YmH1Ti3wbd z3zz<S!_#<RGK-nf@8EMs5|0|~%-||LsOT5q@N-5$Mt0k~rECrx{PrCA9b$SZa+!sJ zong}4Rb3&64~O-3{C@U2B=m^X>*gExBaVkp{J40=#a|PSSY;~<rC*7VU3u=;BNO-A zcXlzpjhVOLuzHAhjUU^J84gFM{Lov$_HJ?8uhxT-Pt@OX_+2`XwAHw^ctd(YKX1sB z4+?HG?}jgI+)<`C;ep=u#599H^#yEf32&K>NU&eE*vefYz3AtR!%IJMu+HdfES8jb z&ced+;8f7ZD|{@RY7z<4+8b5>7%3~YvfVhos$&1rJxuSzMWatecLmN}`$I5&#WF+w zR-vfVOS00rmK=M)v-Rl<C7y7NyCr4SJ`*l;&N!lY$=;*1=ej4uiGzjDC-5`GFgDp- zEZZIJB=R=e&CFfJRU=0`yS+Kz><kCr>wwFle7?M<Va;3J@^dC%ipu`-c-p1L2IB)4 z`rI0v4hx=IQ1Xy%&RuU$*CbEIJKsf*D6LG9Sg@|`XwHp)iCt^<&ede>FnDk}sF<yr zq3K?Sn^Ot<!3Ue{u2zIP9cU<=lgz)WarUlX8s-J_w=g`sU2A^TVyj1I0*7M96ah)u z#Ik;d4?35eR0DKb5{}HfDUxS>VB(23jx45640`<>OJqa~<it-1Fzq|kb%Xi9T8qZ4 zBW|B%73So|tBFs&P#f>6H@Pe6%<M^-Z^Ah;4a(-PcN11hf9QRiYx;-U__b?8wB{`2 zI%#=C_rk$RGFm*B7I<l!Ihl#)$j{nT;ahpbu265)(j4cB_d<?cKfSE~``3NzFI_XY zSh0F#>tRO+lf=VL1}lB8n#J!@*}n4btBdobCViW<)-N?!^pT`e0-r&FnF6Dz{vpk6 zQ;#tm4m{KF&X+Z`v~RnT+pUaeFRqxi*R66pbG>dJUqGMEsUwS+cEn#1IDKSMCg07L zj#G+ezKUum7&>Q4t(hDdGlS=^z>5zL=i0iqxB5ukl6<4o_Q`j4Lr8DY48~svCOP!; zNaZB0>eNj#ij;}ixMi)ugxi-~wnar{X)>+Xe{wFE<p^5{o92_oO$9+MDNPF!pY}{Y zAjZgMWw);L?yJ(%<x)NJlVU7Qodpjzt~{==k$vUmnIWtl%6wBkPy5Pa!1zIK`R4vC z<|{E7=l*QZU)(scdx;SLZT(YAm3Ff9#2(>aaC!H!sHRDrIl7vYR_OR2Oy?9=2@Q7a zZcS`z;ycFNS|_~5+<1qhz?>7D9fkiM+>uTCwsTLV=Ec7&4>I$;GgmTWTI?H8!Z=$| zL6+mw-<*c8OkqK02N|y&o^0ZMFvM)bZ{u@!rX*euxfW(}SjB)TbN)dM-i7ij_Nts; zAN*IzcCX)KKDDjE)s}kKR_wlSbIPwWEo*Vkil@JpUtX&wDQZ`|;9$n;6q$!XIgh2L zavozlGbKJlO7rK!D+_0Q%}6pTo%lXC=W;hsLz{iYGschuruj`#i>q>yY8xGSKYQls z{n$G<=GT@Tk?z5}Pqg)(G1!0Nd1cYUcgNMXKA&}H+g8cjCtYS<h)c=gw%&E(d(QsA z$=wfn7cQN-;-{<FjWeNTf>-9RxOkM!f%itY(n}Xrart90JM(s5Yzy*{Qs=fR{OKyS zk-2u^d40G0>Xkk^d1ZoI=INaK_;*|7(Pv5?QnOp7W^$Vqlx^I6N@CUxZnFn^Hf*~C z#2wZzxpaQc=K8}k=j+d%cDwtdO37!vD^KqmlqF5-zNq4TyY}VbqO6jMhL*0~vi9wP zzRS(FiaYq$-3^Lusky9LvT^D|Z=H+c*$>}TerS}8=MrC3KXb2zx@WG=t{emQ_Q;)$ zI=g3lm~iv6%$F$U;!?Y)!0aY_dw#(jvx)I<7Cko;da8G=ZSIZ2?N=SH#GJY;eAm&` zKyt2n;p?NDXRK#4cxU9b+4hgb=Ph>GIYFz%_rKU+)oHzE&fR%Y%*|n&f5_CWKedzZ zWY?Q5&Mtdq=)S0FoWIU_#`9%woF}L3_I|zdvarvx^V(iUS67&c6@~hon{0kKH#og_ zwyE0f#Zk%M&q(QSkbh(?e)c3!QORHKW0giy4r;>uehOO3Ayre9--I~tGZNx*D{eW* zS;G~*Yr*?gN0AQ`1rJ(SOlC=zIkB?sU&W3a($2z)d`F*6V5~mdTGZ<IS#L>6;XEI{ z=igS%pOd?MQtTqH`KHhB7cmGvxpVl4YmnTxg|_UfGhTg>;Z}E<=F%)aYqlZRIpIE; z8>bf?;awbHKP!h{#o@B^^pan(-<%H|7mSKDf4U-zyK?z$F^wRryAlf9qm<2_m`~HJ zjT4_yF-^IAAJ4qNYnoPT9)7#lpQf+3+PQs$ENk@f{+pbaBj!BU;oPFabuO{)a6^=0 znfj5y<_#C0@}Abq=9M}7i#u^OBLnBDM}Cz&Qk6QtKR(@aO!J6lZhi0Nly#SrjXu8k zAG)czqVQO6%gVFoe|>yX89CKFJ?eIfbKm)&55%4wvkYH#q3y*MsW5Tv+BtL7tNfPi z*jT%;@}7de=l+SYyXDO%^&ZJwuDwukT1ecX=xc`i=bU`}Du4R9te~J=8^7l>Rv85> z>=Sp=NtxgtIgiW#x>Cv%?F9^P-Y!q-nGnLa=tro{h0N#Wd7cHw+1a!<WHqbL{K*lV ztlMq)GVG1f+_oPRn2y-5R=IdML{+#UJ-OhUc0+JqT6DrhizEFRg}#0KRy;cULwY|X ztTEZ1&&1;%E2h^XH2I~&t-_euCW})A;@2%*bYQ=F_lE#>iB+>i-{0a|FTDJwhwDVY z!X-Zvj2kl?{w?*@DBgH#%9LQ=%KoaA*;)+gCwSj$Sl88`e<b<HM@Zt{$)9J07-wjG z$qcPbOP($Hp)|YlWrS^8#f(O-t1DXzTui0fA8a-WNLKU-Qr0lu=qq@xuJnbGd4S*N z&6k)Y5^qYVPk1eo>>c_q;y_E|wZIeakGffIxFmCbRr*e`Lm})RF7P_4TV3L2ad+b= zVVxlPVAsry1s_^PV<enY*afC7xEWEkxjaGm#ob+JlqM{epQxtuQ2A)wi=xbG$u$Cj zhYAeMJLi4;DB30ap_ZAia|4r!@dTOOEo^3|lD{y^EeUIFY&ssomN`#h+tQuIxdA<A z&dM7zG1{0<{J{54!QVaE+U2OS)3FU6C92vF)fU(qIcu{VWG#tYrY7_9?fVZGLd73F z54K&fn?>GF-R>Yq|Gwojw;r16`Fh<C(aSf~qFx+Hoha6@Vw<|wBu78Nk6{aM>pYH- zvTk~J>`I5<v4Z;G#>GEZab8@)%JzkgYyNuq%Y4hNG8uQ9?WjnzIJ|7`GA)5Li)}4e zx&EweHk9XIEOgw1b>jujzy0eCbuJjX`7LK$kdTn3X5_caziZLvYl|$O%;M;<;pz~% z$zbw2Y~dgG9}7Af_*(<+Fvvez_LQqt@kYn}Bcg3SJ^ZrsGW<-g3)HLYs)u@pW-Q;s zU!b`~U@p@f?y~h3>8G^#mWEyXl-PE;cX_4S$}-!EGcG2gcAsQz|1Mr`e_(Q&a9_^? zP44?^yOzXcob=)LI;$yf=_4g_V@kA;xRUSIjd}6!)BjBQ`0U~`<GBxiF1W2HzUksq z&uMxliXm##4z{Sio7lj4SF@~X;*Uo;drq`e#5Sc$sdYsjWO7}UAh<lEq{)Ge$#p`J zqKeuK%XMlJaj|K7bIY@qv3~1Xx%<Neo5ti@6|)cR^=2^$d&#mOxZ9L9Qz<1}kU7*h zX>ODIMyJP2*3ONJDpd@^ifwljL)>O8uSm{N^Ew>j;pxsgFJ<92Cz~(c77875y32eX z&Q5q-qGa&YO2}h+-wT&ZN16XDQG0e^mVkI$$D1aN&mkUqzB*eb%%82?m*iP><z&yg zttxJl0<;zXs7EcoJadOxu<*;3&26&J_6zrKId`nnV$*S{{yB9vX3WYxNh^|sbw6C( zAHndlzsOiL?ak>RbqCp4%^$Z~`UK3BCbFNnf8oQ@7N<p1m?!GhtK3o*x$WDsSI)?1 z2K(K9HIYeC39oo&r)^YRY1!ZM>gJq(^*kSJI91;yeDzw;bD62e=uF`<7JK8oMM5`z zC$8$$FEVR<?4asq5nl3%J&ft`;eMN642O6!Yf5G+cTT@5t#sXDQSW@_-BwD8<!*oE zcW}Sh;ricLsMMat*RZBK<;RDE9v`w21+1Ev%rI+FJsjfe`(gu&TxZh4`;93k%S<;P zR#cn+wB_3l&*WDZ8O?e{KMK0+>bx(};d63^o+Y!sPo^m2vA(UHp>DtD^#?jhWIt3= zTVQ;WCAy!zJMhJe44xv3o@C#k^->SDBV^*{bv+2V@h_C|^(K=I4xvA9JmdSkRID}Q zRkY}q8s+lM8=Q}?i*&H)=%29R3O`pIXNTxqU1nL~_V=M#hn`((>)mp^JElU?^hop$ z1}(``lB#nej@0x&;W)5xxpLyIm?ux20=~@@F}nI;F4xQgrQCKywe?G_&vM>#IIO5H z;Z`xL*rAH&!_K}0b<QrSSxg<*&%YF~>Fro2WSk=NWm-GePu(WH4jZoNS2Nc+3o`l_ zU%qnCC#35QGvis8ht8{oRl<Z<v+F!PBc#*7{#dP&>CM7+OVQ(jd|Arz)3(33rfkaa z-n{K8ht1X*!ToDB-w5!0IduQa40p~amtMU5-)At@>d2ywz=nvG9P93Pywm1B?Q+5R zOm0imRwj*L(f89Yg-vO(aGRtU5!hqA{U^`$6M7PX#y+!MzKevMlNYuWpV6ja?;Kmm z<D=*p)a<b9*ChsF)s5TdJF89Sk_p-`)U|WM16Aim@rQc70v2tbQNDs9nnl0m@vDQ6 zWsbUP2AUoIpq2b$rl;8*+l^usFCvO7HeHw%tYX<V!>^NByXA>zV&Wafot?syQ&#wN z2A-A|Hs-ry`drELg13g}md!KDy5moDZeb9O$n$&OGNV1yH==yELqtyTgIbO=`k!4U zPEgVEHr3mt`m=`5;g-FabE4eNv?G2j8(Q8}s(fTGoX5h}==V68^PYvCRz{bS6aU;9 z6F1oIWH#|gG2G8o%;hwVJ-RD;$KE9uPM!Gn-ldXvvd{l(+}U*u0ZX5pm=m%}^+6l+ ziKmrT>OZX)CYjF7k}1(EeA_we;U&Y>ee;+*-HbgKJy+rNSvtF~tK*>K#-;}w>vCAw z*Y5nF#<cw1e624VRev06+N5n*#dRhw>GHx}HICNcX-kSvXt=4*Tsw`yzf|X-c*8ty zgWES7Dw4dLZj|uch|&Me@n*S;^8?2C*)B7L+Ez3jGI3mE<ThVu_A9|Vv8)qzR2e7; z6&?w<lAo;8y7D5EaSNwk=n2oz20Op03HeXoWX*`y?Ye8C`iP;fTt)ea(=*d%(VG=) zNp6ydHZg4Iym8I#|0-VBt`|KaK299lGCKF!x@CmDvAJSC(N*7ha_YS`Mr`UqQ(yRe zN}gGz>m)nU$|`#=jmv-V5ApTyxsl-DQ^WupXIQ?DzH*&&9lXiO+Y5>8goKo_V(8 zUDmFesn0^3-#yH<>kzR0%qVgEr3~ML$|O6<90u_ND{LP{Zn)0hR9kn5;qppB?n1BI z89_doYMU3Z%wHg*p&ER;bLzpCbQK|C<GeW&40PRY__UThxG3l3ntn+4u$;5oV&}bH zd$vvN<%~GuHl?J&lczRt!n1TCnNK3t`%W4~ZMMAg*e|u;aPe#}AyYPK^Si6&s7LRr z=5#wgbFZAx!jz<$P9CZa=`3@yJ2Iw-nyXH^7MkW9_Ee2C()MzY<C8<zPR)tg;lCu- zVruHkV~$(5yx=<L{^7U6mkAs^b2S^2AAEV;vN=OXgR8(U+2pZ+&evrg5oN{-r)(KD zHi+|W>FYCaOm^v9AXQ@b-|kxK)N-M7JqlS?(<GUu-r^9x{%g}tHpU4*v|Lte^0dV& zEsR=rh$rzPgQz5nn5D+a4NnrwZY2FId35z?+dPRxx8+TSS5lXk_2>$`l)23jJ#&+- zcTsFX<h)d6mF<1;K}XBEFHKywI=82;GGq!<^P9A1Z+JGV?%pZ(;)Ifmq>=Og8#*0| zo0AlxKmPEWCsO{&C8|JAywKoszaz8%2A-$Sa@L3j$vR#=xS;b|@Dvu~o9n~<STl=v zR2f_Qx1`$~NxvDy``BykgM9b>JAIlCexJmu%+7LVZ=k>quUisA6J&p_N%$4ew^vkl z?n;K<$vba9IpMf;(RY`3X1ffZY|0l)KQLh($GIO%c+MqHHZwXOQe6F6vY>2H_miGV zHlN0sLiQ?moQ;hgedYxEhA5ozu`+UM@|m1wX>dV~>)gc69+jI7*4*t`5;@mL!gkij z4lZ`v^w_kd4Ov_Qw<f1dxvA1(;r?vFjn~m1bU&xgn=S5byM7kO1x5AOIfpvk9&w%b z6>(#;*v-2xwK%DY#rDCSWx@|#?Hwm>_+T2ia^nWS<7&<ZtKOW;=}z3Ck&*Ps{?9@C zKey|D{H_1-xc-OpuebM2xc>h;dH?^}`~QFM|8L*wAj~o2N&DID%TB(SqP{43!nC_m z4cbrSCVWlWXcx{fO+?Y+{;V{|sHxAl`$R4meId3>*51xvB<`>4Ej`B{tKW&8*WR(p zdqI--h5M21D>k^CU^dtiJ%5ephkz;94>)k})je1)Wq<EmmxzlE%fW3|E`Hdt@<Un$ z`xUW3-fN5IJ6u>Nvv{`2{>u_Vb_{}RcQ*JQs!>}KqWRcmzMy^J@;R^Ge*C-0KYaT4 z$-0JnHNE6kHrzh>!g6oS{fT>2mYu%YwBmN7z!^?Yso%~VR<jzF%6BNR9WRXGw^$q4 z$ROUik~4VHkL~tHdsNzQ#+NK+6=#_?(WHvM(Jvq|X#GTCVXJ=YFqtQ^+I)}uM2`nv z?~0AK2|Zc2M$GDEg#4lA+gfU|?Ogd+w}oywyiRm?S#E5Kb(rt&Z7+BAT`HZsefGvv zYP{BA=Xd4RUMtm3kM%#c=JlPcDp!+P&&S*Tb!iiySP@_;W&4+<c}+0ScAw8PYt z=*Kc-N-Qz+d~;}__8o=VUn-vS>?WQ%bcIjE;_GF0j;qQ-F&kSKo>R$ad9QBx=B04S zRo-CZ#HsQXoPKIfr;}&DSjeh;X2vs(gZ^8K-`wK-m-RBl`(3*K{1dFglP86&x!BsH z_e*j`j{3^4!Iv)je7Gz1QgV+=OvC&-rh?p-Z;yk87PE4iFZ+0!we0?z<z5wHXXZvN z+_t#w!in{Bc@Ouvcd^~&Y(Dto^&6%S2doacZn0Upv6cPXrRDQE0=9j>5V2&Ues{~m z-JN`~IqGJmKg`^g81)<|zZ@*<E3(c${^6P%;!{o-Uvb|MH{)#Nn#Hd6Ia;bqvUSf{ zJZ4;{vs##aLH?t3sq{<#Ry<rOsxgrzba8+~$pS~0V=c$GeB2Xc%jpw*_DaOlA4=vG z?zVp|PR*<9xv73Es&xWKzt(A1tyzkSp@ugk=GQeDoZH-YmEqX0EslK--9Htbte7}L z^tKD8g>keV6>reG+7#Hdpz{htTKI*PA-)Y-R-QkMejZ6ETYB=0+o9__6!MgGI-E_G zFmmpg*)Fq8CQ+}=v#V0VK-*2vQb~9Uo7FA#Uz-<xcqzQ`N@~<B%V@9G(;b;?dYp`f zz80O#)rTLeo?qB>=%BiVhcvU5iogB2mh>&ERR#f%6#_QjNjtH?=I@TZK1ZD%u{foj zIj_LfdnknG)sz_vMc5UarV75E_2K35l@lzS7%~-YT~zMeWtDiJH&4>qxaVM0^t20~ z5+@4wy>@tJ$F{uW9%Hdhh{N3Axk9tKZkSt~b)UhnbY%K<=Y)F}%AOMQl%i^cF8x1r zdHH;YzLtRbOqp^wJe&I09KW<Ryl7{xOrG$*2}$zo-JLBn3V!p?tK)CXnI98$bJu|j zH7C3G*DIasEjco6@vD1IoKngwKOFe=<n^LGiZw@BR9e<n{LAGj=jU}`SmmQ8;%|R4 zO7jF8lahF=SnT)1Ax918cr^(vd$5pG+#{5;Ekh`(M%*~1r^z6(;Hi3tNivgl;|ha{ zB-IrK(HY9zF*33}6`ofbUAA$u{m*I)z7V;HbwWU)T7WEDQIaa-pD&O9d;D)#U0!uR zeraaYS>CA{VV6=l{}~>hBXNh*Ej8+~n&pY54>k8`>9br?vP@J+I(Z>wdBH*+e^Iw% zJpLD-r%Pyho=`1NySOuPiMp`vLb0hHD~=x8dgvG<lS-w>jCN^z6-Cbum2FJlj~?9S z<f6;=&#Omde<455{{VrI_LrC6a~^orJ7;T`zd*-1?!&ID=2{ePsOFEfJ3QmQ#8W3f zLBB_f7~g*roZE9hQ!}WNrNY%Au|Qe4^_K6g2O5)ldE^#dT-jGB&N-{iBTPbPfo|i{ zhjXJ6Jw0}bE|@5~h{G*`<*`RHqYA6a`JRB_kg%R0VeiRG_m~#_2y)f!NfcCndD;I+ zQy^E~*Tphl4U<Ka_N_8hKXY)F-~+w=PbPX9bO&B*mgqSB<K=Oo5Z`3wkf{r5c$Yhu z{Cdpf%F4xWx>e-(k*d?JNBE9&9R5A6VbZDqOBX{^&zvc@I~VxR_;dXJ#BK}Dsmt#* zmAt)dk|wcsUcbuUV>9??vK-d<bc54eKmF-rYvZjSKP|aB({&<$%A~cfuh|1P<eoj) zyM5u_jVB*$lbm0a=Ft;k|8$BG(@Xj2FRkhlo>v%Tm{?xBNYwcK?b>9tT7*?rvpF)l z^@GX8mQ99>nM2O8)^#^HL>@P8ESAu}q3`4LVdArphKLVes&iFKOu2fF&d7J?%&V<X zH}Hsw*E_&`FGcr^!>sPa^~MSza$P>tXLd$a*?Nn-TEMl`f<akvUZb8W=NZAL??j4R zramydb5~d?<ILx-RIT1QN0%sgoa?mgkjb!k`CGy+U18m&md)LMCcl>T6<KpzdNy%R zR%`Zg`XI9A+(fB_z02=AE5uF}l798@jd!Q_>`N97uj3sYHyW(`^n|l*#wrc*uACPt z!j^Zh`A2!Z5aH5Yn&tDRd{x?l)gF=Q&aVFU$BeRc*d}<Ygmpgbv{9Ix*)A@BSa7yY z$bt5mE8fpPGq29SslP`qU8hxB<nYRsE{_)O(A}so@o1$Guj>yNxs^GyBs7)HoY+0| z6pl$rHhXlc39<F_EE5P|4p?9yAgVD@t%}`jX299BTastCh-50?I&<TZy6-#{r<^1u z13SU=S567DGn`a&7jfQoUCuGPbFrBchp^U$=fwx@y;Ki1_W#Hdc;eTl&DpW7m*daO zh(8V~lK#utj8bcK94Dp-a0G|ATHY&RIV^nIGVjA{S0krO2Ll}9jGNupD4i@2@qDGA z^g-xKkx`6|nv0j~j;B5=SKRHI@l}L7N6obFK!j@(YmB04Pv-N&%*S`ErgA#i9NZ`* z!7=|YdvMW`&Mo^)bn43rg@ojt9TsxR_Fmi^>~0Y&%ei@}wX1|)Y{Yzy1aBj;jtv`Y ze|55$Ei4EYGEu(RG*x1ffbwspmIJ)U%??eGz2@oGFEQ7KLnYC}Md=$)p$p6AnLX<S zgvuT^_uuXk65V`LPCt98e{i#>SW^02h9{d9QyK$Q_RcAq_E}<f)vuG6{cm+EyJ|dm zqW`m#xlz-9N%tk@k6|7+`Xsq3Wu2T@c3rt(=A#h)uq5`?n*4XO{wqq)_`D(eZ%eba zYrN>m)}xs_7JqI@xi0ADG*_tFJ}5|df=83`=N41LNVUdJr#IT3L0?pzeK?;z3|pt~ ztj@Kg`g~VMu&(!!mqF26)-!$8^5W?*UMZAtWTEB4%=J@Fq+b#-D*VqP8}4*nT*xU) z;d0PZb)MtDu1G)GH^W1Y!(T;Xqp$^gSRof*gPcwU!>tmfEs8(YE=<*O$kn>DDI~O^ zs4iahic9MZKG*E@$rFAu?09xiUEy_bP+;>D+eIqID{>1fUFRFC=Wb}{x_$cP%kRdG z&ntc{+1gPoP#mMHaBNNMCEMCdVM*dnPg?_gd(xMzQo5t}=SJ+2doO<YRzJLQyL>{g z{==ON-^6%UCa647dvNA8cgw?l0^5xy>Zu%Lnet35_vV^=!A(j^p_b2<U09T_(kT4p z_NRa+hTIzr_&uFZWTv!C+go+SXA46))8}cI?k*BYYpp72Q*>qDASM&bW2f^ni6wB^ zO8)6bzE=IRmCS3kRnK&c(2?eL>3r3dFq`SNODOAt(iir>SQ5(&1)qI!X}Z;VK&&v9 zb4^3D>N4viJ&KFwPcxpARMs?=)$cT?U98`imqqg`ThAw7Tsp=0C0}lf$j+-g54crg zU0=3oJ1{!WdBO8C#Hy58dr8$v=QB4ZdCxN0vflFCwmyRb;e;6m>dG9C&$%q0@$)Fh zoVl}C>^Z8u)47OWxx#pMrkS(*vy&_R(s!Ja(^+G@bxu=%6(eh}iD{FBm8I%Qp~({$ zd}e+AVcMlNKJ)674qM6_g#C+XnDm7sxZ|ejW=<a;k<K5pG}@=muTx@J8xs7mn&;0> zhONpE=M|JGITbR6U9hoyebuEyvFMSGd2CCR-^@upD(z*RVFi35jU9p}i9Xqd4f&sI zjvqM6`Rv%qoL%cx)${Z?RcAy5->Q?=R)5v+`Z<wNWR+fmtwHtj$zN2O;#wVqyb7kJ zGTL08u(Bti>DC!twaKjZ`ic)!*ZjDw9yD)(M0lrOo}zxhB_(sKzQ9b)MYdv3lb+>7 zdA$&JO!y}6QLrj6`quWM*Tz%3+#BpOHM@TrYU|n9x?WX|y&U6lXj{Om8Y|DllP(+T zi;i>tRy?dbYvE2ktDQ=pWwWL>at5ARIk|@=-HpjP;q=NrmENQi?=xDoPG%Od?b~=m zSx}ILOGIU}V1~Jo*sQHnRv*`6QHWb0`FT?qQ&Zq&=cxy{wAajNahl_B>`YBY!4~eC z4t-&k6+WL9E_QQo`r}$UzvIJ9zSQ+yIgQ8Fqg7UDpB7s*y&xlPrutz;C3(?QU6ol; z&VG{{j6Y0BdT{wY;}x|@iq^_Xss}%J=tX&YX><M9)WW24xYEr=Ddl~jVuH|NhKWq3 zd|O1czSRHK{<d7KJ1t9T#=_cbN<L90P7_KpF9@}VH5_c<v^eB4*N4$vStKg0>cs|a z)6xeurk9mf8-n_Nxcs>6|Aa~Gg|ede<P9-auJtZrvkip$8#<=(<X%28C3TWiS9e-g z%2GdzH!LeJ`CNXhKPT>pOOwhBx2_2OmmNZh?EBK&p3RuKV9DH0uc%p~>(tM3G=_0< zEOGOD@nZ7C$<;zxP3I@&PHen>b+)PG3{##@;!W?0r|wL;rOL@>liBCl+7hF1{@1bU z=x=>hViT^;_~dqPs@}|^H*LpXb?=M&^q$qna>wq-Nh>N>#mqb%u9xG~Ip5#HoA-iQ zypgNz#P;+B1{Ip2lQr#Q#1sGZPoMM9WWfb<@8qWRH{NzHWz!FEXO(}=Y7IJbYLT_d z@|mmFo{TXt44z)%$2a5b*_U&)a#||xH6HygcOXPr;Z}%wy4uqPk~tZxbpLmW-s9@s zow8~c-}%WBtL@%|1~2A2o@P~7ICG<r$NV1d^cF?GbH+#XzGQIhEAjYa&?w|5TA0G} zZ|;l*bu5)P+^*#+?OiR}c*d3MFSFYNj+L7tDh&V0%>338ER=lh-o)1vLbQ&3`Lj`Q z&-bI-;|wm(N-(*eDc7TGub#9`E=AE)eo6TD;>A`^QXZ-M9?@B{a)EK1%DownE^gG< z<+#1zpWW;QZu-jCB$nKf2tKpy#}+ZpL}Q8kX_HN6=pM*?+{+lZc1EIwSXQX}s&luW zd|tM6d!@Zl3d@9*t?w-wjqe^W?x-#0VEp_^<dx6V7Ya;A&Msk8xqP`}p_cqpz2@b4 zY*`6c{&6V3W3#d`mN0#%#um3{nV6DOIB&l~!NI1`1rl3NcdizEb*{;{^{wz_rzGC5 z3^O&w6$OodaXgSzTk_<Y<9f+C7XuC%w>(;XFwkyO)ALn*Y#Gg0CJLyTHC>e#yldec zUOwARO@QgJ-&dKk_=pq1rrR~ms=aE`mGZx>WSIToa#A{jjj`dyU7P=2aO-?<$v%{^ zZgSdB$!n$QKN%LbEVB6CIqy)3+L5!5>im?X=Q+l_ZL!FZ`obi=ZKCXw^r(V==0B#$ z{jNJV@q_Ot9yf7K4*~g?jL**Ze$hDS&Z=zXy`1lrRMK1Bobr^%#w{1M4!th@$)G0s zRJi7!m+K)9&QmLoeB8M>V(!hI)`zcOKUehdtf5#Pi-JzV94#yT8HFWGzZBvoiR7+4 z$+Tr+n1xQs66OQq(!NCpYG?h9WaDtp7J3l*c<QC5`+ctGb)L6w>f{jjh+~}ZK3Q{L zEpz{c2aNM>6|7PEvXE2xDNobl&JvmBN1iW9SDNy6?F{p27RoDUnxwlW@jWnJRIIt+ zzsd5q!Zw8smzHli{o8QQz1ezfKa^O%K0TMf(RWVr#_TVe0ya^b3=VBq%+P)KT4TP_ zg<USUlIs+kCRD{QFm-8UO5Ag#N~3b(PLB_FihstqX3U*GJ3zsmMeUaA2j-9oH~w6C zSY1}5`I^CA;RDl3Rr^}SOAm8eBHvhUwX?asf#cW=Mf>Jp*U54pYz>a7yx(~6)JkjT zF2nF*fqNcYObUUf1(RpEn{}wnn00v0vptW_d}o-WVYlXu&DXUJi&Q0k7jqVb_{`Is zb7bZL34=F23Xhc+<g$1&ymWu4H1Xc$md%E(Yf=`U@HpeRibW~uypZZzL7%Fm<r1y( z9*+u`GG{uvw7<DHd#c846O)(SmuE<rNcmVlIVS!jM^V|L=+c!kW1d~hUael+;M%M3 z>{-^LGJ~V)vp(uR`WeP@rE|i^%2&Hqd`c@QZK#mHS)5^%>e86=utUsZiC#tL&L2Nj zcf93t*M7Qi{yD+<iB-2h9XqVnXkw#K=ob{EXQiQOA0(@_wCeZHnA4y4IU4qG++*c- zSk832MN0kQiX@@$d78Jc7jK;2Qfeoj_I%sB3UM8gH4K}-EoTVZ`+WK&X}R+b%cq;o zI<u2YT$y)gjfJy@+6$JfrI$|L*w4jRzB1`Ms~*d$7srGrONG30DXF|IQ2Z$|D(&Gb zxd{%-jWSFn8bp(LZrQNuif}HtkY!-Dpq=-a%5<gQZVx2w_cc4(D~B~XS#7m3;nd3c z*4kds(P+*5?cNfVTZb#o_AF_Z)JQsatctDF@a(mfOV<Xz+_%6jAaKtN3C2170ut=W zQ$1%&m8h5$vSk}{9-DC8;keb&nSyl}b+ZeLLJR^ANza`8ktf*i@WQztF0Wfr-gx0i z=ky9irluWsLi5D9o*wbEJ8<(-pSOeKaigm{o*HgEt+;Q;x&y1b9e+o4>^?fZy;GC_ z?HP{DHNgVSNiu=+jveCk4RJZnxqx$DfuiFYV;8OsfejHd0>^#0pLZQGKT&X4U*hrX zLskdfOHA+Bskj_Iv0Sp_*9^8~#W{?}w-`GqPW|k^FuQJ*(UUhmI%_3%7iPzpnagtc zoz&FsX0Q{Mb3b$Nzt2%-iQr{M^P{-tyw;IQE|52#wc@1Ai=7_=QeFtndHSGX{#k>E z-<6JEnO-TOtm6N1+rqfXe`T5i(<XAAbvdiH;QL3_89z@qb3VOjW4O7*yU&0p`Pli1 zoN>-8eZNEoFE(hNvww4|!v^uLbrQ`NBy6^c<-~Sf;dxMXDBnfu<rm#gn+ub^YumX0 zQxx-f;2?SCwD^%?pR$ub&p$YHc%pw&MRtO{f2c}8o?CQ6r`k_RLleHtqND=H$F6Ax zCbdS}6y56m&l&Vwf3QJkzsKz*mS;G6S#(op@f)efOw()p-X)&i#2uGWIQtw+ii@7a z+bfZ=-{K07eSTZjqu0kL`{U&U8~=N~IeIn|o?Q8SseQo<|D<=<ZtkANS7>_T;5Wy( zN56yhZrr=O*=*mz%g0-%Z)QD|TxRt~;fbf67T=M=t-QXotE~@&r(ICpbCV;Zd)AeW zKfg;b6r5>4U~V3scjwiykRY#=<?Uv&9sETrJGxu0B%959>gzv|<9>zYjI`Yi+zxG< zZDg1HJkl%n;lPEms|*r*L<-q&v%Jq-_3&B4hw0Dm^Ud3LZrjU*SrT{m=+5T8C1QG> z<%-;13*HYUclU0WIbgNxt90e2-@GjEcE2dHd102V9e%i5`fhWb+}@^L6Cc;?-Zn*y zr=;f3y)6bl)faPPOsATq&B?vG!Lz*R(7_skcMaSNlapBGwM|}^B-rlS>z+O7fREvt zJ82TqS3j{V$+jrm9my`cE_#l8cYpAq%<>wYD|2)2TI|?%q;1pJsJ}Wnf9_sz{jxdn z@%zBX?+e+tzBE&{@whT0-^+nNv26MI=ebFl+s}VC`uI$};&!cT^INOBEwirLwuEb6 zQ8*a6rE`0H?KOqDk(V0teJ@x{D2XujnKk?2rn&AX^=t!m0#a{2o)W<D+hekb;{jWh zA0ov{HyYg5d_H_VuzgEk)Q1cKABmZ@f^Tdeead2Zc`ZTQ<=OOrU>1Ev=BR6rI$7c$ z)RoQaUgw<<puX7b)}e`F9OviGzvbBeeVOa^^p7*+9`3DbyqELrTi9xw>@^?$*n}rd z=iYERkT3m*8mom6#}+##&%ev<|8)QV6kdOe<==zr^}nv~zs~UQ$M^cj>hu3tCuW}0 zd&o7#Vo~n3Ty+b_C$EZ3xYR6<Z(~WGyQwu@@qn;$#eRc3=ZyX^<kUTApF1m}dFT3@ zjvQk9TrR)-(wMuVh&xy5M$w^XmZr`p4{Sg0zul)N<*So%M3-CV$r<mJ3@m$-WmVhU zQV)E+nQ<{kEpw6jggMeVl?#e;&Ey=#Zr7+pOi?<>Bz5*fZoz_|ZO$$UUJ2FP79N|E zaGAq4(6W|0MP^2$c4F5#b7t2=%?ZgTlM8fPX3rF?;!3x)m-a3zIoHy6Z?bX8mf5$i z%;`OUOhEkRk+YjODn@eTh^IG2nakVm_IcQpTaqJuHLBG1<}vk<TbHc3Kd@@<d#YMs zP?b4h)|nk2>r&WOGUZ>G?$fa3&C*~NRSDtQJ&MmvI|Y^)B@2s>$g~oVrPHuSsZT z%8PqmOYSnJ&oD6jaZcRgS>w&yEeUrUM3}iY$M~(=e~ZU}t4U48i238n4=;*ld+Tqv zEZ=!{ZfsU@<E}f;-aIv%9-CF1Sg`Z#OYhvgik%rYyY9@DeLDTtof{6tbN8E0w>B1z z%PXF~yH{=Jm1k<xy?fQVy>s<;-I?2ad-m#s9o4f<^OE(oQ#2V~vTc1`c6HVIbjhnv zA4S*f4AC%~e*4bTMfo*5zkAp0T&}q9&TFlmARM#DTTlG`q>oR{%ByysJv#mNoi|0Z zcgJYu=$C81d0O=QThYzWFQ3L)UOp`wTeb7bjaWU`!s*_Dn@;c5-gJEWt(dx<M{3J< zT3hdaaxwPZ${F!R#nX3}eLlVP*yq!Wj%h#jnf_wKpPb!laa_{58M?nWKiFw$)HE;1 z<nNi1_M7LV>KF^=?234h5WUN4*O^JrpJv`Ty6*nLlMA9>XiM=Ok8+*)z``nf$Hp^v zyFdJmD*OLZ{_l(Ux?k?||5^V1KYtDX6Z!dncFX@iU*EpL{?FV}iQ50o{{L%azs2wW z&Te11DX;7Cyt=#Jzbv=^)%$hQ`MMwF^Z$OhZuk4){(o=Q*KH5{^!NUsH^1vY-u(Xm z!Djov;qz<%f4BdA`F_@yzxpfA?*H>;echMaXU+a!w*T+H|K5t<+4KK}7SE~kssH&h z{{M^Z86o@szLZ}-{oj-NKbz(MfBXOGasB7Q;(-6ZHoyPhes#gV|LXt5f4yH{`|Yp& z;``E9|NpvfU;W9x_S=*C+>*cQ_P^H3|2?+6ekb4GkMIB8^#6bKKih`+f4=UIZ`a*5 z=ZWjRg_k0yU%0+`hxgK-&%Esa@4RbX|F1ZI|JP#s-<5Xu?`>-;{y%B_r*U1cy7d25 z?d$7eq+j^#E!)}@JF{=O_?yR4fA@SmCjBq|SLFZizqabfR{y<qK6d}VU*h+7{Co9P zzxLzS`Mb;hc3+RL-TS@xdfXAF0s+rkumAop{lEW{+1vN$>)*W+-{)}rE%)B&iAC$C zXO*>F<9xp5{YtsV90zUez9g9)==)J17JsMyeO>;t;PjkJ)wP^^i%VXAc=z#Puj+$o zbM=CZrPBA#P1yeZ)z<9zd-Y#$eSKZMx7ow~TK7F`GuzFZ?}cB<`4B2~QFPb3d!OXx zTmsM6pEz7(HTUI*Q=6``Tbby2pH)a-9PO`~s9(M-ebT(ml~Nh}k~3VEcHg_OY~$_M zXMN4y-MsQY`|}xd;oBE@d>`nRSG)9-^e1!v6>hWLH8ovrN&Kdbr(W34lIpTZ6w~!h zn0(=b`#JN&^2e;AeP`9pN-nXrUc)ASV}hLm%aWO?U2JF9u5O%L7`QVg)Ov4RlCkg$ z+f^ws`is<dF1^a}C**p&`(L(+MjQRsZ{cs>wN^L4RAyG`jLQP^Z+BE3EeY>!HG5#O z-zU_%a;n+Mps8ELe|MOQ?!21zBlgL**OOPC{iG;r*!^_+ZGWZZZ~bJqZhcVroo$w3 z>#_7|E6$ZJ#z6tUH=Zq7Kcnth*5Sp10<XTdg)E-2Ve{dfbJ1~0LFau-4`jH+OS^5D zBB-GmaB^o>&AyZE?`37b_r2fy`-3z)KVS8i-+$S^@2e~M{h{pr-29-2iSMuN{e8ct z;LFzUa^LRlE`R>*Ve{jA2k+iy+57v$!Hd@Ya`%)EoMu11Z|&m8(`z3dyqo`x@Bhia z?Cr;o@2%#qa({n+f3XX15I;ZPJ3AXIn|rTi@9r&qe(~+%-SM|y2P*H~{ax;oW9|PJ zilNmN`RiqFF8q-B{@(7r-}&Y5RQ3O}sd)b4+{MHBzwGYYR#i;@T|M2(=EYt6iVq)N zTw83-&d+t~!;6D|tB<$a&U^HB@#E-nzP@|*dum(V9Bsez?XRo&p7{R9-e&7^DdT<L zdv~|<XK=CJUE%+8uCvtpgVyDI-F%scw)e})+^afa9R0VfTWnHWW%~N^n*SfJ3I2T) zZS_aJ-DG_;`~5qQpDrxx=db@N7`lXO?``(?@-@B7c3w_w%~+jhTUGGGj(`5Nr#~)Q zALs9vv#YAO@%z@^>g|7T$;;dQuo67**u1*@o$T+U)g?7M-1pkp+*qyVcJ{)nK+#qI z&n<qeyuZ)ATwczu;=_x7hZk3umuJ4-@3`6}*>=JC{NJqm%dh*-7JGBAIsaGLio!VI z{p!_w^Y@f{r1Dqv{yio8VUo;cu6tDx>)mQEHQB8>dwqN9_Yc|Sef!EPzF$+kzT>!V z?c*0e3Z`%S(BbyOG;iAO|HlrRSAUoL`SSC%i!Zw3`?-472vncXFM9vt?!AL=*UNtt zImOSR+t=@9^2xj6_1+@xF!v{`UHVL)e_nL>xOf2LoK*=<+x^$G=vhu*w8i4dzdd=o z!}guBG!}jyEtP)r%dxAg%e)x;XRe=+QL=T-gLQl{JL3NxJQMJK-MsJjEce~r9ekkc z*(&V`b$$EmzPr7jy7+_J`g6`-<92_0s=xPJRe%28zu8B!YCG%pe!TTndVSr``QI`s z_J*%2RGr-PB;@g|tf_ID?Dbn?8de+pUTyz>=T+0X3*0T;HZN_f3YIUInY`p)b9w&P zxZh9p_ZKexGvj^v<X6J81#Z5-zjWP-P4boh&sJS-?`O9CYO?D^Q(o`awJ)YRi`;%X z%lq1=SDSR-Ji945@p-SoR@u4*%4_HByBPiPbnfiwk<vT0-O`==Lmun&ty^~Z!Bz8h z`#*B~vD^Iod;RoMEwPeXLtmR-i@SCk?T=r6VIe=~lDqx?o%z+OqPo}gYranB%GTH% z!O8BDzS?%>TG6w{C8=j$H7D&jZ*)QX`uZ6C{rjT+oc`!2d6$>}{_gVMPv)t|9N#;8 zZ{1Jre65h*w;nq0cM0jO6J5QhyrN+DnnU}$N;|r2bY6d0Uo_YK^tu<}8&ADGY~Aj% zXQ$W4)e+sEFB30L_#e8)MdiC*tW(U&KU>#4Gtb&@u{icy@5f0S_a%5%>q!Xn%_;u9 zPbK%p!Nu+G=k6&ks3|$^pSfTT=e6ZevhOUr{ouxVb(1;9%S)}U&X9W%C^yk&sr>H% zFS(kdX%}UhI_EF)Ow%$-Jg@tRv3FLa<1)9NtdPP*!s>p7y?lJ1CO-ae@E}+C-kyKI zq}RtzPW$!XL-rBTxiWYE{yI2U`PT93{Z8RRzAu*Y@B8}W*4DrIw)^b<Tzjm~`*Qlj zg7oj&`L(6*&r084G=KW~y4TJ5wLiX{&9DC+{qFE|u16)7#k_0gO59D}XDS(6u<G^v zvtJ){c{Ljru9D+^@qV>VR&4d#t*@`Iul@Kne0{ubHk<O{+}#eoU#A{87A|aCzEUo( zG_qo=!~@RHo5Lk0TIsxATm8N6>5B>1zrJF>UsRo6R}mVuvD|k_q_|hikB%Qt0#dsS z{5K~&e}DJsk~E!3%vUnc_EflCc~Lt_QBH4JReE`C!JN<Y)-}pK-?3aS&Qfw(>^(V- z$2-kGFsXL3{>hQeops+vOkC^G&g4HgdAJ;#Gzz|+eD^D2FVkvEk<-dc&EhwnuFIRY zYF(`0{4Tqf_hR$s=sgzR*AspF{N)ViYSxeO`I{W}U1RI^&)DMUepuQ(tR(uyvxK=} zAD3-g@4K^8dG5#b9o44=4Os6OEZ-QWGI3}6&6ZD-d!rqeRAjhSM){<!3KCM>-1~Qm zaFLbvzPkoX4}?d2i=R^Mt|q!F^O)h))$?cPi=MeON!Y?tLu;<g?Sw^}|HZwn&azzS z=l3Jh>GZU~|9`(`f3@1WDM~&&w4-V+Pteh`yZ>$SI{SFlhh2*#Zys<{S9QLk&*nYn z;H?>KKMOn-m$n((>r3zccPu;pU(()L{8wxxBm5rU&;GXa-s{IlPu|#{5E8xUpWKz1 zw@R{QXJzi%Dw}ld*+0z<->iC;svmv(=IXVhg^{uAUKbaf)1M_7^mvy<!-joZqpp@j zXR&6zh_8w|t9)`XN4Br1^hC4kT{#O@tkb)`ZmHP%lO=P0J+Hl<b@i3#oI{+kT36n4 zF0Xo4<t)1M*fg=Mg_A?tRpeRZo@R+oznb*5Vy)lB_pe`jOp$V`U%7PAw32J@H5MsX zxh?6|f7<mR{%Ll4R-}>4YLzG7?|olkbD4AX+#hc7LZ9_Q`m8OV*qL2uU9n1eV%yTW z7nVPZv;XFk_4}&Y7XK}aX4afo68*sXldHGNTiIVphY##@-1lke>VFODhsu{dzqC6b zC};kC?cL|&^VDvgUiZV~%csng&=*zKd%_FlQrnE{V^W_!v(t|-Tu`+8u5s++(96>7 z!2vH-)jbw%yS{pr!s*a0YE##i>k9q;_Ijg+_<Do$vGamvyMFHz-OTF!yh>&Dxvg=F ze*Y?4!@n@&#s0vTZQN_uIBFl#(1>5rl2Yjq^3Uu2G%tzO)6#n@X6>D1{c+c$gty<m zX4foL{4C>gu1NaGMnQFpkS+UdlQtB;?SH0hHS;&SvYpq_-1~q2td<k}`CaI%ZT3{R z_20NQ%1LfG#=dv!d@;W4pS|-B{Zi)@Yj%IT^<-$U=>D4#Uupydt*y@p>vw$j^J~gV zET8dP+dLtk`T4f+zGWxd-b{0Ua9jJ(C$9dC|7%74gX=c`ezvMl@>^NlrH>}jKN#P< zyCxlx-*RpHis0XWb{BacePM3;ZQuHhYc$PwuKG96y;(Lb_fqZA85i$o8-#_2d(Da4 zU!J=6@z(kO&Z+gvOrJYt#ot%&wk><Nzx=;e=*vKDRe4wIz;n-bSM>EsK8-X;<h>`9 zd5zOE^n1pRHI`w0nt`uc^_Dg7mHjtaf9k#G)uQd2Pk)t_Hd@rdtGu%Bt&jf4oi?*f z&a7FGdFE?{-G8sW>n{k^CSE=jan?lE|Cv?Q8P*#u(`wgeot=Gg)w~%}0nL9`|IJgM z+M@7r$}+3dmt8J=6g_&=Vr%hr9lM>$o?Nl76wK%Eu#!96m?&cQZiz(Z?MjXQmGktU z?v!;ASQk3)%G_1AdZk;9-afhb@!?5pe!jfFryd?o-uKPhM4#_D`|CHq+jqb2&9|zl z%Mg9IuzJfYiDwV4U-VDB?8J5X)&2Dn4f8Z7xP0Z>+p%9@<z}no;J<6iH|6|4*6a1U z;KPIL{u_LkGgpPLuQFMBH7Po~Q16f9j$Jk!`Rnz0)}Q_tedMUER{w>*Eo(!K&8xG& zmcKgu!E|-pE7SMW4c6>cNsJ5dyE?~?({y%E`Lx=1@76}u?%p1kUb!P*pqXv$?|-qm zX|GGABrd7z73W^i+0`xaPWtQ9l4a(>&mY-bDHZ>>+~(B2AZy#22iZGKW6yReD8yz2 zE;Ql$xx(T}#evt&pOoJ_tPJk7%uvps?sc-_d<BcD{)3H|cX)~JoO;f5T|@+rmB;(* zQDw=q-4%n2Lo@R8=UiSAka*kV>f*@}sRt{TCnTwezMFLQ=3X1Eub%=}u8F<BEAId8 z)cE^v_y4(bGNd!v+iu;PUz5&t^z?Wy5x&|~;o-$|v&?Ur^8K<9rpUM3HjA8Jr7yRx za`KubUhk_Gr=4=i5_l;c`gQ%2i_ur#@4XkU^m*Ef?A!0BIy`f|QJ=9qqW$jrn5F)+ zcsWjOt9|xrGSlK})jQUTncvz9*SMbh;<3D_xhXAg|Mv@NDtc;_HVYCro({M<B`Pn~ z;Zn#lwO^-J?d7$1c~O1c=J|{paVbfY-(K~5r*ZYv`T8}J{LVhqnz8WtGc*6t3kL7L z?=!r1?`*r}>6anG+kYNx|HrStenLW4MX-1Iyt#~mqB{#64R^+W5RcdQ-G21jyN#PZ zMa}<PyXRWb=Q8f9Gk@<{%iTy$Xf@umWlL9LVkZCI-~9j2aVu@tSz&iw?C|vUR|4a1 zKQawfJx~>0u>GyQo<#0?p~q{zN*A16vtd>L=fJhnQq{}Fio<`2gop{+r=<P$4SO>w z%=5LIY3a`2dw=u1nd)13^I5K7$=+VG9XZ@qT&Eta64ZaJQ@LBudFATv>rs)PYGtD$ zgEiJ~4Py9SrQ&O78?n2@YwBlluG@3WRrB9?2e_<Hj0?Q>IdxrmjZcg*XMJn;l6xgA ztarag9d3=i(%rB<N9OXxJ+Eg@Zn1vZQuej6Las5R>G-sy`DXlU7aFK<I-9I3W&L5D zf?Bjl){(1PA6SoSU(Bqyr#~_E%_H-Omv`SvU%qht{r9};rDoRKdUBt<^j>&vN#MPx zwQkEFuJzx<Jk8_`)9<5og8x<~&GgFbVt=~&(?6Y$DbbI4vzZqD77Eb0Uz5}Han-~0 zS+8Pt&hv{9yH)gorTVDUxlgetFH64II7@Q&@7F*6K3;tI-s^c6_Fv4jP}9^@JH24) zagDigx2^_l_Au>QSdz8=q2i{Q54fgH`<iWjKIaTuv8ZeJq=Q;{=h&~RM^_2-nEc-R z{c5uC^TOWl$gq#o123LW6VjbgcCF3sV%4oD*K>njXQWN}&UPa9{nqU3*75o4W2?7r zf83j=;`!p#w_t@%$r&BzCmg?csyKCf@a8LRi_dec&V2KReQWTI-P=lpZueyvCVZT* zWA5E)e}A32D(Zb_RaW$>wb4I5HC?;paXlyQt-bZ%um0TC*SMaE{`ea!<DVmUdDfab zsrd%VpSjE{^mRq9dOr=iWN<vTD<t-n)$-ENuz5a0*7w(0uMv6q_2MLsXrILa_qMMR z<<5O@Yq`c3p+_068C&D}=i0_=ZVAqxy7%DpO~>ZjuVTA#MXq+cUU%L0<ujM(n7sPh zRrf$$c(299{@&9vKKs}0zaz?CmKS;N^ymJryO~Fmh2FeYo^<p#cgGK3!~Ke{u7~Zr z@^BsRbLVOQ{<5vf=F(fl{^9JznAld=l4*M`3T&%e_HjjM`}PBc(Q;R>rcZV3n%Sul zCGTnNyYl3pt1s94{XKD}<kO;aUo!r#$gw#bb7}WUsgU(sav%NNb!kaNyZLPPoWqT` zswSLUFYvmeA+9CPCu-@VJvBER54*_l6?;1$^j<4_IPy=ld)`9<kxjAHycef#zkT#d z-Q0@V4d%>m*4`BTRr_oAi=<4ZxJu=dHzFkqS8fbDyn4FBbhm$Jm(>5Ayv*7A?oFN8 zs?Q-jRfehyzj^IicYWQ0OX5=n-rlZXy6=r{N15QJRZ+DHuV<ZZy0_7H+Wv*fZ%(D& zSnWR{wJYmOwF=YA5dHt_qLj5lUoq}js=KQ}d+n>ZlXqEP#@r3w9C|(aS9YFN`^#DH z)4GeUF9}I@O|Xc`Ur>FkqHcbRpW0Ga=Bu2q#p~~B-1K<ly_)TGRZY|SFMX4Y#0;Ol zINXqWX42m%<$q~Y1k^>}P5s!p*CSQiUWQ*Y>h11NNuQU!DXP)#F1xwK{7U%zkg)fw zHeNdO?)}C|^GpAA#0pMt`ntF4Zv2f)lXUdYe?7UY=z*5@vIsAise9+L+MNB?tKcsi zJ6+J?#+|1h-2c};)jz%J>zUW>hZp%ywwP-%`RDuB4_$QFOmF|@@_yj`@v-QkM$`R0 z<!kp|tae}e_pbTPbx*Xa_NGYfaN8f{P$v4|l=Q6K$J<}ZmVVRWowwC2B0zuA{O&U{ zf8)=7KK}RaIp5!}-Zt+oUoVr|$eFj7Q9kolf#~m3SAP}di9g6vPgq}=Q}*lq+PSm& zK52W`Xdhvg&nXnS@0q1u7Mqprm0f+?eM8-ZSLzvW86HlEkchj?x7LJX<vcg72Q%W& zzLMSkY6*woJv*)AlOs<ZY2|Ddb&vE^j$1R^t1~WJ=*}toHM8FOz7M?P`s|Kcc9mpt z5XX!~Jl8+;%{(z_-iz&DPqn0PkSYtFk@ei^*6bzEu3dM^*%F$OJ-s|Ur*7l3`TuR^ zx2%la^zyHCZRI-|>-z#bwXeNY3A;SkPB+%4#n*nG+nYO%yl=mr`nCRD-b91WeS5Ym zF^ijAum5;zwZ+GEY}Jyp_4J;Fr~Q`NdRu3vP5i{V+JoKPTPh2ME=HfxzL(v(dZ~(M z#N?YN&aDo<^Tyx7tT43rRsV-o7L2M}^<0<THLY(tdpu*Vipbrq@(;W26+BpVJoM0S zTgK_D+oJqSnsZ9tPM>{$;@7Z4ak{0U$F{ByeQ$j~Qm|XMCGBspXY*&DlGPXQOr5=F z#vAin_j0VC?vfFo^fg=lsr2SEJ0Hs4FaNbQIkF(IG;hKF-Qo7zz4My*d+nKOiVksm z85}JviEf=yl=b#?p+J>*k5N;xZCJp!Lu<p<zLeelWu5-nA2r3(XNGvqyZezdYSjkr zZAp7SPw#fi+jHm9+d7jeR*U4O%`>WADs*A{2}|D_PZeUi1bF$IZ_C&Prh9uQhaK$s z*}=Xo{({e*=ggn&|6VxOHF;U}%4^bQtFML4vN@Eo>!#1DSk=FqYMf47DiHo=(yx6b zxn<vjSn(fKpC0k}S0xtb`zy+L*iJtlcIwKdsps2nK5hE4dTCkswpC51RBx@}(bBdx zpSPap%*WVGzc$WccX?sL*T8jnQC{;=fy(`-g!cz+o?5#lt8JQT$MK{mD_xd7&0JqG zKgcTnrs6lV9mkXHQ~w+Ydm6lxed)TX9I5})w0FKav|FU{Y<1}2^OI{et9Be*<NfjV zf8}o-T0t+;@_B_<b+3q@^4<PU?e6RE@4wh8`uWuUo3FQ5nT9(RMAqkD{W$e$RJ@Np zd*6<&KekJ?rX&R}o2nHfwQL&irA+<M))ou3pI5|1I&wC3wU+&yQroj?TU$=*y_;WI zw5}FUZu!za_mxxTzNyD_7Jt~cdEd_W;fgsjJ=b5$hRxbgx<BzCckA+ZwZ}SCKL7Yz zx#DMbNb1!ZV{LP{%g@hP9F@B9)HLYE($xn=*RDNU*Zd|n^6l&{-F*|LDc5Y=w28mq zOJPq-!?C^5x|h#B>dI7{_;r@b6T$ZtLAh}kC4xF1?Y`8SFPX71ak=@-%+otgFO{0} zzizX(`Tl!tKi-BZou2)4X5;M8VrRFB`Ag0$U2?T)+eLo9WoN%_K00NkhxZ+4(><$} z^Zz=W`QN#*uKab%=CjqV-C6N(uUhNht}GVLznf96Td{T5ky}h2`X?rFPg(o&^4-oo zIjKutGal>>-YAy;A~`4jUXI1)`hTBazrLPe7N-47b=}uPMK8;;6MY{k?ta$#$m{(k zLER2b#hPBP#b>V`|NZ%A&Hdw?!kYtk^=;M2S#=>@{<HZBUy*qg@p^Mk&D(zRtNs1y zEf<@#MCPyIIrTd3lC;acB7u3Wnny3s{dYCK=I`A6*8N|)*Wce)IkkG<$r*1tgUmB$ znBG2mbeoUhHE9t`;lHygCZD!{J)JYR%374~-=VM9<EtN^-hCsXvtx_uYR7+%rt?+i z{9j{d*4beDyN^rozOMHUO~aeFR<4*dd8-f4&3#+fmz$mXUg{Eg{L9n(Ydb#r?1^2i zy0?sR3-_L>-fQB6{cZ|hYkn)e<ayxK@X6nrWYx1x_nON+FRZ@rzVTS=($!py>yvh8 zAADR_Y<bcD>E}I5Ds$o|n*HuN^Js2S+~u8Kf7<<~&-C>EQfS(L?UGe=f%{>t$uXK4 zVzI^5s{ALIo*D%@Sx(fxJJUzKc;>m&PrIKV&iLjP>^f0DasQne3oi!lm}|aP$NL^% z^wwaD7e(3C(K}C?hHc98{ls_jZE{@EhO6JEe!H2b$LgpNAJ~yPsiX1PrleP^9iEFH zD4wMA@5J|ApS$8e?rwF6JKML{Jfq0eZp*~hrLJyz>M!|EsrfIN9#(Z{mAB)j`^WCB zb&HO>xb@v_iCvRc)oz&et$p?$=Z@Q}Ro7&0Xq>d$-E^7KSMQ_NSHvf?T-&;T`)c{T zD__qa4mI{+_<3Dw!jiJ7H&O&|--(rUDZ6n0ZD*Y3YJ){zD}OQ-rv1x`u2Wj`<MxS_ z_adA-dGqvj|NOlYDkbutn}1zG^2)8H-$J)>>-lTnW@}#?cF@Wy??G|fX}OhA=6Y$? zeDwuy53By;v#<E-d{OA2_Q!+mc5ff21*T-~d-%$H`VaFz4}P`R)xWrZ_Q$rqY?B?O zHpvYQ$(J<#r2PzJsPeq}ciOL4Uyn{G?CQ4Kxq05>Ex#Wdq}ElIPSO6V=-fT|aqZ5E zrs}H)KU4}-@Xh@!TOuUs$rPEtp?OWf<cg?ScNO;Jh`Og<`<7&7bpGDj^o*qE4@wUo zn|kh@nyw(z-dR>V>hse7%a%2)`f2wz?nLJ#y+^-A-@je1`_nsTuS>Mv1-1PB2c<NE zAEbZTu%hNeg5SN`MYBT+>QYaYoq4N}7BA4>{bjDsstp!lOEa=v??fq`*Ufk&eQ)K} z_|(rXZz6J|H>#}OGk;HWXkTDp{ze_$o?oZ!Qch02{v>ir;ORz@?@nLSe>dH`cC|^( z_I>ubKcAlJX0OoCk5pID_;_*0mZ!1Bvm@5Zlz(~nZuawge2G)vN0oU#4^_)tH8Xre z>dPM&dlxQgxxL40t#RD64{LqPyY%*nzdP@1HZ5bO&&HV>)}C1N;1yHx-BWLm?tBzG zRlz^l@9wjj9ZO}sKFOZmbCm1HrKd96x%av*bHCXT9c$}4cb4elyG0wk-bBR8Bp%E? z_1$f!`s^Uvb<3-DqH?F`3ES=4B$Yh*re^i33rl7z?JWHrCOPT$cYoH#vaMxJ8@=3i zO$+(lHD_P)R^`@yk-O8+<@ntXTsrp!^Zz~fgAcDU_^K~l81Y`=)XfEdWxiRt2K<uW zKYiJZ;1x4}O-<bPw(&@9-EB9SSko<`tn+{N|JCSf^j!OKG5gZnUhz4-u90!B2Th;P z5nd~^Z|Cb?<ELC}KJHq7>FZ9j<IB3{6u)-Ax%%2UOP5FDZhvYnZ}{M^X>@C=%8dxV z&G*~1zpBpZ&n<bVc+GzOv0GMqlvvyO5~r_zxVET7WOew`rMBPI)~{YGx8}LvO%s<J zIWkGbtGl=D?%!x2x$axl&fU@<Hb~t|Jm|8{ZRh!LxxTvf<|j6%%9pGPpZDl?{i+zI z=A%LSwGp?Rx9QHUaJyDMJ$aw<qnrABitc{jA;)!Zcdo@kUiq5P*c(^9BjawL;^FGw zyx?Zsy?vLe@3T!#eWg-XusSNbcA8F?e~fDIw);0;+tq%_S5C>wt37e0_tm5?^OF9# z-O1Y4qH|`gT*GPZUEV&4%2)FzJ~EHeDgL+3n!WDwGrM{1i}y`C=zH>#=>h|Hk@E+2 z<Qo!0Zcb6T(S7@=yX%SIv)dlozRYoIK7FZ`-_N5-gTFNOtA_EhpX;aZesTAx<?Ail zdu*T7e7UE3Z2Nm&i`3tjL&OUOwyg2)-ygz%_A!@mnDWzmz7LgVe%XA={iw44JcG5| z9a~GQZS&@GI9F!~@~=5DGoq(gC2*4A^h2q}b;~5a-dQ$ztyW(z_y44t&{dY9JLhj- zrnvUi$#WWYKc^QSNxi%G()*>M$<ZnkR!OH!kN@Uxc5~J0!$q6lMlyf9bej3S^cJbA za`tz#<m3Ops@E>x__^_xz4eX%%ddZ{|9Lq6R&DFq$8Wp0c-?u#@0F+)XPh+u!uO-A zi@(d&e17ud!@q;g%)ho?)383cHM0M<%*FY&@p^9>U+fj=X5YRq_Un&W|0C_w_#T{= ze&fHkuj9SE?C0)6^?3UopI-$0`*-tEo>at<;I*la`GFaGxBOXj_4TU-w^l!&`MT_E zQEaM0P2ftE@a}6q()DurrxIqy@6&vFy{ks5^;1;J|9t=R+U)+KOSaWK&zaf2Z{GCf zNsS$ccZAkGYE{kK{)r?0e8iGN0`uNw9y$7rYd!1iE4A07Pt<wK{dqs7ucv*g-I|wC z`=2Mjit@d`R`YC4LGIFBJ|&emw11W^vrd`5GUi~s<=2}(ZYTe~_R@QQ>Z-qCO`A_< zr~Ry){A_Jz$zH2-k3DWrjM=vN>dQHWvt|ixPW`d>!>xUn^K>6ZuHSj%G^eGN`CZk* zLq9eBo;vO`nrV={Qu}RGuGIPd7jv%nZ@l!&aSqpVtyF#Cs244MuH8E>l!av{9++*k z!^BBr-`#onOV;e{bSkRxi@g|V@#4x|)9Wd_bN?u|)t{(&7If;io4Sx@>ies!_a;v* zShn%%;?mPTw|v!)PILMAy5r!!*{gs3&X+7VC@?#<`Du3U*I8<sy_sKE9KUsai|=u} zpGVKV%bO~>|9su_K3(rW-t~)vU;OmH`pbEaOu&<~imT@I<mW%HDF16Y+0LwF>Gsb$ zr^Em4@h#F@Si8S`@%N(tseN55bWff1OzY=1n?KWM)duaaxAlvZZ0r4)d)n9Ll<Kap zRXyJ@JNc->+imHezVma;tC@1!XZK_ie+%Bii8?KMmrwrkcKG+Vt8kuA&9m-bftu2@ zmrXOhv1L~NH;Y4A6Z)(6e>i%5|BqW!@8zBr%KW%&kL5zcOI5{g)1r>P&ae5{eSMwD zJ9GPe@m~)sfAYUS*F#xs(u>{ORvoRqvrC~QhdFlHj3@s$OupSTq2%Re@55c}OATG@ z&shk}>|iPsUm3r2>#-G4OD11Bdg19G$-50(HS1rVzW)7G{QA9xZ@;oBg{++~@~+hS z@2bcD^p1PK*>mgnpCZfYso^sM=7v08xK~^!G%#Z2os7W82RqA6eL_lt&I-j$I^tFP z_|3oR`~UpXe;=Q3ZnIf#v1P+jzHe#G+tXV74t=uVWZM#VU{Yp&`qYXyQ@?sV`g)ef zqA=MmRoH8#%%_i!t%FbJN3C4)zDl3p_t2jAr`NcjoW4*?h^g$j;d!&X`lZXa@$c|{ z)V03$QrF><y_HFCMSEXQk1yufrF;6Oc2)7O4O!f`-+#R1+OtkdtgiZAZch65tf!@~ zkIp){=-JoP?I};M-<)Q#{-NjYqHnSN2cs-cm`UH5aHyqls_LRgxgmCP@8$Q_-&nnS zPoAmx8k3ea0-rsjxXvcnT4kR<z4>Xz>>SU9tJelS6VZz;O@F>iczXNi_)`0IPZyNW zUUSh{<J`3KlXWHKOX5<q*T%29lvozLA^b3FiMaAUn;$}x-|6nHKYd_ts7q3-^{<at z&VOXx;}-aNu?X+A*2Q|cam91*eY^9!cTwvy#p<0;v)4IJdp6~*wV#=&dF3?GS22^{ z-s63`e2?b(6d7B`;-6ag?cY4R_<e%#++}8|eV=ws$$7kVZrgYD?EwO9Li2yUblA12 zW`5KZUF*zeaf*(+B%<HARllwN{ouocZzmr%vwxSfDL#<@+Djx`Ro8u{c5<@87wxLz z?f)i4$vRa&ou#+p^qjU{_XXW|-@SR}cKhU!IsE(7U-h27yZ6lDm{{pGE0nC9WiPI* z{nmS@%to6({{32C+iNy;ANkhUW$kB{U$&-n@$97$FTTEZxw?M&u59<a*Ft7B{y8mi zb@hGiZJP~#tVvz8LW=9F$<@a5X5yFs&6~2XMSW)d+iKUVr+@J8PU)|C)xPAx^l5Bz zyH4G?_i5>gvaI0Q3%5W1?DVo=b#P$ym7=}JR(;&IdsWT4J@4e%f4r^rRsC3dBO+$K z&ffZp*XOcIuYcVCDs3g}7u`7qwy(EGbayGo%k2BL_>~dofvR~4det{8zrQ&5@Z#O_ z_j5m-dXu+Y#Ob)w!4KMnn<E6{BY$iX%e&m0azW_*nhizb7dJiA)6%avxuxj;y!r;- z2DRV2b2FPh26S9@ytdu0W4pObtMpN$bsfxq-@n~n_b%Pu{`b$D=g-&Gef#`><^2EA V3=9km|Nk>H2s}BI!Jxy=0090Up-TV& literal 0 HcmV?d00001 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 GIT binary patch literal 8488 zcmb2|=3oE;CgwM`+10l!uKh0ktZibVdqMG;*rX{jQIjrAOD~RAFur^?rGls9NbrH4 z+*v8x@8176uUO^grj3m;%g(>someh);L^p57k9q)@HulY{d1S}_Qen5|E&q%7;qyZ zKD|D%CpYiH{YPm_J?4Mh8hBG@L(;}QaVv7d9<;`<oxb4i=hx@!-|lIhnjXL7an!d8 z^H38m&11)Hcjx!6FAIMD_Iq~p%I($K*1`wXeg2hhk(=#iDC(f0$kJe0RvvtPp83jW zpM~?Rjy<VL>e)H{ef8fxESto(tgTx1yV8%pYEKx?t^2#C-T(FM*|8m((%Xus>Wgw6 z{iA=}eeFF{`I*_*ZhSpc*zc@=ebufnPbc0rwsk6fn2?jm{rKA3&8o|G`bf7-y%n8k zom_n6yWfuMmicm1%RYS(-1jba_G3f&cP|afKD~<0c;M%*e0k?>Yl&IwPP1)eUd_7N zXkXGZi486FZ9f<nNLg22mH72_=k4M(oI<JZ&urWu@uBtpRk;NA>5<9j)9#sWm$|mN zam_UIr^)=YEA>tbWLEaY-d;DoS$}W#vAy#qo=r?IJ9_+%;-cpqqV2oCedum^_d(I- z*P%=a2cP3?6Qqka^Vh3}^7$U<lfCh*Afxd3a^BlBcip-9`tjv^e)H@eY~OI_T0q_3 zbLumHh|QmED^vNtZ?|>jPr0@;rwx^p<zuYH&fJzUez0!h)$fZA?|$|A#E(zE*%dCB z+wKe365-Bny%fb+(YUXHeI6_Gbg!#l&t|RMG<kXdhpkhbn0QN`E<635%k8GdyC+xO zrAm`7UT2jMx%a5U@w}|S%yp()_Usmzq4)hvq{gFYR+slXjki?QyeTp(Jv1rH|3P;3 zt1lLwYjU1F+nrbMTj%i3@9o>|eC1b?g}84&es?c>Tm6AueCGDyk(2x1=<hin^KP!t z;gn50JFfgmVxH`Cyw+~Ru@5^=uWw{d@R;_}Z~cw#N5_smt15eHTAhFQ(Bs;Vcf(Tm zo|1a3|4hKjpK0?6w`b`op{J9tWhCuhcU87XrftgUf)jZ<558@-PJiX*m>1F&T9#%l zcVGvzNyAS`dEY(O&#L$i%*l;kx5>Hs	KeZ5ywrpYuC^tM~F`ztweN+1G9@KkK)w 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 literal 0 HcmV?d00001 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 GIT binary patch literal 7565 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�ro9tk;*nu;*vI!2io{bsq1RS3UXJ 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 literal 0 HcmV?d00001 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 GIT binary patch literal 5633 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 T$iVRbKQluT_gXH78U_XcD%BIP literal 0 HcmV?d00001 diff --git a/lib/php/HTTP/Request2.php b/lib/php/HTTP/Request2.php new file mode 100644 index 0000000..97903f7 --- /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 0000000..ca25abf --- /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 0000000..eeec8cb --- /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 0000000..8e761c8 --- /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 0000000..5b4dc54 --- /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 0000000..3d5cf97 --- /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 0000000..1bae2f1 --- /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 0000000..57bc5d6 --- /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 0000000..237563d --- /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 0000000..35a5ad8 --- /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 0000000..bbc9f12 --- /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 0000000..6de5c28 --- /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 0000000..b26f5a0 --- /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 0000000..d050a6d --- /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 0000000..7879522 --- /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 0000000..e32d665 --- /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 0000000..225a759 --- /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 0000000..3ab250c --- /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 0000000..e556041 --- /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 0000000..b345c77 --- /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 0000000..1b0e97f --- /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 0000000..a2a403d --- /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 0000000..a73d4d1 --- /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 0000000..c2f441b --- /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 0000000..6f2cd0c --- /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 0000000..8a759b3 --- /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 0000000..bd3341f --- /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 0000000..0412ca4 --- /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 0000000..50b22ff --- /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 0000000..ae6aa96 --- /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 0000000..976aae3 --- /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 0000000..05fc508 --- /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 0000000..06b7a24 --- /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 0000000..3b2c494 --- /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 0000000..14db863 --- /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 0000000..71ca6e6 --- /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 0000000..0d70907 --- /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 0000000..4529c07 --- /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 0000000..585800f --- /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 0000000..a4898f1 --- /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 0000000..10fc934 --- /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 0000000..bbd2bf6 --- /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 GIT binary patch literal 16338 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 literal 0 HcmV?d00001 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 0000000..c50dae1 --- /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 GIT binary patch literal 43 ucmZ?wbhEHbWMp7uX!y@?;J^U}1_s5SEQ|~c3=BFT0wlx0#N@)rU=0A%AqP7E literal 0 HcmV?d00001 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 0000000..273c1a9 --- /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 0000000..8f0ab6b --- /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 GIT binary patch literal 1654 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 J_U*2(wE)T#0{Z{} literal 0 HcmV?d00001 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 GIT binary patch literal 1672 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 b{8oMR6aL%#z%k7$<rlm4ytN6Fco-M}RtE+f literal 0 HcmV?d00001 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 GIT binary patch literal 221 zcmeYW2?@|Q)H75tGB8l^_vYntNi0dVQgAEE)KM@nRPaj7Q!p|xG*B=wvNABXGB8na z_YL9Y3QjF5OD(cea4bkn&Pdfa)-%vERL}^`%dF7k<%-Y^$S+SVN=?yqs<cuF@CeX1 z(=*aD;^lJA&nrpIE71+9EJ(FdC`qj-(J#nJ%*?Y^NX|$sDo!o2%`DE>wXiU?)HOt? zcFjx9Psz+nw^B&2$}9kj=cOi>Waj5tDJ19Q7pL-a@p8#`b8u8ARk<@T8^;?M>XlTK JFl-I;1pr2%Kh*#L literal 0 HcmV?d00001 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 0000000..f60787b --- /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 0000000..383a785 --- /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 0000000..5ab966c --- /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 0000000..463f25a --- /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 0000000..334a132 --- /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 0000000..7037d88 --- /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 0000000..e6a1d26 --- /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 0000000..f7a7f81 --- /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 0000000..bece103 --- /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 0000000..f99d871 --- /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 0000000..7a124de --- /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 -- GitLab