.gitignore000064400000000206151742731020006534 0ustar00phpunit.xml composer.phar composer.lock composer-test.lock vendor/ build/artifacts/ artifacts/ docs/_build docs/*.pyc .idea .DS_STORE .travis.yml000064400000001634151742731020006663 0ustar00language: php php: - 5.4 - 5.5 - 5.6 - hhvm before_script: - curl --version - pear config-set php_ini ~/.phpenv/versions/`php -r 'echo phpversion();'`/etc/php.ini || echo 'Error modifying PEAR' - pecl install uri_template || echo 'Error installing uri_template' - composer self-update - composer install --no-interaction --prefer-source --dev - ~/.nvm/nvm.sh install v0.6.14 - ~/.nvm/nvm.sh run v0.6.14 script: make test matrix: allow_failures: - php: hhvm fast_finish: true before_deploy: - make package deploy: provider: releases api_key: secure: UpypqlYgsU68QT/x40YzhHXvzWjFwCNo9d+G8KAdm7U9+blFfcWhV1aMdzugvPMl6woXgvJj7qHq5tAL4v6oswCORhpSBfLgOQVFaica5LiHsvWlAedOhxGmnJqMTwuepjBCxXhs3+I8Kof1n4oUL9gKytXjOVCX/f7XU1HiinU= file: - build/artifacts/guzzle.phar - build/artifacts/guzzle.zip on: repo: guzzle/guzzle tags: true all_branches: true php: 5.4 CHANGELOG.md000064400000170425151742731020006370 0ustar00# CHANGELOG ## 5.0.3 - 2014-11-03 This change updates query strings so that they are treated as un-encoded values by default where the value represents an un-encoded value to send over the wire. A Query object then encodes the value before sending over the wire. This means that even value query string values (e.g., ":") are url encoded. This makes the Query class match PHP's http_build_query function. However, if you want to send requests over the wire using valid query string characters that do not need to be encoded, then you can provide a string to Url::setQuery() and pass true as the second argument to specify that the query string is a raw string that should not be parsed or encoded (unless a call to getQuery() is subsequently made, forcing the query-string to be converted into a Query object). ## 5.0.2 - 2014-10-30 * Added a trailing `\r\n` to multipart/form-data payloads. See https://github.com/guzzle/guzzle/pull/871 * Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. * Status codes are now returned as integers. See https://github.com/guzzle/guzzle/issues/881 * No longer overwriting an existing `application/x-www-form-urlencoded` header when sending POST requests, allowing for customized headers. See https://github.com/guzzle/guzzle/issues/877 * Improved path URL serialization. * No longer double percent-encoding characters in the path or query string if they are already encoded. * Now properly encoding the supplied path to a URL object, instead of only encoding ' ' and '?'. * Note: This has been changed in 5.0.3 to now encode query string values by default unless the `rawString` argument is provided when setting the query string on a URL: Now allowing many more characters to be present in the query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A ## 5.0.1 - 2014-10-16 Bugfix release. * Fixed an issue where connection errors still returned response object in error and end events event though the response is unusable. This has been corrected so that a response is not returned in the `getResponse` method of these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 * Fixed an issue where transfer statistics were not being populated in the RingBridge. https://github.com/guzzle/guzzle/issues/866 ## 5.0.0 - 2014-10-12 Adding support for non-blocking responses and some minor API cleanup. ### New Features * Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. * Added a public API for creating a default HTTP adapter. * Updated the redirect plugin to be non-blocking so that redirects are sent concurrently. Other plugins like this can now be updated to be non-blocking. * Added a "progress" event so that you can get upload and download progress events. * Added `GuzzleHttp\Pool` which implements FutureInterface and transfers requests concurrently using a capped pool size as efficiently as possible. * Added `hasListeners()` to EmitterInterface. * Removed `GuzzleHttp\ClientInterface::sendAll` and marked `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the recommended way). ### Breaking changes The breaking changes in this release are relatively minor. The biggest thing to look out for is that request and response objects no longer implement fluent interfaces. * Removed the fluent interfaces (i.e., ``return $this``) from requests, responses, ``GuzzleHttp\Collection``, ``GuzzleHttp\Url``, ``GuzzleHttp\Query``, ``GuzzleHttp\Post\PostBody``, and ``GuzzleHttp\Cookie\SetCookie``. This blog post provides a good outline of why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/. This also makes the Guzzle message interfaces compatible with the current PSR-7 message proposal. * Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except for the HTTP request functions from function.php, these functions are now implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be `GuzzleHttp\Pool::batch`, which returns a bjectStorage`. Using functions.php caused problems for many users: they aren't PSR-4 compliant, require an explicit include, and needed an if-guard to ensure that the functions are not declared multiple times. * Rewrote adapter layer. * Removing all classes from `GuzzleHttp\Adapter`, these are now implemented as callables that are stored in `GuzzleHttp\Ring\Client`. * Removed the concept of "parallel adapters". Sending requests serially or concurrently is now handled using a single adapter. * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The Transaction object now exposes the request, response, and client as public properties. The getters and setters have been removed. * Removed the "headers" event. This event was only useful for changing the body a response once the headers of the response were known. You can implement a similar behavior in a number of ways. One example might be to use a FnStream that has access to the transaction being sent. For example, when the first byte is written, you could check if the response headers match your expectations, and if so, change the actual stream body that is being written to. * Removed the `asArray` parameter from `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header value as an array, then use the newly added ``getHeaderAsArray()`` method of ``MessageInterface``. This change makes the Guzzle interfaces compatible with the PSR-7 interfaces. * ``GuzzleHttp\Message\MessageFactory`` no longer allows subclasses to add custom request options using double-dispatch (this was an implementation detail). Instead, you should now provide an associative array to the constructor which is a mapping of the request option name mapping to a function that applies the option value to a request. * Removed the concept of "throwImmediately" from exceptions and error events. This control mechanism was used to stop a transfer of concurrent requests from completing. This can now be handled by throwing the exception or by cancelling a pool of requests or each outstanding future request individually. * Updated to "GuzzleHttp\Streams" 3.0. * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a `maxLen` parameter. This update makes the Guzzle streams project compatible with the current PSR-7 proposal. * ``GuzzleHttp\Stream\Stream::__construct``, ``GuzzleHttp\Stream\Stream::factory``, and ``GuzzleHttp\Stream\Utils::create`` no longer accept a size in the second argument. They now accept an associative array of options, including the "size" key and "metadata" key which can be used to provide custom metadata. ## 4.2.2 - 2014-09-08 * Fixed a memory leak in the CurlAdapter when reusing cURL handles. * No longer using `request_fulluri` in stream adapter proxies. * Relative redirects are now based on the last response, not the first response. ## 4.2.1 - 2014-08-19 * Ensuring that the StreamAdapter does not always add a Content-Type header * Adding automated github releases with a phar and zip ## 4.2.0 - 2014-08-17 * Now merging in default options using a case-insensitive comparison. Closes https://github.com/guzzle/guzzle/issues/767 * Added the ability to automatically decode `Content-Encoding` response bodies using the `decode_content` request option. This is set to `true` by default to decode the response body if it comes over the wire with a `Content-Encoding`. Set this value to `false` to disable decoding the response content, and pass a string to provide a request `Accept-Encoding` header and turn on automatic response decoding. This feature now allows you to pass an `Accept-Encoding` header in the headers of a request but still disable automatic response decoding. Closes https://github.com/guzzle/guzzle/issues/764 * Added the ability to throw an exception immediately when transferring requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 * Updating guzzlehttp/streams dependency to ~2.1 * No longer utilizing the now deprecated namespaced methods from the stream package. ## 4.1.8 - 2014-08-14 * Fixed an issue in the CurlFactory that caused setting the `stream=false` request option to throw an exception. See: https://github.com/guzzle/guzzle/issues/769 * TransactionIterator now calls rewind on the inner iterator. See: https://github.com/guzzle/guzzle/pull/765 * You can now set the `Content-Type` header to `multipart/form-data` when creating POST requests to force multipart bodies. See https://github.com/guzzle/guzzle/issues/768 ## 4.1.7 - 2014-08-07 * Fixed an error in the HistoryPlugin that caused the same request and response to be logged multiple times when an HTTP protocol error occurs. * Ensuring that cURL does not add a default Content-Type when no Content-Type has been supplied by the user. This prevents the adapter layer from modifying the request that is sent over the wire after any listeners may have already put the request in a desired state (e.g., signed the request). * Throwing an exception when you attempt to send requests that have the "stream" set to true in parallel using the MultiAdapter. * Only calling curl_multi_select when there are active cURL handles. This was previously changed and caused performance problems on some systems due to PHP always selecting until the maximum select timeout. * Fixed a bug where multipart/form-data POST fields were not correctly aggregated (e.g., values with "&"). ## 4.1.6 - 2014-08-03 * Added helper methods to make it easier to represent messages as strings, including getting the start line and getting headers as a string. ## 4.1.5 - 2014-08-02 * Automatically retrying cURL "Connection died, retrying a fresh connect" errors when possible. * cURL implementation cleanup * Allowing multiple event subscriber listeners to be registered per event by passing an array of arrays of listener configuration. ## 4.1.4 - 2014-07-22 * Fixed a bug that caused multi-part POST requests with more than one field to serialize incorrectly. * Paths can now be set to "0" * `ResponseInterface::xml` now accepts a `libxml_options` option and added a missing default argument that was required when parsing XML response bodies. * A `save_to` stream is now created lazily, which means that files are not created on disk unless a request succeeds. ## 4.1.3 - 2014-07-15 * Various fixes to multipart/form-data POST uploads * Wrapping function.php in an if-statement to ensure Guzzle can be used globally and in a Composer install * Fixed an issue with generating and merging in events to an event array * POST headers are only applied before sending a request to allow you to change the query aggregator used before uploading * Added much more robust query string parsing * Fixed various parsing and normalization issues with URLs * Fixing an issue where multi-valued headers were not being utilized correctly in the StreamAdapter ## 4.1.2 - 2014-06-18 * Added support for sending payloads with GET requests ## 4.1.1 - 2014-06-08 * Fixed an issue related to using custom message factory options in subclasses * Fixed an issue with nested form fields in a multi-part POST * Fixed an issue with using the `json` request option for POST requests * Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` ## 4.1.0 - 2014-05-27 * Added a `json` request option to easily serialize JSON payloads. * Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. * Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. * Added the ability to provide an emitter to a client in the client constructor. * Added the ability to persist a cookie session using $_SESSION. * Added a trait that can be used to add event listeners to an iterator. * Removed request method constants from RequestInterface. * Fixed warning when invalid request start-lines are received. * Updated MessageFactory to work with custom request option methods. * Updated cacert bundle to latest build. 4.0.2 (2014-04-16) ------------------ * Proxy requests using the StreamAdapter now properly use request_fulluri (#632) * Added the ability to set scalars as POST fields (#628) ## 4.0.1 - 2014-04-04 * The HTTP status code of a response is now set as the exception code of RequestException objects. * 303 redirects will now correctly switch from POST to GET requests. * The default parallel adapter of a client now correctly uses the MultiAdapter. * HasDataTrait now initializes the internal data array as an empty array so that the toArray() method always returns an array. ## 4.0.0 - 2014-03-29 * For more information on the 4.0 transition, see: http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/ * For information on changes and upgrading, see: https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 * Added `GuzzleHttp\batch()` as a convenience function for sending requests in parallel without needing to write asynchronous code. * Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. You can now pass a callable or an array of associative arrays where each associative array contains the "fn", "priority", and "once" keys. ## 4.0.0.rc-2 - 2014-03-25 * Removed `getConfig()` and `setConfig()` from clients to avoid confusion around whether things like base_url, message_factory, etc. should be able to be retrieved or modified. * Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface * functions.php functions were renamed using snake_case to match PHP idioms * Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and `GUZZLE_CURL_SELECT_TIMEOUT` environment variables * Added the ability to specify custom `sendAll()` event priorities * Added the ability to specify custom stream context options to the stream adapter. * Added a functions.php function for `get_path()` and `set_path()` * CurlAdapter and MultiAdapter now use a callable to generate curl resources * MockAdapter now properly reads a body and emits a `headers` event * Updated Url class to check if a scheme and host are set before adding ":" and "//". This allows empty Url (e.g., "") to be serialized as "". * Parsing invalid XML no longer emits warnings * Curl classes now properly throw AdapterExceptions * Various performance optimizations * Streams are created with the faster `Stream\create()` function * Marked deprecation_proxy() as internal * Test server is now a collection of static methods on a class ## 4.0.0-rc.1 - 2014-03-15 * See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 ## 3.8.1 - 2014-01-28 * Bug: Always using GET requests when redirecting from a 303 response * Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in `Guzzle\Http\ClientInterface::setSslVerification()` * Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL * Bug: The body of a request can now be set to `"0"` * Sending PHP stream requests no longer forces `HTTP/1.0` * Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of each sub-exception * Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than clobbering everything). * Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) * Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. * Now properly escaping the regular expression delimiter when matching Cookie domains. * Network access is now disabled when loading XML documents ## 3.8.0 - 2013-12-05 * Added the ability to define a POST name for a file * JSON response parsing now properly walks additionalProperties * cURL error code 18 is now retried automatically in the BackoffPlugin * Fixed a cURL error when URLs contain fragments * Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were CurlExceptions * CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) * Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` * Fixed a bug that was encountered when parsing empty header parameters * UriTemplate now has a `setRegex()` method to match the docs * The `debug` request parameter now checks if it is truthy rather than if it exists * Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin * Added the ability to combine URLs using strict RFC 3986 compliance * Command objects can now return the validation errors encountered by the command * Various fixes to cache revalidation (#437 and 29797e5) * Various fixes to the AsyncPlugin * Cleaned up build scripts ## 3.7.4 - 2013-10-02 * Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) * Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp (see https://github.com/aws/aws-sdk-php/issues/147) * Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots * Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) * Updated the bundled cacert.pem (#419) * OauthPlugin now supports adding authentication to headers or query string (#425) ## 3.7.3 - 2013-09-08 * Added the ability to get the exception associated with a request/command when using `MultiTransferException` and `CommandTransferException`. * Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description * Schemas are only injected into response models when explicitly configured. * No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of an EntityBody. * Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. * Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. * Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() * Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin * Bug fix: Visiting XML attributes first before visting XML children when serializing requests * Bug fix: Properly parsing headers that contain commas contained in quotes * Bug fix: mimetype guessing based on a filename is now case-insensitive ## 3.7.2 - 2013-08-02 * Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander See https://github.com/guzzle/guzzle/issues/371 * Bug fix: Cookie domains are now matched correctly according to RFC 6265 See https://github.com/guzzle/guzzle/issues/377 * Bug fix: GET parameters are now used when calculating an OAuth signature * Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted * `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched * `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. See https://github.com/guzzle/guzzle/issues/379 * Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See https://github.com/guzzle/guzzle/pull/380 * cURL multi cleanup and optimizations ## 3.7.1 - 2013-07-05 * Bug fix: Setting default options on a client now works * Bug fix: Setting options on HEAD requests now works. See #352 * Bug fix: Moving stream factory before send event to before building the stream. See #353 * Bug fix: Cookies no longer match on IP addresses per RFC 6265 * Bug fix: Correctly parsing header parameters that are in `<>` and quotes * Added `cert` and `ssl_key` as request options * `Host` header can now diverge from the host part of a URL if the header is set manually * `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter * OAuth parameters are only added via the plugin if they aren't already set * Exceptions are now thrown when a URL cannot be parsed * Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails * Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin ## 3.7.0 - 2013-06-10 * See UPGRADING.md for more information on how to upgrade. * Requests now support the ability to specify an array of $options when creating a request to more easily modify a request. You can pass a 'request.options' configuration setting to a client to apply default request options to every request created by a client (e.g. default query string variables, headers, curl options, etc.). * Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. See `Guzzle\Http\StaticClient::mount`. * Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests created by a command (e.g. custom headers, query string variables, timeout settings, etc.). * Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the headers of a response * Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) * ServiceBuilders now support storing and retrieving arbitrary data * CachePlugin can now purge all resources for a given URI * CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource * CachePlugin now uses the Vary header to determine if a resource is a cache hit * `Guzzle\Http\Message\Response` now implements `\Serializable` * Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters * `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable * Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` * Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size * `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message * Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older Symfony users can still use the old version of Monolog. * Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. * Several performance improvements to `Guzzle\Common\Collection` * Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: createRequest, head, delete, put, patch, post, options, prepareRequest * Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` * Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` * Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a resource, string, or EntityBody into the $options parameter to specify the download location of the response. * Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a default `array()` * Added `Guzzle\Stream\StreamInterface::isRepeatable` * Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. * Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. * Removed `Guzzle\Http\ClientInterface::expandTemplate()` * Removed `Guzzle\Http\ClientInterface::setRequestFactory()` * Removed `Guzzle\Http\ClientInterface::getCurlMulti()` * Removed `Guzzle\Http\Message\RequestInterface::canCache` * Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` * Removed `Guzzle\Http\Message\RequestInterface::isRedirect` * Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. * You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting `Guzzle\Common\Version::$emitWarnings` to true. * Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. * Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. * Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. * Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. * Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. * Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated * Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 * Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. * Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. * Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. * Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. * Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. * Marked `Guzzle\Common\Collection::inject()` as deprecated. * Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` * CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a CacheStorageInterface. These two objects and interface will be removed in a future version. * Always setting X-cache headers on cached responses * Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin * `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface $request, Response $response);` * `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` * `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` * Added `CacheStorageInterface::purge($url)` * `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, CanCacheStrategyInterface $canCache = null)` * Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` ## 3.6.0 - 2013-05-29 * ServiceDescription now implements ToArrayInterface * Added command.hidden_params to blacklist certain headers from being treated as additionalParameters * Guzzle can now correctly parse incomplete URLs * Mixed casing of headers are now forced to be a single consistent casing across all values for that header. * Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution * Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). * Specific header implementations can be created for complex headers. When a message creates a header, it uses a HeaderFactory which can map specific headers to specific header classes. There is now a Link header and CacheControl header implementation. * Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate * Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() * Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in Guzzle\Http\Curl\RequestMediator * Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. * Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface * Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() * Removed Guzzle\Parser\ParserRegister::get(). Use getParser() * Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). * All response header helper functions return a string rather than mixing Header objects and strings inconsistently * Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle directly via interfaces * Removed the injecting of a request object onto a response object. The methods to get and set a request still exist but are a no-op until removed. * Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a `Guzzle\Service\Command\ArrayCommandInterface`. * Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response on a request while the request is still being transferred * The ability to case-insensitively search for header values * Guzzle\Http\Message\Header::hasExactHeader * Guzzle\Http\Message\Header::raw. Use getAll() * Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object instead. * `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess * Added the ability to cast Model objects to a string to view debug information. ## 3.5.0 - 2013-05-13 * Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times * Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove itself from the EventDispatcher) * Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values * Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too * Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a non-existent key * Bug: All __call() method arguments are now required (helps with mocking frameworks) * Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference to help with refcount based garbage collection of resources created by sending a request * Deprecating ZF1 cache and log adapters. These will be removed in the next major version. * Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the HistoryPlugin for a history. * Added a `responseBody` alias for the `response_body` location * Refactored internals to no longer rely on Response::getRequest() * HistoryPlugin can now be cast to a string * HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests and responses that are sent over the wire * Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects ## 3.4.3 - 2013-04-30 * Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response * Added a check to re-extract the temp cacert bundle from the phar before sending each request ## 3.4.2 - 2013-04-29 * Bug fix: Stream objects now work correctly with "a" and "a+" modes * Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present * Bug fix: AsyncPlugin no longer forces HEAD requests * Bug fix: DateTime timezones are now properly handled when using the service description schema formatter * Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails * Setting a response on a request will write to the custom request body from the response body if one is specified * LogPlugin now writes to php://output when STDERR is undefined * Added the ability to set multiple POST files for the same key in a single call * application/x-www-form-urlencoded POSTs now use the utf-8 charset by default * Added the ability to queue CurlExceptions to the MockPlugin * Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) * Configuration loading now allows remote files ## 3.4.1 - 2013-04-16 * Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. * Exceptions are now properly grouped when sending requests in parallel * Redirects are now properly aggregated when a multi transaction fails * Redirects now set the response on the original object even in the event of a failure * Bug fix: Model names are now properly set even when using $refs * Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax * Added support for oauth_callback in OAuth signatures * Added support for oauth_verifier in OAuth signatures * Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection ## 3.4.0 - 2013-04-11 * Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289 * Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 * Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 * Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. * Bug fix: Added `number` type to service descriptions. * Bug fix: empty parameters are removed from an OAuth signature * Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header * Bug fix: Fixed "array to string" error when validating a union of types in a service description * Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream * Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. * Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. * The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. * Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if the Content-Type can be determined based on the entity body or the path of the request. * Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. * Added support for a PSR-3 LogAdapter. * Added a `command.after_prepare` event * Added `oauth_callback` parameter to the OauthPlugin * Added the ability to create a custom stream class when using a stream factory * Added a CachingEntityBody decorator * Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. * The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. * You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies * POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use POST fields or files (the latter is only used when emulating a form POST in the browser). * Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest ## 3.3.1 - 2013-03-10 * Added the ability to create PHP streaming responses from HTTP requests * Bug fix: Running any filters when parsing response headers with service descriptions * Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing * Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across response location visitors. * Bug fix: Removed the possibility of creating configuration files with circular dependencies * RequestFactory::create() now uses the key of a POST file when setting the POST file name * Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set ## 3.3.0 - 2013-03-03 * A large number of performance optimizations have been made * Bug fix: Added 'wb' as a valid write mode for streams * Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned * Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` * BC: Removed `Guzzle\Http\Utils` class * BC: Setting a service description on a client will no longer modify the client's command factories. * BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' * BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to lowercase * Operation parameter objects are now lazy loaded internally * Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses * Added support for instantiating responseType=class responseClass classes. Classes must implement `Guzzle\Service\Command\ResponseClassInterface` * Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These additional properties also support locations and can be used to parse JSON responses where the outermost part of the JSON is an array * Added support for nested renaming of JSON models (rename sentAs to name) * CachePlugin * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error * Debug headers can now added to cached response in the CachePlugin ## 3.2.0 - 2013-02-14 * CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. * URLs with no path no longer contain a "/" by default * Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. * BadResponseException no longer includes the full request and response message * Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface * Adding getResponseBody() to Guzzle\Http\Message\RequestInterface * Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription * Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list * xmlEncoding can now be customized for the XML declaration of a XML service description operation * Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value aggregation and no longer uses callbacks * The URL encoding implementation of Guzzle\Http\QueryString can now be customized * Bug fix: Filters were not always invoked for array service description parameters * Bug fix: Redirects now use a target response body rather than a temporary response body * Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded * Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives ## 3.1.2 - 2013-01-27 * Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. * Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent * CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) * Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() * Setting default headers on a client after setting the user-agent will not erase the user-agent setting ## 3.1.1 - 2013-01-20 * Adding wildcard support to Guzzle\Common\Collection::getPath() * Adding alias support to ServiceBuilder configs * Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface ## 3.1.0 - 2013-01-12 * BC: CurlException now extends from RequestException rather than BadResponseException * BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() * Added getData to ServiceDescriptionInterface * Added context array to RequestInterface::setState() * Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http * Bug: Adding required content-type when JSON request visitor adds JSON to a command * Bug: Fixing the serialization of a service description with custom data * Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing an array of successful and failed responses * Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection * Added Guzzle\Http\IoEmittingEntityBody * Moved command filtration from validators to location visitors * Added `extends` attributes to service description parameters * Added getModels to ServiceDescriptionInterface ## 3.0.7 - 2012-12-19 * Fixing phar detection when forcing a cacert to system if null or true * Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` * Cleaning up `Guzzle\Common\Collection::inject` method * Adding a response_body location to service descriptions ## 3.0.6 - 2012-12-09 * CurlMulti performance improvements * Adding setErrorResponses() to Operation * composer.json tweaks ## 3.0.5 - 2012-11-18 * Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin * Bug: Response body can now be a string containing "0" * Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert * Bug: QueryString::fromString now properly parses query string parameters that contain equal signs * Added support for XML attributes in service description responses * DefaultRequestSerializer now supports array URI parameter values for URI template expansion * Added better mimetype guessing to requests and post files ## 3.0.4 - 2012-11-11 * Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value * Bug: Cookies can now be added that have a name, domain, or value set to "0" * Bug: Using the system cacert bundle when using the Phar * Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures * Enhanced cookie jar de-duplication * Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added * Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies * Added the ability to create any sort of hash for a stream rather than just an MD5 hash ## 3.0.3 - 2012-11-04 * Implementing redirects in PHP rather than cURL * Added PECL URI template extension and using as default parser if available * Bug: Fixed Content-Length parsing of Response factory * Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. * Adding ToArrayInterface throughout library * Fixing OauthPlugin to create unique nonce values per request ## 3.0.2 - 2012-10-25 * Magic methods are enabled by default on clients * Magic methods return the result of a command * Service clients no longer require a base_url option in the factory * Bug: Fixed an issue with URI templates where null template variables were being expanded ## 3.0.1 - 2012-10-22 * Models can now be used like regular collection objects by calling filter, map, etc. * Models no longer require a Parameter structure or initial data in the constructor * Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` ## 3.0.0 - 2012-10-15 * Rewrote service description format to be based on Swagger * Now based on JSON schema * Added nested input structures and nested response models * Support for JSON and XML input and output models * Renamed `commands` to `operations` * Removed dot class notation * Removed custom types * Broke the project into smaller top-level namespaces to be more component friendly * Removed support for XML configs and descriptions. Use arrays or JSON files. * Removed the Validation component and Inspector * Moved all cookie code to Guzzle\Plugin\Cookie * Magic methods on a Guzzle\Service\Client now return the command un-executed. * Calling getResult() or getResponse() on a command will lazily execute the command if needed. * Now shipping with cURL's CA certs and using it by default * Added previousResponse() method to response objects * No longer sending Accept and Accept-Encoding headers on every request * Only sending an Expect header by default when a payload is greater than 1MB * Added/moved client options: * curl.blacklist to curl.option.blacklist * Added ssl.certificate_authority * Added a Guzzle\Iterator component * Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin * Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) * Added a more robust caching plugin * Added setBody to response objects * Updating LogPlugin to use a more flexible MessageFormatter * Added a completely revamped build process * Cleaning up Collection class and removing default values from the get method * Fixed ZF2 cache adapters ## 2.8.8 - 2012-10-15 * Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did ## 2.8.7 - 2012-09-30 * Bug: Fixed config file aliases for JSON includes * Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests * Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload * Bug: Hardening request and response parsing to account for missing parts * Bug: Fixed PEAR packaging * Bug: Fixed Request::getInfo * Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail * Adding the ability for the namespace Iterator factory to look in multiple directories * Added more getters/setters/removers from service descriptions * Added the ability to remove POST fields from OAuth signatures * OAuth plugin now supports 2-legged OAuth ## 2.8.6 - 2012-09-05 * Added the ability to modify and build service descriptions * Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command * Added a `json` parameter location * Now allowing dot notation for classes in the CacheAdapterFactory * Using the union of two arrays rather than an array_merge when extending service builder services and service params * Ensuring that a service is a string before doing strpos() checks on it when substituting services for references in service builder config files. * Services defined in two different config files that include one another will by default replace the previously defined service, but you can now create services that extend themselves and merge their settings over the previous * The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like '_default' with a default JSON configuration file. ## 2.8.5 - 2012-08-29 * Bug: Suppressed empty arrays from URI templates * Bug: Added the missing $options argument from ServiceDescription::factory to enable caching * Added support for HTTP responses that do not contain a reason phrase in the start-line * AbstractCommand commands are now invokable * Added a way to get the data used when signing an Oauth request before a request is sent ## 2.8.4 - 2012-08-15 * Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin * Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. * Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream * Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream * Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) * Added additional response status codes * Removed SSL information from the default User-Agent header * DELETE requests can now send an entity body * Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries * Added the ability of the MockPlugin to consume mocked request bodies * LogPlugin now exposes request and response objects in the extras array ## 2.8.3 - 2012-07-30 * Bug: Fixed a case where empty POST requests were sent as GET requests * Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body * Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new * Added multiple inheritance to service description commands * Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()`` * Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything * Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles ## 2.8.2 - 2012-07-24 * Bug: Query string values set to 0 are no longer dropped from the query string * Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`` * Bug: ``+`` is now treated as an encoded space when parsing query strings * QueryString and Collection performance improvements * Allowing dot notation for class paths in filters attribute of a service descriptions ## 2.8.1 - 2012-07-16 * Loosening Event Dispatcher dependency * POST redirects can now be customized using CURLOPT_POSTREDIR ## 2.8.0 - 2012-07-15 * BC: Guzzle\Http\Query * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) * Changed the aggregation functions of QueryString to be static methods * Can now use fromString() with querystrings that have a leading ? * cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters * Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body * Cookies are no longer URL decoded by default * Bug: URI template variables set to null are no longer expanded ## 2.7.2 - 2012-07-02 * BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. * BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() * CachePlugin now allows for a custom request parameter function to check if a request can be cached * Bug fix: CachePlugin now only caches GET and HEAD requests by default * Bug fix: Using header glue when transferring headers over the wire * Allowing deeply nested arrays for composite variables in URI templates * Batch divisors can now return iterators or arrays ## 2.7.1 - 2012-06-26 * Minor patch to update version number in UA string * Updating build process ## 2.7.0 - 2012-06-25 * BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. * BC: Removed magic setX methods from commands * BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method * Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. * Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) * Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace * Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin * Added the ability to set POST fields and files in a service description * Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method * Adding a command.before_prepare event to clients * Added BatchClosureTransfer and BatchClosureDivisor * BatchTransferException now includes references to the batch divisor and transfer strategies * Fixed some tests so that they pass more reliably * Added Guzzle\Common\Log\ArrayLogAdapter ## 2.6.6 - 2012-06-10 * BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin * BC: Removing Guzzle\Service\Command\CommandSet * Adding generic batching system (replaces the batch queue plugin and command set) * Updating ZF cache and log adapters and now using ZF's composer repository * Bug: Setting the name of each ApiParam when creating through an ApiCommand * Adding result_type, result_doc, deprecated, and doc_url to service descriptions * Bug: Changed the default cookie header casing back to 'Cookie' ## 2.6.5 - 2012-06-03 * BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() * BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from * BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data * BC: Renaming methods in the CookieJarInterface * Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations * Making the default glue for HTTP headers ';' instead of ',' * Adding a removeValue to Guzzle\Http\Message\Header * Adding getCookies() to request interface. * Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() ## 2.6.4 - 2012-05-30 * BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. * BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand * Bug: Fixing magic method command calls on clients * Bug: Email constraint only validates strings * Bug: Aggregate POST fields when POST files are present in curl handle * Bug: Fixing default User-Agent header * Bug: Only appending or prepending parameters in commands if they are specified * Bug: Not requiring response reason phrases or status codes to match a predefined list of codes * Allowing the use of dot notation for class namespaces when using instance_of constraint * Added any_match validation constraint * Added an AsyncPlugin * Passing request object to the calculateWait method of the ExponentialBackoffPlugin * Allowing the result of a command object to be changed * Parsing location and type sub values when instantiating a service description rather than over and over at runtime ## 2.6.3 - 2012-05-23 * [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. * [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. * You can now use an array of data when creating PUT request bodies in the request factory. * Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. * [Http] Adding support for Content-Type in multipart POST uploads per upload * [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) * Adding more POST data operations for easier manipulation of POST data. * You can now set empty POST fields. * The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. * Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. * CS updates ## 2.6.2 - 2012-05-19 * [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. ## 2.6.1 - 2012-05-19 * [BC] Removing 'path' support in service descriptions. Use 'uri'. * [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. * [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. * [BC] Removing Guzzle\Common\XmlElement. * All commands, both dynamic and concrete, have ApiCommand objects. * Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. * Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. * Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. ## 2.6.0 - 2012-05-15 * [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder * [BC] Executing a Command returns the result of the command rather than the command * [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. * [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. * [BC] Moving ResourceIterator* to Guzzle\Service\Resource * [BC] Completely refactored ResourceIterators to iterate over a cloned command object * [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate * [BC] Guzzle\Guzzle is now deprecated * Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject * Adding Guzzle\Version class to give version information about Guzzle * Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() * Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data * ServiceDescription and ServiceBuilder are now cacheable using similar configs * Changing the format of XML and JSON service builder configs. Backwards compatible. * Cleaned up Cookie parsing * Trimming the default Guzzle User-Agent header * Adding a setOnComplete() method to Commands that is called when a command completes * Keeping track of requests that were mocked in the MockPlugin * Fixed a caching bug in the CacheAdapterFactory * Inspector objects can be injected into a Command object * Refactoring a lot of code and tests to be case insensitive when dealing with headers * Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL * Adding the ability to set global option overrides to service builder configs * Adding the ability to include other service builder config files from within XML and JSON files * Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. ## 2.5.0 - 2012-05-08 * Major performance improvements * [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. * [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. * [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" * Added the ability to passed parameters to all requests created by a client * Added callback functionality to the ExponentialBackoffPlugin * Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. * Rewinding request stream bodies when retrying requests * Exception is thrown when JSON response body cannot be decoded * Added configurable magic method calls to clients and commands. This is off by default. * Fixed a defect that added a hash to every parsed URL part * Fixed duplicate none generation for OauthPlugin. * Emitting an event each time a client is generated by a ServiceBuilder * Using an ApiParams object instead of a Collection for parameters of an ApiCommand * cache.* request parameters should be renamed to params.cache.* * Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. * Added the ability to disable type validation of service descriptions * ServiceDescriptions and ServiceBuilders are now Serializable LICENSE000064400000002127151742731020005555 0ustar00Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Makefile000064400000002047151742731020006211 0ustar00all: clean coverage docs start-server: cd vendor/guzzlehttp/ringphp && make start-server stop-server: cd vendor/guzzlehttp/ringphp && make stop-server test: start-server vendor/bin/phpunit $(MAKE) stop-server coverage: start-server vendor/bin/phpunit --coverage-html=artifacts/coverage $(MAKE) stop-server view-coverage: open artifacts/coverage/index.html clean: rm -rf artifacts/* docs: cd docs && make html && cd .. view-docs: open docs/_build/html/index.html tag: $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1")) @echo Tagging $(TAG) chag update $(TAG) sed -i '' -e "s/VERSION = '.*'/VERSION = '$(TAG)'/" src/ClientInterface.php php -l src/ClientInterface.php git add -A git commit -m '$(TAG) release' chag tag perf: start-server php tests/perf.php $(MAKE) stop-server package: burgomaster php build/packager.php burgomaster: mkdir -p build/artifacts curl -s https://raw.githubusercontent.com/mtdowling/Burgomaster/0.0.2/src/Burgomaster.php > build/artifacts/Burgomaster.php .PHONY: docs burgomaster README.md000064400000004425151742731020006032 0ustar00Guzzle, PHP HTTP client and webservice framework ================================================ [![Build Status](https://secure.travis-ci.org/guzzle/guzzle.png?branch=master)](http://travis-ci.org/guzzle/guzzle) Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services. - Manages things like persistent connections, represents query strings as collections, simplifies sending streaming POST requests with fields and files, and abstracts away the underlying HTTP transport layer. - Can send both synchronous and asynchronous requests using the same interface without requiring a dependency on a specific event loop. - Pluggable HTTP adapters allows Guzzle to integrate with any method you choose for sending HTTP requests over the wire (e.g., cURL, sockets, PHP's stream wrapper, non-blocking event loops like ReactPHP. - Guzzle makes it so that you no longer need to fool around with cURL options, stream contexts, or sockets. ```php $client = new GuzzleHttp\Client(); $response = $client->get('http://guzzlephp.org'); $res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]); echo $res->getStatusCode(); // "200" echo $res->getHeader('content-type'); // 'application/json; charset=utf8' echo $res->getBody(); // {"type":"User"...' var_export($res->json()); // Outputs the JSON decoded data // Send an asynchronous request. $req = $client->createRequest('GET', 'http://httpbin.org', ['future' => true]); $client->send($req)->then(function ($response) { echo 'I completed! ' . $response; }); ``` Get more information and answers with the [Documentation](http://guzzlephp.org/), [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), and [Gitter](https://gitter.im/guzzle/guzzle). ### Installing via Composer The recommended way to install Guzzle is through [Composer](http://getcomposer.org). ```bash # Install Composer curl -sS https://getcomposer.org/installer | php ``` Next, run the Composer command to install the latest stable version of Guzzle: ```bash composer require guzzlehttp/guzzle ``` After installing, you need to require Composer's autoloader: ```php require 'vendor/autoload.php'; ``` ### Documentation More information can be found in the online documentation at http://guzzlephp.org/. UPGRADING.md000064400000124246151742731020006421 0ustar00Guzzle Upgrade Guide ==================== 4.x to 5.0 ---------- ## Rewritten Adapter Layer Guzzle now uses `RingPHP `_ to send HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor is still supported, but it has now been renamed to `handler`. Instead of passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP `callable` that follows the RingPHP specification. ## Removed Fluent Interfaces `Fluent interfaces were removed `_ from the following classes: - `GuzzleHttp\Collection` - `GuzzleHttp\Url` - `GuzzleHttp\Query` - `GuzzleHttp\Post\PostBody` - `GuzzleHttp\Cookie\SetCookie` ## Removed functions.php Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following functions can be used as replacements. - `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` - `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` - `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` - `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, deprecated in favor of using `GuzzleHttp\Pool::batch()`. The "procedural" global client has been removed with no replacement (e.g., `GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttl\Client` object as a replacement. ## `throwImmediately` has been removed The concept of "throwImmediately" has been removed from exceptions and error events. This control mechanism was used to stop a transfer of concurrent requests from completing. This can now be handled by throwing the exception or by cancelling a pool of requests or each outstanding future request individually. ## headers event has been removed Removed the "headers" event. This event was only useful for changing the body a response once the headers of the response were known. You can implement a similar behavior in a number of ways. One example might be to use a FnStream that has access to the transaction being sent. For example, when the first byte is written, you could check if the response headers match your expectations, and if so, change the actual stream body that is being written to. ## Updates to HTTP Messages Removed the `asArray` parameter from `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header value as an array, then use the newly added `getHeaderAsArray()` method of `MessageInterface`. This change makes the Guzzle interfaces compatible with the PSR-7 interfaces. 3.x to 4.0 ---------- ## Overarching changes: - Now requires PHP 5.4 or greater. - No longer requires cURL to send requests. - Guzzle no longer wraps every exception it throws. Only exceptions that are recoverable are now wrapped by Guzzle. - Various namespaces have been removed or renamed. - No longer requiring the Symfony EventDispatcher. A custom event dispatcher based on the Symfony EventDispatcher is now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant speed and functionality improvements). Changes per Guzzle 3.x namespace are described below. ## Batch The `Guzzle\Batch` namespace has been removed. This is best left to third-parties to implement on top of Guzzle's core HTTP library. ## Cache The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement has been implemented yet, but hoping to utilize a PSR cache interface). ## Common - Removed all of the wrapped exceptions. It's better to use the standard PHP library for unrecoverable exceptions. - `FromConfigInterface` has been removed. - `Guzzle\Common\Version` has been removed. The VERSION constant can be found at `GuzzleHttp\ClientInterface::VERSION`. ### Collection - `getAll` has been removed. Use `toArray` to convert a collection to an array. - `inject` has been removed. - `keySearch` has been removed. - `getPath` no longer supports wildcard expressions. Use something better like JMESPath for this. - `setPath` now supports appending to an existing array via the `[]` notation. ### Events Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses `GuzzleHttp\Event\Emitter`. - `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by `GuzzleHttp\Event\EmitterInterface`. - `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by `GuzzleHttp\Event\Emitter`. - `Symfony\Component\EventDispatcher\Event` is replaced by `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in `GuzzleHttp\Event\EventInterface`. - `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the event emitter of a request, client, etc. now uses the `getEmitter` method rather than the `getDispatcher` method. #### Emitter - Use the `once()` method to add a listener that automatically removes itself the first time it is invoked. - Use the `listeners()` method to retrieve a list of event listeners rather than the `getListeners()` method. - Use `emit()` instead of `dispatch()` to emit an event from an emitter. - Use `attach()` instead of `addSubscriber()` and `detach()` instead of `removeSubscriber()`. ```php $mock = new Mock(); // 3.x $request->getEventDispatcher()->addSubscriber($mock); $request->getEventDispatcher()->removeSubscriber($mock); // 4.x $request->getEmitter()->attach($mock); $request->getEmitter()->detach($mock); ``` Use the `on()` method to add a listener rather than the `addListener()` method. ```php // 3.x $request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); // 4.x $request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); ``` ## Http ### General changes - The cacert.pem certificate has been moved to `src/cacert.pem`. - Added the concept of adapters that are used to transfer requests over the wire. - Simplified the event system. - Sending requests in parallel is still possible, but batching is no longer a concept of the HTTP layer. Instead, you must use the `complete` and `error` events to asynchronously manage parallel request transfers. - `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. - `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. - QueryAggregators have been rewritten so that they are simply callable functions. - `GuzzleHttp\StaticClient` has been removed. Use the functions provided in `functions.php` for an easy to use static client instance. - Exceptions in `GuzzleHttp\Exception` have been updated to all extend from `GuzzleHttp\Exception\TransferException`. ### Client Calling methods like `get()`, `post()`, `head()`, etc. no longer create and return a request, but rather creates a request, sends the request, and returns the response. ```php // 3.0 $request = $client->get('/'); $response = $request->send(); // 4.0 $response = $client->get('/'); // or, to mirror the previous behavior $request = $client->createRequest('GET', '/'); $response = $client->send($request); ``` `GuzzleHttp\ClientInterface` has changed. - The `send` method no longer accepts more than one request. Use `sendAll` to send multiple requests in parallel. - `setUserAgent()` has been removed. Use a default request option instead. You could, for example, do something like: `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. - `setSslVerification()` has been removed. Use default request options instead, like `$client->setConfig('defaults/verify', true)`. `GuzzleHttp\Client` has changed. - The constructor now accepts only an associative array. You can include a `base_url` string or array to use a URI template as the base URL of a client. You can also specify a `defaults` key that is an associative array of default request options. You can pass an `adapter` to use a custom adapter, `batch_adapter` to use a custom adapter for sending requests in parallel, or a `message_factory` to change the factory used to create HTTP requests and responses. - The client no longer emits a `client.create_request` event. - Creating requests with a client no longer automatically utilize a URI template. You must pass an array into a creational method (e.g., `createRequest`, `get`, `put`, etc.) in order to expand a URI template. ### Messages Messages no longer have references to their counterparts (i.e., a request no longer has a reference to it's response, and a response no loger has a reference to its request). This association is now managed through a `GuzzleHttp\Adapter\TransactionInterface` object. You can get references to these transaction objects using request events that are emitted over the lifecycle of a request. #### Requests with a body - `GuzzleHttp\Message\EntityEnclosingRequest` and `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The separation between requests that contain a body and requests that do not contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` handles both use cases. - Any method that previously accepts a `GuzzleHttp\Response` object now accept a `GuzzleHttp\Message\ResponseInterface`. - `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create both requests and responses and is implemented in `GuzzleHttp\Message\MessageFactory`. - POST field and file methods have been removed from the request object. You must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` to control the format of a POST body. Requests that are created using a standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if the method is POST and no body is provided. ```php $request = $client->createRequest('POST', '/'); $request->getBody()->setField('foo', 'bar'); $request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); ``` #### Headers - `GuzzleHttp\Message\Header` has been removed. Header values are now simply represented by an array of values or as a string. Header values are returned as a string by default when retrieving a header value from a message. You can pass an optional argument of `true` to retrieve a header value as an array of strings instead of a single concatenated string. - `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to `GuzzleHttp\Post`. This interface has been simplified and now allows the addition of arbitrary headers. - Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most of the custom headers are now handled separately in specific subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has been updated to properly handle headers that contain parameters (like the `Link` header). #### Responses - `GuzzleHttp\Message\Response::getInfo()` and `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event system to retrieve this type of information. - `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. - `GuzzleHttp\Message\Response::getMessage()` has been removed. - `GuzzleHttp\Message\Response::calculateAge()` and other cache specific methods have moved to the CacheSubscriber. - Header specific helper functions like `getContentMd5()` have been removed. Just use `getHeader('Content-MD5')` instead. - `GuzzleHttp\Message\Response::setRequest()` and `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event system to work with request and response objects as a transaction. - `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the Redirect subscriber instead. - `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have been removed. Use `getStatusCode()` instead. #### Streaming responses Streaming requests can now be created by a client directly, returning a `GuzzleHttp\Message\ResponseInterface` object that contains a body stream referencing an open PHP HTTP stream. ```php // 3.0 use Guzzle\Stream\PhpStreamRequestFactory; $request = $client->get('/'); $factory = new PhpStreamRequestFactory(); $stream = $factory->fromRequest($request); $data = $stream->read(1024); // 4.0 $response = $client->get('/', ['stream' => true]); // Read some data off of the stream in the response body $data = $response->getBody()->read(1024); ``` #### Redirects The `configureRedirects()` method has been removed in favor of a `allow_redirects` request option. ```php // Standard redirects with a default of a max of 5 redirects $request = $client->createRequest('GET', '/', ['allow_redirects' => true]); // Strict redirects with a custom number of redirects $request = $client->createRequest('GET', '/', [ 'allow_redirects' => ['max' => 5, 'strict' => true] ]); ``` #### EntityBody EntityBody interfaces and classes have been removed or moved to `GuzzleHttp\Stream`. All classes and interfaces that once required `GuzzleHttp\EntityBodyInterface` now require `GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no longer uses `GuzzleHttp\EntityBody::factory` but now uses `GuzzleHttp\Stream\Stream::factory` or even better: `GuzzleHttp\Stream\create()`. - `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` - `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` - `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` - `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` - `Guzzle\Http\IoEmittyinEntityBody` has been removed. #### Request lifecycle events Requests previously submitted a large number of requests. The number of events emitted over the lifecycle of a request has been significantly reduced to make it easier to understand how to extend the behavior of a request. All events emitted during the lifecycle of a request now emit a custom `GuzzleHttp\Event\EventInterface` object that contains context providing methods and a way in which to modify the transaction at that specific point in time (e.g., intercept the request and set a response on the transaction). - `request.before_send` has been renamed to `before` and now emits a `GuzzleHttp\Event\BeforeEvent` - `request.complete` has been renamed to `complete` and now emits a `GuzzleHttp\Event\CompleteEvent`. - `request.sent` has been removed. Use `complete`. - `request.success` has been removed. Use `complete`. - `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. - `request.exception` has been removed. Use `error`. - `request.receive.status_line` has been removed. - `curl.callback.progress` has been removed. Use a custom `StreamInterface` to maintain a status update. - `curl.callback.write` has been removed. Use a custom `StreamInterface` to intercept writes. - `curl.callback.read` has been removed. Use a custom `StreamInterface` to intercept reads. `headers` is a new event that is emitted after the response headers of a request have been received before the body of the response is downloaded. This event emits a `GuzzleHttp\Event\HeadersEvent`. You can intercept a request and inject a response using the `intercept()` event of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and `GuzzleHttp\Event\ErrorEvent` event. See: http://docs.guzzlephp.org/en/latest/events.html ## Inflection The `Guzzle\Inflection` namespace has been removed. This is not a core concern of Guzzle. ## Iterator The `Guzzle\Iterator` namespace has been removed. - `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of Guzzle itself. - `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent class is shipped with PHP 5.4. - `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because it's easier to just wrap an iterator in a generator that maps values. For a replacement of these iterators, see https://github.com/nikic/iter ## Log The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The `Guzzle\Log` namespace has been removed. Guzzle now relies on `Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been moved to `GuzzleHttp\Subscriber\Log\Formatter`. ## Parser The `Guzzle\Parser` namespace has been removed. This was previously used to make it possible to plug in custom parsers for cookies, messages, URI templates, and URLs; however, this level of complexity is not needed in Guzzle so it has been removed. - Cookie: Cookie parsing logic has been moved to `GuzzleHttp\Cookie\SetCookie::fromString`. - Message: Message parsing logic for both requests and responses has been moved to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only used in debugging or deserializing messages, so it doesn't make sense for Guzzle as a library to add this level of complexity to parsing messages. - UriTemplate: URI template parsing has been moved to `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL URI template library if it is installed. - Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, then developers are free to subclass `GuzzleHttp\Url`. ## Plugin The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. Several plugins are shipping with the core Guzzle library under this namespace. - `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar code has moved to `GuzzleHttp\Cookie`. - `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. - `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is received. - `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. - `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before sending. This subscriber is attached to all requests by default. - `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. The following plugins have been removed (third-parties are free to re-implement these if needed): - `GuzzleHttp\Plugin\Async` has been removed. - `GuzzleHttp\Plugin\CurlAuth` has been removed. - `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This functionality should instead be implemented with event listeners that occur after normal response parsing occurs in the guzzle/command package. The following plugins are not part of the core Guzzle package, but are provided in separate repositories: - `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be muchs simpler to build custom retry policies using simple functions rather than various chained classes. See: https://github.com/guzzle/retry-subscriber - `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to https://github.com/guzzle/cache-subscriber - `Guzzle\Http\Plugin\Log\LogPlugin` has moved to https://github.com/guzzle/log-subscriber - `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to https://github.com/guzzle/message-integrity-subscriber - `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to `GuzzleHttp\Subscriber\MockSubscriber`. - `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to https://github.com/guzzle/oauth-subscriber ## Service The service description layer of Guzzle has moved into two separate packages: - http://github.com/guzzle/command Provides a high level abstraction over web services by representing web service operations using commands. - http://github.com/guzzle/guzzle-services Provides an implementation of guzzle/command that provides request serialization and response parsing using Guzzle service descriptions. ## Stream Stream have moved to a separate package available at https://github.com/guzzle/streams. `Guzzle\Stream\StreamInterface` has been given a large update to cleanly take on the responsibilities of `Guzzle\Http\EntityBody` and `Guzzle\Http\EntityBodyInterface` now that they have been removed. The number of methods implemented by the `StreamInterface` has been drastically reduced to allow developers to more easily extend and decorate stream behavior. ## Removed methods from StreamInterface - `getStream` and `setStream` have been removed to better encapsulate streams. - `getMetadata` and `setMetadata` have been removed in favor of `GuzzleHttp\Stream\MetadataStreamInterface`. - `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been removed. This data is accessible when using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. - `rewind` has been removed. Use `seek(0)` for a similar behavior. ## Renamed methods - `detachStream` has been renamed to `detach`. - `feof` has been renamed to `eof`. - `ftell` has been renamed to `tell`. - `readLine` has moved from an instance method to a static class method of `GuzzleHttp\Stream\Stream`. ## Metadata streams `GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams that contain additonal metadata accessible via `getMetadata()`. `GuzzleHttp\Stream\StreamInterface::getMetadata` and `GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. ## StreamRequestFactory The entire concept of the StreamRequestFactory has been removed. The way this was used in Guzzle 3 broke the actual interface of sending streaming requests (instead of getting back a Response, you got a StreamInterface). Streeaming PHP requests are now implemented throught the `GuzzleHttp\Adapter\StreamAdapter`. 3.6 to 3.7 ---------- ### Deprecations - You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: ```php \Guzzle\Common\Version::$emitWarnings = true; ``` The following APIs and options have been marked as deprecated: - Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. - Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. - Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. - Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. - Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. - Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated - Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. - Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. - Marked `Guzzle\Common\Collection::inject()` as deprecated. - Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` 3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational request methods. When paired with a client's configuration settings, these options allow you to specify default settings for various aspects of a request. Because these options make other previous configuration options redundant, several configuration options and methods of a client and AbstractCommand have been deprecated. - Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. - Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. - Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` - Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 $command = $client->getCommand('foo', array( 'command.headers' => array('Test' => '123'), 'command.response_body' => '/path/to/file' )); // Should be changed to: $command = $client->getCommand('foo', array( 'command.request_options' => array( 'headers' => array('Test' => '123'), 'save_as' => '/path/to/file' ) )); ### Interface changes Additions and changes (you will need to update any implementations or subclasses you may have created): - Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: createRequest, head, delete, put, patch, post, options, prepareRequest - Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` - Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` - Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a resource, string, or EntityBody into the $options parameter to specify the download location of the response. - Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a default `array()` - Added `Guzzle\Stream\StreamInterface::isRepeatable` - Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. The following methods were removed from interfaces. All of these methods are still available in the concrete classes that implement them, but you should update your code to use alternative methods: - Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or `$client->setDefaultOption('headers/{header_name}', 'value')`. or `$client->setDefaultOption('headers', array('header_name' => 'value'))`. - Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. - Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. - Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. - Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. - Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. - Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. - Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. ### Cache plugin breaking changes - CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a CacheStorageInterface. These two objects and interface will be removed in a future version. - Always setting X-cache headers on cached responses - Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin - `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface $request, Response $response);` - `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` - `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` - Added `CacheStorageInterface::purge($url)` - `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, CanCacheStrategyInterface $canCache = null)` - Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` 3.5 to 3.6 ---------- * Mixed casing of headers are now forced to be a single consistent casing across all values for that header. * Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution * Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. * Specific header implementations can be created for complex headers. When a message creates a header, it uses a HeaderFactory which can map specific headers to specific header classes. There is now a Link header and CacheControl header implementation. * Moved getLinks() from Response to just be used on a Link header object. If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the HeaderInterface (e.g. toArray(), getAll(), etc.). ### Interface changes * Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate * Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() * Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in Guzzle\Http\Curl\RequestMediator * Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. * Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface * Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() ### Removed deprecated functions * Removed Guzzle\Parser\ParserRegister::get(). Use getParser() * Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). ### Deprecations * The ability to case-insensitively search for header values * Guzzle\Http\Message\Header::hasExactHeader * Guzzle\Http\Message\Header::raw. Use getAll() * Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object instead. ### Other changes * All response header helper functions return a string rather than mixing Header objects and strings inconsistently * Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle directly via interfaces * Removed the injecting of a request object onto a response object. The methods to get and set a request still exist but are a no-op until removed. * Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a `Guzzle\Service\Command\ArrayCommandInterface`. * Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response on a request while the request is still being transferred * `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess 3.3 to 3.4 ---------- Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. 3.2 to 3.3 ---------- ### Response::getEtag() quote stripping removed `Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header ### Removed `Guzzle\Http\Utils` The `Guzzle\Http\Utils` class was removed. This class was only used for testing. ### Stream wrapper and type `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. ### curl.emit_io became emit_io Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' 3.1 to 3.2 ---------- ### CurlMulti is no longer reused globally Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added to a single client can pollute requests dispatched from other clients. If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is created. ```php $multi = new Guzzle\Http\Curl\CurlMulti(); $builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); $builder->addListener('service_builder.create_client', function ($event) use ($multi) { $event['client']->setCurlMulti($multi); } }); ``` ### No default path URLs no longer have a default path value of '/' if no path was specified. Before: ```php $request = $client->get('http://www.foo.com'); echo $request->getUrl(); // >> http://www.foo.com/ ``` After: ```php $request = $client->get('http://www.foo.com'); echo $request->getUrl(); // >> http://www.foo.com ``` ### Less verbose BadResponseException The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and response information. You can, however, get access to the request and response object by calling `getRequest()` or `getResponse()` on the exception object. ### Query parameter aggregation Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is responsible for handling the aggregation of multi-valued query string variables into a flattened hash. 2.8 to 3.x ---------- ### Guzzle\Service\Inspector Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` **Before** ```php use Guzzle\Service\Inspector; class YourClient extends \Guzzle\Service\Client { public static function factory($config = array()) { $default = array(); $required = array('base_url', 'username', 'api_key'); $config = Inspector::fromConfig($config, $default, $required); $client = new self( $config->get('base_url'), $config->get('username'), $config->get('api_key') ); $client->setConfig($config); $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); return $client; } ``` **After** ```php use Guzzle\Common\Collection; class YourClient extends \Guzzle\Service\Client { public static function factory($config = array()) { $default = array(); $required = array('base_url', 'username', 'api_key'); $config = Collection::fromConfig($config, $default, $required); $client = new self( $config->get('base_url'), $config->get('username'), $config->get('api_key') ); $client->setConfig($config); $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); return $client; } ``` ### Convert XML Service Descriptions to JSON **Before** ```xml Get a list of groups Uses a search query to get a list of groups Create a group Delete a group by ID Update a group ``` **After** ```json { "name": "Zendesk REST API v2", "apiVersion": "2012-12-31", "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", "operations": { "list_groups": { "httpMethod":"GET", "uri": "groups.json", "summary": "Get a list of groups" }, "search_groups":{ "httpMethod":"GET", "uri": "search.json?query=\"{query} type:group\"", "summary": "Uses a search query to get a list of groups", "parameters":{ "query":{ "location": "uri", "description":"Zendesk Search Query", "type": "string", "required": true } } }, "create_group": { "httpMethod":"POST", "uri": "groups.json", "summary": "Create a group", "parameters":{ "data": { "type": "array", "location": "body", "description":"Group JSON", "filters": "json_encode", "required": true }, "Content-Type":{ "type": "string", "location":"header", "static": "application/json" } } }, "delete_group": { "httpMethod":"DELETE", "uri": "groups/{id}.json", "summary": "Delete a group", "parameters":{ "id":{ "location": "uri", "description":"Group to delete by ID", "type": "integer", "required": true } } }, "get_group": { "httpMethod":"GET", "uri": "groups/{id}.json", "summary": "Get a ticket", "parameters":{ "id":{ "location": "uri", "description":"Group to get by ID", "type": "integer", "required": true } } }, "update_group": { "httpMethod":"PUT", "uri": "groups/{id}.json", "summary": "Update a group", "parameters":{ "id": { "location": "uri", "description":"Group to update by ID", "type": "integer", "required": true }, "data": { "type": "array", "location": "body", "description":"Group JSON", "filters": "json_encode", "required": true }, "Content-Type":{ "type": "string", "location":"header", "static": "application/json" } } } } ``` ### Guzzle\Service\Description\ServiceDescription Commands are now called Operations **Before** ```php use Guzzle\Service\Description\ServiceDescription; $sd = new ServiceDescription(); $sd->getCommands(); // @returns ApiCommandInterface[] $sd->hasCommand($name); $sd->getCommand($name); // @returns ApiCommandInterface|null $sd->addCommand($command); // @param ApiCommandInterface $command ``` **After** ```php use Guzzle\Service\Description\ServiceDescription; $sd = new ServiceDescription(); $sd->getOperations(); // @returns OperationInterface[] $sd->hasOperation($name); $sd->getOperation($name); // @returns OperationInterface|null $sd->addOperation($operation); // @param OperationInterface $operation ``` ### Guzzle\Common\Inflection\Inflector Namespace is now `Guzzle\Inflection\Inflector` ### Guzzle\Http\Plugin Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. ### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. **Before** ```php use Guzzle\Common\Log\ClosureLogAdapter; use Guzzle\Http\Plugin\LogPlugin; /** @var \Guzzle\Http\Client */ $client; // $verbosity is an integer indicating desired message verbosity level $client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); ``` **After** ```php use Guzzle\Log\ClosureLogAdapter; use Guzzle\Log\MessageFormatter; use Guzzle\Plugin\Log\LogPlugin; /** @var \Guzzle\Http\Client */ $client; // $format is a string indicating desired message format -- @see MessageFormatter $client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); ``` ### Guzzle\Http\Plugin\CurlAuthPlugin Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. ### Guzzle\Http\Plugin\ExponentialBackoffPlugin Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. **Before** ```php use Guzzle\Http\Plugin\ExponentialBackoffPlugin; $backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) )); $client->addSubscriber($backoffPlugin); ``` **After** ```php use Guzzle\Plugin\Backoff\BackoffPlugin; use Guzzle\Plugin\Backoff\HttpBackoffStrategy; // Use convenient factory method instead -- see implementation for ideas of what // you can do with chaining backoff strategies $backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( HttpBackoffStrategy::getDefaultFailureCodes(), array(429) )); $client->addSubscriber($backoffPlugin); ``` ### Known Issues #### [BUG] Accept-Encoding header behavior changed unintentionally. (See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. See issue #217 for a workaround, or use a version containing the fix. build/packager.php000064400000001661151742731020010137 0ustar00deepCopy($file, $file); } // Copy each dependency to the staging directory. Copy *.php and *.pem files. $packager->recursiveCopy('src', 'GuzzleHttp', ['php']); $packager->recursiveCopy('vendor/react/promise/src', ''); $packager->recursiveCopy('vendor/guzzlehttp/ringphp/src', 'GuzzleHttp/Ring'); $packager->recursiveCopy('vendor/guzzlehttp/streams/src', 'GuzzleHttp/Stream'); $packager->createAutoloader(['React/Promise/functions.php']); $packager->createPhar(__DIR__ . '/artifacts/guzzle.phar'); $packager->createZip(__DIR__ . '/artifacts/guzzle.zip'); composer.json000064400000001746151742731020007300 0ustar00{ "name": "guzzlehttp/guzzle", "type": "library", "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], "homepage": "http://guzzlephp.org/", "license": "MIT", "authors": [ { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" } ], "require": { "php": ">=5.4.0", "guzzlehttp/ringphp": "~1.0" }, "require-dev": { "ext-curl": "*", "psr/log": "~1.0", "phpunit/phpunit": "~4.0" }, "autoload": { "psr-4": { "GuzzleHttp\\": "src/" } }, "autoload-dev": { "psr-4": { "GuzzleHttp\\Tests\\": "tests/" } }, "extra": { "branch-alias": { "dev-master": "5.0-dev" } } } docs/Makefile000064400000012674151742731020007150 0ustar00# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Guzzle.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Guzzle" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." docs/_static/guzzle-icon.png000064400000001443151742731020012102 0ustar00‰PNG  IHDRÏ&‡ÖbKGDÿ‡�Ì¿ pHYsgŸÒRtIMEÝ,×B—´IDAT8Ë•ÓÝKÝeðÏïx4íljsÁÍãN¾œ �¡ÑÆŠQDì"ˆ^n ¢¨»ÑŸ0‚ê.ˆ#X/x±6ÖÕÖE –Zó¥3_[f:�ÌÙ²[¾„›,j6/­K…´»Ú,‡Ñp.|zÁ�ªR�H�zG-»Žqû êpÙ’Jï„|Ô'«µ„7ì”3çŠ^ç½êž g¢Â�à kY)q,«5$î1뎕פ°ùQ‹‚�Ôfç9wt›wBÁ%9“2þˆÀº:"…àÉR2qZ5ëqK.©@«÷A•9Ü/.%†=rî»èyƒb"  Û]|špUgx¥ˆÏKºçM_é–S.nUZ(3†i;4gÅ�rÑs>‘4¨^^ܰƒ²`ÕÝ¢ënªN‰áÇ}¨ÝYI5æ½àŠvP£¶¦µkÜö». Þò¹guÈXW~0 ÅÁÆ1®É¤c ÊtYP0iÍ8hR z$ŠÉk–$õ+¸ Â„1uö[ßê á´£n1ËfLªö´[6Ôh”ó7DŸù؈jg¢·¦üeÒôg¯ß(=IEND®B`‚docs/_static/logo.png000064400000743576151742731020010620 0ustar00‰PNG  IHDR_»d`– pHYsgŸÒR@IDATxì½€]Uµ7¾÷>íö2s§Ï$“ɤM „@Ê„ÞÅ’ ¢O,iÖ‡íð=}ŸÑÇC‘' úP$**¡’�F™´Éô™;õÎí÷ôýÿ�If˜ðó#$AïÑpï�{Î.뜻öÚkýÖoQÎ9Éy ä%�—@^§Wìôv—ï-/�¼òÈKÀ‘@^ù柃¼òÈKà H ¯|Ï€Ðó]æ%�—@^yå›òÈK /�3 �¼ò=BÏw™—@^y ä•oþÈK /�¼΀òÊ÷ =ße^y ä%�W¾ùg /�¼ò8È+ß3 ô|—y ä%�—@^ùæŸ�¼òÈKà H ¯|Ï€Ðó]æ%�—@^yå›òÈK /�3 �¼ò=BÏw™—@^y ä•ïßñ lêìtoß¾]ú;.ÉŸš—@^y œTïJå»*õ`6‚3#G®<òž½Òž¯Šªãhì,¸ï¾ûNÉÜžo"JC¿okiÆt_®UL›½²±Q>©4OÁÄÍM±ÀóMMÊ)hîm5áÈuCthÜöæ¡àÉrÆy²¿ÿ­¿­îíõ:rý[çå¿ÏKàYôÝÆçûLSlzÌЯñ‰•¥ÁB5IåK$šÛÉu¦š„\dR«¿WãSÊûãkSJÚVbŸìn�ϪGîSW¬X1ò½£¬í=Pà�² N”9aðrÕÌz¼Š`Sª„ãº6U Bgˆþ4«„dNÖîßó7G‰«ü!ÉY„‹”2�cU–NW¹*Ûçbü=mžìÜ5mñ9œ™½—UE¢øþ¤ΟÆ"¶,\㪠§—ŒdžöØt&‰UÜÃ¥ÔÔ8—Çj³‚Ë’·¤jz†û¼�„‘åq¡aAÅr"S.*õ@¸%o:Ê™ÈffM:×6­×¥ ‘çê�;ç�³óïòøÇ–€ønšÞʦž"ŸìZNmºœH…›M‰ôˆ¦ye µ™¾‹ bj#¥óê¬M6‘ÒAê�(ß•+W Õç]]Ô'葈›).ÛH-¹ë®¾�‰Bæ&Aža.KÔ·ëœ"ËâqËôwË’Û¥›çÌòˆDh¥¦E¤•Y%uÿ×Ê×±b'ÕÖ*‰èP(K¬ˆÄ¥NÄ¢òpe©@í GñR‹‰ÀJ%…Ša]ÔÓ©þþ×IQÑÛV¾ cS2¦jÝGHÏ{âñàP’Nt� 6’Î\�m«ÂãÂ�ç»+j‰ÌÏQ D‚¹ljîÏÂYXùM9‹<L§ÃÛb–?à±úP(þVÏâvìvl®²ò‚žS± ½U_ùïò8x×(_ÇJ xʯ5¹mº™9“âÙþ�Ï7Ä(÷Çs®NB:›]ž²›2“Læ(ÝȰ~XŽî…Ë“$Ï‘M>ƒÛ¼Å=–’Íx-&Í$&ó1·m+\±ˆÍklÆû)‘s’bs3“ÃY&†¥1s¶¸-«f¡6ÜèßøÏæ�ì\�˜“ üeŃÑT©ÀÄb‹('0.‹,N"œSè]’´͉œxLÎKEê¸TÄI"æ1¶ g™3g™˜*%�¾Þþ 2±¨³žuì9#ï+3Υܶ_ü0±ô–»ºEËãYþætš] ŠôÆÞ¦žÇ <²n‹âí=-ñ‡<±=·��'GÚ \m%T*°lK–DA$—Mjm…õ¦Ô©S]ÂZÝN‰h“¡,3L·%2[ i—(Ž.:ÉŒ3¹ñYB…]ŒñAݰZ™(¾¨¦x!•èg\iúëç7=¿Ó=~žT_W”éìkØU81ekµÚþ®udjÅàØïòïóx7Jà]£|Ýî’Ù6%×Ã7ðŒ[ ¶ �é–à=`K-¡lJ! Ö¥M«_•Au1>¿®xz#nÊð�Ù§µÚšê ¸¤s‰i·å c‚$)u”ÒrÎ-Ͷ…8” (2ò*Ôv\àvŸÎ…@JPE²‚•öZfߺçÕeË–YJ‘6uV¸±ŒËBÀÐ-ѹŠýö€éQ;.ŠDœ>9µíëB?@/‚’ ª¶¥{DÖ …Ë'@ÃJºÉ_�]ý$7È ,H¬Ç÷Šœ_‚ï§¹ ›ù#‡cùy.X²h,vŬf(ò »³3wߣ•Ñ+†­{ŠÉJu„莥­(…ïñJÄXøé;6†àXîïíßÞ¼¯¬¬HÒ-ó\ŸÂž=¬EM‘n[°¯ rºŸ3i_RèÓ ‰ wZ�‰ìI“ãRTJc%;$j*óÁÓ£Jj3µ}ª*¥!—Ûr˪šÍd̽ƒƒÃ.‹[æÎU¾9UHRB_2MF j\&qK»ë˜@‚KQ˜+úQiÞ’:[Í�órOrï�;tCuõ¨%ì̧*XTǹŒ†¼3v'Ôö¾ÁîØ¥55‰ùä_óx·IàÝ£|_(kµMîqKRдÍj[Š,ÛÜ«HâPÊN¹Üᔘs¹þ¨Ä­EšI'z|ò¨åX_]íXˆ»^íM�—£ߘ­"uU\ß!r)E¸•Óm#í)+êÞ³9&Ü4ßçl½á­½§tùrúJ45�XÆB�K™ +µR°yP‚&ÄÎ;G2 XÊj”ž¼¼¤$c[¶hŠt ?lÙ¦Iž3 {',u(|r/4}z¸'žÌ(h×ÞIüÛbu–,h¶a®—™^l×îÍñéši2ÂwI:�Ë çû‚¾HLËmQ).ºÄ¤�÷f¨ûA´÷EBÉ�Œ?u×Ö+O§ÏÚZ ?pÈyä82–™G?MÅ´Þ8ü¤®¨ˆ@ÎÃAÐ7þNÈÅe®¶Ý½‰Ç,úEâ¥ÜÖZ&ùŠg¤Ó¹e˜Ï*M i|šnqÕÍxuˆÏmh%�×W¿aÕÛMئ!Â}Ñ­êH 2°;aDÓ±�ÁrZÖ‹nOº;Žüû¼Î& @/œUÇÈx`Oa;>æ ÌÞaQëçT"ë(#‡÷KF¦Ï0…'dEzÆÐDÝg+³ 3ÂI�Ì~�éÁì˜&ˆÓ$³íÙ t£Õ²ôW{¬ìº +›.¬ïL fW®/ã›ZaŒß’"Ç &ôÄÆhj±Lù=²$}ÕæüƒŒÓ N˜ 7…hsrtõrXçóêÂ0:À"Ö>ؤºÍi†ÚÖ�-;ýÀ¥Áß›6] oC.è|{BıpmÝemÐ í;°BTG­¿+JK³.ÁµQà›&¼ ‹�×'ki+›©,.ƒBŸi ¢œÒÌ� xúC&µw°w6ïì}éùƒ†m¥ Y9·hBqR·Í�D“X¦«æµñ%m‚Èÿ/w©«þM”#rü[H�û2%©ÒÚO4ôgf�…åÍÂb„ÚÎ{H¦îM\5#¢(†ˆm¬ó‘ž€„u>Ÿ!ŒmŽ Šó»z7�"J®ª­ÕˆÁ^sIòzìxvÃm¤Š¢$Ùôùn¿AŸ?wÕŽ¾ø9¯öÄ«´ÈÈXó¯y œÍ8ãhGÉ:ÄeRKH´©'`‰.ÃOnÓð íµÑ‘ÈûX!¾M\žÐ³á–²CõõG"ê|Éc*µ&‘¨n§Mùp®©¾~ØÚ{)q”hÝŠ4¸»×,”š̊ܦTB¨Q Ÿ$þñJIKtn=^ñÿÃöïnëNM�/ó~�ÒK±‚òû_*’ X-ú¡0B”HS(¿Î‚RÅqé³a_ã•]±ó¡`~aš„Šþàùロ _u0) zŸv.¬½ƒZW,,íÁw#È‹cÆ<òáùX,°ÁS·Ö‚Ø•h‰®Ò–V,ÿ?dµè4õkÕp=*Qò_.Y‰¦ ý^%�z$v0–³¾¤FSV~%gõbQæ5¢[ÚwIq°µá`´Àò¸ x\Ÿ9¿ÀÝ sÿ?á ¯ÇÓK,•ÌP¼ü7Sýþ“ú]W5GÇy%Ï£²ÈSÛ_îþ–£ˆ�—:ú&Ê%¶0z®µ¿4ì–§[š«%ggçË2[¨f\$“P}K '²zîÜ‘�•1-2>â‘Ãfö9"©eÛsàÇÉ@ŽÜȬYPY™;aôù?ä%p–IàLZ ÊK#k;úÜ}Bºœ´æ’Rا¹ Í €•Ó26ilì#uu£ÛàÙ±;^ÚýòËö˜q¶ùø~בs¼„T�œMȦÎd�Ï+T¨ºZ|ýg¾2{Sv±» Ö^•‹“""Ø%ŒJ¥Ð}!Û&Îñ³æ|ÿ`Sl]mm�æ,[z“×!¶Ð&\Á9Ï#ðôàEÅáô2¬¨6w't*‰!X¸|À´n%TÈ"ù0¶óˆ¶kñèýç=7g%mêk‡ëa–ÀXÈÖͪÆF²¯®ŽÀßúæGúÅ3á—õQÆlÕÖ¶J‹ÇKnÑȘz‹Aì!FÜŠ”q›¼6é…{cÀþ£à8ÀM†pÿ­$ÒŒ·¿E½|Ö¬}èÅùG¶ %ŸgalíþÎC1^0ëÅÛÐÚê {CÓI©†{9QD¯­š/ÀãÃåǜ봗SËz\R"fú>'ýÕݱ�†ôÀÁ¥“'òªâÃÎ9ÎquuQ^zœöMË—‰2  Âum‡¾þâÍõõÃ>ãÇð}D 3Znñ?ä+V8w sdÃÀÀfd­¬Åzƒ²þww?íl6Iå®"ç¼ü‘—ÀÙ(�Ó­|éîÞ^�*x*¸Æ Ÿå§–B±=/¹¤Cܶ;‰ndSMø3.õ庺cp¢#¬¯¯7ñoäãß|……:ß¶øM‚ ŒÃN?Œ~Bœ’0¶À=¦Í{þêÁ`'Ô„éYÂ¥Ô¢Ss^mxëûÔSO1luσÖ…ë¶óÛÜD@ǣʧ°<¸y ;ñ]›[™bwAÛ…Ë`ÅRÿÀO·mû…|š7f”ËêêôW£‰V.1èLî#²P� ç^¼¥òuš@�èø³™°]| ‚‡]fõ4`6.ñGC´ºªé¶>ò—‡(/ÚÞýC“e‘U›&B|”Olî+Ä[|p]Wò¥ оÁHÇ—>zõÅ¿7ÅÉäiM‹”ä*‡Ûv½wõ\^â×i²2´úæ»®j�¾²ýñ²ƒN�qL[{ð\5ÓÃ�úÜ1�¾ÑNòoò8 %p:•¯°®3Y#‹J…ï«*še2wÒ±ïDÀgGÂ�»¥ž¾¬6»:]x?÷䣰x|2ó†hF ?Âm™ OŠrB2R='Ûr × ÔƒÍa‰ÎB÷AXœ¶m‘'D&oвÉ>ê÷õg£=¦§ \N( CI‡ˆ–2½È—ƒ1ç(NdÆf&âr˜”g{;¬(j IÖ–׌ yø�0mqŒFýG¾s^�&;ˆæ Í\DmJƒñGBYX‚¡„蟂àY@qÉ3¯„:—-;’ôP¸\ÜŠ7Ãu[tÛTo6ÜB›’º m÷fy÷”ð¾WM…C›MõfÇÜš°ƒØ:öûÃét±­ÑY€”XÙtb@ñùvY&¹Œ2KŒ&Í—/­!'E<½¿«0à÷ÝÁm2[i'5ÙÏÐî4,f•D·‚ôs¤hÿò$÷ˆ×y{àOŽ…«K¯*Ž˜�{ï½—d~.År›‘·¬«¨ ,\ø‰äCnÇ)£nǧLJJÆN#ÿ>/�³ZðÓ2@`TYñ䉳-S¨Ö ï¶tk°P ï·%šÞ›lý±'nþà ¹ê1?¨& lwäR¡¤¦U¶¥µÉÑLvN‡ª/èÿjDñ¯%»ØØ‹M&-‘½€½Ðž0³‡òÀØv¶w¤Ü2yMµøFè¹°xË¡øàe�¤3½YT]ÑYå‘R5ÅáL•ßÕÓ6”ƒß–îgCÚîq�¾lútÍh»JÛå]*ÈDêÌj¬+Îõ=Öh“&M:Á=òV‚í̥œ ×SN}¦e%Yüs•OÎìSE?׬e NTu]uO4Ô]/¬JOÇôÁV¡O<º°S ÚíBIÁÞz¯«Ûp倫ºj /}‹Œ1‡›bÈbS÷¹åØXùŒçv¤#�ïF‹ÓI„ÚWÁíÎźMeŸÎ˜ð‘�‡Ì®†�ˆë†WçzhBIËÚ(ùŒá){P0cÿ- žm·½€Ü]‰uf¥5?¿ÿ»عŒ^;Òÿô¢"«Ú¯dï=úgwÓÏjX _öîç�xèÅÞänÍ6ƒ[:šÖœSRrÒ…m¤½ük^g³N›òu”ÇÁ¾!Y�Hܲy4ëjŸñˇ³?½ú²ÔìPÈq#œðcl¶â³`ÞŠ@Øõ#Wáõ(«k¨@çâ‡<^h~˜ho6>Ï` Ú*a(»oSÀ=0ý¨r˜ZésÅÇC±v¦ÕéHÉš ä–B,¶“»èÖqÏ1ÛýÍ�͇•֦݋çÕ�—:4#ŽÄ·Y"£•HÉÃú��íþt  ê|eÕãÛ™¢vݤ]1%[86­öd@SNFªí2ðÂ0©mdêþ¾º@:�’¹DÕ †mÇ3:óx˜UX\R7PU gC�S­ß3Xv%jÜn­ •ƒ�µE>�¹Ä%fVzõ¹g’ެ�ﳊ—ºÃ7"øS,›=PáuõáœcÎs¶ù·}õŸ�¨0IàÂN�ZS›‡uêyîâý¢(ß|¶ÍµmÏ®½ååå£ÊoGìR—ľ�ûQnówzÖþþËõ+QÝl†Ûæ½2%56—6åÊ ç–—�Z­Ç�u䳓TR9½6ŽVÏéV%z Æp�f!›™úãÜWç\ç¼ÆÆFv²y�´•ÍKàl“€x:Ti–µBúÁUÀ�näâMŸ¼}FC§6¨¥z2UOjeQQvÅË×"‚Ê®vÔ4Œs 6èäSÕ¦öO-*¾ÎM3ƒl-ŲÌ9TÞ#r»Š3v‘„áíÍmdî‰ |�°×aýêh‹¨Ô¬eê(˜n�ÁÙϸ‡r¹–eð+�•Mû‹Ï¨\xÅýåqÓ²‹²Òa!’2ÛŽ!Y U²iS¯mî_‰¥ºv C¤Æ62æ}ˆ¤zr¼ð]^- ¤”{¬²BZ»»·ã +^4¨+dóìEº-Uô³\ÿší}Úesk’ЖŽÂdØ¢‹ÁÂB).®³ ¡Øc7X™>Ý ž`… ••”&³^fÒ)* Ä _™BHªÐ0A´š^ïÀ¢bìL/†¼ ìNJ‘M¸ ‘º�^Iš,–”4Òû0Þ$U­÷ܹ£òy±=V‡¸ÏÃ=Nó·(?¸dr zt¬d05wKa‹&Ajq0ìvÓ†®X¥"ˆå�¥��p“³øaütm{â\÷KÏKgÄr$a zAH”Ê�«ns‰Â¯“¹¨¶z·åÕÂyþó/®r„=ÛïfÔÞrUEAÚpä”?ò8k%pÆ fëû†f &» ¿à~P�²[PUÍ’›ýÉ¢Òá„ÒÚJ\]¾l�‘3Ê=Šð˜Hù4àj-™Ð÷ìïo}Ñ \9’ýó�iÐõ1�ño‡Fð©Ñ6ø{Tœèú1?ÂõÑĹ^I|n‹qNà zìºóàotÚÙÖ›ø™IX‰¡j·,?LBãüyìA×· ”š”Öy]Ò"¸t/b”M§h ðuBmPìÊi”ZæCIQûY}ÑÉÓe�3·D“¿’$áý°Ü]\ ·1=õøÜ²²á ×ƒÏƒbj¨ZålFÆ0ªŠ<®¨.fŸ¹߯i�] 9ŽsAX XF?Þ‹’ð×hºï02Ãì1sv&àXµÝÝÜÝ«$.‚Uû ä…Ü£}P%â]Ø5”À'¾2¶kËŠfÍû.þüÊ÷QYyF×Ìk(µ¦šý†ekO¤·&ü†Ï÷¥h|S‰²>]¹»�÷ª†Ù£ˆt·i)Lñ°—Y߬*Ú0âûvæŸ?ò8Û$ãí̉´� ågå¯ÃZí³t{†L…J&F³Òª‘µtQÄÓýòá;)åûa±x•²ÜœP®µÚ¯ SH~¨ ¬&ަ IÜr݇wÇÏ.Mƒ€‘ À…€x)·MsÖ־؅ëÚÓ·àú‹aœÍ ø]H° '“ w”r ¿p�f³‡�Rv§NèÇ�®û%ÀÏ~$ÄÐNìZÊT䡉Ζøø1ýÌ ›¶ÙÈ2@Ò’?È4¸W‡ÑÎ÷w\U«¹¨©¹dÆ=¢X ÿïH"Q‘Μ4ì™°o[Àec‰Ôøs¢;µÿhJî Š·5!]™”•‘l.|'ü ÆçÒ6ûWÆùó^Bò§‹f�»Ä2¬ÿåTxN…k·¾ y¯{tn™èüŠÈÁ±Š·J’ØôÊm@ðHQ²(Ê IBÜ#s^ß•ø€$ŸÂ‚äR�†ƒƒƒ{¥±Ä×QÂ?lèµ—'IÈY œëúö6¨À[?>£_:Þc‹’E~—!ŠŠX!Y¢çæj¢·f¤KpêMè¿M–•/å2Æý¸ú^$¾d=ûšÐÖV<2†ük^g£N¦`Þ‘q:™G#?.§ƒæ5kÒðL…6�1²ZÕ�ºûZ×1ÇŠ#.€ƒ6:ÀùÆ"“@a8ª¤�?å2j¡e4£8Ýθ¨&G,)çû‘ý¿a¡¨e“Ù4‚ Ò•v{è=0ÜÊ`…†s–=®nYã¨r¹väÕÿ/*õõ/.+ÜçêØßàÑØÏ³ºýƒ™_@6[¬'¬ÆMŒ5 *#×ÿ*ŠÖaÆ ô4¦ÄÉ9š.snRK bŒ)Q k5‹­‰PwñÆ®ÔÍÀò‚熃;6,±^n_ÿâ�Ëg ãœ�ïbøsÂ>ñZ_â’ÆXzYQ�1-“Ë< ÅÀýЉ�{*c¦îçŒ}!Þ HI#ߤ¶ø=@ëCO�͉_s†è46“<|/Õ�[AôþœM\p¿üÊò7n‘Í’˜ïÞç[ã7¬‹&îA?ßDD5.mdí~׬‰L�ôsk”z%Ü!‹‘[ÒñÁ_¼M|}]§6ée`¸+"¯@›W ìóÀ"¶e,ûnnÑÿ�}d&2çY.‚ÿ¿>þGÛ{n»ajiëã öº²Ú%°óÃ$|®óÌýC;†fìïê*„r>a1vÆ’?ò88-Êw],W¥M˜uóšÎØ-ÃÖfêÔÀŸ8Ñ0õžœa=ÏíªÖRßþWÂñ &ß‹mØW œB QS£ÊwK[bä[d�#(Ã4üØŸ´YÙð�üøvØ3%Îc«m ˆ'ãGJðlÝøwY?B"Pp¬ÊŸ |�tÙ­ÈÌڞ͖­\y„ÌÝÁ•ήÅWÙæÓ°_‚PaÑA3cÂPÎ5:ÎãÇ’3a·!/ (8>ìa_ÛÖ“úòÖ�ø’FBä÷L™’–í¡­­&]»lra·¡%4"òJ´Xï%¢[oCGi¨Ã‘¥£ ÁŸ0á@B½xo2yÞîݽÈ4>øÍ§B‰~ܧ†2Ö‚Ê’ý@< —Å<`åÊÿ‰ôyÄ>ómAºÕm«ñƒ›_Z»ç…U›ë®v´2sï‚�Ýþé—£C_]ß™ø¦AÅ;�ê‘öŒ©‘Ç�õ÷X.7¥)Âw�ÝWÐTÝ"­6c_7ÔÞW«÷Å–L 8vî�Õ[+» DuÏâµ»‡Ûl–}pé­wN_5—Xï«-è<ÔÓ´NWøþ3nšIÏ–U«VÁõÄ�‚ 0´õÃѹs‡g¶¸ù~ˆÂ�-RDŸ6 ¼tâ·mû�ºüøû¾ÞÐÓS�ÓòJØVþ8£xÇ•¯Ã`©êuH8,3á"ä&LmhN( `i|!˜-…M³2ìæ¢h4üfÒ€É{À²hI[ËjSäám퉚W{7…þ|~WC‘ÅM(PY±wy ö�ž¬=p1B–˜Aq#ØÆ~™´§Ò$õ0ü—ŸÓ™±ÑWVv|ˆ˜U“n}®GÌ”q_ÁÌ®Ðñm_ä¸?¨�…Á†[šUJ¸ 넨>{U›:âŸûWXÚ~ó ÒŸèG8ãŸ�Á%©X Éw„׃ÝkýŽ3Ï7Åü¢àc°Ú-p9ì3Œì_÷lq”Y@¯;ûSŸFÛ—aõ–â�¥qþnHMiìîBƒ•¢‹pÚÒÝv Æmh™Ì¯ ƒ¯à¾û“9c¼*,ÉŸ#(ö¨œH ]uÕUš£Ô�Ÿ£, 9°’-wÉôÓ £Ua#ü¼ÿçâ ¡ŽT.ºß°Œ/Š^ƒÅ´dÊù‰œÅ>SûžAæÑ¤ÈŠùE—@ë±SÁÿ¬ˆäý>íÿĽõÊT¬·u~E]ã‘gÄIP¹¶,Ü ñ¿öÄs?¸r‚¿ÉÉlÌèÆó Ó¿ÝUs·G‡]2jî� gÑû@¦áàºù CÁ‚€¶ý' $ž ¸„¥à³ø!ðæµ˜W^sóŸO«N°îNUï[c±*5MæÊ�¶K;""ÙŸ°í•Ó;ÔŠþGïkìõ½ãÆ%Ô´U}T—ì &� Ÿ03Ù¥KþA…³2P4VÛ&ù¡!“B‰!ÂÏY9´ÝëØÆ>¨ZæKõÅݸºëM�¶˜Šm:»P†[º§/}ù¬‰œ�CÇï\�õÂb®ïy»è«ƒ�ÛÅÞF‰ ø4.¨h³ ë”ÂyÖAn8½8–ç¦þÔ‘ðņ‰L8“7·»¦DFáj#£¡ �án¨žƼ V}+ÒB¹5‹Ê¨ò8Öðâáp|nñ¥SM›M&L<ˆã9+“뻤¦¢ï$ã5A™¥ƒå·+”Óÿ×L :u ‹,“²ñ�ôͦ߆{B¤¿ŽÈOÐûîKwßrËPŒº·Áê¾!‘Õ÷€�íwX_¨ ¸{VvÖŠ�ز;»„‘1;¯ŽÒ{¾©çÙ Ç;”Õ­;�0X¾ÅŠu¼N*êlg×™4½¯ –lÄáTÖtq@ËvvÝt48êr~ ˜ì›°„ú ¸_0ÑüÍeE®ž?·eý©6B¥Ìܰ¬îX¸ÞQäˆs_‡�¶~cWUDü¶f“�*ÅžÊUm ‘¹.²ms*Ð?$‰FoeuÚnîÿîi²Ûô[<�ýʃº¥ß°²‘ü}œ°ÀŽ´ŸÍKà�–À;¢|Ÿ‡Ò*$Þ‹‘�ÕÛ9¸y=p¨ÿíñ¸nöÊ–w¼äa±SÊaL夹ñxfp{ˆœ´tNQeå@_w¢8áiP~(«JXj’mó*ìÜMee f¹Á²Yrå¦MY¶`�“}vÒC�v™Ë(Ê"¸jŠÇUxå cíHÊê:[⥾‹áø<ª.lrÇã÷¦ »Ã+ÉPêvÄE„û¼¡Ì¯ö˜-âK›¢ÉñÌ´—B!M�%÷5ý%<¿;‘¥7Š…ŒÁåFƳߦ\ŽÝìÓ}4eÅ¥¬´rÕ¡€Z}„̇;Vb]IõlÁ–fÇõ\U¡—íÑ^lùåÇõEP„N{F°t¾(’ `¢~=¾+ÜX¢¡=‰D“m^ø“o5laµd¥Ÿ"Ìý}ø8~åɦ¿¶çî/w€¼8&Æn›)¯i¶¾îÜâ c/Xeñ Yx޼#®@äŒöÅþ âц*üíõ%øþR)›�R¹ »† ²ÞªY,nv,å#‹Qña˜•Íhl¸½µ­Ö£™éY3ëfTþ¸jÙ*aB…?høéC½œ¯à«w­vù<²â¬Puñ®µ}™™’eÔp̓lï3š‘¨7º‹øÜ‚ÊÜÊÆÎ'�«…[æ"¿$�Kê:ú¤�kYãÙ›ëªà@v'©� {ånnÔd¸y9á’ -ÖÛ/åH?ä%ðÿ.Gáü¿_}Ü•«ágdAe2²r ž¶n¼ò·ì¬1]cÖ9ŠNêñÓr†ZY*Z¿òû÷õ‘%KØe“ç|[îŒ È”� »“|£LeÇ5O^�¦þiª7A±¹Èù®fèÏÀ1×-‹éÍTÃFPˆòdW}G4“¿�m߬·¢°×óú�MÀ‹‡+[ Û�-üȹÃekÿHràpÛ2é�9‹J’õ;·(Lvö¬°B1x{‡³åÜÄ1¥°"+{”ÿ:ô—?7ŸlëîøŽõ’que¥±âM2ÒÖõ''1ƒ�W�qV4k³á*àáì--/Øæ¦�Œó@"óoªnßàò ïŸâñŒZ†M¹\¥–ãOÃ¥±_ÌÚwñD6à)ø">ÀÁ%C ~‡XÉ�X82SŽã[ØÐ»%à§ž”®›·‡¥@OgV-žT6€gÆ¡¸ ª�‡È®7‰¾z„oad\c_×w§Þ“´´û¼T.a/�‚ìÌdÍ[< ÖÔ�™¿S5dxJ¹D>…q]†{Š©zÜ@q¸NVÙ’þðñÐ=§'rðŠ#ëk7-#‘è˜ìge¼â¿HœÌ7/ÇíO™–Ow‡_6›föû×ÖTÂ[󻣱ȿÏKà�À)³|_<”¨#.D~x·Þ”lPÊÝm$—[xX�Âé_ú·vg‹.œÐáϘ‡VV�˜³…Å|ìm½égu=W +žªœeÜ"LJ¬j$ÁõcÌȼõ?„¥£xÜ(ÀàÛ°ùëYÅÜÁˆx;ÈUÔ&Sàmýš-Èæ¦¦ßϯ­òéØÃòò ìÜ80¢ˆÍñ ²¾¦ìÚö¡n›QÜDAðŸ�€Ü5Üâ.(Ô—¿<Ìs}Íñ|¥sÎwò‰il±([dI“0ƒ5Z–½ÁäÖŽóJç!(¨c{&¿x¥™„]Ë*+“ Žûñµ¾ùføpÓý’:ÁÃAe�fz„�r4Î÷–k:NtÚV‚Ø£cA*·3¬¶ÁCúê�*³'¾ûÝîåw~a‡f�¬3ÄÄÞÊÜë}™Gà0_ÏL1›’3ûç Ë©ÁÁ= ÚÇ—.Õœ65-³�ùü–j›JÆFÝ$TàÂײ…¤“Î÷Ã};c¯µ¢bô¯vèC îyÃ*u¾{€ìx=ðb+À±q‹HÄz�Û—´µ~ŒâuÎWrbÅ�hý{.�²±»ù5V¸5Œ‰`«ÿ(±å0¸/¾çøÁGÚ?bi»;t䉣˜¬ëÍÌDá½OÊ1YŒëàGÿ]&—íqËÊ@ÖÒ:ßWSÙ�O�Õ1Üoþ?y ü}8eÊW�¬b“ ãr9«ÇUæþ "y»%ÅU5µ/·{°Ù²:5Ñzâ¶Ææ €dYYLÀo7ò'˜šèìéí•Æ–¢ž–m7׈<hmJ§™¶¦Ì5!ü¢›fEªûej°1z=TS ˜Ë¾ªz‹�'ššþü‘I“R#Jà ¢bp¥EíB¸/ªQ0DÞ?íÊï7l½A;/J§a{L@/É%[`Ý ˆl÷q¹©²RŸÓDÖvzSÛB¢åSs¢S,UŽÁ&)5�¤’�ûö$–××g55ÉK®¯|ðùç;¯š4 õ‰Dúå­[�/,¹:)ÍÞpàÀÆ‹¦Lqüº#Ö¶ðÔ¡hÁÎÖ¸ÁE~�O’ì¾”6PÀY›ê×[­�ƒjº8!46ì§ë¢Q ÎÜ&Љe·}~ ÆH@ƒûÑ’�ì¡å·=Û7}z#_¶b…hö§P‡™ukI@š�}˜QìíÅ=XXÈ<¹j]C «'ÏѶtuXÓÜœÌZ�¾@Ìà=}{Moq±=¸}¯Å®¼2<…û¸ îÞÔÕÕQdÓ ðó-$G*Ë�,Ã÷jÌ�ìâóBG{O\·Õ°•HêàªØ8²3hhì÷e}ô ps\ƒÔoð ñïfTþĸlow›\ê’}²ŒQ•Há „ü±þ8Å=¦;¢gôv›Yßzú< Õ¨:UWí÷÷ ¥|ÍX¹Ná~olÏù÷y üßKà”¹km°5ö~Ý´[ün6Ð &ùÉÏâì·Άîd¹ºÇIr®ÍÒYDÕÌŒËç*“-¦¥Ô�M°rFV[ó€Š}×J›°#c_½ Úí;V Ý>��Ã-ãQp@ÌÄg†ÚhH‡eß×in#XlºVîÝ«^;}Þ{ ˺QàìAbnÓ´€xà9‰ÑrÔ@#v0bµ…ÐM¨Ø!cÁ@oPõƒÇoyGæõ"ؼXÐW-V.àS*%+»1¡ô$ù.ƒ¦A/SN˜-삽=ä<îi„»¹T2WT[¼–¬±¶½®ÍÙBTåhBúÇ$`ÏÔþ,ûK™;íL&­Ò‚qAÉÌÍ“]Þ¾¬¡�“�d‹÷gÿZ &/QÊ| î�ëa¤þÑÿß„$!ž2ÍEÐêw€�ìþp‰÷éjÀßFÆ;öÕÁºêλÑ"fŸÂÝÍÎ"Q®› ÔA‰Z9ÓSx˜%‡®)|§âQKYÃÕC,·Ÿr�‹i»£h07TfÖÔ÷öGÉF'OæuýýŠ• }ºtéÒáÆQÚKÿõŽ ì®méåµã}V…±�p5@Ë?ƒ §)È»5ù[óÆ{z0çÞ’—Ûb ¨$¬†ké/±d×ÍPúHTóÃq� !W @Ôó�»Ò´Ì™’,È\庪¶`Ÿ³y»½yKùoòxg$pÊ,ßùÈpZ…r>+Ët]Џ$ndz9��XEo6|ƒ*ƒnj]¸<.‹ · € ¨¦ýêñ×(ÔŒª„e`9ÁmÈK‰ÂÇÃOØŽ¼ê¸1Ôˆç�Ô—n… À¡1Tà'„oÖþš›+�€&ý¸N©}¿äÅ@Ô�° íHYn1-ÖÍD£Ç¦®þ\:—Þ)#P)×,fÕšövmÁ¼y0�ßÜX*óùAx@/EâGR7‰'©S�¸"D15L\CÌT�çšáe¢¾À%òä–Dç0^ØPD¹pÐ0¦¤Ò¹ò°,µ¡ªðÚñ“üsIÐXןƒ9MÆÆ h¶ÇÁpéñx¤�UBRÛûÌ‘(2�^‰-öùiÛLcwÀDËþMgnpíâÕŽ—åÈg'¡á²Ù¼’ŽR=èËy�"èE5[2©žá ¬[È%‚€äY\”+-Ó*ÐDS˜2¦Æƒi‹†Ã *ó†LÒO’ç.ÿHFMf™Yí¶6´öº”içe^ëÏ *ª©¶øÑܱ#ÊfÏ~²y瀺b¼o4€¾Èk}„Er„ïðëû^/_±bXñ:uC×àTé|˜Wy�«ÌÉK!íí‰ðn)«9)×#óy=štÒŒ '½\v‚Óy œLÅXŒ»pßÜÙYø Š«Þq$¸9rÙ»æˆùŒÊï]3ðü@G%pÊ,ß‘W·N¤,â³Ý®ªõC8~ô¸mߨönÚÔéfC‡%ÛMl3Åää?lAÊitḠãÛþâUØœ{z~�5NÄAº_y[mèÙ-ó+‹š_íŒ/ÂÖò'àO%`,Gcƒð÷›ýÌ2ã«DÑ=ÛtE¶¤X…á |Bº=ëWƒ�Æ›%xŒÌíÍ^7ÇHÀÎÝhU!‚:)èˆv$x4 îXAîá¶ . Óš ¼m#´b§")©žd*›™PÖéúœjƒ¶ù~XàXcì5ruaSýÑ­õ–¾ä(:Ó¶ÄW<ÌŽÁ�îÑsâõŠû1&çH_G€ÍJ™óàÓ­†ÑŸ“¹Ø‹}ߜғÞ\òÆ1’]TTD‹ëë™Ü7Ø/!‹£,jJýH"Q[F}9f»J#…B"§( \² ØºŽ£¸Ëòz$�£¡Q6™sUÑ É‹ä�$1¸^mÑÆB¡etŸÊìÍýñá£>ÿÑA¼Ô5ô 87®Löwcí©ÿX4ÃÈ—À8W¢jêãŠÈ—Zö›ö=­·6oz:Wÿ©Ï}ÍäºC(ñÈ�¿ÜÐÿÔS'â’GÚXÙ<,r‹ÒÒ2ÿའÂyÕs¾„%~¯·¶ä¹ú·paŒ\6½ÂÝÄ®üÔå±\¬ÿêI“°q{sÃàlw~,ÇJà”+_‡äÆÑ¸Ÿ˜r¤|úØî 楶؅‚èjïݰªÛñ÷-_¾\¸ýþ‡? I´Û*¸s·R3øì‚Êa¢òáË�ëÖvÎwQú Y––Ãjõ›233¯?kY?t‘ì¯MœF˜q ¶Úý’Ìàf„5›ú¹WȆdÞ÷Ûü`hÉŠ¬þˆÂ:eVCc¿/ÃóÒú Uà+ÐSÉ¡„S†–�3;“%ÞJ+“jgIOd¢ßJµgŒ’T禕È…Ÿ§3Ñ?4uUÂçIܵ=£éüÍ9tFY0Š÷ŽÖ×Õ9VÞIÇÞ "¢¬˜ôôùl»?J�Eo 7ôwþ¥�ä±¥�°ÞÑ{�Ïo"¨`Dh¿ØIk„JÚ• !qCQ,5ªKqûY”ˆé£�}S<ðXv]ñô`(#‹êð¨^êŠÿÐ' ·jܺÕÓzÂIávîû¶Ã±@ΞŠ{~GcçšöJà”+_gU†oÏy–Q —R§™¾L(ò€;F,„ÛíÿƒIºÉÏrO�@ÁPÎý:`�káëKÙT[ÛõrqëHõGtŽeVráe÷£dü{‹÷8‡‹€t"Ó­Vdármß÷:)/‡×]’Ô"�7-šE`h–õNÝ'Õ¸faTqÃ/²å�V@¹`úÇø±(“àß…hNª<ï»�°{W ƒŽQLGd@„±2y§ær*ÛužX¸ÔñûROHu«ª:‡»ytaéï¯íÝ�Ùò1øwÖ£€é¿¥Ó=I.–…²>gþiχUÛøªV¯¸¤âHñÎ5­½PÉõ¬¤ÀïÆ®â[=U�5oµè8p¶TZº›‘׊áeÍ.ÖTuý²º²w‘éå·ÜùA Ù8 5-Ñ-;,rýñÅ•Ex¼Ž]ÐFd›=;%pÊ•ïºîXUæÚ-1‹{=~n�ÓMkÒI§"`3 _-ĶxM*�¹óÚÉeÃ$:[û3¥2}J©}| îÕžäU0| aùD%�ÒK )›Ë¥rþ`$hʽwîÙ)úü¨ÆJ V;•’Ë`1V¤’À[’ºq¾O�.HZf¬o¿WMó?®­yCÉ8¥â3&]¿}¥, ±*·ÇN¯.JB«®ü±S·î„Eî©ÃKbß�µèRšê+÷û‹Tš]ímÌn¿ LrcÇt¶½6BæÖרni:\ê€JƒH*3'E«BÏÖ�Ù-�mcÏ�çD œòJ� c¡ÎÈÝ‚)/t ü(Ëk@Š^ÁAíE2œ¯E€!줊þך@`دזB‰MEªê‹ð^UKˆµlÛ8T]]=úãéM)Ý”»÷eË=ͺåžr¯84>äI—ºˆ:© `®uâ óyWH :DÌ^3Ó ‡Êa°MÔl>xŠQµòs‹š?½z|ÑaLftWÐO\’¥iÅCRÀLòe3(¼ìN 3d¿rkÕìóº·vµÄ�ÒDcÐ4˜*‘Qa;˜³Í½^ÂÛ(Uü¼Ìo?ùÀÿ‰Ã*mìugò½cí:㊔NuIn bø c\® ëi8bØ`÷’€»çÑÊÊJ¹%N}2ƒ¬ñ/áùêgòνuß§\ùd 1cZw¹`‚ï{Ùsƒ{€Ó À:VE9[‰j?f‡;F”ë«í9ê÷ÊuøÑMéM x!_[8kÊ1¥ÉËýÄpþU¿‰¿ó­§™ÿöÝ �J”FÚÛëî(PÔ=V¾ŠÅú/ eúcRÒ×w?×Õ7o^ÞÁ^ßßï” bÝ�C(çÇ?˯X“ć”î¡”—H”Ý 0ø¹AŸwp÷�xçärÿhŠ÷žÞ\Â%yõlªCPE‚Âtï³ìÜîT2Ûy7jži99\«þçÈÈ"°åŸÎ9ïò²Í/<� VNºÁff è>[Á®š;¸wû+~_é`Kó®¾?=þ¸uý]wUe¨ç_¸,ß\¢¸VT'º}swý#æLÏ/ßÿ8ån‡M›6¹�êéß6 þœä³öÛiy>8nAJnÉžT¼®?´¬VÚŽó_ÒÍ�CÀaRPLÂö¡Â`,Ó»5ïJxãFý¾ƒÑ;œ!èÔ© x½EdLr¸ˆke+!Ó a*ص„ +Ã+EšùÓ@zä`%ÏF»þ_UH«—ÓÕW¯xøáã†øUcç|�Û³øß׊÷6Õ�‰]]Z:³÷ŒY¾ uò»ˆg±‡›Ÿúqˆb7‘`Wªo†. nfZ9ÐxVz˜PkºåÇë Üð9çÙØ w‘¥;‘‘©N畹„‚¨<ÈŸ\PtbE—Âç鬛ò)W¾Î _9ÔWƒ$zðSÙøôÃí÷˜¦ýˆ`'6ï4ŒXL×yY.Ç£àeñËmH^Ãla1ÀþLSÉ nþäÒšk°�uÌè—Àö¾¾‰CúQƦ>XD„+½¸ÂCÅ©nYS}’èEj÷¤+îÒˆ5ôC¿`±“„ (@[¹€r}åÞUh¾å–[†­à'Ǫ‚"¹¬7kd 1É’;—I>}óì7R—ßñ‰�éÁY É D?—ëÖ3©²pw=‚,@É–¾B05Õ{¥ Ìw:„íëÛF çÚé{€[· *ÿAvðw¢yV‘éº~§VQð4Ú:!È9¦ûüÛ3  »Nýјìè –ð¯(tøéºîo•%¤ ¸ æÎt]wy¸ò³Êk–_ü™;g4 ¥Ì 3C„ë;àþ� &©<iŠó`�úæ[|WIÀ*Žºá' &PŸëdS£^ÛÃ&ïÅ㱊 ÙÄ|•¨¸~€-÷˦𫜬>p[OðL¶…@?hrùû3¯_~¡³�wæ.L,èFpï9l´ê8gf,uQqÈ €à™9ö&a"šÕ(§µ§¯,Ü›míŸñìÁè…ÅÈ-dÒ%~—ÛæmP&Éèm}}Dñ:£}jï^É0ì«$ÃnÕŠ”Ö¹e€r&z^@�ÂÃêAÕŸ™)å{ýxG”/8PcÒ2²os(¢(L°máJÛ¶P#�Þ qòUp4|S°…Oi��gŒ¶>ؘH[rÉCÈÖJ…¤2Eì}ßÚ–t ¾Î+à¿q#ÿ‘¿ž‹ÚsÙtàEø�ï ÒïAV¿.#¦ÿ˜‘Õ7ZÀlƒ€'×5 t\^âë«Ge'ëmn8œˆDŠ` Sñ†KP3ïAî½yÍÐPÐ�£m­.ê5èzXÑ¥2•žÚÚºw”°çtÉÓ ¦9 zD“ëDQÒÌ!öúUȺ™<Þs�‰s»âld+V0Åš€š/^>kÖ1}5ØEŠ"¾uÑ«Ü]±ÒMX`J¼¥ .‰!›s Œ£�ëÓ5¯|?[§Jù N  óˆUá(Jîµô_emý–©}Ͳ´�éàq<üfA�X�-Q `õ¾P°çS¿¢è^¯û¨ÿu'v”3‰ÀÜŠìÐÄ¡áÒA{*ù3þ�%@뫉ºe|Ñ.•è÷ñœñ +Wa”äF°ØO„›�9ÜÌ>Ð2/ƒž¾Ž0\^^ ¢‘Ajú"%i9üÍ Ì>´¸€o‰ù?\³×£VàIù/ŽoóT~¾áö/—QÅ;+ni¥Ì¶÷¬^ùÀð 5÷⊽™äªAj�1ø–þЇJƒ;JÛ!ÒQ”/UX›†ÿ ‚õÈݼßç }[�/"õðOZªå­çNå\òmý}xÛÜŽÂí—ƒ³/7)ˆÒµEM�ýO××¥ÁÁ�‹ÈÊu:5v؆R*0},”‰à\È‚<å1"Ù¯ÁŠ97«¦ý Ä/ô¿2dŽ«wМÕf—”BµßV÷]QY?“A�¿Oœù³O¥S·l™gNiiHïéé[á$ÌŒ+nFtãáÞ"Õv�z’�Cê8Ã3Sru˜¨Ü7!ævÚq—ž–�-hSuõUA2ºP}eØò^ŽŒN±�{ ׄ„!¸ÒYne3ɗꊎÔ߸î¶ÏÕØºt'"�%–nI:å7YDú¬®ªRLþﺬª�0{ºFé¤W‹²{zeg'ŠIä�6 8¾J“˜�†E¸…z–Mv¬»ûͶÉóXÿ¦#/W�ÁÕ¢rÈø©JéæwÜTü’$By1û!Ë?§¸ƒOöV¾|aY¨—£ÔO·Ìo_|ù8bòs² £Éí&»mMúÃÅ“+{÷"9p4g·øJîø¢É_mþ0êL}'è-ø¢a±ÛPVë×êΞ¨:¶ «¦õ -À>õtWWá >˜–•\³’H· È`XÊV@»ìùͱÀ™ò{Ÿn9Ÿ-ý�å«75¥]D,�ow„(åS§””Œ8ôSYî§(¾rÛõw\™Ù½qCZU×¢”Ì´2)pŒòu¨(‘$N&;´XÑ-:ØÛ;v›y¶È-?ŽÓ ��--=H5復‰ŠÉ–¨¬<æy‚³Uy?òê(’ŒiOEØë–¡ªÉǾ"­ånšzxq¥wÏü’t0¿¸îŒ*^gÜ FȃŒÝô\“‡zv¦¬¦>„ (ήšô¡É€•�“ô¤U?…xJйÒˆÿdHÖ_êëß(¹å´çü“ªq žÕTdS܆÷Žy7ß>åG·­Íj}MÉ„,į̀º ¦{Í óòK?û¥R\–ÿ½9Â; ÇÛV¾(ÓâeU“çq¦O4+µmV4˜€{wÙ²á›èv$QA·We$0m„€Rwát¤Iâa¢�Þtæ˜-’c¹XŒ£¾F´…ŸÍ„Ë&W!0p‚ø4È&ßÅ–€S@ÕcÛÜni<4ãì”-.Þ~\¬ÀQ¼ò”9o!¤® ¸C)u,ĺ}2Öî�ÁrGÐmʺ�~üM“I÷v µ5\PSÓ‡6‡ñ²Î5Îq†§KÒŠÞŽŒéu6Jž�tæudãé%Ô½éÔ‹ l#¨rò°hR_[ºÓдŸd4õ>_Pþoó¿x<«3áœEçbWú)àêG©Øÿ6�šŒJ!+>õàâB÷˜»¾@Ë¡œÏ ……ké"ÈóˆøL 㟠ÿ·ÍíàD¥mæ¹ J7ËD»6Õ -QXÙX‹L�û@Þ»°#~=Š÷.C-ˆÒ3a¬­"ÒAžÓ²Ïo¨)ë\1ÆOµ¹)@Õ÷ "Íiú¹ GiRlöÊáôÿÜ�ü�“@G–”Zö”’+6o�ò?�SF#ô ­ý¥LV~‡gå9CÏüJfË�„EÔ&Ÿ‚.ž ”�åûÕ$¿æ†uðÒšpÂé¢á`4’ä’ñHÅ–”KZ áI+fòdõàŽÒ;úÑ©âLÊ%qi8”ØÕ“¨¶á[†aý@GÑ?ŸKü5àe¿ˆâ@»iÉ®ùhIIf쀋¿ö…K“2{U¢§{mþY©°x3IÇ.08›ß=ðÃ;æÏwPDÈQA•˜® âS­¨^I!±ƒv®ûøvÇö‘j$pÂvíïmöÀºuêôE—ü¼$ Ã0À‘?Q&rwã�rí”)š£X7{C/¥±ŒâRf"ã�‹‚òº%i”�¸kQ4þ_dŒ¿ML ®âöÔ€ x?P(¤µõÅ (_gE>†›õïkþüwŸoiˆÍ>oèd,E×b$ä?6(æ%iK³÷‹¹™z|"Üd1Yx‰H‘!fÙ¿€csó%UݘýðN˱–ý3Ο/Ù¶"Ú&ŠÒû²C(5°ç´àŒâ3s8 JNÏN^Äî^¹DS@c°–"EÓ‘D¬QÂ>v@ •ž…i$àÆNà÷#H çº ?ñÙÒtSßø(¥rYV‘K~ØÑõd(º_dîÍY-µŠ7åœëGýäêê^½{„"+MæLWÜÎŽ y4áãÈ©ùÿžb ¼m>_‡¬¹Èçüó¾Ý¹ª‚Âs.ÀZ� pbG¥Ï7ŒÑEa2Í»»:£­ûÇwÙP¡9Ýþ‚ÂÄ«QÂ}ûÞÞŽÖBôUу$â)e˜Ýh;DÑ–)øB�šöx:”îôôåð)~Îêæª««Í¬KND}rß¼ gØj;àj¯×hI$÷†§‰Û3áw¨å&{ÔÊå~âª.Ú²(à>†mò¶d.é,8b–¦÷9eв¦am ¹:¦ŸAå;vn�F:ÍtÚÕb'›f––ŠCí¸äž|n%’š¦€]hê€ßý§‰>ßèNà@J«Nd�ÏË»Ö+Èß³5á÷ ÞìOõŠr£ ÄëÞÛ“%O>ô=§œÓè"ã´±µÙ• HÉÜÀ`:ö¯Ëß3а;¦üûS'�·íó=:Û!ÓÑ�]�ëa‚Ž•‘d§vÚèÍu\¾îîx{ŸV�2Í{Á^6ËîŸaÐP·X;êosü|9f–!ñIŸ(]­ ró�“Hƒ}} ÇøˆO�ò-�ͨé]ý‘gIp‚HÎVyÌxùÅUŇ--ûQ!Ÿµä­È»#\åôâIe� °Î�9wä-Ç–H6ó âÖjJd; ‚]¢ÏàíÏayRŽY9sɦöxÆmñÍÅ�{W°ôŸÿÐО7(ñÛcÇÈJ`D•oP‰ö‚¹ì •y¯¹sgGÇPk1ˆ.gÞVÔzÌR9`šºŠÈÒÊ WI]Ø×M “�¡hÐ5ÔŸ’è³>䮫Nºôÿ*õs°Mc¯ÿwì hF§`›=€ÝNŠHUÕ_·n[,ïôe˧öƒ°š–¶â«YréœÝ=ýK^EE ñý๧ûk?è©€HØëó”_ÞŸëï€YzRËTËRij ~å,×ϯ�ÖþxÊîAòßÕݺqóSûäxô=€?â¸Å)Ûøœã°š± ÓÜŒQåû`6Ix6ŠZ3è{fMEAÑàÄV‰k±?1Ë[…Zm?Æ~æ§©¸ù3NœÛ°t¯ËÊ%Ce»{·­ïûðý‚-°Â³´ôÁšãÓ&ÿq"»óé&Ç`>ìœAâmM¦¬/«,¸m<õðöFózrÔ:^±b…‚rV_êöø‘-ÖÕû$pçwkLþ¨Ã~8íüåSV�àÿ|ûíN�O¶¤ûúí¦æ©ûO´Ô‡Y©b1ZPij—´Ò÷&]÷`´/7-ÒŸN[ßv%¤%»î9¶çå€ïá«Oj[°P·¸î]‡€ÝAÉuÞ¸ê4wÜ£ò÷µbD•¯˜ ´©mëpß¶÷H¶È;gÈõ° 4Ü»¨“´éxÔ×ꕘ½D�”Í1 ²9Ty'‹\�dóÇîuJÀ´°Q!ˆì2>}âÄE7Ý€d-[ùiË/;v[[<ˆí@�¡=v·[€Š­Ñü÷‚€fÞ‘þŠfçÆX'¬`Æ” dÓ¸ãø4$îôö´îòeg¿bQùÖ¢`…Xü�νÓP'i�´téâÊãý_øB}¼·o¡�Z]^¾Ö‹$&8ëÜ)³²ÊöãºîÁk¯™P1�Îu��Ôä7«Üž“füZi[ö^ÿ¢ø·¦9UbÊ'�ý_ò“þ={ª‡ï£{»‹ßÛ=pÅäÜ!Œðà}Ç^ÿ> Œ¨å+š0¿¤ü’V9vwÅH‰ô)Šuœe"VjÛ•> bõX#[N_1È£Ìó–•K=H=š[î“•#–mmqlk=ÞÛaêóãöÇÝëïëòØUgœ¨¤¢ µ‰óÑ“ßˉö›°$ÿ& óû$¼·ßGÓØÏüaå®ÙÙÅ?(Î.¸ XØyã%ëèÀE±Ú Ý| RlofÔ‹sf©©¬0,)öçm�]Es¦ÈÖ.uMųl²(�°qU¯ø4T³ó²¨CKÐmÀ¶ W s§ˆäa%÷vç÷9Ôû9’ùÿ[±IŠqº= …E™¥•ðPÃfO¨OÊs€"ËÕã%¬Ò€R!OÂÆ¤ñ$þÅ. ]B`À—8¸=·ÕL+¶EÛèLEU©<� õKˆ´TôûH†ò¶üê¼3 Æ(jði7¥ºbûlË-´)ߤpÉíM p5àÖ îsZï8æ³J÷òOýû²%SÏ9’ÛS¿·º¤zçÃ{íNÔû�ÒpTås’K$=�úµÌ}) 7 Ÿú‰’i9­²ËcÒf…fáY&àÞ‡p{€»Û˄ˈãÿûŸ·|a‡­gmŸR¨žñ‰O4îI$ôÏ-½¬ø¹ƒùh[^Ue�JšNvs²g/À™>T!ø’ÌÈ@ÚH d«—Hp&odzê1äêÿñðÓO�¿ÿ¿‡wt\`S#Œä]Î÷aŸumç Œ¹_G¨à‡@×ü'*R< �ÇÏyîmØr „Û”¸)Ó)òªA9ýÊŸx5co€ÐIÒn OêÊP(Æû�}ðgüç¼ ²uºK/~èP�Ä^,D,®¿b°Ôö Æ5¥Ë³÷ÍÌ í?/÷-ž+ÅÖe~â}?UT±TèüËäÜV‘À‘V8òùaMaÍAUõ|éBÓñB ;ÑAr¾Ï ßD¶à†í؃àå”/ºðÂ5×|jÂUW�9h‘ÓiLGÜò�ûÞ|“çMVŸùö=yªêLO¤­ e‰nÛÒB_PJÒ>ê>gÊô#“´Â×MÃÚLdøá˜üp¢qo))ÉÈçà=÷¤ n¸aÃE㊄Ÿ×%Ø�g§@WB<ˆH†÷R‡jÕùxÄuqI–- ¤9ËçœgäŽ*güøXo<þb~N89s˜¿ºö$H([ž>ÔœäZ°¦µªsk·Á…¥‰ªì¤º·û×.£ÿÜý Ç™`»Î/5§ä×z‚KÙéHw˜zìõ“$a`‰™Å\‹Òâ‹þõÛK?¹çÄwcÇ_—Àˆ)_¡tW<*\·žûôáØ²ƒGúª}~°=ÃÞp»gËmI§ûÂ*B5ww¸,ÅÍþ.¯ 8 LáZIqéÄt#!¯W`"³€“b»„÷½'cgŒZ AM?ÊT:>¥ë[D’$+4Ôß2"Â8¢ZæEl‡§^pÉ ŽguÄôÒLJ:Õ½+ñ%JÆ{?�OˆVzµ’´—×ùÈoñ°™‹Ï ª�²OB¡!ˆæ_jº�Géü¥ó±Æ\Œ¨‰IÎÖŒøwf—V>¿¦¡÷ÉUÙVðÃ{›ÎQÂÓ%b©&'¦,Å>’vÜ|p!YáYT?—½'žUIþÔ{©,õtǹg:<5•ôHö¹€ý%™ýo|>bn‡GsY1JŸ®Y£pâšÄlÃþâj˜m6»AÏ•YžP± ºúº�bÄô‡ÏîV•:ÿæpéG»÷w^z?àA¢ókÔ ?yëø1'ÿÿÆT8=C,ꄲ4(MÛÞ %rèƒHÝ ¼SkÅÖØL§~ÊeõÁåUÄé²&c³ex œ|È?Éü "+SÌ––3@Þ©MÿÛß £åØoò©K.š£•UÔ£3xÙrŒÂƒŽ cùŽÂ¨öÁAHÚ¿L.k=Bû6æGs7ö%�ó\î^ï—ùïõúF‚'?Ÿ | Ѷ‹À{m{ìP¹ÈMºû¾-[2)Úb¬ÖîêˆÀÞÿvÏÄß Sn{Ä0'f1í¼)Óç$lóUÇöÚÀ�Wh¸éå&g×qƒ-tUVóÔþ† ž²®µÕŠPvCXQf ømÄKùýêŠ Ô–o D +Á*Óô¾0¾`bŪaQÛSnìØ Î( 8éãèó¨Wiºî ½é,¡’ß©5¥yæfùšpŽ·úª«„öƱ#}]]̰m02ÚÐ5šEv騳؄\úÑOV¼y$ž³½£÷Ó“¾Å»)Ú»Á©ýU“ÿƒrÎ/2æ•Ø®{QÉÂ8_WQ¡×d‘¸g[�àšµ€÷uú³ÜsTB/’¨w?³Ì{‘Ù,Š’^©)êÝ›Ÿ}ãÑc~à=ž'›avs|ü¬ ï½w ñNóS|7bÊ‘å6ÔÆŠTgœæÅû{-³˜V�´ÆÝH¯éH?‘å8ùUU™màŒ‚WÉDž„óa«·é*K¿Fi®¤—´µõ}ÖâÚw�.ãÌ™2@ù¿Ö™±ïGŸBFU¤ûÁÍ{ RÎ? Þ�Ÿuª�ÃwyR¥),¾ŒÕí,®¯Ìõ^Põz*¾�ÉûªÁ¤k×ÙÔØøW3åÎ8¡¾ÐÙPÿç’Šûs”»@ª²ܬor¢ÞÞYÝ|n6Ü œ6£Â+¶cÿ6­ô[Â5!”ö±ÎºŸ˜U^o[ä»–­¿èÓx˜Ò:RzòèW {žJɃ)B·’Êßýë±£¥tÏÁƒ>T¹8aYKIÍ[\-gœÿ—’‡du¢ìüÞ”Ev$²áýã£Í�IFqÿ/ÉfìgN ì'C[7—ó ˆçCLá‘P¨ïŽ“Ìd²©z¸¬”QƒÆl×RI´«¦ˆˆ"yÅ#ýþðÇ�i±$TÖqŸv¤à3"¤ï4éæˆ5Clûý“ç^%ËôKäMÄòÓdÛî§­C{Ö/X° “¨$dš6¼òÔ@²'+7�gÆ™dJJGA‘Ö‘ñ%kÍúÆž)qæÝ*{òÝ6k%.NúÝî�ÄÒ‚ ¼@ejK€Ñß"¶s‘ÍÜõ[Þm|ûºmÑÃn7öö˜FÌò­oÚ™°\Êr<Ñ`R/Ø»l\QƒÛŸ¬¬<úcÞk«WÛ;Û3ѱº²Ï¹8øq€ýHâlÜ3P½xµÍí¥Mó«Žêþ�ÆŒÇ~õÙqþ±6�½œe�Mr}Y–÷-¢{_O¦Ü+«ëëOêSÔòËÊ=ÉþO›ñßé>M‹Ï�×áÍ›c˜œÙ&ù¶Ï_êë�ÿéPû^áÏâ@-bhžvžö×lâ6"A¥Ía|û âýÜ‚¢·¶'儲Âßtm~¯ìçwdëç]­ý7oîH䳂ɾžÆA‡Ü;ùHÈ6fúdéã–ÍvÈœ4éžù™$1¯ñlçç:Ø>¦xÿúl1å{ܯ°~™»Löh�öUðµÙD:ä‚– Á³pñ¹K§ôÚý~h_Àò8’É#‚„Þ»Ýà¨úEÂH}5œþAˆX¼`\îöÚìì–‹f$}J½;cgŒF 8м„2~%&k1çRº²:“öšá5ÁØÁOÌNêH„kDÛlµ+®â„(NZº|Š \™vMÇ1]nVè‹U…eßÝÝžX4xýh’] jÕ©4ûIø¾‚œ”~;éFÑ¿Œ[A¸eÞU9ýbs¿Ë=ú~¸öBà¸xΣä%•K3Ͻí…Cà;>v8Ú•yû¼xËZ¤ûÿ´¿×eçß듼W‚ýCk_üå”MÎ¥ÖsI]y&ð 2¶Ås=v¼£FÌí ~åù–öù2 üêim4Ò=Ïq­"”Jw-!œiœñj²-íN€þé¥å‘CPÈŠ&å|V§öÓ%ZÞ¾Y…$5èËkâÁåý¨˜STäµ´´�«ÊJõÁïß±Wc_Ž ¤ÂW—]ò2²ÖýVê#e¶ÒPZêÝÙÑ3_VÔñå†ešñþns+êŸ(èZ*ñà·]N>@-V;-ß·Ü"ÜW9ùÇ åÕà?hËÄ8 Ùë¹pžnÞ8uXÁQ#ÿ#-‰ëáËöôöh®â¤Ré\)Áš~µwcϘá4(¹·¿Ž˜å+nmèÁC Ë´PŽVTTÓ9âjš2¸B“SRáÝͽ§×gvâtÙn(hÿºy\Þë3 Hr¸bu#9óPùŸ«díšœžÄÇ'¨¹ó†®¿½+cŸŒF |kΔf#ªá:{e7gwII¨Q4ϧªÁ]z+¢ß’$~}Q~@dÁÙ3 I2ß ÿ£é:¦¾ Ba%%Qæ±âšßBùªÎÛ­ïÅÒúí°øbN@™¾~ºÄ| wõÏ¿·sPñŠÏ€2»‚32 …8_‡- L ÉþtI4¬RI:›5Jk~iiíÛ\2Ÿ©.íýÉrbøýÒ}�û¡´­¯T(Yˆ*?a!¥UQš¡�Κ€. v ¢¨Áév5uw÷²ñg”ýú&ˆ‹&ƒçx¹ã’VŲ6¦«‰'$BÞâY®@…B¿†àhküªú­€«ºp/–ã¶ßw$©®ŒjOâ¾™…ÂD;éYT Y¬R†“±CŒöú#y<óFw ò÷ƒõ�¦ý¯mÎ�<™=ÿÕiË.„s¿X`Lf‡B2yÉ) ïLí ãF‘»d<-COE�wÔUC× ß…½þõ¼¼ƒBÁ�d{ÇîuzK ;•*î0ÈfÐz×{î‚8jâÄaš%™ž�ôÖ‰ºG¿°­9¨¤ÌxÚ¡ªòIö�TÉùãüþ6ÑÃ­í± îºÎ9ÅÙ9“™ôB™¼Ù«qô`u8,ÊËŸÇ«íýÿÄÀ*¸—°xÝÚÜÛÚZ®( ,‘™w›ç²‡¸|ÀLöNU|òy¶ Ž3Û<Ä%å¹Çö½öá–´Øj,x¾º:ÕÊ*û:v¹q‰Hë%™UIÄÞÓ¥3 íßekòŸt]²‰%_¾îƳBÐ¥“#®|Å*™"ٵȫŸ«Èî£[ÖÊyײ•¶Gʰtnsew( wnn6PèOX&™¡E·÷&SãÀÚúü³³3TxÒé2À‹n0@ƒòÀßìÌ íS¾eDGÙׇu}<ôéF¤IdS⤉G-8lX¶0м”¯â·ûif/ò0P›z…–ËZmf.ž�±á e+.†‹¨ÄÓƒÀðÏGóûº>qôþÏK’ôA6Û¿Ô6ôúÊÎ;¶2¹Ë±Û£ªü>Èô:ü_¿8·,ź¼wÌ+íƒ|Ž“£�°þåÔ5. )Z®d˜¿ëBõä€D¿å8.ó1þhš8ïÃs|_º.o x²Îz#jDÝb–TT>ÉLk¨L$–U:í=ꓞ‡ß};ó³˜É–õI† xø}ÈÑfX•T ÕØ„õõ¦”xÂÒL‡lV9ÛÉUÚ Å{Öl …,Ç”aèN£x&ñ9®Ý†Û>Û¥Ië„Ë!�L«XÁÝЩ>L¤\|?±…*Ë¥9è$±u>ÎMu¢âò}bÕ*çÞº6ÿ1ØÛL£q �>Ü�0ÊÝð›‹X‰ä9_’Ué_ OoKQdâ6—ƒyó3ÄÍÀ÷”ç/m—¬õëB8oÙ´w/Š]¼ýXQ™Õdéì1ϱá~-Ÿ{öXŽ] ¢–õÄéFîÒ0“¤EÁ9 œ¶pûŒF˜ßÛ%sòON V?ù©Û§x¼gÀ�"#¡3 +P6Œõë¤+fÕ"sí5Yæ–œ¥N½d‡ —wÅæDb[¶î-c²2‰–T6­ÝµË+(ÿxÛ²ÔÑ) žtÌÿ¶†��ufJ@•"ÀîºÔv¿¤3g/‡KŠQ¸�™-à “ÍóKTV Ï Žf¶]ŠêØŸ€CÓ�šê¿Òë ?vëø˜ç]ô*6®oê3Õò¬º£¼\•PzÕ+Vô”’ðü0ç5‰;3<Î_ûã]÷ >rC� Ü p9½ÿvLç.péqM_¡Äßà„ߢ„ .ÜÔMž=/—ÄOïÕÓóEÉòŽM}z9Õ }=Íþ€>—re<÷ømmiq€t"篼¥ŒiÜúúúíµÕÕ ‚9»ŽS¶|ÅVNˆL¼fQCmDÖ % ×TWç ¬ _qÚÀäWÚš²Îï�£ÌE†gõ-aŸ v}d·myI)Ç•3gÚªÃ_W™Ë%&5ºížœ·muÞºÃØ»Ñ(�€¤öCáÞŸ²ãÏÍÏ�î¬)ÈÚ='?°sznäÕ™ám“#Á�FË�çÃñØ“©®Ôê—ý‘û'Áœ“Êvó2óE°vínjÊZ½¥Åw¢¥å(l:HC(—¤\Æi¥–Hçôh”§èÛù¿©¨ª¸|�Ð܇ê_õú¬GÎ û·ßyç�NTó@r½*îEÛQ¿»æW?Þ]Sê~OINK2-½¤ÁË×™9pí ÚÉä´çàž6ÇÓÿCuœÿF½½›“¾#{dmowû6‘�%ÚÁmo¡c›‹õ¬ÒóÖ¬YÛìì:NUùÒ]±Xt}ŒD·"%±âü¥SÄ€ôM1¤ ÷¤]«ÂW8)) Æô—<ÊÉžWT¿$ •@kÓU8Ÿb%_\`(L6]ËšŽ:®)ÜöqÃ*\æ¯ ø•ûÙ5Dgwo“zϰhýhAiéÉ8@g{ÿÒª·TâÞõ¶ã^O÷SnX.„L2Ê•Ø4ÀÌÎI™æ-MþY�îÞgËä>à|þÁ[o­3OX¯ññ¹ >\›Xi…ж¤%†§WØDž7`Áæðœ�&µ÷û?.X1À²Ìt”~yº·A)K-.Ÿ³áýþoÍ‘TT¶Fé fFémifü :+«}‡®À?žãGŽ&Œ]÷‹ó>á—¤¨ç~›1æ¸6“46fv\ ³T–…äõ&^ݽ÷ê—MÊiﲋ4ÂÊŠã6͆¦�î …2æÙh—¯Øþ§›rl﹟l[= ¯(=ô@GG $eiH>~?ânS›t!1ðëT‹>x؀𫇪ç\ H5á¼é–K¶x6¹½×Î~mÚ‚÷DJsbÁ¹ƒãë»°@:öÅ~)™ïó‘_¡zÒr^«)ÊT''YHDðÃ\ Þ•.¾4“ÎŒ6�æq8åÎ…i¨IEq€´Y:nçvåçgî)³¯®îhI—cJ)H‡Ï9 ö“ê PG}O�°+µ ' …;(ìiÓ¦iàÍñ\?xSÜ(BÔ§ÜÖÁ{�½ž° ª‚˜0ã¡·šV¼~Æ´|(qB–‚A�äâ1¡­Öiß“YþÐδ£ Ja¨t<8r地n#¼CÝÔâ¼î²?W¸¡­oa~$vžëXYšGv…|Ò^š„éÎ’ã¢ÉemSÀ'hW|ü3QYñÝ9•†¾C³ƒ(=D?̵©óí€yL$f±LXtÑl€¬ïÄ–b&eìeF¬¯î mC¡RãˆgjpŸLæÆ·*\R½jØ3›îï�Âï ù¥HŒ¢¾¸{™Ów·ÌXÜ€eŸ§:Nri 2·¬¦†­[y[™ ࢣT Ÿ²Bó…TXÜu¿ê;oZii&â"` çO¾âò›nšü2ˆçsqº»ùŸŒ¨Ò¥p¸-{6ª÷™–>^­ªòåååY¦k²æ1[éÂSwœe)|ÑUÐ=þtMƒ:£<Ò§jcß nÊ¡šPxCϤè Ê9íFŽ…)qÅèÝK…ò| ð?Wngœ¼¨(^évT{Û,0½bLŽY­Þe•EM]±ôËžÓ»û²š£…MSU³¦‚ÖóCÀÿÉ4s^>†t"‹>úÉr,¼çñ µ½Çލ1lJF×1äc=…nypš§jÄ ªª±Š¾ÖÖWïùDϲ ¸„R/®öqéOrÆÛGz?¹¡±±½¢PûP¡¬DÂy¤·A7´�Y=¸‹'4=s¦;‰« êœëÊD¤(�)ßS¤3ñÒ;V¬0_ïJ<Æd¶Œ8Þ�´¨ô®m ½/6™í:y†ÜP˜/n|Ö%—L1 òida�çÛ~9QU•qUÈ”‡)\]HðI!T3ìè\J:V�_`·×!JÊñLßA_=­ù«Œg¢4ßj³•ŒCªl^YÉÉí7Œì¶x²¸+¥—©¥3x «‹Ý„àï|XLOÊy¡ÿª%$%(N5U-sµˆ×öîéSC¡ˆD V¹pxC” K¤#ŒØçàãWÍåû+*F7{Þî˜Q7̼D:õ˜/ }–g¡gÛÏÞwÚî+¦yÅ>Ÿ‹h-³T5!²Þ†O�VŸÒ àÈ^Çp5T )"Œ] õ¼‚pÑ/w»©µ‰D»¤Züƒ~.Î¥Ò¿�r1“‹Ò¡\÷é½»kŠá‘Â�­m‡¯XP:äãÿKm<>?eÿ,ˆ!6³Á/ÊÍÝ¿°$´MÕ´_¤¸9¾ë©'%àó]\>gi@œ'°–YÈK&ÌÝZ±XA¼±1ã“ë±Òȳ÷€°×yËj8Ò<î‘Áæ�½ža˜ ì�>ü‚帿ú© Ý|Ïõ&ÀÇ5®\¤©òrø0¹C½?Ë®û•TûÑÅŒ•ˆ óÖëµ齞Ÿ´"A²ýYïK»Ò‡�]g*�«�§Z™J»ï×¹®^}¼�ó ÕßÔ\—š¶Â¥<%õKªôp2�”:mëVÕÒ¯8§¥£æþõ�Z†ðJlNrî–s³}-¯?óøAo/c‘ä.K5™õ:¡¡KQ›épý”`{h¯³šü!0Þ�Ýð›.ã·TÍ+.Â�3÷>·4Ü (á·Áü'$`aqõ®ÂÀíPýÞwû·7–&MO[\˜ÖL‹\žSÊd! ?ò™A;eËwV.¿fæͼ½vvTäÓ­š÷·MõÚí(û}{Á :ѹ‡ôDÖsøRËÀ¹È¢€Ñ‹ÖŽ»'-goº¬[Eø|áãuöõ…Ì—}¤«¥UT:>É<ûè쑯ïÓKܸÓL¤«•›¦“-�˜‰�~=¨¹»Öí^YC†¶µk›{ÿ�{öÁn§¾'嫇64÷ÌseT17pLwä°¢0Ó±çs‡o}i|Öë£Ýò½Gc4T竾hýòß‘‡:`#ãà®9¢)ÔT¸ü¤gSBþІŽMO´^uÕUH<ºEÁt1僜;b± �k}yVÛû»ìƒyòà غ0µ�ëDî2Óï�%ì{ó"Ê—ð÷ͺë\ÚúbtãðjÂîöìb�¸>—³öM÷þ¤å‚ÿí|Å–X�ž¸SUù/Áß ºY{›ìH)½R ûz jgâcpÊÊ÷¥¦ÎJ&ûÞÇ9ÕídzwÖø¼m~"„ñrÇÀÕ &«&²ú€ëë#>íR¬’%ºß½{ûk‘M£¹Úy¦Ã–¸–™ÀÊø¼iõܹº™~ý²IE™Ü™(ر6ÿc$þ…Åã!¤»ÀS&9aÕõb±dVaa¼è‡u]SƘ4Ñ‘ê¼â¼Éñ�ýãõ„•-Ûj¢WÖ'ø(IxœN ±Ð /Ü÷ˉ÷M×ç[ |®èÓc �«lJ¨ìWæõ¡hW>,¢Yuº¹-=ž”œî’ÎÃ]H@±ÄóÞ‹,‹×îË;ˆøWfwñZ{"ÏUƒ€évû‰£me‰2Wº´§¯û�/\‰€çU¸ä x /`’7ÞL­)ËÝN° >åAÅ.Ú"vÒâoa€‘‰³éçÞT€ßG©f¬0HÐçñ/#Ãk¯£Õ²¹wëºß W¦¸þL:NÙí�’$+a[Y®ã„AžnÁ3ä—u\ V37‘�:ËóÙ «»5,Ž1³^TP~ …5�­(kR‡ïBEɹQQé{¢QxÕ 0—3I°cmýÇH@,ìSB¡¤7ÍÈ�œ­i�s »*N¢xE Pþ|ÛºüHãà–ŠW|¦Q)Iã¨_Ÿ`Ê,™+ïÂó~.·â ŠwhþŠsGÛ!¶êÂ¥ÐÙ¹þ-¿¶ë<„¬³àpLu[ ǸÈ2í5²z±Ã´þÉOéûûK§�ÿÝM›BIOžÊm{ÂâÅoeœÎ‡²È€c¶fƒB Ýu5�v]0©´¿ŸÃàzˆPF§ÀI¿Á¡ÊÁÅõ]þ÷~êS^hé)Yƒ2öƒrTÄ‚bn¦§@îr±Oõj(s"ÙŠÇ¿€JÖ—ã»ÔYxÂòÈ-‘ùï.¼þLz=eå«$ä489÷Àï¶ŸêÖĂÜ(ÿ2hü”tšÞÉÓôÇ ÔXŒÜûA;)ê·e°RíG*h-gä\ì㲬ÔY–ó7ãCœƒç޽ŽIà�$�Á”¶´øTið¼K&æÄOt#X,}Ðr¥I(´ÍåÏZÌ]àÒ:—‘�ÁëFëë£WßT´(Rx…;qÞ»«G¸;‹öûMßK©”ôRkŸU*3É€2ÞÑšè9S Ìq„Z©¸U�;~¦Áèdaì&µ†ëQÄ ¬ú ƒ}–m?‰$ O™X‹EÎÍtÇR©_Âèú’3¶ëiý‡‰¤©ù³µÛ ñßVÔ_ætô¬E=ï#^±z¶Iž�¯ø!0líDÚòíVŽìGjÀŨyx¹ªB–A�MPX G~ý™ðþ”•oWk³´BdÕÙ‰ãK¬ñ–-[ÚeÂ~ng ²^vQæÝewÇv̦ ¹åò{ Jy£i�é]6-+Ï~• FEDóL˜§s‡?àÃß‹6‹-êð¶—ƒw \ÉZU•[q™PÀ‚¢PTXyÛâá Y0s‰Š^Ò˜ä¡ëÂŒ^��Z-7T¶Ãï8úÞ+ÌŠpªLs]sVøœs2I+Â÷zÙ¤P÷Êš¢nÙâ/ƒÊì‡WŒÏï‹P弨ª4wëîœÖî�Fd§Í´LçIY¶y sþòýlÕp²Z<מÍØÙ³p_|4`°U[Ûû.Be\Ewé¯À§óé#ïåPX½T’èµ–ëåÂ_X¦ê Ç迾î()þ�ЗWetÄš_å–ôX«Ûÿ¬Ï´uå°0ÐM¨@ò0mª$=o[�ÍC�AoNYùžSá·aµÁØç@>ý1WÁöîþÙ…ç/Ï>§0ø¦Gä�ø¼‰úRAhÉæ‚‚¦G�}=M=vÿÀŸ•NgÓ,*REœxpÏ{=;% 0ºWÜrKÙzˆ„òïë[ZP5�Pñ÷a”°‚®Ì¨Ë- Ð]ïìxÏ÷Zž•¿‡M|@IDATUé+y××þµìz÷M+Ÿ|å§O¨f8Ö×á z¢ؽ¥ÌÞæS~N÷‘âšÝï¹F YÙþJ,ãóÞæ�Î)ìr_}nÖ5 ÉW!Ýf¤ýî,(Їer^Dó­kJ|$I¥É b.TÔÜy(\bqœ_jGÜí¨Š÷'øy/�ç7áf8çõ–½}·l¨�v&“‰ UjÀ¾ÚÐÝï{Ôþ5ãüãž§ž·ú è`›²÷ìYc¬e £Ë¶ÝŸÁ)|e^@�ës¼WAkù• ++ÏÈË)O¶ß46Ú)Óè0=’%‚ÓQ‰Ô/VCÃò>L/ B„7Á�‰RK�ÍœƒÊ³Œ¡AáŠHå¥3Êû$@Wn¯[ÙÖ¦æ7÷}€*Yׯoè-Åy£Ü”ÄØë‰E"ù²§�,Ï]6·¤òBÏ’Þ•Ò¢+žk‰gÏ+ž¸´-M¯-ãbpšº«Ææ¥\ç?¸áÿŸ2¿ô>ÉG?b­ �{S¨mñƒS UÁ¿…Ä�^˜ÏuÀ «à2ÐôhÔ@LTû| •�*qÕTdn«ùýoù}!±  å'‚WÂ÷º¼*ÿ‹'6‚ã!Ãæ… �èïJÇâŒÏNZúf\B‚ �Ø™Ò/(°Ž|t&¸"@ŠÎå¼iPýGŒZŸw%úsÕf[‘Xy÷üE—ìhŒE™ã½†Ê!ȸ¢-ÍG�ˆq´”¹ ×™@êëÜ49c„ö(ÇÒ“I¦<ã¿ÁÇ¿ÇXWè’ç«- 7…8÷L;NYù’ @úˆêÝÄére/[òåNÈóû™CPáB&9|óÍ,Lè*l1šÀ@õ{Ìí`T"™â™Ã…•ìM/àL›ÒG´¹&‚ðõ¤å 6cý0güðóÇÞ�~ xq8m}2Hõƒ„JE)¶åæÜ?4àó[ª:ÜÛ›ÙEqM-ÇC”\½M¦ñ©X²?øY³cY·Zº³ qÞ2êcCU0„•æ—HX�_ŒÙñßÌû¯«ËÐ&ŽfÉêîK%Òõ˜«VÈ¥C\Âçê÷^i¾Þ¹ôú{ïô™»ïAíÅ+'tj¶¾“»K‰Ò "ô×,.U$P @Þƒà\7²Ýºj[œµ´x&4!]�“sDÍËZçõZ�ªeþn&‡> vº¯'$2ÛcÆ €¢íB¡ÎK'LòU(éæ2ù \?kóüã²_míYòbC{Þ––tIe{ÿǧ\téìA¼V°iômôå7Š+�*æÌ=NYù ¨ˆk[**E‘•F£R$ÑUQ� ¤ûªg;W—f•~¾ÞKánÔuëMÏr:IÀ/ßy‚Eë9žŸi©‹´õêâÖÛŽU‘nèš g®˜ÇZþ÷H@Ë‘’ Q6kÌy£—¸ÏÃZk൒]2 rO¦Ù™�Q*mµ#û `ÿ7™Oýb MZß1˲7ò:Œ¿iÅI@éfš‚ŠÇ” YU3uÇw®¢r}Cmíq–àßÓæÓÿš@•xÊÖV=³`¨¿�=S5M»‚©dåå—\’/\;ÇúúÉzÅULûéï·6´WöZvüÏqzTÙ÷h¼K ür ÖÅmw’ëñœUÇj6 dʹðÛvâo(Û™ kÌêÿ^^^paObG\jÙÚEVÒjµÙ/æåG4½Ì“Ô/£Á¯kšõ}Œ÷­Žá-�¶bÅ�5\ fÄ…EÚáHÐ.ïõõ]Á Dmø‡§ñûSV¾b{z9ø}5 }Oœª­+Àk¢æŸ)gûׇˆéó’Ì6í¶ûûÀ÷ð3/fµß 8ßp¹HŠ\�*Åó�|x7ªÉîõÚZ]ÇØå*\}p" ¿dìý(—€ñf ·?ݽµáM³þ‰í›[Uªïfy�=vþ¾ Ežödÿ#Ï­¾W g87ŒýxPíR/Ê]Ú�9÷MŸ‚b­M½ d…ü+@¨Û�6Þ:„á�6�SGOÚ.£:sÛicòâõoÁ§F«hë«óRžžxŽÉÞÆûV­J\rc/%æ�@�€B¼ü×/¬ÏÄ\Ö5uŽ«äù3 3òþ›¿^–}pd›>¡ÅN�úĺGv9ž”§Ãæt">4žE²åì½ó„JÄH˜2\Îõ’ Ü7ùk<¶Õ†4 ÏÜÙ¦’eÎ;dî ¤�&`w{<êý*ì]ÿÇ<ýká~x EuÂàׄp™s•¬jÚø¾>mÿ1¿ §ðÏ"ú>Wö¤ûÔÎíC�™Òpœà|&`«Îqø~kkG?aÿPž—ûBqö‘Á…HX‹)»Üµl7‚d†Â¸á\K ¦•ašQG’§E2‡Ioˆ;¥Â¥€ÿ= #RBö–‘Þ Ê'˱ƒš´e Ãèߟ0E½TÂz u锸ûñTGâvU’¯Q¾�ßkÊ+=ª3D�=èÓjPA¹þx•=pk‘âE="!ã/¥‡»¦Í(®˜×RmPÖ¦ G`ü¥kN‡ÏG„Õ¬#aEQˆ°Ò¶H²r¼v0ŠU îRT’%aµg‚_ŽcõËlqŠ1ø¢ã‡ƒRACƒ4)²ZŒR“Ë=ΪiºWÚå “#7ƇŸ{:n¬ ÿx Ô·éá)YáË+"J¶aÚ~åPÎÁÚŠ£ó¿Î›Sš_ï=BÒþ˜³èGå<íÁ–œPÃ{r[y®ßhO»&Ê¡P±»NK»ëÍ�:_\³?Íöçú`þvçjJ'•mëSÿÓu6Ì3ñìåÑ|מWܵ=n(ZV>}ÀN~<í�kTÎâÀä¶Jž[°£õàö|4 &M¹Šštiôé–{ ¥óNË §gä„Roè½]r?{óƒç–Üy’©QÕôýí={5¿ Ze2ŸQž7Ñ Ø4ßûÇø:ók‰ôÔóá÷]…4ç*$Q¬wmövÔ Ò®•]:mò+ÑèÛ2ÅOµëN™K” D¬¾c¸Ñýu[;+**†Ü*'iÒÿùG#bùZ`�DÑB  íÛÕ1ä›ùK½kÀ™%ïZ\o9’gà]:v ¥%é0Ç…n•Ék²©÷Æýþ®31up°Oc¯¿ê7L›ŒÕÇuÒë×8¼õC7{æpw¾dŸhÒ .Çì¥x õ¾®Ë·/NÊ,êM݇“€TUùív[‚wdèÁ&p ÌB`øŸt×… g�O¦½åW:q¾ ÎYží(äª�²ês‹r#7Ù”¶~ÆŸ1õä×Püà× ×»2ä+šx8ÊÚ]jà¶¹6mºÁqþüq«Pä„ãƒåå}WÎ,è<‰Õ;tfmUIKÌ ¿³¹û[7?­*ʪô›»ö!M™´Çê–s'Æk:è?_�²ÿæ@:õŸ ý/,¬ð¼oûÍÁ¸ñ†­zk‘e»U©Eé¢Ùꌃߟ®¯±Cÿ? ÈZŸl[»àó1‚™l“!…z²û¤;ûKü~ßW�#ý—¾ÜÓS:É,�Ð7 ÂÒ¶E1½Kt�-LÔÕ�¹N&ijà3#{퉥+¬� §lÛn$S;²4Gó<6UV¿*{dh £Y²tŸ1wî³{»‹¤©D))@½À÷§ÒÆ<,ôC׊[$ H@"žîg¿E¥Vl�¥ ÒðŽóvð·Ïü×v#mÛó.½ eƒÞgØÎ.ŸM¿¦r÷WWW�ÛpÃ`‰‹�)%»²S²çôx>¥°<®ÒLl[ÿn–ÁÚ¢P÷»r#¯5–E7ÍŒh‡°#¶w´ÇÊʾÈ9›kyΉÙ_Ó ÂÛ—Âu4ÜQãþ§kKK‡\F'Ê¿://ѾnÍ^*³=¨xN¹Ì‹ÓW(pâ'ž{:ý="ÊWìå\I©À/B]%>¤¡‰.립ݡÁ(¤ð똲kÃù>AVQÕVn¯\r)¢«”NÈÎ0u#b{Ê$íDt;Ë(*Âw: n¬-ÿx 4�5$aY \2�î´e–-ÌÚF¦Òmˆô~[äN m@KÊIú ÔéÏCQåý ý" xw ´P"qÏ '€’ì–Ò$©ž)Ò‹€KEm‹5ùP‰åß«ÿû_x¹ª*âò\'‰}o"Íï NÉ}I@Ê„åª0ùM®ùokîë{³¶–ØÜ”b ÓîRo ÷èøšY‹Z‹§rx"(?x—ËK$‰Ÿ "|ü¾ò­þ=±—RB|/|û”å¸ãnâ<±;Žtµ4¥ÓÆ+¶c®•={¢/�ƒdÚÓ÷å°¡*-‹k¨’¥75èßÝ>ç CZžïcáêYÕ›;:òKò Ïã&¿«\9b�°GòTÙòƒÛ(Ãfä€0I‘H `@r�ntªæß½Êž¾bkÙß$�ÞNn@ënNXN*íuÃh-‚n‹J¢» ,^‡¯ï ˜¬wõ26ÄøÞ…*óV JJ-‚H;ݤ·á�‹–¯¨FÕnȦ†Ÿó�ßçtt½=Pô75óL; Š 0{6ø$öß®*­ÿàÔhÓ0߬/ÆêºƒŽw>ÿ¥•»n>·*cmöûÓ‡á‹mpñp‰¤ö{}“W¯>~'q*2çCòºج/§û›_­­E‘Ýw8D@Mk'žø iNáYLC{Ïåž{Zn#¢Ø�'G ©ÀðF’–ðùŽ’KLÅñÎs¹ºXu¤8³éWbÙ¶ëîAÜôÇàëÛÚÉX5\µÇãìŠ0Dˆ”ã75%W<4BÐxÎÆŽ³IÓHëM—T}¥RÄ>¬Ö$Oì>Œ_o� ‡‚Œý¾[±}fŸS€œŸI`éŸ`[¨‰Kèúމ9páqógÓÁ¶bKÑÎx0ßHÒÅ9ùY�´Õ´ì¶ï=‡‡®BóDjïCûÚŠ¹&MÐ{§îJe ¤þØ¥CÖë©H ã·Õ4í6.Ññ�HΔ5k–/ÿ‹üÝ|\VÙ¹¯v% „{DÖÛð߯éÐd)ʽ¨)m<}�±|y2w «kaŸ‰ m E·k²²”{Oc‹S#sv±ëº=ð×ÿœE·¥äðoÎ) 켬¦F`53Ç–{îI3*=á î¼Êå¹”^ødScÇÙ&�=w¬°8wë ƒ¾ºˆFö×Ô÷°¯Ÿ€�&u}4ùçê¼@Ç»#‘CY=‡êZš»ÿÇñØÏ¼túžHYt ¶¸Ç= BŽ,äË‘<©‹ÚéWˆD7 `ÜÖÕ5úËÆŸCWLÎÍ)xððᨮk·!AâsXˆÞmxîtÙ¯|üw ½¡ÁóQómˆÖ÷¨’?¦rZY99gÄžIî&‘âí~ƒ8,n¹ôºüsŠEæÌÛ,[Ñð|„€Ù^… üwJ³‹lêî . ‹¸mÃÚOQžêpŸ›�ÆÛ:¾Žˆò½dâD$A‰z%Ñ´ßÐópÁ9›= jÉCHÇØ£›Ö×cÜ|pn~V}¶Ùe½|äHv}Ë@¶ VBØÃ‰!« Û`¬�'�Á ¨¡’õëɈXé§ã Œµé䨹÷^IãR¥aP8LÜ3,Ð#¶�Ù¡¾seÍ»5èùß�½ô8D»|bÅøªœœÁäBø0.xøÝE BÌ3#í¸Ê¦HŠ1ˆc–‚ù�]¨é³ð¨o¡3à®Y”•ÇÏ ’ô4 j �ÒAŶëw÷tƒhg<%Ö< tŽ]Atð»SÙLðzKjôÄH$ì¿ÉIøû„Å}²ûʳ+NX|‹%Ê¿ïsÕ_K¹PÂrJçÂùKäøŽF´“]:}6"ÊWtÕˆê±ýºgL*miÁŸo¿ýÅ÷»Ûú#¨êZò¢êÁ®ÖÖÔ¦Öî . ~L–Âÿá”oäK��\yÃmã0 8“‘VJÛT]ou)éï ¡Ú·î7öîì�@OÛE€mÒ)*ãAÇ¢y…ÁàТþB§@å  &¬F¥„HY$�ì_S¸iú³qÙ]ÿ²¼é'J*gÖ…ËV~ºTvÀEâ¤w©¶–ΓèÀs{Ü®íÄkGëßaÅVLnuTé!`=wéi{\2�âÙóçgx„Qä3•íT¦Ï�®.[ïX|Ç#¦?ûŸQÜ”·TÀŸÿ—d-µtâGÁ”H¿æ¯¼Ã“ú}nÉ7Ãj~oòXj³PÆ/55e­ÞÒrÚú}GDxpë"…›°wûTj:lû8:H1pÉ?¦Rçî?=õÔ@eN霠¦} 0Ÿás[ üE¨@{#QœUïÿì«öýñ�Fš°Wˆê;/áÚç1Ù�›ljÚý¥�û|tI`FÍë ¨AOßïÕ°#dôLÃä�ë9ô‡N"õœOÍûŠB^�|ÕxôŠ8×n±Þh)Ã%C [\î¨laüs1J–ë¶:ËH$g"—êÀÁtº _ŸÔÚ×�æ£P–ë�øèîŒÅÆËÄû3`{“lâ=‰Xªzúü™àjÈì:Y€õK¦ìE¨}¨*7¯``os>ärœ|ÿ^9 4Ôeuu|ÞªUǹ–ï'PRu ®ô$Çâ÷Ç=qkuÚÖ?‡J8�¡pêù†G®±=÷ùë�Ê,®A;ßå÷e½¯¤"tþà=N·×Q¾™Îr×§0æ®R§UˆÕò¸Ayº$»5�~í½Ë–¡Ì7½QTµ˜ú ”®þ>cî—!®?3º–þ‰ ï¾4�Ð>.ÓqaqFäñ¡P¨tý˜ëát›?ÿÐöä/sƒ’L;ûݰŸÛ]¾påÐŽJ缄y^È¡NŒäi’Dn„§ê�ä™_VôÛpZÛ�ÀÛ•u b;¯£c#×¼LÞqƒÔÜÈqÃ?´c§ÉÍ…ëF¸|³JÛUI{,• ‰/Ø/áêpôÍî6ì/WçOËMîêkÈVZ ÉŸÖ ŸZT¦¿sð÷u.kzM¥o”i{¯^ù6ïs`\‹ðìÅN€N’BŸO̸|ð[.)Éß•N™?ƒû¡ÆÛÚ¾¶Ô&üº'xPõ8Xe$ÔÒÓ6ÙbÄ”/Šâµ,f™Ž×åzm",�±"2‚À ß�§Aøßâ¶4+×»ñ厴™øv”FSñÇíTò'–km1Õ`Á.7ZZLäíŽã¼ž$vÔÕ]ël ˆü}Óxô\µ{_�s8ž:ŒŒ¨²ˆ/”£HÊ‚«V¯>ª€]·ÕFa/THøÒ+¿�yã!FûÝÞ$©ãLiÉv~H€r9N ªD÷a^=Mˆ±9•4_2mïY¿Ì“Ų¿òñÃqŽ’?„²]»«# Àö ä¬øÔ¿çÃÓíJQû =3’}A…�]ж–‡UöIêñ™qbÌÌfYÐj²Pó@|¡®IîÖ?ÜŸ þc¡#)”Û!\ŽŠ7W¢‚Å*+ÀnD…ÚÎõJÕoE]·_\¾ 1Leyã'‚qÚ¶O%ô´E< uòTç–êóÝá‘H?Ž�¢ wC#|nﮜ2¥¹¹_�ªg,a™ÓBYâ¨æg •×ÏŽ’X) ,sËóÚ@³ñ<¹2 æc }ŠTç*ú«Ì²îërr�¥Ÿê(�Y×ר›BÚ0³�µº_××D™–éÄ@Ko=ªüš¹žO¦¬óê8o_He ‘Á³Ò%ÞkfÒ"�ê¹ã,ô‡èEÔ¯ÍV™2Ýóìxx -$qàœŒÁ0tî(|sÙ§ïE kyþŠ%¼kâ®Y 1KW ´R™ÊzR»M÷œï„dv‘ÆùZJíû—oš¥äf\8¾q½Ñ€º—¦­øÄ ®ê¢òéãT™å \}7¼ôMb1¢–9 «ïó©\”ŸÿÊÆ.¥»ðe2n’}Ù³ƒƒ›ÁýiËÿcïKन®õoíU½÷ìû 0ì;£ DapÇ-š“¸ç¹FEóW“——§;©1>}jž&5 ñÅTAd¶†Ùgz¦÷îÚ«þßmèÉ€hL˜Tê÷ƒšî®ºuï­{Ï=÷œï|§Ü¿ƒÞC™e(©C„oHôúýáx ™ðuAɆ"$-eý)>ÂNE !3žžN™†ì�8¹¹™úä�bÖ$QÇv²Ïx*,K¦�EW¾Õ@<À®7)è�ÍÌfÁ/ç”–ùšÕUU{NQºy~ E9;òH�þ =ðÀ•v­Ân¬.Uúvw±Þ¨Ân^4/®³vF½æ!Æ#Àtþ{Ö²oÔ8õyhÀu&ø_9‡LDœÖCzó_YÌòµc0c �™@,§à3x�?ÁÌ2Û"£Ê{ïx¾ ‡tŽí¢°¡ð Mƒ~YÈòB¸··9'Ú2]]!‘#XÈX…ã~¥(â#>�ü¬Â2“S&™…4Lâ[·Ý–â‘ÑR%ÆlÁ6Kèü=¤úàfX*C¶eyy‘iÝÕîÆW¹Eð½ÎNŲ­K@´³Êr™EfEÁòTÖyÜv¬⊠PÜÇâZ瘲P vÎV|üùW"TÙ‡&4»׌|}t~XCßâÁœ� 0”x뙢LàlG œ£ïØÑNêëë‰ÌªMQ6!ÿÌ´Ø5Á�ë ¥„À¾~&q90â3k Dv×tt&Þ€U�^=r|1{à’åËA~ãNŽ&�RF1²µkví _M¨ PNõ-û”ZņÛËõ­;õ†ŠQÛ8×¾xs¶-Ó¹ò|ðØsЮVh®±œqxìy¼Ír_huc˜} |ཟ‡ÏK[[3§�Åæ’eZ‘�¢WÕµæô¼ú\?]8iRfÙÎØƒH~9J5’-ɨVb ¹@•à8}réÄU—.œ·ó[ã»yU�&%÷Ã;‹¿³“¨r»¥?U �É%\¦uþ¸¿BSz àóºõªi¿ f#‰FâµH](Þ!ËÓN²!qÔT”;ò‹ÀY n¨× $7"n»N ª ¶–!¾œc¥LNŒ2ª±mù»/ë§ÌŸŸ%½™·ˆDN²m~zÅôÊ=èˆÎÍ`±¯ëéûo†Sn†Ãä{Øý ‚ r!†¬Û�÷nIÁDz#ííî#ÿž{€�Fuë)—éÈpÕí=`-S '*�%Šbõ^Päs8ä]àS—ûÎ(¥æÛ=ã»I+;ίTãÙÚ×ÒLó~žûîãÚ¶ ÒêîôF�a¦ !¼VJ¶ÌÞ§5Òû.±Öb‚µ’'t«®°°ô ¬s „ïY¢Ì}»Ô[pYVªŽ«Çz?R¤ÔöþL) ŠtÇ ¶É¼í§�íG~Í`ÂÃó'XrÑRd7n¡�ŸÎ×{dîÝ�€óSÏ_à㽺¨"T¹|k]‡ikë7c‹ß^œËI‡Û»ã�· ùugúºûãÑ¥šd¾÷ÿæÏ×(1FÒˆm\.â2DôºÖ´¦HÄ Ã–HÑk` ü/`ô°�på´±«`Vǽ9i™kÕDÂ3˜Œ'ÿŒ#ç/N|iê‰^†g�+UXÅ/s‘¬¾pBY=“|‚tX¸7Á6ùdÂt€%ɸb+(ûÎP$ï͉þöƒ"(œ©Y�×v¥Š6&aϧGÿÚR¤ýê3KB¦¥1éžv•y¹£)z8Á^›5¬1ƒë‰aÝ©Yñ ee”Ô“D¥&@¯¡\ÀdÞz_wô£%Þ(o^ö’­55I†pK`“ŸÀÖÏ>ðõ >p®W‘nƒf»Æ§¹oçëÍýÀÁZŽHÓ©'!bn[Uˆã™qr-5kæ¯;œÎC"|›ÁÏÛ—e‚%E¼mÒˆL¢�œU]­I,ó¶‚çJ<O=Œû¤R’ &#¯gXëg.Ã]�pÆk�Vþ×Àº!‘¿”wÅ[‚aé*ßøé_Ú°a`kq8uÜ‘ºüs{ -à ` ¼žž0ë†�8«�ð„‰RšI�ô?䎤å.s IÆ~µ±,Óì#<7³Öë(´¦4ô´|ÂÔ)|PZ€lÆ·[÷ã«G|uÈž—>ÿÜV¥Ç5½áÔ[q~!Q�vûªèÚ³Ëå˜{Y‡¼gJb2ã„fû$wŽŸç¶Æ=�.bCÙ’Z„� ‡Û(Šš!6�AQÔâÈÙßÛ y?Lœlè ®±xÅ8ö�ÇWcùz¬�íÊšY³ %‘ÃîvlÕ¹ÑYÝ8“¢òf‰üõ‡Ãù�…ïbØË÷ì‰Ï* ‡h)õÚ´!_){ÖHú=ÝRô¤=Ãz¤~Û`Í™*�!h8_G“à…B{–Þó«õEÁí:qN«ÑB‡a/0±Åq8ò-Žu,­9qØ–‡Î:R‡O¯Zwô§ô„ó¢ë¯Y*ûŽé8ï®@n0—å€ryAô%Ç㲬Óœu�ØQq »®ÓxNü¾#z¯£«;‡c¸ásüí ¡P‚ÕÀ.�ÊëâÄp7@³ ëÚsÝY}®ª·‹€b]v›Â‰¿œ#ËBϰ “n»mè0•èrl~æ¹ÝéR­ÔóM.(.XÙÚ7…ô¶µr<ÿ3D!~”j#²í×°ÎÿpwíöÞ—{[“ƒÃø›F<ÕÿìVÓNŠ”gm«8¶1s.¾þPi0‡|TPÇC*ôÑ-{ÊýÞÀÅ2qw1&i7EâOèzQ˜HÏæã«�t�˜Âìñ@2ŒB¤Ûn?k®«(( �œ1¼)’þ²üȆ 6úçŽoBÔJÙNÏES€]¿³$¼™š,©²GnþÌ÷À«-ÑSA–ý;�sž€ŠƒÔU0;°Î·TÕн²ò=†'Óô¬ó•‡‡ÒÀӴ茕ú½À¹>ðö.e%®ÝÌjåHÚz2v«“-—ü§»gë"*t>óô7@¹-*gžp¶,‰SaO�;œÙV™)XR÷á�^æþmÝ—øö*POþVä�Uñ®d_8&cº—† È‚sJKJºƒ¾'S‹ß‚¬�ÐpCX!@¸¢£”ôWµ÷—AGwºL3qFù^½Œn˜þ‚z¼@v$ö-¼-¿85DZO>13é­îTzzm­ø£ŒÁ^‰¾Ú¬°îã¦Ã^]=¡¡ ÷ Ü—¿ÿ97�›Kç˜ó,Ãy7žI?ÅXÌ"ðⳜ_))Ã%g!–à¦ÇÇ$dß:Ô¢' øÈá8±’wI1Åf2Ü¥‰ºÂ‡LXÚÕ噂ÀìAбp/â�AúèZ‚PFD½�„¨¥ Ø{ºl†{ÆIg7Ô Ä{v=x¤BŸJ¬êÊœgó�çC]Î�ß—Û¢'ÃÂ=!Õt"¤|~÷ÑÆâbšâ`þcO¼¦ÏôzCfBõ[’ÊìÊ¡�wH«ê'®Ä‘ Ë Ž¶å��…!ÛnçÅR”g“¶Ç`ùz"2ßg]FOfÌŸ¼S_´~ßb¾_;h€Å¨ÒaÅ faéé/{:RQ¶¯d˜Ü} 6µß�Ÿó¯îî-é"‘ãv;Yu§¾{óÆÁŽö[>A%}w´µÉEQÁïÊì-¶cÏ“Ë{Å¢Ø6O%7vᤡ…ž­‰C©S™£³1ã‰Æq—'ˆšõÅ4&ПíOѤ Ï¶D�4îh™%6qÞžSQðòáúù¡¨˜k³F�Ëô;ÀT.KŠ?¶d/Vð‰"“^·Î¢Fô… )æ·(o[úV˜26‰>¶Ä#?~n{`<¶;À/V"}+Zœ3çƒiÁM® ¤�ïM>‰¥ùÛ|@é8ؘ¡ÑX#Æ3õ͛,Ç�H8™ƒG8çÙl:ýßÄçR¬êgé%œ0¬$òV—öNZUÇ WuT;ê3îÞ#/xé§}fõ9;:µ/kÎ\û‡Rˆ[žêÐ\o¡pOiPž€Àª×?nÇ»¯ØO|Ê®mö�&;Ç‘˜øœ¹ùU3ÅH0ÉCÈøÄ„yZ}}— IZ0oE×eõÐfÎO*d °>ñÃþ‰ðõðl—åØ>ÞuÁ‹ãrAÊEÿÂ9½%R°xLEM-k0²trm¤¹»»mÝ›ÑÄ|Тi'æ:ò#Š8òõ¬"¬ïkˆ/>ÛêI¯?ªð�—Úz·®XÛ“Jm'íøê½éÑMpF{© ÝoìHõ“Ç"éã/ õú5Dë àf<ϺWd³Ž»v[×/ö‘pÁº•(¸ou' <¼»>–ý;ÞL’úyÛ I=ÍJ‚g§cJ«;4b–ûImRׇ1Û_¼"&�ƺ9‰ðM‡-Ì ù4¯GþRX–F©ŸPT„(ĽB’†—yÈ¥ Ã]¾†R—e¶# æ»~Ùw�¡¸ 8—üÊIû‹…³'Ï™‹\9XKë¦TõtÄg팺�|Ù»ìÈwŸŸX©ªHPlÞÊ©æ-ªæþ”Xæ%Ä´.AÿBÃ&¿àxæ�U—,„9°Õà`�•WrÛoÄb’sçε9…$@ä”m¥ilÜOc>°œÏûçWb±àIßþÎ0ïø£'pÕ£ŽŽwÇNèñ�;í¨|æâ|PmyÏ”ª�>N½WK[¿$мxt¹¯.Ãp·dM§!šqŽY¼jÕG*Pùrvf=aqÊnÛ°'™~åª:K9y̸ôZ—s¾¬ÙlIJ7î—8r^¡"ÜÁOðrÛéï­Ll=G¸÷);­¶!¢ßîÇ�_')Pª©@¸ü´m¨T5ó©×¶ùâôEÑ0ý%SYF¸¢xL²Çaœ[„HfY�—tO÷ûû‡‡”ç€!¤^ÉÑc}eÒÜùž;Ž dV`7ÈÕDìDýÓ»cAºM9ðº#Ÿ?Û=pÀ¢ÊL«1ÁãŠb†Ù¶]7;ßYÞcÖ„ßÌìã^6ð¿êfÿ;'z PF„è¼Çöü>“ùò»;£Õ ä.Z�V$îR†8éþTªù\Àoƒ‰â³ÝsÿXíé¼ jÜ%’Ëý/Ï:7#Úï*È»K‘iæ(I7ϩۛ¹xpé Ñ_‚$ ®È}Ço[?ÍêÌ/tb:iÃú⑯• GM¸mPLúN©F=¸Œƒý=»´´ÕQ3펈¬Òˆ:4E§-²xqŽí,®ZSáõk¯Ç¯ÎV²),ð¬È�ÞŽsÂwA}½7Ì•€†Äl@9Xù‡ÛwC2àÎ]”î¶,äsa®7ñkbà {˨ÝÍ…³‰cÕÀ"‡ÉàÞísý¯�]”Ôp›ÈBÊ€†’™Ó§öùé–ç­>âÏOŠsÏ=¦ìN“¸ÛtÓS¥�9…ÞŠ‰”h@«Tæ‘?ÿFPÏñ߸äSû™ J ¿¼3Yˆ‡æ¶­;LF'ךŒó8K”ÛÇNñž_yÜ)ÓìÝ™Â]ª,t¤š­¹s™»uÈ &ΦBÂA’Mš�WæÄŸ°AñŒà¿Çk‰�Xû-¤˜´ª®¬ë�æ¨ÿ´‹¯.É?÷Skôað ˜u\Â3 l]ƒ¶ÁüÉuÍ;`[�†Ÿ0\{QI¶8zÀ‚˜«uË’v ¼HŒçÈt0žÝë×ì›Qº/ Öà­‰åkáìÚw¼�˜Òác1§?R#¦óÏAÌ1|9yŽÆóZjEBQ%N³-üî�€ì¥ïÛAô[5|§ªj÷õÑÇÐûs…ÆËZ7ö™ØÉ Η6~EGübV¼§©GÖô1E/_j?ÊöeÃÛ| „ïû}YëÂÆªÀNÜ2ÐA«�<3àå…í÷ärZ3Ó”"Ð;ngÍXº%Ö³3g—ïèxüëéL S\2¤Ø%n•mv},kC¦ãÌšÇ3.\˜£ª¤õú´:P—/_Î ÆKª*íô}.Û3ý<8v�~¦BiêÔ©<òßíÝCûGº�œ^ÕÈõð-ŒWWÊ•t{Ï Pž¤Ì�»‡bE§‚ eÂ<°Û}ÔµëÒéb-k Â1³%¶¤±±N{zgOIH’K_ÇÂÁJR7k˜ϸ[Árõ®i1ëUÁÝìOE÷ˆ 4V§#Ê}ýke[©à¥Ï¢äý¾Ú±ºc] “×4,îD;œsÎ"M Ü;³ŠDWw¥Ž•xRÿæ{+ÿ´`Þ¼�¬ãGÕý³þý ØV=®ümpÊOAßbwOÆ#—ó/ ù¾eX†Xâa€%e’ÃÂáD~7KÛüôÎØd‹¾XwÈOϨ Åé¸ß]4nze¡¤s‰tˉc«¢ôú5½©™Øõ6Š÷‚·¯³é±ƒÌÑ5ð •ù§'ËMd9ëhÃb;Å®ÀŸó4Ù¹sý¿RYí¿Š|âï-—ý�a9“‚,?)Éš\‘XÞ¥Ûññ$<ªÂ;™(îïeH4_ÚH°Ì÷!:~'¾°"sqo¶)7ø»¬™p¼«áA4ês+Ô Ú)ƒø7É78—iÀR‰„Ì{aŽË¦MkÂ?¯áeå“Ë«§Ãªî£Âq3Î0é6Ûc½fZaôÈ�Æyþ¹~xÊÉ—~¯üûVXhNCÖ¾ƒ½H*h©p¤ç—¡ëBäÞV ä’1“F5 Šk¤N£•[ÃTµòƒþþÊÇÀÒ�òrÚ½ek"\ÓxÊð)óæ�ZÑ’)Ë›ShÏÄSÏᆫ�ŠŒ´ž3Ê'Œ`øøÌb;4SÉ�ºÑµ·íÛ"Òß÷i ¹º¬ÍŸô»?"ÁÐDKCãÛZúŽaøî™W>ðÀGn=»»»uÆä�!ýÛ·¿dÓz žKA¨_B\ójÖ¶n¶m{‰m;qžãgBPÜ­ë>Awž´B¾_U‰Ü …¼�±øÞ�úucíZµüíH"{µËÁôe™ßæ-æ|?ø-¼´=¬Dd»5N˜7oð¸ü¤MýÌ_7·¤$Ã0öË@+MAÇ��;l�³ÂÏ3§%ñËä5øï¬êŠÕ¢±}nû`«£k¿êˆ4§_îŒVkµõ゜º›ØÎ×b’rýýÛ¶Ñ] 29^ÛbÊÌŒ5'ST4îë—ÝF!åÐk|¢&ôÌ1à1›f;нYg/Ü@{�¸N–&ØÇŠÂ÷-›}YS§ª¶“í7­v‰ôuÄ7ß à1 [_yçe/î±HÇ5=òe.ç!Ó|_l�ŽçÓ/rr€a‰m›±·‘­‚6´©_]€ì7ƒY~#rC]qTIp7ýö7Åç ždšîÏÐ5£R¼Ì�+ö¯ÚI Aî‘8ÖçÚd3/º¿í�õ?slMM>u»¬9ê+I춌ҺpsF],{d�H±·)v»á¯åÖ÷b¡…Ò*ü=ƒm‰DÜѳgË�ÒRÛN§ Õ ”H$ª;„Bˆ@>ÈÌe†–øÕÔòòV<Àý 3>ÕUø¯Ãõ ¨D!kÜtäÁ)…Ý’LŽwÓ¥œ@ƒc÷gXÛyªÝÕVÔIÇZ¶q$B™ì¾9V–÷ä+LwíM! Ñ_o÷ê Úp8Q�~ôÑG ›@“èÓu'ÿ~ýÒïä±UWBp©ƒq$"‚¬*»º³¦x]ØZâñPoÔ*1Ã> žÓ6ZwZÖ�Ú8­ŽµÃ ‹G(P:/c›ªk9Ã,GBbDCŽ&ù?�71£&%¥j’Ç 9.R{çÌOt�JWœ*Á�3ʃë–u§ ƒÄy4¤MݺÔÆâ‹K‘4[ìá�ë .ÏC_5A“-“yÎ ^�3}¼©ž8³®lúÌ¥û¤RVnÊ ›2’=þÖb=U‘‚‚¿0Ýמ�]_ßG¯ÃvìLœbqÁ’úøòýýy>Ó~êWŠÿŽÉ0œu×~©�mEdRäNb–š5 lE{ë'ó¡î�-ñº¸kÝ s£"0ìs`åYÑ�k|ûr¢õ鉥¥<'O ¼-•TÉ2÷ÒÓ÷¬Ý¹paã€pÝЗ SÐQж·À}T”7ž˜WP�Ì?çJØŒÏ,þ�Œe!Îì»^i; 11Íty‰ç¤4XC=¬xÁ×êC+è}TànØ%Sñ‡ekÒaP3dš!¢+†I’÷ëè�“`~˜�I±Fo¿›y “ý ×!“¢pñöTjôžD¢.è ~Ó5™r,‰ÝIس»UÞÚ€>©’3réè�‰gI5Rl^ç‘ýÕèSj¢«˜C3e û@ö¥ªÂ®¢Ñ•o¹Qý]x¸÷(®\¯ûÈ©ÈX?K(Ÿ0P:é©0¡/æÀc_™_S½– 9¨G¦`ÙØ£ÇN8é¤*�äûŠצgMr¶Æ¥¤MNƒÒî¦-çM'% #Ù^*T0·á¾PøÑ¶Ez5^ú’¥´bÍ¿Ü *¨¶¬éÌÍDpê ÕzDO·c€Gˆ -¬„±&v€hiša& ¶Ó½íÎÕQíËN•*Õ+—ë%53óÚ2ýq윓ëÔ`ÑÙõJ`~Q¨èšI„~¿Þöp}ª»ËËÚmˆ…y5£ÆŸìW£䣒b¶p”'¬Üçs¬ïAë–i™ÃfŸ8–ÆÐûéAû¤ f¢¯_ÿï5k¶vÞ�kÞF4*<÷^� ¸íívFµ°2µ‚Íwû¼Üz8KŸm4çBÎÃg”z •‹¬rY3ì�ô¾^>pMÞÇ B'^®[Éd|HáVʱ—õâ¬jF›‚\_o?xçNä{ Ý�´^äÇ´/´ˆs/²ó7¬N*L„ Tº8´yB§:¬ùGÎ4ž(`½i¾>Ùc0¸ç~O¨ø§Ë[âûà�X69ÁŸn‰Œ•|[¾g_‹åÈ«ØiÞä·É�-vú]¬Í³@KØêñú~ÑñÖËÓ6ûLBSG˜âÀN)ß7*á¦áŽvMò»¬m•õe�Ódž†9=¡:ØÀ‚ƒY-)õ¿âv‰“Ù~$�šuÖ5JÏEŒñIŽcÏ„­yk›Ò™|ùôüÀÒ¥vÂî¿LjßÇë]+rlo�"¤$äâyÐî0öFÞµÿ½kǪUùû>èîö –Ï£\deísðý�É»ü3åß�aÎ|�±�Œá¸e"WÅŒ)ª*“xñvÉ㙂ˆ¡Ü¢5þÄ3+5Ñý©éº7� ÷âYþÂQ çý­]^;ú¶}ƒ›î4 NܨYÆÛmŠþò–=[6®úÝÝù4= ´CÚKó(ÆÅ!™Ì½ O�€ú›®šíÓ¨ð4EðA§MŸ “ÖED´QËš»‹¾yE�ÉØÓÀçà‘9&URWð¾‘ÈÜç2înäN\™èïÏõû7ÜPà#ü÷0©ƒÌŒ³›½%žÙkF  5œË��84Y.GW7m– :_žoC¾O¾gú¾3¹dçëÏoSéïŸa)HL7æPG¼§×æ¦Mãý¬;‚åØº¬›V0ßö;$×Úæ°ÌZ›XRF³8‡!ÿ†ðÑ’í—£ýQ‰Î¿:B4ï—‹-H§µ„jÊ3+Ž™[Š‚0|ñæm'%”G}³EcöKF©‹¾qùHŒ³p:c¼V‚‹XƹÞù›4Óþ�Ÿc®äùBC**o³§¾†z0‚l§È¶l� hÔ#÷,ú¼Ãá2á›Br¬”eŸiÜ7lPãwõ$r&‡}�tJÃM¦a}O`�û0‹á]uý0 wCP-c9ûFA°ïƶ¦]⎜ÑXŽß‘Àþbìâ3êÓXæž9í˜ÂÔˆ#ëkù O¦é`m�ÞKS=oq/o¾]ÿú¦¼×QsOÑÅ«§K¬3Ù”¯‰¯ø…Þ¿¾%ÂKE�ùª"³—‹e5õU’çlzžw@IDAT^ag™¦}�·(X¬ZÆÈ„î°%â]‡=»Øh�1�¸§Â&¹Ç´Ü2…á‚F¤ý)ìu ÝvúÒ¸9ÿ‚± $ÆN¨)—õ˲O·]g]©¿Lˆ¸?�ø†å<]†,½·v-ÄÜžê�‘ˆEèÌáú³iñÖ[o<Öží»1l7£‘ªŠL÷)Þ:ªÙ¯C¢@„΄ƒÓëºì.C´^l(Çf�P‡÷ôX¤}5QP›|NøÐŸ¨¦8ì®» Ä³IuüÞE¢®wlã�ÑD¢C9~Ö6¾Ê3üŒ´¡ý–¥€¯$‘ŽfS&‘JaR”×Þ³u«¸³të…`•¢ÑJƒM»–/ïu8w5LQ\AÝÞpTŸ[ÔIu^­©ÉmiÂ$ §Úèœ,ÆËw“¦öhÈ빫 èý ¬‡¿»=m-ð˜+†l‰ÎO=~畦ŋs[YئZÅÚúÿƒº}4b¶é, + / a=\Qr‚o VHL0£ÝhC®�´O¾HGò¢ 6/Iî%Œƒ4§Èó‹Ï7`ªú… óy(-t.ëî.¤»ºë¡}tÑû²ãbñÌÙB€½qê óFr<)‡&g”cÀ®ÑT÷¸û]Së„Áê!N`¾ {tušÂ²BWšMŠŒø>¸F3^Iz$N2É ¿?‹*nó‹Ò.%°gÑ:Ié´ ª»×Z|ˆº ’júøXº¿\ÅÁ@=ÉÇØÇ U#§A»ø& 3ï#x`5òÙ•ƒ§vªË°¾ ËÍšwà a•k+Õ"±8ù!hß§–b”1‹Œ/»2?ÙÉ™Ž�Í€g™Ïâ—Èwú³•BmýÄÊY'Mܘ H!“3ß0[[Ã'^yãØ-0%×#Ãæl ™zËJ[ÂÁ F¯„m/„År¬ÅØý…¼]ýñ=*'hÄÈ. x¹�÷GM6 Ç{j&/²• lå©´f†š©'x%HRÌ@-¾—Ä|| “™^YÙ?Hø¹zÛ“0Ü„¿í·F !ØÙ@õr"÷#UK´Ñ¶Q$Œi†ÿ²ãÅÀ®¼�×9~d{woÆÞè6ùß…Sö>”s”Gƒ$­åø{Ko¦<·@š<Ã�¸{U$òw1ñáÙŸÛcÆ0o¯™Òã|À±É)�]ÛLÝý•Ï*áü?K‹ä?§zŠ�£Nat‚#™&ØwÊ®û?ØÝKttxü#F¦Mçòî„9¯vÎ j–rahYßÖÃJzª°ÍÜ ÈÙÖmNÆv;AlaM´”ètâR-QÉϺ;°˜[ëüʹ==äCf„|y[;:@øÃŒÇäí‚m™Áv|L¡-œÎH¬Û“Š¢¦bŽ—9÷ØIì6]'iËÝÉ3V¯Ì2ë-F`ZìÌàEßmæŸÓ†ú•Ñž™YH¸vê‘–X÷£Ã}âó,^Ü儊'3ÛÑ¥FwÀ6`v u©AµeÄþÏæÙŸØ6iŽ=¥.4á™_#éùŸ½ w,ÌHÃà4g-r³¹gû.Õ4'B˼Ç~Š•øJCU©�2ןTpB .µ‘së¡MÏ@­gñäxÃdÖc²lǽJZÓræŸQåç{ýçŽÍ^ðJ7zòæÍÈøº=E 5%´ÐÏ‘{TÇü%œ¢“‘O�Ìdxƒè€Ká3k»;õ _8a‹3‘5¸6ãØß:æ«%eœ}-§(÷p²oòbh1Tð ÃF6p¦xe¿™¾^9éœmHpH5몙'Žóár9 ]"¦iPë­«/'éšgTƒßH"4,ÌxÜwÌXò{¤Y®u4Ø\[ûV/¶Éû&]î6ºØî‹çÏ}ö l nYÐ'Lóû×20w-‚¶¼[‹ïÀI7š•zbSí�·-¯Íû5ŠK;r ô€ûÒˆâ]ë¹'ÞÓ·Àc&þCùÉ0«]3a?�ÛA¨u¾‡÷Cß; t]ë‡IÌmvTa°ößRfö ‘æ‡9 f¼a(97v§—[-'üÊ}ß¾¸~1TYt,Ç‘“�’(Œg¬Mƒäi1NÔ.Ö …‹�/´Òÿ€¢'á-ÂȬvcG*õWHÊ gäþSY™q‘Ïp¯�¸YÆáQ9FgÅ4ô™¨-ºÍNÆF¿Í1t�ä;ÚgÙVXîm ,Ú½~eÇš»ïNk%�?3Þ7’[ Þ› îAáöhxê!¨GV»ÿŠ+L;Ò¹5ÕÏ()éÅwí¦ÂÂ$!%Í b0Yؕл¬ ¸6Ádþ˜íIþïµ®ÏÝâ‘ÝWÔTzùoªK· Nž¿wð9ã¸1~ºV¼Úª®.˜QâíEïÅÂ1Pµb�cÛѤYÞð0Äkü¬ÕÓuŦ¢Áe}Qÿ^ˆþ�ù'q„a=jOO \§;¦»˜÷Ø·‹vðQl·^±ê[…ä—´�´ú²­À9�DŽÆÿ‡Ë…¢Ã¾$)Ì¥;ög-îò»W7û÷õ¥ÝÁ¿ß¿¯¤°êhptÀÄÆ°ë‹„J}” é¯2ÝDu»=eØfJÍ.ñ§¸-TÈÒÅ<§~컲^(þÒ¹dý¸hÑ"{FIÖ±~Åv´2bóï›® Õáu ™ðý޻­½PRäÝ,^)°ùeM/=8БT#Ú'h%œ¯ÂS>mäÈ©•ÞÒéÔæ“ï–ùÇÕ`HG†‚zf­a™Ò°Êów?”£‹/¡^î‡Á|Y*‘¹Ê•……žùîâvØ¡¨³ŒjÉùòpÖ4×^ èàѼÎÕÂH¬"¥´ߎ.aùƪºKá�="J;ÉÞÿÖÚÍÿ¸ùýÝ¿¼&ÚÒòA9!ýt+ Ûq¶æ§Æ½4w{à ‡Ð?kjjâ3€ÀÀŸ‚—~ïJ¡ L€»ŽÓ„+Ž»®¸g£Yã%ØÙ¨ÆïRâùéc*û)é 4ÅžÒºSÇO–õÔúÖã{g—±$Û™¬,¿º‘ l{¡„¦WÀ9iq‚sÙ"ð©–‚õʶ0YLÓQ7jùI•Âü+2/ÂñèƾüãrϤ¼Ķ~ˆÂN�‰´Î4›c&³½–¥ªÎ1rÀÿÍ‘�Þéä Áëÿoûn×É\e¨î¯±Òqè1} Ëò� ¾.�‰çHÆ,›·ûã†ã·x¯f–/ãÈù¯=°k×.Ž´$ÌlZS©tk{|œn!qøá»š›søm,¾Ø€±ÆHHxEöGJP¹›¬^¾›–B zƒãós“ NÓ�æ8ºúe ¦ÃYÏz,ñYHÈývŒ´a‰§LeÄ`Ä‹úD2¯ÁS2kÒ×�{dkKÝÓ;Ó%wmê-�™úb¡?‰^—?غâ$L„.”�ã�m¹oðî*Í¿òŒð%ß•¤B+ßÙž‘#ëÊüåÇnLfO¨ —\XVRðÙäîÁ6üôD,6àÊϸêHÄBæ$%æ•a~ uœX#�úe9Ç[W«‚ó£1%þÕN 8ë5ØiÝlö»5aï¯2þ—U‚çŠ1sOŸÏ�/¿2f²ñç=<ù‹…d¦¢"j¬£ÝRcï%,õöd&þ°iùÿß;n‹M†x3iOŸßØÈµ¯XA½£Êø{ÞÏ~B7cw:²Ã4˜·,FÝŒïoá{ÿñ¬òP+~ÿÐõ´Î ÆWÍ™²`žJò'ÆJ=óŽ– G¯€¯ˆ–I¯)™<¹šH5#2@ø‰-Ðî7að�b;îNMÌX ó*Á¥#78‰PÌ”cè•ÓjjBºRTGy—‘^qEƒ¹½¸x[å{?ä¢ÖèÂè+P~"1î%eÕÕ9{q²*ÐÂ[ÁçCe�›-ö+ìȇ½‘—¶»HPøãJYqfY…÷«Ð@.#<³¦’‡…jß±ôµ?o‡òôK#®®9¯&§N<Ñ`ò#"ÆuŒÉÌ™9xßòÞ^XÚ¬£@⎀XRЮÑ[.w¢˜ýæÄô+o,ådä02O®KšöÿÀ„ôˆIÌ?ÿ˜K²÷yYç.iÍÄÀlÉ׃ž¹–Ñ!V‘°K kôþípø{È„/mŒ4·Ï™¹ãƒ­†VþßJ8rЧ5[AÑ<ŽâcyçjWwO›ácB­A,ùšT"1Ò™´±eÈÙÜ[8 ós&âA7ˆö:Œ#nœ¿¡1DË*À/,^btFev ¿IZ ˆ1WQ6ÁÖšN$´ËÇ–Õ�‹ X �ÉSêý�þw8w"‹¯¦X:,ÄAÇ´^XTÁ>@8R@à=¸çÀ>̦šv,ûiRÃ�´Y|ï�» aåF#Ⱥ�Éæ?çï=rþp쉵?ƒ¹ö.ÏÊÂ}FýÛ$›ùßFø3òWSóëÕŸkš\Õ…ïr¤8œàLx÷Kø8¢¿›¯¿ ƒSM$ÌtÖzÒ6­7°»¡sžc"MM’GL"Y ˆÜñ¬*‚8RþvÇñyx¡Ô'ñãáŒ>1(q_F?)e»óõ ç¤ð£‚©š@¤vðo‡Ãߨ9ädÎ�Ôåµ®^ärSjš­C`øT۪溕,÷.¿Ï” ¸àÄ„­íÒûh‘ÎÁwQÚeß1ÝL2Ÿ«�Æøóº})aÿÛb?ër�weŒçBÈwA*hEW­jWüc ¯íz(uXã°ï=ÌrîëŒe�_›¿‚}4Ï’µìiþÓÉAÐNíËÓ§”œkØnV�j/œ<©4ó~_æ—ð†�€——á™4 `1€Ï¨ "ž��Èd' 3ýTðN.ƒcI&ŽG ÑÞ(—öKI Y^íþ˜ëÖÕ…´:˜<èu:²9—!]0ÄnÞÿqNCsÃf»ªo�¢É>Åïˆ�’lo0JÃ[!,@2ÄÝò?Ž´MÃïõØŽîÝ;=åå®?íÔ²åÁ}��Î[Å)×ÕKõ­RüÃí4—š|+Lf*Q7Ì®/ë{o-2¯T%$Ž™À:|L5õ¨O‘&'ÕìÆ�G§' k¬­¥“ËE¯¯Ëc0™¢òÖà±J ¯�è-�±4©›©Çƒפ9ò7a™Ð„-Kàý3Œådx[Ü›¾X‘°ß#T´4 ¡­?¹=Òöh>eÍ[=©³Àô|oó×f\§\ÜX�NZ¾¡'rüˆ‚9.oß)2Ìi3�œÁÈu©·]O‚dГ±¶÷÷Û]ã’>Î÷?5…Ñ �Á&�üoGÎî´DÄðJ<@ú_º3§è¢ü•`�sxü¡©¥L¼�€8«“XÌë¢aœŒB ¹-Dð3}a‹g¹•øn?�´dkl¢!8/(¢ó�L¤• ]ª¦[XäåKÁXWÕ›ÔFH"ÌYL‘Gžü:¬˜{:÷fÖŒ¹@u�1¼Í-šVî9¬<ªC*|_Ù;�ȤÑ4ÝŽg#¦iéP£ø¸ÚóL®lss³/*9ƒuù³¬\„Xý&QކQ°£?Y‰”bƒ”¨ ‰ÓÃí0Ê|­ÐJ2ø~६ëKÏ�À½ çH¬ÄÎ�š£ß(‹¡7ÇøIfgÚšišÆ#˜Q^s¿ñx¹oÛBhLûÞIîDµÕõ])4;ÓtÞd[zþ¸2_ßæþäQºE¾„ÌÉ—!JzØ\p§ƒg�Ä�¯æìÌw&•–öR2 DüíÍ<ƒí~ „ß1è‰1dØMÀŽõF<Ýûª¤�š·¦�˜žI¤m—Í$4Ó@¶k³§Ùqš™*/w°ê;޽Îëp0ä…­ã­Ð„�92XžH¤‡£9-¶t‹RÎ<‚…”jÔ>ËS-b<GÄq„¡¶jÚhjw7êLVÙH\0°U¤×Ã"¦ZZ?˜¯¶V把Œž¾>1\TäDƤ‹}7B8F„ÖDœ‰d;-2nœ:&‘PH`–î¸ÈЉG#âxE‘Ò)ô®ÒŸ¤(�¸¼É#Ù€ëpÁ"Õ6… X7gÍ]Ý›)…:ÿ4tÞr˜L4²°»ï2ºéEðÈ ¾1Ù¹±ï¢ù'Ïá�ßìI}iäëÁé&ºÐ0u4m’T¹©f;˼vªU7gN*µ^[»Ö Ãn9ËJëöFOâxÞàzgUûšðq¿1B?r|dÐw;0'éUÔwónS×\G̼-‚xewŒ“êK oDNó¯ jí² ±ZAƒu‘j±‰žþ¦‡�ZÈÅ  æVìd ñüSŸkíž4ÈÕ’Jîµ¹¾öŒÇÛ¯ÎH|ÚÈò¶ÄzR|3²Ÿ]J<Ž«ÏÅ än§þdû¼f´BDÄ=9µÄûA¾ÜÃá<¤Â÷ÕŽèLØg¨Mg}¦%’®ªØÍQ�½hRioÉ}·W�%sæo(øxÒ‘ç±…vŠâ¼Òß?M*Í Ù\¿`&@nä¶Œû½`ºÍ/›tÔ½ØKž�-"áØç38>\¹w÷™gGʲ·ß•–a.�ðóüW ’¾„ Ì[¤�l”Nñ?N˜w6ÒËô;DôZçÝ—½½ïŒ… 9¡Ñ9\úÇЬ/‡-Õ‹ ­Å“nq´èæ]+Ë;¨m�FÉ1‚÷ÇæNçxçt†aeØ`�ã€CÑóÃ>6ð›IÝ ãÊüœ"lq}Ø7ÅYžéÖ±ÛåI¯¡qh Ë’Ð:ªžI[Q/5Mýa>Tã¯6\±;46¥ØÄ3öë›\Çý“þ[A# ±õ? ‘F%>üðÃFþ ª{»eß$¦ZhWOH3Bœ Íøf`˜GÂüD�n ØŒa„T�8דm¡¥�{ŸäÍÎÔïžx"«'ã�!B{,Æí��¡ÊN+ÒuÃûØ/RÜ6+Ç2Ù„’-»=âJKìÅüy¼=Öõ‡¼öýOêÂÏ}±tŒ´ž_#`jµÏÚLΟ1ÜïÂtØ–Å·º³j8¡YGé&IŽ/S.>³º°#øcÇðŠØ™/þÖooK 7ûõå²ö¾91ͽ@�õëht^þÇM½‰랉�Û68êË�*õ?”ÿíp8©ð}¡35ÖÚsAõ*4Þ]÷¾»"Bqù†nLdE£ÛÊvWbZ-ŽølŽßPTÔCmtùk?êœN§KZ-f´ÍɘŒ€På"0]Õ"¸û&ì\ê ú=Ì»vÇ»,âtíœqeáWÀ'´474®8ÐE�C¸ÏlÛæøJGxîdW··y „wžúµ7í®‹$oG®€¶ëE™?ÊjñßÀvŒ ¸�ƒYÕNäp0Umü3.´dÈ8Ò�ß Œ³ØÍ¸ÛŸÙ³¾ã¬qG=[ô9¨õûgyËb¸9�%@%0¼�P6ˆl Ÿ�pu8¿��ņ}‹‹»Žõ¾!ˆ÷(ˆ¤Cƒ1#œ%t#²,a l*™å3’ž0I‰bE^¡ä´èŒØ‰~¿½ôïÜ‚´ì€?hÈò«}çDÁËw×ÛÉfj/?à’ÜGjÅ+Fœ¿f1¾/òIÄœžåĵ�Å^~î?é(tè„o}çxVb‡É:÷~ÌŽšAàÐDÏl$ün‡n™Ït%º—AHC¬ìJ-B2Ìy†Cõ Ñ*€ ^9°°2Ð,ø„EÊ&šãP-+Ìw0\7! �¯ xãBgûÚçeaÉ·õ‹|¦;µÅ;ºæès9œh�Ä[È<ƒ¾µÐ+wõ§ôþºÚ+"kM�\°fþ¸¿Ò>Ò{Ÿ}¯SÙ,·| õèÁú÷O@;جá¿`\Õ€É�^·~8‚/°]³)�VŒ- ¾r°ûÿUß ©ð¥ÑL©ê1°Å²S@ª²Ýa„+•;ÕlJ KW:Ñr¬jÈ£zÝËß>œ†¦~¢-_W<^u…eð×�†¯üa›µn�L´#>&äo„êKg²Ã0e0ìEñ¬+ïøYó}×ÜxØCæÃ.ü¸Ë{ž[üëÛzéJºqãV±g¬Í;'Š¢g‹©E^¦º5½É‡AÂ= 3`ˆÌ%ª[D¿?ðEmŠªÕàŠ}€Qø­Ã"ö—{‹‚›çÁ¡ƒ‘ öuÇŸ“a6œM{ Í-†°ÝÊq`f°ø pg#˜D‚ZÛg „D50[` òC@Êã¬`yûFCDw¾j‡bš™$¶æÐž9D ¹1hˆ$:‹€J.jÚ|Â; z™l*e{øÎewÜ;áB¡?J¤°ÅÃ}× ehû$vh°Ç¼^þ:@/á�7ÌÍá—óšg¾?¨­¯<˜8 ]°ábbý•eííˆ6›3³ÓAéX�ˆ»'¶¿¶tí�ÛjÆq8û/ðî ‡6ô6›î¹Ì¬Gv„”Ô�ŽKõåAmM4š½dSÞ¾ç²ot&W _" HÚ6�ÜRÛ䪿Q ´bQÚ2+À†ÉÇý’CЀãŒM@g  Ñvlpµ“9/W†6|Ò1˜oó‘ó‡{`q ±£ÄpQHö«lq…–Žô;uU^«Ôç{°{WÒÈηÿ®v)r¶±ŠrFo<¯(©~ùdì‚é‚<é‚«Šd×ë¦e°{kø‡M»ÈSjµ%öš‘h Üis`æœÚ¢;ÕˆÙØŸ: ±Ÿ >³zT—w¦z+ᛃ. ªð!ýÉŽ-ˆY6†íÞ;‡ñÙlùk›zò™HiDÒ¦þä� wYa#<1Eå¾êº3Ö®m" 9áK;|Μ9¬=²!¸²Ò;pB´Â;@+„V=¶¤E¢%xF2 \\î¦ÚûÀÈÂe˜X öp©�öÉÎþÎî·Þj›×ÜÐÓÄ �~†4µ_ùÖ·îĤ©Ž²°Âo IÁv“ýzs³É<ø »úêÿ‡ð[ly]Ö@ŠÆH{´9âª^àÄ'@tÒ|RŒÙÁ›÷þ‚N‚ñ@0¤uCû¥ÊšKf•–&¡!2òÈðl‡6»³ÿÁÔ—røÙ²Æ<„h_�g—ÀZI¥ Q¤0v €Y±i8`7vŸeË O€ µ„ÖlÙàd[¾ îý¬šU¡e«ŒÊÝ5óª«íéJME Oc¹àéÖŠ½íž¢êDÃi¥SŽÕÞ�ªiV�«½ÉPÚ7ì‡'¦Mö Ю´A—i!ûAŽ%:ÕaäŸ�¬Ôƒâ’þüe¢šl×<¾ gY3‘ì²íÜùóÁT¸ÿS^9¨8ÇÂ$³ÚêS­ëÖióç×Ã.퇖 ‹4Žq�À~7a1GSáÛ€Õ½Ê9©•Œ®9 ïåz‰÷§9®-Ù'y|@=C ªHFeoÆ©,õs†AÆ!ÙâDŒ¡—¶zgû=èȇOܪxg•íÔŽ ÙÞ×\³�<Ž–RWwвDÅ{nÒÐna eh™Øå·­øàÅS,ôô§â?ÍØÙ d)ÀÆ"…a±;iôv¥"â9fx.Ž^â¯�9õ©ÁÿÁ;Õ£Fr@îX`:„ÊòÒà߇¿‡Tø®YÕÇM)ê“ ò¦U£,+’XWCÃ~BËaYS"ܲŒeö€§a®Éé…AÐÎÑΠÞÉ㮸›g«Ä+§�´³ç~2¢´—þF…2ÕTg¤šbé§¡íL˜8O{§'½¶à~l»VGÉ�>.ÓªºL£­:�6ÖÕQ¢wW,¶A'5€1Ì�¦x &m‰Åö¬�Åb”I­©2ðþf˜=æ76Únc£ô^o2ŒëXº4à ±ª¶G1ËÚZáiÅÈr@p†áƲÄ�¨ìË»6ôØUþM˨”$eÙÑ€w£Ø#Lu‚à*faª°ø°z´@›ÒLZÔ*m[I�Œ‡L> ¡ÆÝÁ°ìC¢Bv „¾;�ÁFþ 4Ü>ªÕ± {¡áT~Ch=KÉõPþx‘eGÁ|QLÍipv@æÂ£Æ9ã�ùõ|DÒÊ¡ÃUÖ¶56äMKŽ�²E_¤˜%뜑 ËÁá°¥±.”ØôÚîÚ¢ÞŠ=±Íª`N`Y™Iû׊¹€¾÷X ûw1¯ ìÔ…Ú¹·¿²rÀ,@¯xnWSëicÆ[@^d‘æÕÙ;)÷ÝOO=±dÒïõõÁóù˜ªó+›Æ�øuESÄ—V-ç u |‰?èbŽ…ógo/[¦åÙ¬~G!¹IOǸŸ›7pYÐƒÃÆ¤Ø§sf1;ï]i("åø/n=òÇ¡÷ÀÁÞñG• %ˆùË®H�‰Õ\½‰¹òe°û©†ËPèÛauä„ÞPÕhÁ<¤º1œ°Ðwyvm�É—Oßý’†ßÖÖÄú ¡×1¬4:M×ê'Å1J©‘,þÌ¥_�ÿ ÎòÉÞ»—-“hn¯³¯ý^ùâ½~¸``S%î _6 oIÎM%ãf¶ê¤ÎßÝrï"pÜ4©ÂO v¨£G%â70 j謇6; ᥿uþO #|ggT­Z|±ò6gÀÝ'ä<Øþm¶×nàjFÍÙxÒÄ÷cj͆žtÉ l“ÖÇIˆØFlϹE ä6�˜ÙB еlÔL=ácCwN*ø«ùe¼¯VZ#D�þpÒ�¶W�Qžº•§Å ³^Žñ¸;M > òÄ�ä�‘÷¯¶—®£Î˜(�ÒŽš5™~ˆ×» û&Ãu.Dß ‹]¦_Äv�dô%hù°D¤`Ï€àvkÐ8™a�! 83ñdß•%æ!Ä,_û.‹¨öBß íFâ"ÀR´€ NÁš�qC'N•è; ÷iŠù@2œE§íÃÐÒ{éAcñ±Ø®e^ dâ`î_ú;-Yk�ÅÓÀÜ)^.ËÎE'Ö÷Ú–®Z@É X¿tqñÿgïKÀ쪪t÷™Ï�窺5¥R©¤’ÊLB @B…`A1QDEA@EhÛÖñµÚmÛølÛnÀ¡EÄ�Ø" aHÂ2O¤2Uª*5ߺCÝùžù¼ßÔ-*¤û}äI|çû’sëÞ3ì³Ï9k¯½Ö¿þ¿Ösœéö (2§Þ‰œ0ô&5ÔJx=ú÷‚Fßè«? w"ìók²‡rÙÞñóä]ßÿë/ÑÔ9Ò8ò[DÚ~�°àO”<·Ä9FØçã�¼vâ ïE>d{I7Án ,§øDVTµ–pL+}~\¬Ö;¹­ª®`¶-uÜ]¤TÎ�üû{ý÷iõ|éÅ€2æ,”˜6 vNpö¨ÉµìÂוi89 $“�Ê¢#ªi–³È˜¶„§H˜&h_´¨ðZöu”k1¹ Éôå¶ùËŸ`XÃ_T´Píá¯ÖïÛXt/\Xz½ÿÙàécT 8Á¾R+ãRuÓ÷V…�5ÔË,ë¼Í³VÂx݃€·Ø �ù;0yA$Á¾ì.PCø.¼nèÛ„DËb|VQð"¯î†¡Fø…Á¶ýuN–u`^AНe5†Í®”iç‘8ƒÄÆHfXMØ‚R ê r…}é ´Æ/;³ A ¸'ÌT{9qb«CÙø¯æz"Ïeu6ãcQˆkšw;ýbÎÝ_ìi¹ìD_î`?ÜZä20½C¼ÄkO½7˜ú)_îͰhZ'<Ô)@¿­n?ÀÙü³xàÈ[\„:µLÆžŽðÆWÀ–x¾«ë›¤ù¤ åÊ%§ÈU+†«Š¼u5[«ÅM¿Å଱”¨ ½�×#ÙŤFÜ@„ sG¼ <†£Ð±�twwK‡9rÚÀHGœmˆ×3Ÿó˜Å�#xËV©–òmx3åPÛêÕ<Õ…k™ð\½Õ¢·>Q&77¶ëÌì…®.kbò—š^âw¸¥?É[+dÞ¼·v|ÿÓ_¼FãÇ÷͘6ík==�7âÙ£è©àã%]AÛÖÖr–øŠ×iÊ„g½j^ ˆ ×`ÙægNœe‰®áÉ�V JFèOºË2˜îÉ¿¿×Ÿvã+3›%KÛí¼±&‹‰cýÖÛÚŽ„Ñ^]ò í*Ië�\šV2U6çh¤^ª.:Ù^TIý3lQ–ï2™ß@\ÎÒyý~• kç]qE4Y*Õ;I°e­?ä¬ÒF“%×Âä]`¶=‘ÃAPÐ|Yuavûæ¨6ù*2âS{Íà¿_ãŒÇSCi»ª!¼†s,`{u�ë?p~x�ˆZš9?'HN`ˆQBÆÄ`ÜŽ@]N‚Ç ât)ØVB ŠÆá¢¼ˆý°±9ºÂÅÓÖÔµ|Ðé ?½¿÷Àåsé¾lhÊÛ±–×¶y. M²3¨¢Óôñ߯?AÖ“ƒ9°×aÀìᥪ�kÑ¢–·<4[CèJBxám{ÔÁ‹eÜîZjÄ}ÐÛúdZÙ"s†—†~ì�Ìðš Tu` ðCœÍêNönnk;äK‘�9;èó�hj%ÈMAOøyìGÿ™^éå>+ÖgÌž?Ÿ¢EÊýÛ¼ì2 [ÈÐ4¾àv1Ï.)'±Ç¤…&+÷ôg_�ó çš³‰�žö±mûÂa�'‡×ˆáþ„> [‹%h6i>s!’Šš,ðoüq1¯¹ãÞZbiUéá¼wÓ¡¯â>�Ï4&žá$_ØZlô%\ÍÕ­ÙõÉõG’©ÊCÒ@‡H(“rZÜ5ÅLö`dõê7'îûþç¿|Œå…ÒdνO1»æSÔï¯ò7¼tM]ùݤš‰ltõj¦„jO.çá-¯ˆ²sã[|)Ü÷„Ø2öÖ˜b©ô2QØ¢Óew•zýwÚ�ïp±qÍ*P ¥|¡~Jä-ƒ� §S„]ñô2Ž•�ë¥ÂSœSžÁg‹[ãm‘¬¯TR486¡òmÔ«åß(9åé°näKdï•­Ä8”eg;qšª§K³«ü�«ÛhnîÐDgµ•O)‰žžø”çÖ=—HhK.»ÌƲ¿™ò"YFóçÉó»7=Þhûá]ó oV4¶U/� • ¢‚a `ØàV2䕸Á|3lš¥‚Vt¹]>düIá‚�ÎT£êíã� à [j&ÒJ§‹ms‹¶¾´!ê ö²¡ Õn8~yqYBXµu'ˆ¨o ŽNgãÆwl³ri&ŒœENð•Ž}}bîáʉá”ÃÚÚL éÄØºÊì¹d›_ßä¥å$YÚï—ÁôâÝAÀwfŒ•@�—E‚Dï͸S2·!äÇO2Ã~`SÙÔÞ„2Ý ¼, V»ÜˆÖå«Qq3 ¹T׿ðú2tÇ?·W�g2ÓAÊTo³l8�íØ‘›Ûºð®Dj´GrEÉçû*ò=*âú˜�Ü•�Hµzï»/`_~KæjõÎÀ†s¤ªjÎuR|™ž›*H5ÒÝ"qܪP‡bë%‹ã3@Ú§ €1ä-˜ý�"ʃTé³&ÏÜŸîÀ]x§†ÿ¹ zÿûÿ§=�÷“©‚"„¸‘ÃÒš/íã{lµ9Ž’ii¡ÏŽºþh|+„ã~jšqÊ ÓEi�-/äáܘȻ`û§þŸ6øÿòà§ÍøÒdÂ|Pöî- ¾1Í€JìØ£ë×Ûk'1È£k'[, õ¡‘㇇ó½ÊÍõó¥�ü…0*7j"ÿ«*êPfYœ¸Q®q§¯„W<€Ø,pœ5 «SœxWxd˜ŠRK@«�rĵw ¾¨bß­åœRA#apÿÎǼaZ,Zä�™nRM$>_®#žý�Áq3‡bÚc—Ìz«À†©c§�Zk( {@Yç ä1l 2Î6¾¶µSœÙ\s6ÌãL›µt‘!£3˜;�'�U´‡%bŒ” 1çÅŽ—¼©V˰pô;øÏ£"¡îI 5 !±Ù$UE¨eü¡ÛM«ÆÀ+Z¶Ú#œ ‘ç Ë»Y;á;¥×<}0žpn ä‚Nxú•M¾ò¹5Ù{þí§T[à ú-Óà‘†ˆV6_SïÞ¬Ÿ~“ñÞ÷%�«Ãe{|›É ˆ˜ÛËôC*ø<1TÀï4�f߈J'ôë¾KÞòb*C$Éâoô KŠKæ6–±Ù;G�=D-ÌeEn¤6Ð ã>A¸vl4;a Ö½¨¨ë7œ�ÚƆÒr‰Ðn³í’™G°Ú*r…¢AG5bsÛ)ß䦿ÿ÷{Ð …jãXã6ˬúø}í?EÙýÑO"n?±)£ÙÞúÎyL„Š-ŒÿD¥è‡k*äªâ@iŒ-žßø=ú€ëô,-Ë–!>'“†”ag ¥íÍ[úåmó—Ò˜µå…¾(®˜wïüúÐ <2ýÊÖpŽN9¨ñ¶lýjžc_ù_3ªÖ¾¨æ±ƒ*¿¼?˜Ï[ ] :Q�?|lßdêFêÝÙ^̲&IÀ\ˆ—m1nX¹#P¸ýn1ì¡S�²ˆµ!F¼‰žÿñ%³ÂÔ��ß9$Pð�åè$¡Œ$ÊǦûÒi.-Ím‚'ëktÁß4x—)*WÑY{”nÓtàn‡ "4qœ=ÕH�à;ÅoåEAµXöZ©³L~GuÙ¬üN×®jv®˜7?vqW�³jõdû�ðª…E� m8+@Ÿhâþ49õ3‘и'EŠÐßL"yàÊS=3@[í¤QÔèƒ<~Ý_àa“©Áu#4Ž��%Y$ýÆŸx|«núœüðŽûAðó³Ý¿ùÑI/ÅÄm'|¶y•ìBÐ#ç!ü|´m<�FûuÂvå�4I†Ë»ßÍ•þ£e,‰—ìëØ ñ¶œi,ÈÊ܇w;vJß`ð½Iš8£Ù_f4ëfp¼€! xÌ³Š©oG4©tA1Èëá7ã9<åÜ“Ûòþßù€þZ=Îê3Xæã–ƒýqÄ/^ÿûƒÇ£ù"Ú"j;®^ñä –—ÚÑÉ|ÌÐŒù3X(’—Ììж¿ü¼óO›ñÍ¢�ÔÔŒ[l‚„w {}‚˜©Æh¸ñ¥Íã8é¥.C¸,@¨-öjLÙW�•ò!‰åwýÀãåý‘Õw @½Úª2°téÒS¼Ezì]Ï<“¬v¸×AžçûØ«�†LY1JýíÔþLXðB«ài8É•¶™:œDH°¤6“”½oé±MØ�Ôy<.4-ýhemLö±Ÿ½ç‘,¡lMýV.ûêîáŽñ�Ý_fI L˜Þ°Câ#©te@?å>X;.@:cò±U;ëƒw†Êkfêì…Ñèºu'=h¸$B4 V`7ÆJç€2¯£ìéò\Œ còÜ*çñ’Ö‰Ñ µ ILTxc'ˬ®‘Ä"œóÕœçzÓÅ| -¿ ÍDBŽUa÷_× Ò³‹'y…´Ÿ¢S\+€˜øôÕö°Fá·S'¡èùiX¢öîˆãFAt¯†ÜHæ—)—kj¿ÁH‘’ÆN� �.^P?»¾â�W®¡²¦M¥O+ߥe¯fjË Œ‡ÑëaÜË ÏÊïEËt¡DPŽÄy�zÏ28‹1@¹-—àëxø@âÕÿø^fD/ çL¯öMa­æýõ{Øôy(šÖ]'ëŒlêó@âÜ,±Ükðd—à9zÀW-~sÉÔséìŽ>“›ê€#ÆD–¸”ý{Àëq¥‡° ·9>�„wÛúR¥Iþ9¦Pü>gqÿê…}€’qqž]Ó¬­¥ê¥î\óâ çý;¼_q²rÒ�»‰ƒ›ö'ñWº±_Ù3A¨b¨Éã9JõºþÜËZ9GÔ!fœL˸ˆ„dÆ™Ð@éXù}òšB¦"g-Y(šòg c4w£* —�ª©%♸õꉛ†ïÜ ÇævºÈëã^¢ dæ@€ŠR?ZˆäÐÞòî´Ý‡ÔÀ®'‹ΰÉ^+ÝÐd4™Ê<CY0äK®Àcly®£Ã*Íô´`ûÝ+ò O²iÜ€Ïûègyηnt�ͽÉj 6Ð@تŠýΗʄD†½|ßÔm-Âkg€zIaà{NW˜¿=oªÜ?Ùðí;žö%ñœL„ûQ¡¶ö$hÙV”"_öÙÛ[˜ÚæsÁ™ð7¨¿[˳âwRŠ2å>œ›^Bêàî>�7‡áyδXQÒÔЮ<èË´µ{¤ú•ã‰(bÞ€‚Ÿ¼Äás†¶ è– €+0!š�-ÆŸ/™py°Í]Å8�O#n,± vÇLÁDéßMzqëÙ7±d{K4Û–ôŒŠ�ÑŸ},N>ñûýE{€SÈï ÅÞ§VÏŸ1 êÉ™BövLÉþïÏ¡’AÖäuí/¸å‰Cq÷Ć¥Ty3ÿC^4þw¶hý5ßÈ2¯MÜæLø<þÀžŽÆP]­­µI� œ‡sz ²Bp¨… 7ô¼­×I_^4? Û€Dã…&Ë+dPT»åÀêÕoq �ô5áÁJ׃á~2Xÿm®AÕ{Žî:·¾&‡å-“(OÚ1Úó Õxí¾‡¶±Qdü Gi“›|®ð§¾}ÞQ˜Ïr‚èäìe+g"ã¾i9Wù[–Ñü’_ª´Éò½–.U«¹?þ—‹Ä+ÆåÂÛns"VŠºK˜N´@g™”oLEwbƒÀ”q@™'È<ûÔ¶––ñ¬~°fª‹1íI0ÇXP·²Ò]]e¶³ò!BnOI½šŒfŸ˜}dÓ¦r²ABÒ‰†Hh º—aã~�˜õÓ(VÙÃü Ã?¹õ—Kšþ¥Þ»7èôÀtà@yâGÀá¾ìg%»¡}’g,: ×$I�Úÿ ÄŽ?Ž0Gç�‰êÉ®Di|—3콦%T¡*Z1sôø4B j^ìKÔ ^×w²ô“©!échÈxl˜nW[…l¨ä8 AyìÍWd·œ­ nôw¤2wcdƒŠ{©>zpíǽê-èö5%ÆøO§E–YûZ$…ûz èÆÚE÷}93z€*Xx�ܵÕÑ©!ÊoÝI‚+Ê&§Ù¯…þ!<ǻӞçák¢Hj'¶š>_ÐFŒ-ðûÓ´R2R±•Pcž¸Í™ðùmc&ï¶aH�uë¢þ¢¢ z@ÔBŠU¥-·´Ù˜"ùLkkùE«œC÷ûAýI1MØÅZjÄ`�;dQý¼]Ò^�˜Æ›•í*kj®¼õËgY¬î ´µmÅßFŰU¶¡kšh-• 1½$¡1Ãw\P7ƒÞÓó0KU˜Îû!»îƒ4I4³`q§œÌ.š˜³r¥Ü�¨�\ƒJ›ÀE‘4GÞÌ´ç‰ ÷Q³¶y+€â[›šˆÒÔ@¬7PFÐ*»ƒÞ&1¢ Ó,ÑŽÀùä�uFÕ´T'OZ(pMÕ ŸÓíLï^(¹öæÜt3(u„-�¡£=`¾lŠÊ„[Êž#õÿÔ“\&s<”> T¥•â4S><Ï€lCµ6Œý§”–}ÌïpÚ0*íà_ò.¯©ªýª~‹ÍGÁC\Þ­òß7ß :þsHØ•`¨ÿ£¿¾ž¢LNZ ŸdTëxµQðk~ƒ ó‚´á“NÂ:çyïAÁÎO±-Oo;Œ< ù4Ì^ü!¶Š™bèö”K_‚�‹jȽ@o–½Ê‰Î‡ó{âñÝ¢èHôoU¶®ZåSæMðœtùo¨ûYH;çóÊ•­ÎÂÆ~õ‘û®}¦~AxO&b7ë[u'=�•s¼¿~o{@0ôš]ÿ[7á§q.î°WG‰¾Gž‘S�Ü>š®�‚…†ÒáÊÄË÷°\T#ŠL_&Sæn®_z‰OŒÍ•C]gà{ÒC}:º›†è|’äçU$×êu…|Ôiï`eòÑ&W˜f¦ËSOz.†TݸQ^*ÉZÖ—1�…¬en1˜¹BNÚÜÔtJ¢é:$�P0qnÁpÌõ'Ý—ßzWõ˜&¶� )�ºyg5ÌXòưÒôâÀ@èg¯%>ã{¸¾r‚«rlÚž¸£êUzöN`{ÿÒ?õˆT èk³N^\Ñœº~ÿV¶/O•áú ßõ\ Ð5>%§Û”Šf=Ú€�6§yb¨…>ج Ü韙ð·ÍsúšX¤É2𰣇§çé<Þ¬ö¬+æßغ5/ph6^<¼s¼Ê‘nwA„ÉsFîéåµ'Ê®µà, ÕuGAÅgq+p8$3‰}Xë8qmt¿±Å^‰t�¿¶jU9az’‡¾ù‡ßôVùÿT4“Æ_ œEb8û:’¯OÃ#Þˆö[#¥Ä#Iå+ýÞ÷8!ÆÀ"õ9Þ6¿å¨;Kšu5¼†s ºÀý80à�Á?ÖHùZÌøÝuËrÕŠÙP˜©¹ðâÙ,oM«›¾ðU$“ßû+:µïÚó¥/¤šY*ÿÒ€[øÌùá<ÏC˜á `´I@<·™cŒM|ÞŠí¹¨>T6t¿ç�¥êia„­“Ÿr㦬¼8Ç4µG\²Þ·öD˜á¤V'3šäqH-!·Õ¦”„KΚÒÏ:ÙÔ6G¢èõáÌG`Я‚ÐŨ‹óš9�@þДÏ~¥ÿµ¾DÜëJ‡ôˆÓil>AðM�ƒÚ2ƒ†Çf!]Uð¼ÚÃÞñ�@U~Íx¥ß» EÌI‚ VˆµòÔS®1@ðPÍ�††ñƒìر†Ò¸w%¸< )b ÃðºmÞfëÌòx,÷¯�¥ä†vP&"ID6õÆmK]v�( ØFx†jU‹×!l�W&oÇA`Ãušm¢ÒBT2›ž4à 커3¦©»W×ÕÑ“¼ VL½kÀuKµóLNe2˜”ïý�.ôž¼Ú¯±mzÎæ>1ož.{ÓÂ-3A7ü)LçJºþóÍü{±5º3BMÓ$FœùÆG_¶Ù1*I-fZnÄ´]t¨Åàml<É@Ò{„í)η¼d¿¬£í÷‰ÄY­sÌuH濨V«@IDAT]‡nP֜ȡØ÷vòbæwï&›x^^�ûgOð|ÇfBj ú±²P ô�Jä×£CGž(yœRP«[Z(Òáýå ì`8�,ÇXLõ5ÙØðLÇ4 0SÂÔíwÈK< ¨Z�ËÇ–Ú«›îs†êîÍ©æ4¸%xn†,�‘^€Bï…99³–wm|�R�uÓSsÄiÍœ5•óÃCzjj¸!‰’ש!·£^Á+—÷ò�—û|éóI¹ ¢ÜO :À0ÿ YŠ �i?©›ö?›ºU857¥¾j?p!ã/f¥Ëh<°NÊ'ÓD‚�l­)Úì¿`Ân{$iv.•zƒƒeI!(˧-´<Æéá’V˜™+Áj|¿™`8±7§’®æPý!Í`º.üÌç{¶årC{~ýëìͬœk»},¦I ýž�ó�MÆGü+ç‚è÷ÔH³õ7Bnž¹dþ|Ô�¼e×(©ùåwÞý€Pbž°Ãˬסàa±ï[Y÷€®ªrSSEMA"s*û Ø(q${ËܹÕ"Ý‹DÛ4xÏ‹°™‹ÂÌÀûÓhþò¯?øàæM==�ÔxS¯ò¨mo¾�!Ú}÷­cVÝt“,ñÞYÀ,ž /Öt‡Z\M\ã�¤×·c0‚nÚ2ðJ%EòÔ¶ß|¯p% lf ĺ}X ÁӵВ‡–yÝ—f™ÍfUx7ÚDû¥|LÅbüà8ÿ†ÂÄ꩘Ü;,c¡‘}(ä®Êž‡®Žé;æÄÝi{7f§»Ñ¶ù¦Ûöcð颃åžfÙíÇPGM½ÛW0çB£mLž©H¢3ŠÛã…Í8gÓ¦ž‡ÛÛ›è¬áýå ëI`«!¸ú‰?69»£˜Ï›Ðü8ž¦�i&saÁd÷ùLŸx°C%ÕÕdöìÙú+©ŸÃ¡ù2 ôO"©q á®ïaû‡ñ.R'âŒ[Þµñ-Š¢—”¬ë$��3EàeEÜ)ÅR;• `Ù�J¤][wïÌ]ÞÞ>þ¢Ó^™þ@‰(I°¸€ËüHf†D×YCþMW'ÑZßrZÊ�Fúõîë‡k?øu ç=.Î^O,©j¥¡T>¬¢D¶¼¬j9« sú•03;òÝ�Ο‚R�iȼÔ"Ë_{Ðï2L[D;‘X“bFžl™qéêG[Úùv¤é»»»}IgDúCO�Úg¥©ç^µ6¯[§-¹~­°íÑuT¦¨l`p¾“®•zbˆiŠWU¡„0[öíãÏo˜Çîë«ü|)lWÀÊœMË%�¢ÐV®,Ô $á 4ˆåcšŠí‚, ®‹¥’ñYÈgë(��L`‚}�i×¼á[Ðqz¦À€ºæØÝ‰ÑWA-c eè!Ü–`r"´î;;¶ÙÎedLmNªèv~÷ÅSëAqâ:bRd9oÛWÃpï)æŠëWTùé€P¦�itˆ<’Y�ö~þ•¯=pME¾—%ªfŽDêëO �`»·[¨¶œ¹3ÆÖ dc¦]‚ëǬæ¤þ¤;ÒÁýð-ÄÙÏ/±dëm?øéMø:áóð7Ä52ïcÜ!»ä‚<(έYÙ¸(óâH!ãI¥�¼×u5 çä¢ê¯°ÏûÆ—vè¶ð–ý¬$»^Öã¹C™ÃÕÊÑÕaÛ·§§§%àÚ�¾{á¹­sÙü¹ÅÆ9÷¡éG賊w÷ï×À>ùIÍžÇuœ±¼¿+¡ä2ïÙp¹ïÚø¦‰Ñ »vužƒ*c0ÍÑHiÿÞMªçÍ;TU㟢Qõ±hûI^ÏÏàÁÖK:B� ñþ<è­§CRànLÛ5TÚvA„qÜûìL¥¼Œ(Ê]‹M¡I(_[¡ÍBþ~ħò‚y,5bw\Öv‚í‹Æ)ïºà’�ÁøN‡éøR©¨<㑘¾ØT…5Û¹¤ Ÿ!è怠âó,i`YÒˆÙb¨ÍßïýÕ‰J ôF¥Á™¼f,÷ ¾t˜X{õjvZéØÈñ%×ß5ì«—Þx×П®¿%¯xÜTTá¾%D0œ¤Rê,p)¬XÑN�aŠænnö:Õ3µ6äR,Þmô ¸r'r‚5Ë. Bóf%Ê'Z�(è#Õ'“¡ÌHO‹§êV ^ÓPAÆ»Có9®*oص蜱Ñ\–)Û[K�hOG�ÊÈ N�dp¥m˜Ñ[è:æoK^yÙùœ÷Çëדík:R^ùìWvZ\YWGÉéËÇÙ“N#öFî€öè>õYÓÞ‚–�[ßRŸ5QÍÈX‡]\„ÆÄß2°ˆKsÒ–8 Êlwæ×Ëúß-e|$KqШ Æ0+j£÷1n{í‰æŒï^e¹Û˜…®CÍl?—w1ēܨ[ËqoWa�55‰PÂn³À‰Lø¼dÑã²uff/±ð¦Æøþ‡3¦Êj —1^éyí�a : ’¯Ç!Öy}*a kì$~©ž%ÄïŸAÃ�ÐÆS§ ³¿§g 9ůâíKSX*!“<¹3äJßµñ¥FêõX~'4²‚Ô¸.™Êï¹±½�fÁËÅcos¡ h8Û! ÷˜–²Ã[yBÏ꽜“ôÈ~¹fj¸»Œ¿Ä%Kú�¥’‹jƒß³bÙï•dùvÑ%|0�‡-ó$é÷Û'(ñ~í쳩ôÁe0ä êÒºÄiÕ©¥'B£€,õ*‰ÄNSS‚m}ld�™»q¥…xKWûEv6$�¾ì÷TQC2xRÓ,d%H®�ÙÖ�Ø–‚nÎCÄuŸ)šWX¬; ¥æ¥F8æ.) ä×$;Ìdí:K í:ÆŠî§vì|ä'B)7Û©nøîtH)¥³XGY_êHQUx§(øEyÎ<ª®.‡RƃÁuH”µsvO�°=Ù$Er¦c^ B A&³ëÙ<™å£ KÞÖ�3&óPåR\¯ß0€? V^ã|Æ0j"«iXv­ùÐCuϸ²ucýOg—ñ+WB ò`�ŸÍg‹Ï¯¬ Œc™utˆGSœ[vŸ8áµ7.\éªÆ´¿d±çH6dB©ñµ¹X¤­müžRdƒ�ã`]Š1y–Ñ zÉ4C WÀ3̰‰¤ª–*Á„Ë"Üð‘¬P=ýßaD/ÀumU¢Ñ‘ÇÀxe,]ü£É-÷"Z*Y–ËÏq^„]ZðB‚qžu™‚}8 Õç‰Ç|ÿó™ÑÒ¼Δe}»�0ö.ko|íiBQ]XdíŒÈ6¡,Ü ’kÕ�AµÒjŠ ÷º½¾BÎ<$ú½¨è< ÿ[ÙîLYs˜*¿ë¶ì=œÌ{Üâ¹Y‰[ÜÞldv$ò¶^Õü�-3�XØnÁÈuxϘÈUà˜?ží–“4\Ò?Òñ6ðzÖ ×¯/%;%ƒ¸ø×ÿpÿý‰Õ«Oæ8bI€êoGWÆñÅì‘ÇfÔÖ–=éZ•F§S‹ò˜Çò«qþaCä.ˆúË–Å þB„@š‘˜ûãV¯4<{Ât÷Ítq1Œô,¤ªRè÷ÃÚ5ʶ½)­èsè…r� “‡]á<¼ˆÚ²ÍèŽs³ ˜¼™Ê'ÅžÙ ÞòËiQe¢ºžG`LË#«’ùÌn¿³à飜u9ñfçþäâÿ˜Ì«Ÿ^S âq�§t®cOÁªIÚ™r5ÞÁWø·Ôu—¶+ˆ{�dx)BA´R)Ë`fñF:–Üê®’7Åcé.÷1†ú¬e•>ß×§éí•nþЇԦ ŸÔ:·U Ü?!¹aCâç«+¦†;qoÆÏqäÕM·«æè¡¾éyõõŠR;­1Ø/¨6w-Ìî5€S¼4@V‡£ÈäÄ’æ´€«x›#Ðîtˆ×?·§ Æ]rz …Âkg5@§ø6Òˆi¿écƒ�D\oÉÜWž�ÚÚZ«GÍv8Má9MT·-s¹Š.—KJù0°�ˆDÿVÉ2þ4ÈAäô‚È1Û‘-?z+J¶ÓˆxôOr^ùàÄë­ûýõ{Û]鬪_…m¸ ø+dž\&¼¹ˆçúP_:ª Üð(þÚ/©š½ÞÒúÎáHµGºº‹Ÿ4Yv% ¢…ª:ÿ ‘IÄþïí•�|öÓb|cºÁx@Õ1pNa)mm®©ÙÛ,ÙššZÖÜÑß°xî»ddÀ»®ƒå)¥”8<=<É.hí ÊÝ‹¤ÔÙˆ/ÝòËLF~NÈuÜ›ªœj0YÂlœ¹•Z ”×éôfl}G×XKìÁ�¬—å a.ë¸f�§/« ¤‡´Ò°®[Wá¥o0óñê#Çé N�K Wã´ШˆIBáOjÉ9àä™X¦¬uqøò�ƒX£Å$8‘w1ª¾�°|#µl¾ðÔ‡4Pœkyá"؇Ô>Ûçëi’¤¡ƒ»vIæóR)¶Þ0äD£YËçêûù/^L}ýêåZª¾]l`ôëA(ÝÔ¨fŽLs»õã—É�Û€\è>8 ï—ÞÁtÇHA˜p”½å¸!PQqëd¦ÊþXwVõgm¤<¤„¥Òh@lÙòCÿ˜ƒS•Fã³½' ßs³Ò�à€øÔ~Rˆ+¿ŸV�Û6¶Ðß›çÎ%sŽÂüúzí>|?dY�Ðx¹ i#p­)¨ “YÐ*X©»ä-‡÷fü‘ïàþ| ‰¸vÑ&C£½Ò®™-jqFêf"$p5üðK0 ‰¨’8˜0‹wôåÌÙ×ø¹+mhr¹´:¯T¢kúÝÒi>Ä˨17üxÛ¦®¯.] ¸:ƒ]Ó¥º}}ji«*È‚À^ÄXüÃ#žè�<`UŽýþú½ë<§�iüUæy <(:ò(Ý·ö{ñ9àGÁ+òpr(öD JÚqim-Mò’¤f¯Fèð\@#ž{l’³Ù�³¥mS¼Ž‘÷îJÞùÌe è;oòßÿ /²FyÄmÍà]ZÈC1«°c'/ˆÇÈzA¼ù Ûý¼Ö H‘s¿rq懶Ñ:ü“÷ÀA`ΉÉ7â`HæX²ªªÉziµ×)†—îÛ_ïÍèŒý$J[㸠+Á\ó�&g|©¶Ú÷Ѽ-_Åz¼7@-áó`cp€jî¨,¡ôËàHI€Ò©wS5uR0û¤ã 6¿Wpp{ìߟ¸¬Å�pÖ{w_‰‚œ¯{NcÝ«›ÞÙI½›Ó}�ã¿!. [ˆÆl�̳n g–ûä¯[—�Áˆ!¥Âã3“Õ3#Þ,©k%þšbV]òÐwBUˆ¨?¼óƒ5ü?œŽ¡ÉÔsŠ®ž¥äÙèã�1zcÛHašÓ00¸w9ãU®³‹üï4yC*Ÿ>�÷*ü"–c/mfc_ºoûä¤Õk‰ÌbŒcWªº‘^o�WÂxCvi&@ïZ®‹g�¬Õ½{m9^Lϳm8¿ZŒy+¬7t¾š-)I}ŠoÿHg'Ûä÷Õp¸´àŸïø…l(Õk¬öw¼Í\ DÊp‘§G=BÈÏÔÀ€`‡±º‹¼mçà‘­ˆÿLúB™e~‰}âÎúðÂÿqì3~LZøP•¡jo%'÷CåoÊcqÅ­™© ŘêÊv.ƒ’Å£ÝC�LÁ«™ãН�0p¬ë—ìbï„>ÕNT �æ œÆ£Œ–TL±*bÚºÛ+1ŒþÓÁ yWPæ~�RKUÁù|©Tú4гÀ5¼]Ë{DÛÚ�Xk;3ÿQm‰ì‹ô¤=ož¼ˆbZ%1Cµìd^3PÍÙyÍ„~;t›oÊŒ®“ƒ)k½Ž×†+Œü ÈäýÒ þ©²qϳ õ'ûÔí€sP?Î�—¥ïÀc˜n`4`ª`c�ø—�®$¤8'Lí–xQ½"˜¯�ŽÎqêüeÀ’é”òØE3Bƒo ŒÞ"ÉRcénÔ»À5áCÂ1ˆÊG(.iƒlÑþÌ¢æÀ)u•>§ëçû‹e^ú�¥©Ÿ™~yõXQLe CƒŽÜ•Š^8Þu¨°­¬9XùñýõÓ/ eÎê-]�òÆGPÅ–PͶ„†ÇßÇÉ�Ý<�þ¼b'ŠÊ�¢T´™»†yàü¨çÕÉÛž)ó§£! w,s¼:02 ð Óæt»;pìñ–žØJÝ6`Ù?°æ œß ÁÝ‹ YXI'Þ_•™3É!2\1|aIß�ˆ_Ê̬˔:;WµÀÂ�îv×D Åv¹œP:{òÃo4ßZ¹–gëƒ+É·Q®|Èd¹‹P9Õ(X¬.ꈎ²àn4ì—γ‡^Êdæb¹#.(¢NøÉ¡¼U&¯�®'{åK<=Ôìñ{B’39`l^4#U1ª€ÆZ;wvö(î‘h5ªÑñÐ<¬XzÅ‘)=ñM=8Þ›RÈ�…â¤`ýÅqŽ›a^öJž¸Ì*œ"‹ÞŒ¦É� ’à¶’eöÛÆ+*ž¦Œj5La¾Æ©«à!ÄПÓ05�Âr¦Ár=¢`os鍯9òRI“5ϨbV¥Q̈¹ÛV‚HÙœdÜ’;c$PïÛܵ¥¿°zé¼ò}Ä®DWÂ[¼¾Ñ/’×?ÑðÒß `8á‚9(Ò}õ°šÎQÃK¿[Ÿë_÷À:œ ÅñΓ?/8øZ"qèû¢jìm¯ø¥“KóÛ{¡¥�—ƒí48—‡3 Õ#±ƒJiJµ'/ƒÜ£Iî8/ëcO}dP0I_¹M·w¸TQ×™{MNziî¬èö‰¿�éŸéÀVéç3½­ï¶}ÉQß›EÑìüììÐØ³þo)ðüãÔúšarƒä0�øþñ¬šØK¢­ÿí¾ïÕ§ÅøVŸ+ñÝ>שhV4$k�=ÏO,ZÀ¤;…dóyÖ^‚tÒF`GxÞ&¹ÌáŸ>©åó%;qš� þ�8Å é�ògwpÂ-¢d¿F‘ž ËZêYÖ:ú7Åó?õ›ä‰d‘k°%3ä¨ì$R2m”zͤ9°tž·ìåU»ñ¢*Ã27묵>¿ûÅi�ßôK[ý̺Î5Éœé+VG˜¥ûbO¬KT›Q&Ûà-™+/VÿmÏPã°m¸±m SI}–N܉B–Ûˆ.½ u¾£Á€Sëɇ[˜m°FwFc¦ yk&ªH:T›/"ùÒoæˆðz ˆ‹:DFhàm9.n7°V‡a€Q˜é1:6mbîko/¡2îèæJlœ2«ad  þ·ÁËë`"úÈbæ_3¶½–NÓÇ—ö&¢¬'�Ž5AJƒpR÷‚�»}t!ûÒìææqƒ=¾ó„çßy§§0ZhDø¤cœcã0øʱºÊ¦m*ä'M O|Cgmk 3w9ÖÙÉLžEUö{Sˆ´†DõQ"aÀ´Ï”4élèÜþxó†4§ú"9†Á+¨+t{ªy^8 v¹àNÀÀ‡ÇìÊñÞiMC@ÑsÎñ.›òŸ§ô×;í÷n£‰ÓûŠàXS¸»}‚£ñn�}&î¿f60ìÿ aº“¾?Ó`7)tgtñÁ§4Ï¥8i·ch¨DZÏ\ã{ZÂúƒy¾?±r¡žœ ǼÜqÅhRïr4›­åß­ÀM“8>©–!æ÷b}VŸ·™ûgÉüC±9¼§}¬ªlÂqOùËç«€„Xã–å]jŸcG¥FŸÂ˜¤–L/e)™‘MI©3Å‚ö‚Qo_ЄJ±}1¾ºÚåîH!¼QNîQÖ¤j/D1¡|Jè#â8圕/hÌsÎ'¿ð-„O†.n¯¡X ^Ë>êa|U`U4IÌ9öu“Üç\‚Ðq0åô�K>¨éÅoú¾;@6ÚŸÌiƒAžß‘° Ð…‘?$sú‹²—»»ÊÁ%<†cS¯^¼Û%’Ìçw”2¿dªÆ"r¿,õ)º½Pî¾MtH!  ŠJz°óò¹sO"²©´ýº¦×¸jÕMR1À8/žY7> žæ¨'ò ò"%­]~^Kp+Žy’ᮜƒB Ö:âžKY6ó“ã oñæEï:¨ìû?]?ד¼Ê+q÷jT&Š×ˆxgmµ®¼ÐqT; šº™b4·ö| T7âÍÞ™éºôÊó[)Õ'Ûö‰Ûš!YoºSñ|ÊTëú÷+ÏÆÄv¼xh ÄyœK‚¾ý_6oNTÔ�÷�ÛÀiOÉ� 2x$³xñbãÀȈKŒDt»;ÆŸ5$´ þô]¾\ÈhMSÎ U<¾{îïí ÛÎk‰éÜ££ð³ÚÐ{Ò`–ypÿëéJ&¶ó¯ùs'Â{š\å*ɘ΂¼oÃÀÀ$fÃS.�¾óúÔù«x‰°ŒU2 NEûZÕb~Ǿ¯½ÎÑÊNgÀ§ÕóÅõØA·'Wà–Bî|šÓTo"®„«'ÎðÒE‚è #ÍÿœD¸Àø†XÓÚŸj »j £ý×ÑäûI:~ lP,V%›‚¤;Y­7¦{‚ºuàLË#a¿ÂÖ;+`²F⪢05&� šä¦.œ-HÌy`‹"}„Zvøˆíènâò#ý=ù|M€CÑà\®~æo±K#8¦ u„¶ŸLy«ÿÙIC8™ütF5{‹%s[ ê·Q¯w.X€EÎä.=Úÿòè’"ÛÄØU²Ñ#õôç�ë]º,Du/uçÜÜ ‰:Š™Ñ8¦ÚËG•SÊcúÔ¨²E´dªÁ€+ƒé Iœ3£–~•dËT-u´ÔÓå‹.m©%ƒ*̪×0Æó´˜aͼ¹˜Ñ›zjTW�yX>ŸOë²7à• £)Ùôy9> AvŸ«È'‹éŠæ[å‚/þØí®”“›t@݃;w¾DN°Ã‘AŸ¯ÅaÚ³á„1ݺüí /=Ž_fêK¬°h‡mN9²¤!TL?—Íî¿Ôç/⨜ïíÖ”��~�Á�’åû>y;ÈÞoGAÍ¿æ÷a0¼] t "MlO¬¹H 2àÄAl�Gà jŠ(9EÎVY8¿µ[rýõ‚Â’Û¨eB‘#u¬ÕWh˜3¸)žíÓ†JCH4–gGÔ@^{×Wý9f›&;ÿ¶ÙíûPЧÆÏœóUËš'8¥¸³®îå}1Åój8“ȇÔüñaÝe6údo,ž0E7:.FŠ‹¡Ïìê/~eJÍ[=ªú18‘xÇ>Z1¬ÇT†¤w�_›å´±ÄH €²Ÿ~ö‹8Ä8Šfrßü5þ]#NM6—‚rª¥–¾ÐRïc¸ŽA˜òÜð°sF©dn/­“fBMM< h_€þu#ß](áÇô‡»Pd¬ZËT^aÖüñAâ›gZœnã‹Ä{WÑŽÎÈ´Âã>ot�lÚDÈÌ…×;Ÿ±˜y üyQ Û‰Ü_Èw^F ä{bZ;±çò ¶+^ˆbøú²Ò!>—:!öè9öXGÇ>h•_‘у¦-Z†¬ °è˱ü¦/ž+;د¶ï® ¯ÇêŒÁ�Êé5 ¹øÎÀû~•Užœt3ƽ·â…ð?²�ÿT)¤Ý?×á9ÔP´ç[�V‰ùþyK!Ô·Öz¶{𗃿µb«€ãåÕŒþ ,r1Ë2[AJ58¦Êe;P‡»¸�Ì3^^€å¡íÁÕ€ë2êã:†µÐ%*�0ÈÍÙþf .ž?œUÕ—äëå̃l‰U!e?šá3Ä—5»Ñè¨^²‡ÀqÅ& …¸¤«^Rc1Ϩ HÀW�ÂLé[Rõ­—\R§åmïŽëÐÌ�Ëç®b¢^ðú�eñrn©ÂõAÑ'lߛȽ¶õø‘î10’›<È$ð‚º§Pî:$²Â/EÕ„Ó»‡‡;»ó¤¦†~ý¶‹GP½Eá VÑPfÄ�X)®:_èÏnÃÀ�ž†ߟ£x2 ”‘ŸŸ6;¤«¿3QLë�3¨©©n+Þ©ìðzCxøÂò'ŽMÒyÒŽ$Ý1�uèÄ�>> �Øï☀da“ÐwÛ–Ó´‡¯ËGPúÁ)‘ðaÍ2>‡WöC(Û~]€ÛT(š9Ñ/?ùó==Ïß�0Z"¾aß¾áÐŒ[­”>Ÿõò ?~ö¶ iÆà梔=Âè@¤8^ÐÁÜ ŠöMœÅW•×§À-2f‹3€š®ù* wBS¶l Òƒ£‹¡–r;¨,öª†=xÕÅã<÷³Ë—¯Å±©îót÷p´`Z�‰¥y5F‰@_Ê1¥%Œ,Àÿ_±`�5ZÊ‘TïhÍ ÒJY齦Å71Ûµd8ý&9Lœô{ºlûáÅC™ß#å{ƒÌ²[KÄŒaJ?E5/dm½—¬_ƒwûmÇîx�þ?íÆ—NÙñíØ½þYã’5k¬ÇŒ¸ p{ˆgŸ��5½Ä~ÍÔõËu%y$/Ëî{¼w <ûA:LÃ,bAÝÅH‚-E"é{õ^ï�Š7ת #fZÈ¥3y Ùm·Ü¶5ó˘V.±D³²!d?ñÎÙ^—À8Àl8õÉ碦ŸG3~ë]4l|ʲr=­lYÀ�½€»#žMgy¾1Ô£–‚gMKÆ †óŽ?uîE¹¢Úk;Jó>í×\‚™ÿ9û%&oG-ÕV,Öè±-®Ù(åž.±î¾ ÏÝ rqÉ�3ç†æ›`õ"Y¨KB–ŽcþRi€‰K¯¦› 5Ö3­‰Ä×wXÍ«ÛÒ£]£Bs ‰ ‡bž0?U˜jj¼yqÑíw9¡cæ‰cÁéù°`KHí‰U:Ñ~�§ò5….64æzV õA ®Í8€ö€Ä;ˆši�Íy);€`7úåZx /�U×ô}tÈü³†ìü¨Swrµ€ZÕ ^xÕyÐGò ZÛaÜ Ìdn^ýgtô*�úÌýžÑ o /hVbŽeé2Ši@iv ¢¯ žw)õÚN*¨Ù5È*-’dq*g¡>8‚sq¶iä¡V±'1˜þS–ã·Ì¯vÇñ°”Ÿ—µt„E|zSÜì%%æÃóëŽ},ߟ䂵AÑJA#(˜ 6‘µ5e§5#Š�þÄþt°y¹+þä–,�;)†Søg6Wô£8ïsÀ+¯« {ŒM ’hG(Œ"Aðü)‘¥íM8Îjàìòû£šJPnOÌp![t±´›‡6o—û ·*îæ°%—€ð¤‹Vâü„"hš"u·ƒ·+|ôÏÕÑþ¢?RÛ‰êÇùƒððè@ôF2)é×ßÔ ½–O6F|>VôFý·á|r‡û4ƒ çúkYhâü�túuYå§b¸Lë‡÷¦H´Ý>¦.IÒÏã 7®�rˆ”�/M|¯Eøpý¨ö›)AIÃ3½dSÛ5Â�ôtk�E«« X2)iq†tÆi7¾ôº¦£Iƒåô¢#4@« ÷«,ŽÄòã—yfª—un2kBeojb_Ð2詚k™ú¨€ÚÅæ5FÇëÔ˜$ß³ÛÛ#ŒŽÈððÆ,ÁçG%.¿..y~±n[ <ÈßF¨w;&q1PÃqR£ÂèóQnüp�6‰„Ûï<•XÜÄÓ“‹¯œRgèÆ5¬ÊÄ’‡Hï÷ˆÂ%²^‰õ1Âða쀢Wm>ªšL·¹MÖøý6oƒL•[�©ûº“gãÉŒzÂâÏx® $1j’ÍüP×#ùíbQ_Ò•=ê évN­g<+ßXëÍ„äyʈå%§¶\Ú0‡³¿—óåÌŒÏͺBE[Gá.’À‚†òà0¨0ˆiº0Ð8#cÆ�úgnPˆ“í†Àô²šµ�á٤̪`òP|bî0óG„‡æ Æ¡óà­†‡{ÔUx¾ƒ rG[„ÆýÓι鋪¤:\>TLòb_SÈyΚ¬Op oÂé:%GǦ•+kÑñòùwò#íH˜þð™íû§´žMA¤Ü¾2Þ�Ù©lb3HÇûžÆ5%Gð㞆7Š ¦ÃZT{�²`Öá&Ç TþlÿÈÈ2ÕU#4´R9*ôÔÙµ3Ì+éoL­K¬­ƒ&!±MÄÃŽv‚)Íja%j~°Å_FŸTö£kÃ!DÁíéÎÆëÝ޽ÙZ¿ ÄsqLj¸ß nø‰á}û6�yó º-õÖˆemæåEà�¢ñFmÆ9ÕZ±ßÄo¢lùy܈Fžoæd}¤F¶Œâ,á*À 9F:ºn;ŽñV÷¨f-DIûßçª|É0 „ ¥šâtã™åMMR>–�åìY5l!«5 ¸\NÁ–=ª @â¨0º¶hùÿ«@ü¢ÌåJ¦rN_ß�ä‡Qˆ?�åGP„CfFèeãùcÞÊÍÛaßšÙUy�-ý.@Ä<üŠO›š‘½íIIÝ3­«N»ñ¥jµ;†óSsVIzq ×ýjSÓh{,÷$kXw!@¥Ëo6tæP"wüØÊš™§<<á`]<ž©xùœ¢nWqN!�e—‚$C<·ë$i äϪah€?±»å`ðÄt˜ç07f¨øzL’ø§ŸmðS<-c~e§\1Kœ‚ÇÙ‹çþ.¼À‡øÌL«i¤øäÅËÉ�««¼ÀyP*µß#ˆ*¼Žãž§=ë ʨNÅÿv¯ÃÃb¦ª ˜Æü„nz3i–i6m_’ãò)=n_ÔÞflßÙ•S‚Tç‚l­¨ó®š/Z.™ BΑս#!ËGª†Då*¾gVÚµ�eÄoZ%fÚé�_çF U>¶ ï ˆ4âFù,ÎÉô£ÃiÆñ áÌNAàò !Íýþ×ò=é]àÆÂ÷_Ƕ>ÄS¶—<•ŒÅò¾…q*/²!OE _‚s"\óÁ¬•ùõFÛ~¢6 IÒÝ$PS|… µØ{Ž…àŠ‚!î7z:=Îz†c�/Ôðb´ü`hMÝz?¼xÇe—©ÈØïCa]Ìët~.o°OEÇáõN-ïG ˜{îY‚¡½_TAáãeù$Î$’`` r¨\b˜;Ï�/ñ+<ç)îØ1ô`Š•SUwpO­Ópß+!$«�~n¡F*Zfa{p'¢òcñëʾºj·�r£d·ŸøÁ§LÔç{ù J\mXþ*Î?EDûurà€I�o+”4æ*œ�PDÞÓ*׉f3"Í>û_&£©þŒåz¸¶*\Påë»öé^0ÆùÐ,€ v<�I†Û‚Ѳçk¢‚3BUDk7wt°×„ê›@Ypït<^ɹ}|ÀOcð°oÿü—/G kc.-ÇÞ+=ˆûƽµÉ×Bþ´Ý…ûö:ð¶”äÞK� à%yÔ.ŠƒÑUóÏ­-júíð ë È qM]£»v.Š*më×3ÚòåÝV2y"|‡ H~«V+nÀ¹é,dÜûµ²¢9©èNhÓÇGIå´)FéL ªÌ'õùµSæŒLáeñ¯ü|Pïéñc±y‚Ž”Ïk[=M¿}sˆ øœW ç›Ùá6õ*ÉU[[Z,<º5uÈáµ\ o½Xî�©+-*mx“ë§A6žÓÒRö¶vÒŸ�ûÈ�í‹tîÍ‹¢m¯&b9¾P%éÈQ”ŸÉFŸ/»/‘ßhÛü9×D£S[@àÙÔKëw¦êл=áàfªp�Óæçf4õ¸G/‚§<$ktˆõí�Å~Çð®å’_ðÁAzß%Wî„aê#”Çeþ.&�X:‰MaÔ/°˜--„-î3t2]€Èë 7Ü ðüù¸EÊÅg ÉMŸÇD;BPí<²í‚E‹ÊÜ™|õ§Õø"ÑÖ ¼î•¡<ÊØ¨Q˜�9tqË1ËPºT*±¥¦Â‡ƒCk£´&-ôExñ(J¢Ür‡h¹nYÛ¦�ÊÝ�ðõ"]±(êIvæµÙôÍûÁøR)›™ÑÀhqÒ¢íc%iê‹Ý…Ô°©–ffºKèx â¶¡~ÿø–ÁÞö+N.®4åŽsZ²ë6‘ÃõÒ–®³†|æê ÚÒdÎT4öí÷¸ênýê”-ÉœpåôRÉÒ}¶Ãå½VmÐbŽmìéyÅ㌬ABäjÁéÚ–MÙ³d4 ¢èž¡le¨ñàò¬iöbtI òÅS…�õ¡�‚4„ÃvIúäàÁëf5ãà~½¢¨LÖㆇŠÄ¥eƒÛ‰µFµ;o"ç^¸úhÄ“…œ�èû±¥P´‡ÅÐÚÕ"džXA¢%Äãr™/è¦+‰@lí;¨¸\'¼{œ¬rœcw®õ¤�ì2„`!=RÚÙ>f@*¿s"{®íC¸çb/?n.‹öOÁ‰Ø ó +ñZmg%ÙTÙäË@´Ú3\•I^Ì'�ï½bQkñ º6ÆR@uÚE¼æõ`¸j‡Ùœ.ˆò’�;ɱJÅá¦#Ca¿×µ Ä+K5Ó¨pÆUãQ-3ÞfcSŸ@às¨CÝŽÙPgezbˆÁÌí°µóm��.© ël8¦;Q6ÿK „çâyþ2‚&гÚzƒe›Xä-­©é7ëÖ‘#k×Ö¥�Œsfømû4EÏà¹ÔáaÓ¶ „vË׌2á:Ô1Ä€ùj¯õ²&³#’IF·ïÌÏ{ñ­b!Å<˜â[ÍIx�ì b ¬xF9Ñ’5Í»» óiÙÃK�'⛕>¢ë˜ž›�Lr3’u¯&seºÍqÃL‡¡™Žé…çºØ2¸ �ák´}û8ÄKé&åe²á¥_¢çbÒ�ªh´’3ß ˜­§ŽËZ‚Ù�#¨eØË Gf"k–Ñ aÍ2gˆÓ=:öKö¤ý®#8„.¥Ìy¸,ðÇ3–·©–k­’<ѸJ¦PÃZl ‰`̳’+ˆÂ–j ö~!öóÞ)¾}æ¨O§2)Že/Ôu’‚hIfQQa»ƒK:»ßë­½�ó¸Q+è÷°ù'õä|ܱ1î�ò%þ~x8bê̹*ù‘u„t¬ó¼Û"Þ®C¹ïÛiÊÎ…,òÓû6l˜sáªÇˆÀµÈrðl�ÛPfþ¦©s—€Ïc�s#x§“:„-v”8£Ïu>€g‹G£H&È'U®ó¯i­ƒŸ ¬#¬®ÏîDYQhþ|0K1 6’¦Ãòðqå_:‚ÓÕqVCKÜ!Ë�P^¹.š„;%�t¦^ûi3¾Aw¸ÐJ™€40áùgÀ• ܢР¯¦£8úÀ„_à8² ÞêÛuÈ ‡Ú„Ô�Þ� z9�¹ 3{9Ö„Âmm࢕¯+µ¹©áÈ·ºí œd8a!’QPÁÁ‹ÃqQÄËVᆠȂŒr¦Ù*³aÍf—³àñMrCØ?ŠCÀ6¹ÏæDn¶Cw†‰høYƒ Y2e»¾Ç±öúéwa³RWº“©ñÔDAÊS\+buAhÄ @ à ýÆÔŧ…Á£%~êÌAÀ�ªàJðžþI®õn×›K›³y¿.”;lVÉå:bÓj›áòÃéã'L‡�¨‚§ŒgÌz�Õ@Œi”^ò43å†â?‘†0´0&x<í¨¡–pÿ¼åŸ/hô¥1 Þ/ß\}Ë«_ÖR“ÙÏQOšºNÄÄ#�’¶Ò¹¸“ød�»V·èHvÐ°Š›v·¸�–ÊIÇÖhÍrðA™BxA5ûarŸ¸püÏ�tþ·%‡Ì9ò,Þ,(×-o«;qÓÉŸY– NñÄ>~–Lp•+ßÓ5Ï1¨,Ã]ÅÅ0‘ú{ÓÒ+&ÚÐø/@•8€)ó¬nêûƒ�9ˆûÒ¥]HZÙ0=]Ø…ú¤4S¾¬uÁU³<ÿ®;Š´þCÎåWeÁÄÆ¡Ã~!ézÊ`™)hÅÁš™þ&0Ùý-ÃÍ ‚iÎåpº™BÉþ�[zhÃÿáîMì*Ë»ñ³ŸsÏÝ·¹sgËd2I&“dH²‘„ ‚�ˆ€TD±*"ˆ¢ÛÚÚ�Új¿Z´€X¡*nÔjdQ–°2!�@’ÉžÉ6™Ì¾Ü}¿÷ìßï½3w˜mýþXIÿ/Lîv–÷œó¾Ïû,¿ç÷\{ÍÇ�z·@¹;6Ì ,y§3n•V#ð‘F�`§Å¡!šª«£$N†ïÁp‹‚üz��q“ýœï‚–“Ð-ýYÀ…òÈñ¯F€¦ÎÔ�j‰çìðÒ_€a¾œ3LÎúÓŽiþJ *Ö@›]NÞ*Li”Bö,ÀÀN@göæ­B9}fjîõÁô¿€·Ð,㫆ûnmjʼn?]^e¥ÂíúþhŽÂsP»Æ Õzt ÎÓK||ûKÊ·nÕo¨ årHøžh`Æ–�/ †W\öM 6.Ë…§{êZ‚�¹}…eÓø1Àb3®(¬ËŒ‹T�·ñ$\iVePV¾˜|U¡ŽC«Ä:` :C-E¢‹ƒ²q1*[|Ø`�|`±Ãº¡þXOSû6bÙ™~ ’Õ°Å,)%FSwÃŒ~ÇâùÌÒµI¢à£å/Y?Ž^ ÂIaO­ý¿ |ÀÙÐo�4ׄ—Z�íIHTóD=¾J?Š¢ù²õ5¸}@b™2˜�ªÂ„ßÖ4¬• ׮͛l'›/ýS- f2XÕ,¸z1Óh¦˜Ã/bY»È´SãÝ›)ëÁ›{Äöº¶ QâèÃвÛ¥†�¨¼²¶¹{¢’[iÞU׉*c6A�ˆãø‡{óÑyU³ÿ–§Ü‡‹TñÏ�]\�ömÙÁ?'誢¥?+øëW_qí>YÑ.ÌRÆm¸ïKq…6“W3E]~òlrŽ_vÿ@Њj(ÄôîÞ¢¶¶N$`ñƒ›MÝ‚Ë ãg3 f7'?NŽG-M�@匔ìt¦àò�p©Â“Œ®Ö3¼(<ÜìĆ˜hH!—~0Ku4ÝÏNÎùJW—IXeÛóåõ"/•A¶)’&å3>(ñ”QÀÐEAãtªˆRer¹«Ä%(iòwvA­™&y;Q0Ö_xð‰$Ï—ëz{?þhÂ7Ïaj¼ùb^fyi%´“« qÌXé&˜»ðcñ™´ûÔºV÷9�2©PyÔ"f°½ÖÓ£Œe�›œq3 ¿–1Fäõ£½;w«eߟÏ�¬£Ô¯®Po·¹J߸N„²¡–b–ÍöýhZÛÆä299¤|ÑmŠLLÉ9í¨w ´Ÿ¤f–†ˆ6S�àÌÚªSF7O mKß ¥Ê…Õéû3ZjÛå ÿ¨ùR½û,”2zÆhú5Ž·®†¡Žd/+¡äÔ峕‚e½&¹Ðf^‚Åîb· ŸRö癋çg�÷‰w\¸6ö\4æj¤äc|’!jUÆÏ&²P#Úìó‘®Ñ�N*Ý1±�±;—x‘)É|0dðsÜN2^+Û¢ª…Óæ°fÌOìÎdŽ]ŒìBýÀ§¿8·µzFi7¨ZWÀ²#=_ZùÚ¡½vrÐë z=ZÁt”dk�ÑoÊg*ý´·- +†23‰œ,"Ô»;ΰ>�Cýš °Ž á€"²æÉÊöçÛëMøv]P7Z ”�Þ=D�æÙÒ™*^‚eõ“@"¬BÏkãÆ;ƒ¡W6—¬b10"\B²ªòK»ÃÛJ±Z=ω ÏﻉÜ8ˆ¤ìæ°'·±›Õ–Ù1YFï­È9Gs}{ƒh"Kn38lZ»©þ&/µ[qæ·Àº� ²Hå-?ÜÉ'a†Ý‡<u$9RÜÃÑ:™H^èV @d}‰ÚÚsòÄõ! �ãû!µ A¿·(/YÐÙ&\Gí¬ùõØW\ÛîËV¾w¤³¥`ˆ…à¤(ËØüTw7‹Œ½òµ]t­Ÿ=›t¿à„‹Žö3 F6<ÓATÎo·«™´*{“X9LpœzÅ’ê©ð­ð)R9A9ͨ¨�§ ôq¾\â �³Ðâá·è†Ê>œò(0lg!�»2c§×¶7³¾Òå©WS0–#ŸEÔ«3ZZ¼£(¸-\Þ�OÈÓ9ú÷÷ݯø�ÉoÐî ³`ÈŒl† d{+½óEpi„=’ЩF „k76¤ñU OÕó1,Úõº¡Ý3kUýw¶�–ú93O¦=¬h» ÷m6z—7èÏK±�ðC•¾D³ý$­X”ÍáúètÓ|žŸø+=Ì^V²î;ؽ7±zÆz@ð{ HVø9òn<È`§gÁ]¶Ê%6¿‰K( _\ýÔàh.©ëÆô8öLõüK®‘ìž8>ÚX5¬¡WxÍŒ¡:Õü±Û3ã­¼¦ÐJsøŒv:z\‚ï ÖßÒ´þ”HKž˜™‰ê´óbSÖï@dÒ¦j£½<§î}:Éü�žc©ŒÓŸœ<鬢]¢^eÓ¯»ÿþ 4³c²�¤/“¥˜Þ!@Ñfé§¾´�þ&ð[뙤z7´ÉWÝãã²åæÃÑä”G³¯þT‰÷õÿìgêôgHŽû~·újWñtQõ¸mb êmƒ“�že¤‡-*LÖh˜ªÒ,‰Â?˜÷’äB¸…Ìÿé–NþE(’û,6ûÿ·ð%fé:ðÐ“Ü Eë¤ÁÝC±Ç)ü¡!®L¤s7¶zÏÑzÉ dc©,Èv³À'YÝh·Xøë˜Î 8=³pkGã”òB÷�wµËf;æ*&6RuSÇêè “%<µ9nYPNÔvÃvŽÂîTîÇ(×.sêOkðý•Í®Nu<êà|cR-(ï+‚%B†ÖA´Êw4›e� }^i_žöŠEø5�åí*áX´[èìÖí;¶ü @5hÕ7Dz87¡TãÚx&ˆŒ½òD&;Bè Ç“™•BA÷uE’¿ïíÚ–s/[×,J¶ç*ß èH|;jþØ5+ŽsB ¤dà!Có æùªw,»toEÁn7úvö¥â�â/¶ñ� ³I˜±}TÔ‹_ÒrZ<.Øt§èQ/ ·©¼”/bÚ?Dó\°äR5”hÆØ~‡ z¹# ÎTòé9, m¼ãŽðÕ[÷•³ÆÔB*­di4jÄm$r¬lö&)·ÝÃ_iÙØ£%%5@IDATêUrõ!ø:__×Þžß=¿ÞdX[È c/3+äÓÕ=  Ö8“�ßm;âˆ+<ƒ‚Ó�t´¶æI7‰åä[¾6Ë ¶½ˆ«Cg“ñF;“�£ú&Ò+”0)nÁÑK$Æ*,\O<uåm…çNSÛ9'µ³PìClÐãµ<›yñQ…Z�у¶}ûv6«²7€ðýƒ@<¤¡±Ö1)³eËK[ö¡^¢Y›Œë*if¢Ñwh�TÛí·Ì<ÛŠõq¤Ž‘b«Úyª—m…•Á¸Xú�BɳߣCPóûkÔÔ«qÐì7DA>SÒóëN¼+ϦS¿é¶=Ø ’ù^.ÔäåýíðHùÁúQxñÏî>øì‘�3W/lH–;ù_üCÆÝ+£©Ÿ�O°9|Ð|> kmŠeD¿~J|Maõº…67ÝÝ›†p))ŒCÈüó£}õ³/f¾ö/ëÚKÀ!2þp|ÐLg—R·Jr0Yæ XœC(4z6Jwâs˜õ ÝçÔĸ;?®èÜ^Lilç~ýÿö©Ÿ÷/ÖXÛúmñٛ·l©O,"ˆò9£“–Øks‘è»Em¶)9ñy }Z™K›âz†Ò?�ê¼_ƒÞîhæ1›Û¹Qf¹/Ïà�+`ö•ý�»Ž x_„gW:ííCüŒÜì·Ÿ§þ-EÏŽ[Eë ô‰à<)‚rx •‘ SÕf,¢?Ø»ùôÄ-òLQÐtR°lJ8’}ˆ&AŽ•Î$HÙÛ¾‚’Lùœ"!¶|«‘>Ìõ×ÖV…�‹®»ýÏd?ò+:FüÁà¿„1j˜µn±nêøXÀHé†Çð! mÝà_´‡0.�½‹”‡¼ôk{5ާc&!Y�âJ )ŽŽr«AžÃ°’¬™j}Ø.ÌEúßRU3—ü´¯¯|�0ÿ£ðw–µDH@Ç^³mŸ&Ò“"еw¼¤Ïü¬Uð©.Ôì+³Ç‰L˜§@H_Å m,3Ñl:]¾>r�È6r´§¦;…¦Ó‹}ãÉ–—úb-$À•ÐÒK$IøªTlÆ17ëõ-e;z(ì?]dÍMH¨ù¨pŽ Ÿn–c¯€«h=\µðßFtÿ~•Rþþ¹ßTz05¿Ã†Îß„EÓò8Axlß ç·”r�©”îˆeé[DÑú2XÏ‘íF® ç¶H2ź•¿¶±1uUØ3°«¡¡oºöG’zLÆxJ¡–Ý#¨è‘²‰tsSÓÆòõ&4u$¡h‘Œ¢ÍsRbãÇca¾À_cçØzÄ=¶2‚7zï±nÐHQ‹q=E�Rq£T¢5ƒ|x5ÇYí6†ÝšµR/ffȇ2÷ ÌÖðò’>Î�;ªÒ"ý-àÞï€i‡ í j¾§ÊÒ ¼’üu¢pÀïzÇ>×›\øâH±‹Ù9N�QbÀ”ƒMŸ� ZÙ:]97�;>Â2¥—�f�*)Ô‚#”â=�ä.yDÔÆ{ç£rÓÆrüR^Ó.�»®4�P‡“äèhŸ1ó+.‘Ú„ûÜ“I)÷¡€Õ?Èób#2Kù`çá?·ƒÈQ,ƒ[hÙ«Ë—¯}üÙ#é£W/t—Mö7æÆá'�l ´¿ë—’LÁéQä"E§ôõ–õ*k5{ÐIs½ðë•5Ó&�½š×— áÔi†Ñ`³Ûã~*VÀql�v5oŒ dR/¤F^Ц–=ŸŽG }ª4[wÍíŽ/²¦r-0P¢Á1§ÂŽ÷Êšîhç¦�s{dz£<âaÄ‹ f½:˜®;àDÅô\õ„mÜÕŸþ²3M»·ÞÒtP È4+Ê,úB2Á3ûú¢¡<È}l´Ì›¥Ò†M›ˆ–_¯E|¡à"Ê€§Å4&Šï)jfc£²/‘µd3/ðÍK{zÆEIŠëœ[a29?&ü°õ�ÈG²QDýÁ „•ÆäÃ�J¶¦M›J+J•ì>‘/fªbØQ&Ç6—n,<‰r!çß‚Ñfvh�ì' .þû†zY‹á­¨�6ñKd­´=ättÜ j‚VV ®þì�³9ÎvëÕˆLs0ç ž·n0(nžßÁ¤/kiùªÈÓ<´h€NÊç. 1òŽõ-ÛŸïiZ»ö;Z‰zJèUgƒø*77€ÞŸ¤d÷ÀJ?•%f¥! ¾$f&ÀÓÎi,†‚‹§ ?sø5zAî!�ü ‹u0<ópÕø¤o˜îOÇG‹Œ‹ÊqÉ+Ñn�/¾¦fôïEŽ{�sŠv¶xQ;U&�.Y‰^¯ØðS\æ5ª!Þf³ß1}%‚{‡žýù X…noÞ²f"†›Q51+…)3¡Yô2„‹%É&½¼ïø@~SS“ñ»B׊îRŒàvÒŽ’Å~ÇýYUJÏÙEf¶®‰�"ÍúƒÕÇ“v)¸@3‹7›e“%ÅDÚ7ëI,º{n�äü¥5¦1�j\ÿÎ#V$»z¥bM ±Qà“Söš™ã¾á3Ê …ý’Q9Õ‹*0ÍO‰cç®áìkºP9ÖÙY„`{WÁKîïµ/(™Ú•8L­aé¯0¢7"–ÒCL<™dÝS�ÑBŽNc²�É9Bö7Lv×-øU&­jV„£ùþ£õÙÃU§æY JÜð«´Ñ†à’�ÇPÞ�f47cŠs8AãÑ m�6FtbúJJdjQZÝÁÚl^3oúh‘%Zš“á€`y´Ĉh7䃻â{½äá�_%RfUÁ(õiŠmhºI M�„¸èïkm(oIÞ“†NYûJ…(%Ø þ=R¢³ÄV5 >YvF&1­ÐÊ#ÐÐ!Àáá¢�'J�ÇN§‹\Ûv.}¤ØÐÞÎôï9á0gÖóC�[Ê�J8+ãñ’ Å'¢i¿–U‚›´ëû– *wâmÿ�gÛ5žëÀbÁÂÃÝYA‰x–W€át3ÍõŸEÒÀ„Ÿ]g¥¼Z¨ Añ<¸=pçp^˜ì‰u²Å‚¹‡ÌGhž¯Ã ÷rL-“þÙt' FÆuL£¬Ï Z®*ŠÝúf5£J—�Dn0—ÏôëIRh®ßGäSXô§ !½®hЇ3d‚ßPfW ê¯ùPu~±†zÛ'`f„k¸vÁâÙ‡Ó¥db¸7 7Æ[n“i÷á¶rÙ¨pY }T÷�¸ˆÊcè« Ú�åô�"ÛN¨¦¹ e©æ€Ái0àO„â®#@3šXȱ!4bœJIY~ÓÒŽSÀÄs9k1ŽáqÂÑz¢ŽÉ•È0TL¯ÂUœvæcÅbö`b‹@eõ¢/ðV"íÈ‘3Fã¼Â? ѰžafóéNnå¬ ?b¤!ø^¨SdÓ¥"}ÐD²N˜¶©œ­ì%÷ÚѤù,] 9íAkzý�_ÄDA0’�¦W «iBT¨Ø‰Ô ï1Y6÷à OZºÑŠg‚D6Øbâ{Å噇bi`-§0¾Óx}a5•ê(ËVr @F´VHÁp2§É“®bòÔ G £h°TM�bAâKÆ\u½ÎÒXjÕ¬­Ç6ˆé�B’˜Ìi¦ž*Z4DÁÆòq𗶍ˆûЛ6•‹HR‰�~†òy 0~‘ÇåÞxùšÏ¥’;Ǹüêêrp¯,LÊ;Oûç $½PFv"gqSÏè å…#Íè3 BÛ³´•¼í­}Yæàt%D›†T�‹*z)é1µ\¢ó|^Q¾ö륷oÚbê¡"�bO•n 9§BåÔ�¤Tñã)’¼ãÈú†CˆÐQÜŒ~Çw»½ÒLã<³žkÖ(Ís/ü?<§Œ¥cIÂYQ´�³F–dÁp,Ò}¸Y7ÃXø,ã ÆÌŒq�*e£¯ªºáùç¢Ñ„¤óH+<°3ñnü¸P8+Þº\ÒßË[jã[††žÎ©™mLRAOiGÏþ\%à¹yädêžæE[ ù~Ä”ø/¢8ëQ¾¦æ/¸<)LjÁ�ç]36Ö÷£ïõs×Ëèr± ½`ˆB#Eœ¬î1fVãÙRÔ5sÙ'O ÿ}º¤~Êõ)hýË0æÿi0_ü9xªKº"àgúÒ•×Á’8†]Tà[.À‚A .‡»†† Phò5ÞPcRáfW9jÕ­=c#T]Û¶-Ÿƒœç|iD^ìN%�Ør1¬j“ÔηõÍÂÊm?;'¤)ã3ÜOÄ3%TÝ2m(¸ypžÏ}ømÛŸWß³ð-_ <(+cçôRA¿ÏPñ³pIÞˆÜÍvA¨ufôa;�d°õç“ ðšëìë;NpvÜØ.ÔÐKÏIÀ}@-€UH;(ä²Ô©©`|…dÿF%µõ0B×Ü@±½šÒ³ÝóAvÏz‘÷8BøÏ©å-f&Ls pÔ�„ZScY†(†ƒ>ÆYö÷’>r”×GÓC/2srë¦M4?šCÖY$ š9øÚg,æ,ŒÁˆ@3`Ø¢“�{…‚ƒËKy㋘Šw"‘Ê�9[­Á÷ �]§i¨".çå…n×”°'çtP¨6 GAI Uù„s¡b¦–I8íÞ®d6¿R`íÕ‘Ö*d¦Qgý¶xJ€QàE –EHÇ¢ê †åÅ6ÄŸ>]*XÐjËŸ�F"!Óã½ ÀWDd“«£n•ú\±FÞìéù!Üï:ñ¬B|ì‰9ð·vE –ÝÄ1íÓk ÎTpß‘¿r³J‰—x±î•H†Ò��”WÉ„‹ ¼–‘ xËÛÍ @ƒDÉ EÀ™ü7Íákp#9ñ*ˆ‘´ÂQÐd6ãæ´bžÙ™üÿÛÏ:�\�ns©1À]Eu]óDÅ ²¨nøüçËh²¥¦ ãDÛ'“úÍñdwq@£ÍSª*R>“ÚXPÕœ#èÈÞ½|ÝìwvVÄ5A|¿“Zy¹·ø�Áoäþ–ïñFÄ6ð~ mÐÑØQÞŽü³åëgÆÿµ( =v�­QÔü©C/½V¸üâ+ž%II°*>š±$�/Èøt][ƒ 0ÏØ9k/Öú,®ÄRQ ã:�ú�vý¯”„9No¼ðÒu Z‘´õüÙ–Úä'pŸŸE¸UM©êH_³¥¯�[Ô8ÿBÄ r¦U:ƒÁØ6škÑuãoQ¤/ ø”]²©pú>é‰ó ó»t´:�ù«R£Xˆz–7¸ˆ+óœ–LäÖÛl¶6æ•ÁHäuI�ªX•ýÕP÷Þxˤ6O|ãçÛµ‘‹˜ò¹�sEÿÈ@†IU :�2i�¥þñ¾L¾ð�£}Û+³ÛÂË€Õ’3@�L.Ï(¾¢ç Þ¥!IÍàvMé§P<ÿE°´±']}ø½<±�H)&˜¾‚Û]åBÝœX±øm¢Yæ vY|´ØàÛ®v?=–V~èÑé¯Á³ŠR=Æ/i3'úÓ=çhYÅå,ZKÆU5q#Îe²RýËCxRDÖ©;íûYUÐñôéí[_ïÙñÜÑEAWÏJY�£ÿ,´c�ÏÐËZrÈd¥ÌÒ �/ñgxbNO &ÒYCãªn”S#ºk¦¹“{ÊõŠÁØÌ8YÀxâ�în©ƒh�““sPd 1ˆ%KN‹¯Ãg^¶ Õ^_M6Àõ1u,ò+ !¡sŽk�îüh¼ØœGr`?N/C�_‰\¹•œ§ÆN¶}·My5`j6.¶AM¡Kf —9a?V¬óvƒIL"ŠìO(?w t) 7yLhuCCÄL, ¨w;>©&LþÞí·Êw¹áa¥¨f—D‡ô¢ö 8A|dEaoZD3v±žÆ&Z¼ÒáqÝ걋ŸaRô5¨w†l<š¾þSŸr)ñ˜yàò™Ur\R�uI`ÊSRšf\hñ%'å Ž–;͈âYzýÆM›8æø-^ºd]v+ßñ^Àÿ>óµ¯ÕœL§·t%'ö•Îþ'¯+Á<&SÞ' ýч%ªæy¸áT½ÖsL”ø¿ÁsÙ‡¿‹çm“Dþ×PV¿Ýõ«Gðó÷e‚ŒËuxÞ ý±ð§#MÏÞtgó6Œ 0[Ü.YÛ&Ç™a³Cd ÐGªÍuU͸·ì\øÂ‡2Œ—F€¦¯ãis=<Ë/C®?²'2JoôÚlPšÏ¯Æxà`6pœáZ¥šôÿm=DB*õÄtx/ó‹�ar«àâš×1)x \³Î;c&q3½mß÷ýã{Ö|1p¬WûS�Úf†Í#ÈÀfÖ×ÔÔĽۖnêõ¦b—µf2‹&ŸÕÁ(.: Š’´;$"�­¨Ó·�Òé S³‹¦~L["ÒüEÜ=C•å^²Ùà¿3æj&[ësZ‡!„vAPì2åq̶x&‘XRÊ0�œ<©bu‡Vdk­ÝÝõžz´›Å¥|©Qæ)á‹c€M«Õ4ÂÅîÄSMŒ½h€”…ÕÂxh­ÄBË€›²'^È¡Ë D'c¨```¢B91êÁ¤]¾—\È€¼™ë}Ëe2¹ ¥É¶¬¥!Ñ“‚[B­æÆ¨(ÀïDÀ’†ä‰|²ˆºS”¡mn·×Q “ëx¬Κ¥6hFÆ´£4æ#�MèÏEâdÙíÞÞÞ©¦&¢mO4¬Ïß�u‚>Òš=€&�ñ,‡2ôVJÑŒŒì#‡žÜ“l¿µ£²ÇÔ+,É.û.ƒ/…dÙ×»· « ™l&¨#LB~n·9;´Y‹aûáªEKZø.` »îö»gºßo'NeGòJfCõÌ*—�»yÒ:pÌOU*ˆÀ|z߈?fS87Ãký|®äŠ%_²~S\Ш¡ÍAQüõ¼ùžÈÇq_^–B°6á/4ÝX ÊFh9p~ìIåK¿C®¿žå=êQö!ËÏàÔåÉëÑÙZ*]g»…-'âŠÈS§ÁËb5­C>J*ÍÌà[V³O€ØÿW@`êé7{Äps(^€DÒ “ªöPõ±X6n¤¡ÂÍi¹tJvRÔ©Ž·„ãd�àQبÆbÃçX§³9tËAh ¦4Ä©�ñ†˜ªøÄç%\=PÄ „€_¦È›—64ÌïÆ5¥(Ù>"¤4QD|•�y:-é`šœlyC_Ã1Â�ˆætÈ 4U`8àkM:_IêMù¾Tö!»slì·U÷;ÕÅ Qø}픞S l¤ÌHg§yë­Á_Ù­üʈŽQn„ßßèSâ›6MPtV6"š¥ds]�Hÿ_!²¯ÝÌ£ºÑí?ß_/7×Ý!hìÇJ¢¸ûL ß×Q›¯Þ× ‹r-„Çõ`*.ñíAÇûq±Öo~óFõì&/ DÂ¥yu¿_>q´ÝêV[ª±Qǽ-»À .¤»Qgî$¾õç�æ…;d‰@³éX¡` âÄó)æbÐÉ]‡äÿX¯ÀM�eKŒãÖÌÂ8Ë‚BãÑ\:z¦Ú]çr0ùùªÎÇ9§}7_È}Š£mqlƒ“*g gFW,Bªë6ó÷¢Æ"‹]HýÌ£Iä8Ù‚`yQ®¸¢Î¿àjä0.À‰Ÿ“¸Æ}]£{v‡Pøj|\¯¤�“WÌbJWÌ©ç}Ûm„Š2ú » Ü;­¯ÀúQM UÎÎôæ™Ó‘g$–½Ùð•™-;Q2½(“%ªj<æêã°(|Â`�ãQ[õ•©3È‚¼ðÊÇ,Ùðܵªžós"ûïùxú¥� ÏÍüůŽCÕÿð?¨TeÈÈ)Hç ùã”c"6‡(< `âû¶M-|Ó�ñ~¾ÿ£_År_èÕÍ�Isïˆz‡ìÆ;‘ ÖÁE0¹k½áPꚇçÇJjI56B!CJàxJ´4a'Ã*6ø[O :Gø~^ÕÜL*”¼ç¤&šR2Td2‡~O9ÕMøEQÌÇ3‚ *B) /�ã ˜´g`®ôç)Û 'Ò ºvq©¸pèÐ!“´§Àx.kÎî¢^¬©£¬½;]¤¦Ùá‰äbð#¦eɺQ¬*ö –\&ãžìÌä Ëy†J@</j�Q²|�±¾lxáéþA²9ɯ¿îÎ{ZC_¨YAÀu€âÔ‡XΚųÂ�Onܡҿá¶ÛÐÄ!F9!޼ˆÊY‘5 gÈËX’ðÕŽApÅ  %m¼”Še£J¢ )«+“¸²µººšdËMÜÊ·ɈpOmGžÅäÂTÞÖ�Z|9ÿŒïñ—´ïÍR Äòžhd·\±~)(.‚tT349=ž(Özª" ³ñ a³9ë˜&‚Qx+‘„ì r·õ`|»XÖz(`5ø&ÄJŽõlÝú ¤ëdôc&Ð@ê�ç×ûû ²À3*/le÷ÙÎÍÏbMkUZWiÛ“pO4bß7ª¿Þå2°˜i¯�§¶Â…ôqDQ .¾Ç0´œ¢‚y< àmRÓÕ%zü&W%Iˆ{°/…I)6Q†�ë|Á²ê D®qÛüëf.¿d^'ö%ÛVZó,oUÉо�bž:&äs %G€)î!Ð6Â}Õb<Á#^¶+PúŽ!ÝÕô Fhä×[íy#‘û^¤ïè£Á ûw}ñþ�'îC³ÝâY$Ž _½ªœñôŸMæñ90¢¹l0cÿP¦ùͱDë±d~Qw²T�>—õmm…šZ÷3öqûΊÿlêz.½4ÔgARÜ…ˆýª~Tùå*BÔE”mö<‡ˆª¨ðw[\ÐÇÊ•c’ýU kÕ[0ÕmÈf‰Cs†¬3?~áŒE!œ˜&<*KíËëÆ,$hÔ"¸·•i_+¥Æ=4î}¥äZÍ Ô +Q9ý§±ZûQh‚7á(ÛE…[² \;ð�ZEd÷ù  •¯Í’›‘ñwµ(Rë|ÇM/] èÖ®doae �M<8¤«‰ûi ¦½a�-ˆHŒÕb>žñò¶ÙaoÕ\”²º×$2©9,:À‚\‹¥½‚ØHNea@1J×ÏQiq.8�/f%ö e[hù�×5°ó��¿VlZ8/m¥×.Y¸t!’YâCKÞr>Xfw‡ü³ë öëÝEd¥[‚¥rÀ𚪪HR‹=Ï¿XöQNà¿)åR-õê–йªÀ §45ŒS—9³Iµ”• Üø» ½ Žg”_Â| ¨¬•À“·nî<7^@îÝûÙfwàÌÆñ˜‡)ûëIŸŒå‚£Ù¬ò˜Oñ—¢žÿÎðHïo‹Ùü›PþfŠ™ë*}cR è+¥&yâ¿%xªìó§z=g0ÿ9éeÀ•bÎÀ-ËöúT!µ@ÐãÐîú-N·3ЋÐòFn&ð²UœÀ5E‹JÍØ‘nâ±´^/.6(½…R<`3Ÿ Z££¸å¸+&˜Ä¼(¾X0Qx‡Ös'‹Á ~GÅ0)fΰÊ—-E»[øoéªö+Ì{²ª4MÕq1¦×ÅøŸ�vòìŒðµ•ÍóæÁ='8Eü.ÔC–4ˆ´Ë2Ø1}±K³-Ø=m†®Œ&tŒœ‹½ ˆ;AIê§¡JÀ=}|°ßÔÛµárÔ¦5”õ+ò,}öëo1à¿C©Ú×(ø’L›w /ýöb<ù—¶þþ³dQ!J0•aD>›C�"I ÖVx§�çæÐøA!t˜ÐÜ) Â.ÛŽ&Šõ})²(L¼R0…ýg’¹ùÑì)\�²qÊlø`˃°Zr7ÀÃü—6™ù §dûK$,åu~å󑌺£§F'ý¹sŽIŽË3öFØ]šµ Mš)0ìJZ°f#ÃÙÏz©×€6‚ÜCŠu¸×SãÌ«³OÀ·zOVÕÿJä0UÐ_¨¹äuûp=KoÞÄ£BG3ø�‘¢�2B™Â£¥"YüÔ†ï\wÝ9÷U+ Â 1 ¯ÚÖÕZ[}Všgõ¾ gÈ�}6bg°|Ñ=¤û¸ÉF-þt‰çGA+TÏPz�—øKÙ甃d±YÕÐ�cèn,ß¿,, c7 ‚9G+ܯŠpÔ0*-!èµäü6”E>ÌÃ\“9RÒTgº¤Àíjõ!âW�éº"O¨DYpiè<¶õ€—Í…rQ>ÔÜ«· õZ‘æ.½X•vòê¼y"4ú&F/�FFÛlÔ0]V˸VÌ^qM“Ç]Óº ™l@±TµøýŒJåè\îµ�líûࢹå !ÅÂØIÜŒo¥Ò׋´ùmœ?N ¨ë ¾P™±ìœ&ý™Ë!_8h­b2qX_ÉÖèD‚Ðîî!±Ôˆ’Q¾YïÓ? ªäüÝ&4;X±Vs`"hOºã±±—æ5îRÒÇfŸpr T²Þ`ÍlÑá¸Y³L$bq‹+Ý6d¬%à‚|õç(D•ßßÏ×÷Ü¡ÙoLÀ{ç•‚ƒÆÛYk m2O¡¬UÙŒÄá‘ßÅZå…æ¦}¬F411Pw£Á0€qeÔdÉ̸e{Q1èþŽi>V��©Q ¦M-È?à2âNm»­vöõHÅ 1d‚ÄúŽÆ¸6ÍåÂMa>ž »Dûm\Ÿ§ðn0Ê CœÎA ¡i ±Y’NŸ�l¦f"ßi²ÙšÚæpv¡½K°x@¡H·F]�Š»µˆãô,¸¼îï°i$ìpˆ)�¹€ l]ps$ ¬cÈ¢J›g`×h±O›h¬šÚ·ã¹2·îKf Êkójø\�#]ƱövÔ¤BDf̘ھ(Ê9Ö¥¦IyRæ0Nma©­6˜·²Kœ!ù(„Yn.ÈQp//–i–(2(=a8S<šÏ?³Àn� <�Ïçd??z+ccF1~¨µä®qz‰…fǬ¥K]`Cø úü"¢ágXžÍä ¹3Lýˆm—ʲL¯¦ƒÃý~P„™&š¦§ñŒÿ QJCå¬60 £¨é¨À‹t\Uñó1&/¾Î¨êÞhº�¹ñ‘¬Ówmr½r¶«ˆã$"¸IŽ„òö�¢úÂ¥7QññD¬ðܞǾiûÄ—^ÑJÙ—œ^±y8M-MÆ1M4QuÞŸB¯½uÒ‡ŒÀèIà¯MJ¦¿´Ö¬}%–Úà2п¬khÈý ëpâç›À íÛê#eâÉy‘ý„øI}ÖŠÀ `¥ØŠB�kýHz!Ú7þGîa�“°sE�ü‘f=^c¾ã€|€�ÇäÇ©ÃrQ®(ÅÅaQ³û‰\¦TÃò%qEãTJP‘Ž.ª2­ýÏÛ!€/ŽçKz<ŸHîd�´´bÀ¶oÄØw�«ÖÏ“)¦†¢¹Zp€d>€ £0<åã4ʧ �¯³‡’X¬Žâð—D·ŒkæÎÍbŒÁûnóð8/{RÂ˧Íb}ØÁ»MF0hë2Ì�k`½Ø°è>lä†mÜ0:û> 2fßÅ^SI_}ç�Xˆž†¥æOßú³±fdîµÀ½&Ë"}´ØÕeU*ƒ€}TÉÎÊß}÷Ùî±ÊE‹K€½\­€Í™¿—áó§oÜÂZ¿ÙhØK¹’ŠÕW@@‚ÿôò_œñ= _(jæÎ±Üo±’ß ðy”HGâ’qÐ*äv_6gBøÎhðïA€‹Ê9Õ’Íë=ùgá¹E@”l‹ !¹ây™Ÿ²äàªÙÈz ¯Nk"ÊDL>*r´lÊ\'&ÇÚ¦&y¸`-ªt­ºjȲ¾9ó ×)û)o8»k8õ3@6j–tá;R)/é¬ýFà}ï¤8EÙŸá4ÄZžXœ�ÿÞÝP:<ÔIô–$üÃ4[†Ê;�ŠŸ9“7"ÁBhÔ1:ŸI•yUb³ªß”ve//:{L¤©â}šòV¾Ákcûd(rÚw“o#ol-Y™fi¶:d³A ¡’øò,3¼ )‘Ûl5)K¸™{GšÕÔJ—@Kø+p\ƒã•ê:þª¢aܳóŸQ�æÅ4ªLØ)æ»ÁþMó²e'Œ›³àñYî)ÕÒz¹�+è%²•ïGÛÜ€vv¨;Z‘çÔΪÂ÷ýø+ ¥ß?|_)Óo"XDu'óO—Š¥DoØ7FöÅbB¶A­Ú·YCZVÚõgwVG´xK{`ÞqüF¶+g =¶?ä©y™Ÿ�»h©½d]¶aÆ_ggzŽ]‡JD��kÁ"wµ‹fžqpÖ«ªC[àì nn­)goå�DìmÁß!ÝüÌWpQù!Ä k™5‚ioÌc1ˆy>„4çŠÅ¢øÌ3ð�¶S™Lv¿ä±/@`n üϧw�žÉ-k˜utÿñÃY+´¢Ü7ô±Óe±®ÔØx~ |?Ñ�ttŒ`¯)+´|„?Ñ?˜Ÿ ‚Õ‡� …:ÛßÛ „ qÚd•NÔ]‚bÛw˜ Tø4Ïoˆû$FE *²Nçw—ŸcÒÈìzrpò¾çW«YÞS¯¶nííU5 n9aB™)v�Ör±J¦PÙo )6ý$ÄÌ—æ]ô èîõxQgfaäW’ÅÇ:Zƒ$m°Òè=±ìlžÁŒË5Ô­‡#òEv+¨–€ùä~“5à‹ æ°ÊN¯�'Àö{>É�ç>’~uk¼aÍš€ªI�ý±ÙùM—¢Ï’²OW,y5¬Þ¹†nÅàÌ( y™×4¶˜Í– ;æ„“ð[ i!/'¼¬MUÎK^L¡—§Â^§5Ez5U·Â]²'”OÀ�JÌy­e™Ë¥òl¸†*\qcι78Ý/.59p#hòÉ– �ûÍþÓ�Úªà…ÖŽ¼zêè™»–- 'úVPQ<…’2C`nF�ê.Þ¤¿Ð“ÙÖ¨ý%4þ&UÑ6¶U¹ÏâôéLñN˜pÿ #£°w|¼¯:à{Y°¬mp�¤¡aÄ5UÙ6¿ÊC =  ž^ïèF”—È"süàãÿ¾ó¶Ûn#0²s"òŽ œ×ÜšB4ôMº6 œON‰lƒi(³mŒCËgc]—̪¦®w×P|™Áð¿ãXƒI±/&KÅ[¯j �û8Xc㘖¤’ÞsÒŸ8Y Þá™ñ¢öìç'Y½ž?;2—ãå0cÁ "íq+ 7ȇ@ׄd̺‡S#=ßC±Ë²öNOÞ¾VÑ ËpÎ�_þÑ«ÝÝ)âÊ9çâþç?Ð^2ÄϽqã&îØ±-åêÈÓNKoÆsÛ„?âuŠJC G�=Á¬AEå1Y ª@ÕPõ ‘Žjij�œvŒw} ͘Ź©é™|ä™ïÍ|ÒЬzÞÆmÝúÐw#¸ù§¾'åþîK߀28€ŒÑ5pÿühiHêÕqÃóóÏ|Aµ« �D̉ ú9ÄT“n=黃<Ïm�B�½ e~¾2ìx×›ñ>}ùžWòÀ\‹«›Áο(˲є‘eý†MúÄK:vB¢Aî–o ’Šk ú¡®£±ìOQ” )À�ðÍ ÅìÖ­Á)óvòžX…£ÎÞ3/¼p¶cÒܤz½Ð}(ë]8ú£ð¡}6í®]½u ׊4—YÛúãót�¾ >^·À19#›+g(qƒƒ‰¢iÄÐ(‰úÔb@÷âø˜h4êžáv1¥‚V4ç»"èW€SÆ&f³EYŸgEîoûò:9§i‰'a(FѬnZ»¦û;ÛF,”Ó/YT{ÄÀÌ{°ØçåÐDfºhnPe³{í©Ñ)­·r¤l)y.óà€{ÖZéç„•�3ä÷Áÿ8µÆÙç­{�ÁRȳ%–Îk,!=/o“ÝWÓƒ¨ýßÀby¸h˜;`±B lJÀ ®`ªu|×ådÿ탵OA�4Á± ÈÑyP:Võ 'LÔw,,•>þ¾–Ç%Ao�1õv” ÎkMŽ;Å a \±¢®x ø�J›êΜN ]¢;Ÿ&È:Œé?¬-éëão[qI+4á)?:yæ\ 8VL1Âïû¾¸ˆÏÂT@ À)jwjüqóL¿6squu”è}Ý2ó?ʨÖ=ªªÿ ¥xO›ÀDÒ¼D–˲íȉ$ö“F]¸w¿ØÝúŸÙê= ßß�ˆ:ºÃäèÏ ñ%;ãú+�b?ˆZZë(A¸“NŸ8!\ì>Û pßÞàõV7Å#éKô\>”טWT¥ð¨¡«ÏSMáIBö‰‹%uÈïè HÙx¢e–¬Riö?PNæ(ôN¼w˜¬õ€—³þé…÷2wÈ®>‡` Òïè�š‘.ke‘ÚZ�ȰUÒòz)j*›f¡HLQ)I RF}}}‰|³ àxêÏyè•.¼ë+ñEeÜÁ¿¼Ì]mš ÁÝϸí0}¢0wæ¬ENƒåï@-6wžcÝÀ ¶LéílF�h‚ ÀÐAãà/•Eý4ܬ[ÑK�QÒ#6܈Ò;ŸG¶Üqø³öòbÕ'-rÀ×û¬†ÞÛeÛº“¹ÖùW]»ÌW«ÇyE螪ü `jæß�Žf¾)ÉÜ×,Ý|¡+ …l3�’‘s¾6p(oé|T!¤E¬Ao¹/DÀ’çRéÙ–X#Ïu÷U s—×Ê–25ßæ5M€î¾89ðâ÷ý£dÁš¾Ù¯ly€1 ,Q_»ÊZŸ¢úòdA¢Ö}º™7ŒÒD°?�ÔÛc¸B¡”¹€PÍŠ‹å>¤Jt×ó§3Yíû¢©Ý)èæ]Š®}ZíÝyS½3žSÜæpD°üOM^­¤z-ž> ŒqÊ�w£¼S¿“sŸï�ŒK2F‰vgëMMð\&ÏoHÎ)áÛN&HS‹?J:%MËézfç´@éŸò~ðÞMT#ȹò¥9,}g#–.›J©”Or3,ªœ/)Ùâ¿‚¶~Üï/; ç8j%LÓÎï˜cï<âŸö›?ø�ýgݪõòU(ÕF ã!̆ÃWÍ �y@Ѭ¯c€ØShêšÔd€Èê2 ïì<¬lw±¢pÖ§(Aþ¨(³——b±snЇ>ù%Wm ¶¾srÿé} Á  ¾�s¿ €Ť\ ʃ«ì »jˆ»˜/iúåðጼ"­F£”>Jë¥�K/Å#FÓ<Ÿì‘;ìÃñÇ"9˜:Ñy¢ÅÂ7M°ÁSƒszÞþ¾>X;¥“nÁÃîà(<Ü̘6 œ±•Ç*®‹²Ý xY k!Õ¹0ÞWÒÁ¼dK´${hÉŽ¾±Fb"úý~V3D]ÔDÉ+Š ‹Ct]¶¬;pÏS€’ïŠb6ec>/PÂÌxFP® FÙ<Ü‹hUE&} —ò­“[·Æï]³†ÙùÄ/cëf…"…ÞÞd>EÙz$ו}ÔeAD‚-1-w®=«g¢àT–÷EÒMÌÜ‹.nòVw¼Ü[œš¸ÙµÐå÷\ÞŽvê´ADz‚ÿ¥‹ÃÒ °÷ÞK[$Ábçx®mËîÝSÚ¹ìý}é} Ú†¦ƒlãFPÖt♟Ô묀,j”þÐóÏ8â«!»øŸe=ûÀúões˜’ÉqRQclpøÄþR)þ²§Ö³‹ï9v´äsî/ÇH`4çÁ­ƒ2÷ÖÐ7þAÏ—œã|lgU[ÛŒye˜Z¥d%cˆ¤o“4i¼ÖÁ³{9’lcf-º1šÓ,s‰HçæwNƒ™ é1 w@ùNæÎ>²ÈÿÉ­£$¨ uŽ¥f=3ŒDÞaaV:e …4¥~Ãä™�ÖhÄn+Y:‹/ó¦Pö Ë]Õheûóéõ=ß\hWH�`2lARó¨$ØÆÎ ï<[K¯n¾`…µäHOCJ»ô¼ôôhÃâËŸ§ÝÌF}ÿ=&h ©�sàø�R0€4¶)í¶|“d±•×ô9ôP”­ Ž€¾¬H2 Âð™ªD5�¿Rï �FÎÂJ”•X¶F?ä&J´ëC¥OI‚ù´wÈݳZ3Êìø ô®µ1(,«›�¹¥p”˜Yä<°KÞPæqv9ˆšÞ ®ÛV|±»—Ô0\àÀXêÀæ‡ïú¯Ì0" ŸïO^¡›º÷)ˤ2°Õ<Ë9g ¯”á�¼ÖV:˜V/+q¡ùÅ@lTQP�†:‚@Ê”_�ä WûŰ%ùšÕ¾Û);öù"‘ûgîÝ{uéÒQC6¡1b!žJæý}îÅûQDʃ4Ì S³Žé´–F5ãtgw÷c3kk_äyÑ“«8 aãÇ^x!M¬ˆ³‘|ÈZ¸´áH<›}u.ñÐz¶êÅ´sj°ol ar2�årcH¼TÑÍ‘ÐÒV‚yhöÏ~Úùƿ܊Ĕæˆ�ÁÙDs$mrgŽŸLÝÖî+k,Äï¶c0ÑdÊ5Àù®u‡çü%nû^ü•Ïóe˜Ò�é_àÞ_'HþO*ý±W?538öð¾‘HÕ�4ƒ��ôì„E�Œ«s4àç^Т˜ìLˆ¶†Geßæ8ˆøSôP¦ Ì^dçLë¶«Ú–ý#¶Bé#'‰z!Ýi?``QÞ ¦@š÷żFŸþ8�æ?¬j¥9�eÚ·^tˆ2¤ú•k]c,ç ÒŒøøª€Ã e-ºÖAq!`�Ökqõ6(-A©ã-åÂÏzPö%QMÄÉ/G`ÅÕ÷Çéét»�^X*0'0ŸÁsbïyvÇ�ÈwÝצiZŠ’ê¡T­a,.B²'‰Yº§|2gsã®*ñ�,ÅL¹�Þõ@ï×ïYøV—Rc ÎsÖØVÖf=•ÈmùÄÒ–âÖ Ëh6_0Í1ÛP³Aܫͫ׻5Cm†°XeªÊ‚–>“3½{�N1 °.DZ“*OÝÝÒ€­,éCC‰ÜóÛ·o?ikk“K%þZ'ø:ýÁ¦�R±�L2¯ÊÕþmÌP½’ÍÁ=êã•X PØ9`U’ãJ.ê*þôÉ‹B cÄ÷$aBðHA $\0•�gS…{$�†ËãÀ�_¹�¨+-�ߺáö/o9‘¥z⹤}ÿÁ7ÆïšÌÆšê,Þ ¹ã”>È_Ú ÷‚Æ”;¡QŒßƒL”"2–‹‰Rñ¬3�Dº ÈØö¤­ÄË·´5–ýÜDS W ké£�-vÐUú¡1߬¨Ò±}«V=Ù�wb<ÝvaA»yë'Ç–ò#�§ß�ÛãÚ&f5àºñ»I8j7#8¼¼½½€µÁDGYrüS¶@§IoîHàq í0M O>ùÃÆÚào™ÖLXÖ®áL’J¾…Ì;¿jê'áÒ9ECZ×ø|eó°ïH_hÆŒ\"Qˆ@ÓUWWOPMÍ”nlû/sŸ„9¸ ©¢(6ÍmÄÂpl:‡n¦8ö¦‹ íÎú:¨õ«€àX½Ý²žè@ÿ§ú‚ ˆÏåFÜŒjÜŠ,ä�"`;¨mB³Q`y„êV*Æ6òØA&Ü6:ÿå«£0¹é  âXüdÎMün˜uc¾_Âï&j“1¨n| Ãs-ª¡ÄY�ïsòÜ)#W<Ë=}à Êö”Dk«�b» Ýâê�ˆÔÄb?*Àû×ÑêÉWuy!ÌnÌ[Ûf…âä7 ä&‡¹²k"°Á¥Ñ”Wò¯AU­^ضì~t± ¼¯<8,(ÖV¸IÇ^;¹·©ÑáÎj¶™™¢úb�¤Ÿ+Ê;U³{Ë–Bxƃj‰ïv64ðò@Ÿv#•¾ ‰UóE\ 8((Ö¾«›¥è7³"óyJz:áÎè€Üœ<§r8¢ûÇz÷ºP5_wÂáh”숻ôÒK!»¡Ý—‰�ºøÝC!�mÈþ³ŽÞã±Øí¼Óײ@Y³ùyMÎ ´ûwL4Bí§Zúç$†™ƒ¨Ø œÁü-ç¢ûƆyÝÀbQW¬(BЇ»ºØ­ÃÅÚ\:ôÀj…å¹åÀ“úA(\€”÷Â'þa90ãØþȦɉO°©ÛGR�AûíÀÅ-�¾¥Ø“ÛN5;¢“×{ÎK¾h}€Å/! 'C{�'\lU–,QcÌ:h?ó!fl2Gƒ.*ÄꊧYèÀ«¡£ÍG�-a‡P’­œçþÕÐûö‚fSDÒÊP9¤!· ,öÈô¶,°ÎYRÜ“c¨h1·Âø?5õ]¸:PÍ¢s‚xä¢ÿéŠ+œÃE­ŽítÉôÞ; ø§º)¨{½WøËÌ‚–îO î´Hü§éºÛm·´…Êý¬ôeæÌ™J×xÆIñ@* ‚ÓÉ3^àÍ jI9Yˆl‘kœo‚—ôë= _r�ŽÆà80</@ ¨c€Fø3;�—í޼Ŭ”ñÐŽ'NÐMžãÈ "X¦6`ó–" Dÿ“§J•rß䘵µÎFP]„ꤧpë%8c_›&'ÚKFAa·†Ú0¬ú‹UÃZ �Îq Äå!†Š ý%ƒë3„LoN¨=µ+’îud¢Ã+››³UTÈ(Šø�—̘ãD\µ ˜ÔHr d…ß*¥B³ÆkàjY UáAd£žõ”…ÌxX‰¥‰À˜8ÒÄ¿ë þså\0+RêI:ù}[z¸oEs;ˆV&¹^'4Jbj—ÍqÊã9G¥@~þÅÈŽ�í👦ýRÎÆ¨T©¾ªrÜmòxúï7ÿÐÝO�“çeføªgiž�>¯q.;w麇”¨?còÃ+KÝÉ$\Â’%ÔÌó¦å9œH ù …x„pk 0IK7�…ûRº$�†ÂžŽ‹üã òyœaΗ-2mà(àáz“b³ÇW¸ Ä‘F/idr¬ùèm>ª¦©E¦µëÀÓõ´ÞmdAÝ6”ÿ öý„Á¥(6÷u(ÌõH½ÝØÞ•9Mµ‡§4[ðct¢üÑnä__%0ô%«vÀçþl�Í&ÐH”Ÿœ«¹Ý`·ÿ(ð*,O°šö[ä6ÇJœeÓ &„е6$ À%³Öb­aÁª+ JqCà ¤¤¦idá Îàà7Pj|†Mžxäàÿ ›@£ªÉêÈ : ž¨7Áb6Ì–´(Ê\5Ìrb�¿(ý¾ˆÊŽØsànr)F.Ê3‚_º›§©ëÍê?Äe÷ÂÚ£_¿ýîv E\:€DX¸šÖ`ã·Êxû“Ü�ŽZßHg”zBD‰È„mhúÅÑjÖV†Æ‹òõë@IDAT?=½DÖŽçš�¥r‰À3ƒ0 éª @èóÒÏ;½ïDÉúc4Ëáó¹÷FóóNf,?™ˆä §ººèöO!ü‚Vä3�ÉPý;^7¿BšÍÐ>¾ƒMæ“϶‡ß"£.GÛ9*È ‰X ™V ZS8¬xK®çi¦^Ñé­ßw�¥ôœ©Û±š÷ÀÊkðýB‹]�àÒ­ð|Ââ!Kg¾!;<“¢™¤•¶"Øì@àÏ­Ö� ]/RRIÔ{ªEGs1Q‘j�|¥Ã ž9C£&ÀsŸô;ý5ÐÜÞõþX�}ðЉ ®tê€o{C]Y@~‚r†ºAi;*�ã”èâ¯�,rчk�IvÓM_y+U4‡PjÌÅ`6kEþìzøÒ;ÀŠøAŽ1—s~�ž×7 RãÕ–©^…ev9Gs‹�¨qMÜí®GâÒÊ—L,‹^|i0Vã²×¬4YÇ'ö|ò“U¸Vü§_’lAÖT¼7�–!Õ.�ï§·5Ÿ¾£IvÛoq÷}pG|¾ÖO)Í„O+|ÏÞãÊ ×~»Àÿg%iÖˆ¥p7„ë¤Y¤•ã¬mò¢t½ù¢Hn1\(O÷¯e[›-0¯ ÌZA\dÛ¡S§œHʘ‹)#Ôw­þ­…zïîñ¤¯ÓÆä~k­‡V½§¨'!Œú²È(䌄™¯¼8>nûù‘<~J¤éŸk%ó!Nb¾…;ê*.xûvçÛg´l”>õG´YÈ_‰J®ž%˧~ðÏöÂå+T§ÆI—…4+=ÃãÈÀ§)æ9¡Y`ŰÛ@I“Ïè…”¯±~¨†˜“mˆv{í6ž˜�h6Ùå}s¾@¡0LU5Jì|Ùœžßdñú«N)ð¯ä¶c¼d¤ûÎ%™€ àa‹"p‹že£®ÛÎA>å’Ç'Wò£9Ínx4é,˜£ÁQa “éã»òy7(ª¸õ Õ™±’6”‘«‘6Û�þ±"eþ ô1¹-¾Þ!Lim•¾W^áŽùOµ‚s-pM-´ù›V¦"´ÍÜs4%^Á IgÅ®ƒ;¶Å¯X¼X(¨ËQhîP[AÉ�ê‰GÎ6z<:x“鑌µUÄb£Ût~Pá­&É’�k¬– 3 âÃÈ‚r`; A�B}FÐ̘Ã7]-¥H_‰öŸÈ Õ†Çß& ÜGUŠú"¤ktA>ý…“G»q3¥fô¼Éw@B5aŽ¢Úˆ¾í^yŒð;a³ëlÊ™„¾QÙ?ù÷,yŠL˜£…×ÞtJýW76��H ÿηò ,“yŽYi^�÷„“oΩy«”Ðpš)!këÿ’÷àq•gÚðéezѨ÷bÙ’{ÁØ,cÀ˜8HLB %!&K€„”MûXÛ›¾ÉR€4‚½@¨¦,÷Þd[n’¬Þf4½�þßïØ²!ßÿ\àI3§¼ç=Ïû”û¹ï8Š(­S¥ E͵qÔ”¤¦Çú"C;§ƒRÒdü|XÉÜ"0|DI^ê•S•¸§�*èréõy6µÊi‹tÄ“Nd!öŸ :æ�‡LÎ_pêD8¾Ã#q~™ö¼ÞŠÅß·´}u>õÑÜ¢é]£y;¯èë/®ðöUìj#ƫο ¿ BëMNêê?º_ïQ"wfãX´-úv¤l Ãü5f;qSÂ.1(Œ ö©’\ŠBx@Ð2'+Üîäª`h%qLiÍ'|v»×o/,ã)uŸkM†¾ õÙxF}èÜ\á‘ÇŸN<­^¦¶”ž¨¼ óП”õË®ñŸû°½?oÆ·GÕçFR*º< ÜŽbÞ„žAUt•@�¶–Ó˜“�?$'òæP ðàîP)½m¾3áX-C¨ä4ÕÌ^„–c“µõ`A›š4ôCöŒÕò|뎡&³¾xb>8J?GÚEQ~ìòo¤X¦”b·-ù9§4L©ò~p)£aƒªƒwü=JÏþÅJ=`¾ªF÷ûäD'r²òö £l<>¨€OãžÝÑÁ…ôýÔCYbm­Hq†ŠföjZmâá‚­éÅçcË/ž›Jyðó.~5Øëƒƒ}]ËÊFséÿÈ-ý_ý¹?7 § Vû‡`@s‹>ñ"W|ã;³‘ûžƒ”O�lâtQ©¶·Aåí ñÜ}cÜ£vÅïG–ºí$¾!6Æ&Óm šP@2ðØN<$éUÀ>þøãTùÌ9Ÿ‡Náç’º:´œÓ�Nœ‡¨n“¦hOcþÅA¨vI;ÇŸNHÓ—é4{;.¹‹vB´ ,ªOçÉ|ÏøÏ}ØÞŸ—öbr“¦Þ’UÀÝÅè;=,í(òöù†(IÅŸB.�_v Ä¾È œGM%^\6æ¨l¿ièÎ5H;Œy‘{†S—;ìDK×^“ =[ ôkGòÓèaÿµ¢S#€ÂÜøÚñ]Ï%þØÚþ:ø=¿ƒ6õ¥$÷3𴀝¾ßýXAÆGŸY2©‚ä�s['¨ÃÚ::DYs™¼S¼uÿÙ„=˜Á—o�—ð3—à ê@½=\à=ÖôNÁëôÎù—Œ…³~–§ È™*ƒÎ&˜�~ ,c+ë£xŠjæ ð$kÀ’yó1EÉþN½O’øoƒˆk.`i°®Xqeý«lc¯ã æV¬Rw«™Ðß° �•&!§²w/Å£ …’±ãŽ?EE±7“GsF-È`— c)â~�0i0”E5Äl˜ìuˆÄŒ¦ÜJ·ù_lj"œÌƒ§]zí<|¾QI;Š\ .l6"=÷9”D0(j*_kgè¿â˜�HR5åSÑ�wŒbñ{f[_äÛ4ÃÞJ2ð\Ó?ÇŸ“ £dx$wMã·¨”âšjºõ"šdš«Y:ö‡xor% §äZv§¦ªºþ0*Ju°Ðo¢¼‹zr#À=FX^Q4Þ‡®¿ozÖݯüÒÛ:.ON‹F+³ }È„x°0¬}Êï8¾òŒÑ"ûÿek&pšù+–£+@Ï©!ÿ›@»=9ÆfGŒ…]Ÿ®é?MfGÖ,öèu#mÈ]wûWÊSv= E´ŽKj C˜Lï9ŸF¿s¾^·ö%nÕ)í®¬Á<>—A V‚©s5/››³YîS(N¹¢Ä»büsu4˜þ<Í™‹õÙ~ï æô±B;okz÷óq¾Îó|ìç¼Ü<••Ž´É98Öpi-IÅ{)7°fy´Ä WVûTR$Aˆ3p%Q”ܳÉt¯Är›LŽßÒQD)³Î\ É˵N¯ÌH-�šžêX»6‘²8µRà¨MÓ¨Y}áÒêiOìîw‡M.U€L{”ÊzhNž¯…_–ÑŒeEEé­Á]-¢n¡¸ohÜV‰4feuuö`$²²ó3U‰{R‹Ãj†¾ŽwzÜöÇyÎ7·Çq©‘q»8ë­§qfÔV¯A'ó!�›ñÇܤÞr¨Û+ç{nB}qHÕóQ•þ�F§÷2W…ÖÇïbuËÚŽb†¿®Æ?–\ŽëX“¹žI ä7Ï|j¿¥Ù«)Þº Tï…*8<Ï:ð™H÷ß{ýžÀØúDw¾[·&š·`ÈËAIiuÊ„ÓzžÖÑ%‡„1ò®·¢ðw‘ÈòvÑIÑÍø®Ýé.„WľÉCXŽ\!pmô3Ð[kËdÈýӱЀ >lÔÆ^ÀßïE¾=ßdùÑšLø"F  Ÿg€b£4ê¦Ô­ð€ë8ì�� ÐvõÂ'`´_÷ðÆà¶TÊ x߬‹¤¢^‚µF½ÖšqÓäH¿4< ',e°*ÀT%t8_ÎÐN r[bÕÊ\›nnXˆG¸?˜œ�ñ†c J1FŸæù pècÆ'÷ÁÿþÉ:w%¼±ïQƒK¯GDE((2££zö÷™dŸµYÎô Z;vÅcý@Pa­ŒRÃ:ø¼WÃáÍ”ÏG0!ïûÆ‹ÔvëC:Ýœ¥�WÛBé û‰¬B•`N^�y²ïÜ“��¶·t=Ù¦2|�š6²�_ÐHo>ÜÛy9Á8íõÊ"›_hï`@/®›!Ån/‰àX¤ŽÀxºÝoIܯxBô«ÐëGÂ?ŠEŸ_( ù^äÌiuç`¦¨…™¯ “اÕà¨×Äp¾ÃF6¶ÞÎuÈíÞ,Šà‘¤¬=ð´;t™ÓDÝ…y*á)�RÚ‹Î/ÿ,k³¡„Èjíqx…þwÝ]æ²À-ÁþŽ)55ÁÃÊð³ºe»MüñfôÝ7½““~×—Çý"kÐóyκ ü›‚½ñݵ¥®ÜC­ù½ ¨>] Ò—Iðn[“¦ÒÓTPBÎ52¥xâ¢3îœû—€½t²vy;ÈM~cQÚŸð¸€lÖRÔ([ärÑ¡™B1«ÛÀrCŒ;ðßyKBy®„Îÿôú.Á‚²9sB$4@æÀÄ[ÆÎdÆx1RíM’ÝÔdžÎ&ÖJ:wˆó· ‹-û=Ї 9x`ýò‘IÃÊ?f©‘ýæ l§O€xÉ›‡Í¿ Ké:Ö�å®�Ä#¯¾Üѱao"¡.†Ö˜N¹§¢`†DªMæ-Dºì\8ž'UÓˆkÙ‘ûÙLª‘r m`Ÿëqduó…Ù5kÅŠŽ’Îê&ºÇ-°¾Që²�»”åMèPùÌRg`Œð˜˜}Ã1pÝÀ¥ ŽfÆ|»ý=²ÓWôÑý—ðE4wº_áÝ‘ÝÙ4]#0–a”˜gOv` /m«‰‰åÑB»9§…ßC£Ï¹bUÏ4b=ëÅì(öét%~Eп[ÇÀßÎË–>¼·Ãª�V+ÛÝËîÝ.[ìZ”¡A»É,CG¨å¸çÇ{½ä ñ-A®´îK8½Ûvšm8×ï`O^?÷³çå$ÏÓNÎòÿÑ}B²$wKÒ (Y�L*$ô–%·O%˜ÓéTZ[—[.— ÖÕzâÕ54+þ'ŠûІ|´Â d¶MýÁ†£qËÇi `ÃỪ”…™luFæÌv¡�JÐõkø,oyFz OÍmð„VÁkû>€Ÿ�wMØd^D{3èrs^ ˜RÔK°j 0Øûm–ç]¡úéÝCQgfÚ¼…Óöt+UªÆ6",îÑÌ!”±bàè¹ü½W–W[YoRt};Ë[î]ÇþÕø…¥${5“ù=Ò!¯£ÐÑ`³èO $(ß�³gëTgËF´·`’M@.üä<§±L2sa‰¯/®JŸe.q:å…Áº,8/¨zvÇÒ…æß;‹Ó¿'H !À^ä¥ß°XãA\ÿ-àÃQ{ìï�aLrŒ�ñ¦?îÆ¼¾³—x÷/¢%øH°{ç, Èž*‚ÅŒ‚Ca= o÷÷0À5´òEÆ\xRÀÈ÷¶Â }ά‚ò^!¼Ì¯ûK'\zQù¬ UÎs3°¶·Ô F¶û�D}>£«k ¬ô�Ê×þÅUUC@5ævÎpyù¹IUJƒB÷²™aÈ݆·úÑÕø»Œ]�uÑ÷g±¯9ìÂ] ޏ“Ñ•ozø[Lš æÂà�~ɨ͟8á�ö¡üß´¶ñ@‡èƒ‘– ºÌ2µ$‰ÇæØÿ}D?œ%ª$­B¢›Ö`ª�<£gÊáMËtÏÉÛ %òJ\ ½ |ËöpO8§Ò¢Y7hœþ-ÇŒÅöÑï�W´_§9QÔ@Ù ž®Èœ±}ŽÿÜù~�œ¾ØÏD`}¿áøãº¤?ÅF~þÿ@›äËý‘ž³ò½äørIu9ŠÉÀyù"¢ ´{ ¼"7C@õ9çt þiãKpÝМ1ŶÌXkoi©š™8zRt æ°®'Ò 63¬èY ‚”Ïp¦p£MËdÒŠº™r{Ö´lX›¾û�9±1™J.¢x¡œ­MlÍèl ¢¡\ž�\ f‹ïj;AÀû5•ùBæ×ñ?4²è^(YöÀ¨´ ¤z„ÊÄ+ò†ÉwÀë;ø¿Å@,„è´ù†·Ú;–[&Ç&>†Þ‹g7ë¦�¯š÷qõnŠ?ƒvÙ7x+CöƒCÿÏ6û�ÿ °Àß…2ÇN­n¢ýôMË ßHUQ/$îžKkéÿÊãò¼ŸíH%/݇YÊH%à?똮ëŠ*Ù7^~ôQtLS§Sü(Ê} a|5è×f³ÖÏ/*Ë#ÕÞÿöœ¿+/Çd¼ò:UÈ‘ÆÑýõÓx*ó=¤}~[Û òb33S"»dÏÜ÷Jü2-Öº¶\4Bo,* ó<},Dý@¥bc)9Œ|8ÒÄÌ e•S*q.gà&<»¬i>¡˜Ö1D7"¡$bÑ?“eóW"m®†Ñ‡ä÷dò<ÑM 0|…Æ)ø¼ï^ï;ƒo4)³[`„Û$)*C OccÇÌ=ë�"¡w>wú�È!’c©�}´ö6¢²¬³èžÜn÷?mßÎ=ÖùüùŸ^æÌ™Ã÷ÒbuÆÈÚÞ¶ ^‹µP·!µ¨¢hÅ„<O¶´|:ôUbWò<{ ~–­/¦ye—°(N´�PÞµ'’<À*Ôõ&ƒÎSïa-sP/ô$ð§1#C ZŽ’zŽš¦ËxÃ.øÖ§¢qÐIp�óÁ ÊÓÞXbðÚ‰%¤r››,ભ‚®VøÖëº7—›<3‚¤–Ç[³¦R#ÑݯûÀÙ.F6’UTõ�@©o[-å3ÖÄ£p44”ÇRTjKU~�©3»{!¡„c•´¹ ¹Ôk�:±+I¶�:¼ýÕyè[ ug0ý ÓÒœ÷nhÖ•@ÖeHËw³¼ù;�vÏóøcÀ\æ®{ 6ÜYeüœrqšÚËô]2ÅM bccBüÆe‚*ÙY¯Ý![œ¬©*�Ln/¼ˆäÛ]Á º%^�îÛ<šf7÷e†ÿkymm�àk/¿+ù"†È 9Ež¾Ù¢^¥ô ÕfÕ×C²,ð¡dïþææ‘ 7¢¹c¥¹zõƒ±E_¼ï¤p&#ê˜ôu Êù9ép¼QK<‡§F~Ž0ñ+Hö—¢€š¸'ÖŠ&Ï{©ôs—L,HâPäSM+›ÈKnKÄh¯Z@´Ì©�Ähüæ‹®\J„|€ˆ”úç]ê¿tÅWjÑ\à¦E9b£D'Õú‚•”/¾µ#ò<(Ú©Úø°fÊ~èí%šïº .`Yš‰(â­Œ]îÖkIª¹ùÒÿãèæôY~8þÅ%ÒFbóÂr|½Œ5�nVá.™U7™ëRø¦É| y�À+)téòÀ¢`YË 8™ª}†Ä!]óÎÆKBÐB§’AiѨ¹M8Xt�eÅFÉþÊ0M ÄO NwŠ†Ç¸)óßÂ޼s–§ßEtz5õ*ŒÅ�IgS[.sìË„ðÜÏ~˜~þ§�ï ¡ à0UV‚¨Äˆaa�¬¶¬«g@ ¥Ø ‰|ÅàoT‚Ri /RÝ.¯Üh@£G„Èè`œZß}¢rQþ[¬eGíLÏO '��ö ×Ã-CvÑëž…bÐ%‚ÏY�®3FQÒ}hª<Tâ­å”­ãX¹#¾¼ÐqÖC”é½-ÔçíO„BTts:9&1äEž¢+yú¼ñ!»ÒŠAŰžçÜ\+Ã<êìg®ókÂ�È'ÞësPǯN&ÿ²R€z×6 $E›Ág@róË3ŸéÇÝå•Ó:a¼.GXÕ™Þ0$ç탡½Ð?ªÃ›}Ü6XÃÚJ�ŠFÅm²ìR� ãój3Úp›ˆ¡÷žmìÉ9Ü…ù ²§Ž³ “y¢@­Y.È<£jÚk[©Må…�­j:¶ „HhÃTPJž Rýß1¨¿âé[€Q™�6ÿ †ôøeÕžÆÖ×�™„…r¶nOŸÁOÞv µq÷ß[iþ­3ú‚“¥nqrÌt |Êð~ŸÃŸŽáÿ±çç«¿,©Ïà?ˆºØ%ðv‘†RVvZñÔ†)µcÄF¹±¥ÐU×PÁ¬Œpòy¯Xj¦HIªO, cß¹�›·»F&"O|+"�À\ûh¤§DZ‹Jw ­'º¡ÕÐvî®ô’/ÌJ*�ƒJæXK$µ¿/–È«¥ê(� FU >é ÆÔ¹ÊÜž?ºÿˆß�Œy‚Æ ‰Íj)oÙèÕ¨Šf7F‚wˆf*ƒ¹¨ ƒçAKëFÈ Ì,ë”(© âöŽÞÀhGÈžW•æQɇ>¸m”RüË×ßâtýûˆÖêq µjh»�¤Fþtî™Dí*š:÷ÅÞd‚áÝÂsD\Gˆþe1Õ?´øŸvËýpŸ]A€#@ ¡ª@:³Þž”7t±Í6DÈ\~¸ûÍèfvàÆOfLfŽ©[K¡#µ3Qîó*KçAbœ–‚Ö¹^QËny¹¨(—Ÿ%© W‘ŒvSþ‡˜÷Vôq(Ë^¥Š» ]ôSïøþG/©‹FAó>nƒD||¾ÇÓ ÃKªµ£s‹ò¹óË‘+¾9Ø'�?r&{+çä¾Ë²ü—tŬ�kkÏ2âãv"ÿõpæÎàNã.¥F$ç·¹+T´o$9 苲›7Ç)N{(5düj¾�¢9£Bˆ+ÜÓ/,rÀ³U‡?hQa´µÞõK-‘y“MðY‡#f×Ëʪ¢ "·QòeÎY³<$l‚!ù�-Täòˆ‚D‰»l:îc)ñ,J_ÅCwÆçÞ¢ƒçàÖÂü´ÿB?�D{ò_AÖ¶gõ¼Ò1¦gó›À@SÏ`¤Í”AÇys:¯"�x¾FRéåü©”n[ŒÙðäM79ÈqÉö‰JO7R!k3#÷¯Þ¤ôO‚.2@h ÷ €îtøJ�&Þb÷ÆtÊøYgâß4»ïß{Ú½84–Èë�Ý‹ÚâŠI¬ßsÓ†ÎÁ ì�&°'½Äs„¥bxÏòtvööúP±ý<¶±ƒ" ¨`VÝÅNDñõ&‰âSÛ‡òVbÌRjŠªrÈf¹M𠔇|¸'†þSmÅõ}žÎ¿'B„\ë‡}#÷J1S‡DƒÉ·LåT&ËwgŒä[ðzs÷™æè~È)©­;Eé×#í¯èRür\«€f¹bÚHd²¢r—îÙ»wÌÈnÎfuŠ085jö ðÁï¤$Þï1! WìÌ©©�hxh˜¿  M(Y޵EJ�üÿ¨Îp¾«-’òª”9Ý4�gMuê!5ÀöKn=õ<麜°ÏÇ©Yõ§¼À^`²à ¡è‹àU.0dá¹|p½Œÿì‡éý?íù.žè w—:Teh—Ô�}{~ii–ÜÑ‹ Ì_\$²ÎÏI6áóð\kPèÐy$�Ò�Z"ŸÙ þÚëîýz£Ê›v–cÃÛr^Õ)W{Çrˆélv>>:ÑÒ:Ø¥û¢†~_rdø^…Õ¾„|Ö�tÃØ„î¬|†åo±ž÷w6²Ržë “¿Úö.M3÷ÉV2 æ§Mš¦ü<+šÿ 2�ߦÄþ¨&½³ä$�”LyžUcú/ M]Cµ¦ÅþªÆ9“EFú:ŽqPùèF»žç7¡D ¾ÏÌ'© Ýü#eŠ �50ÓÜÑÆBQ46îÈß]�B¦¾½#Pù£¥*صtÒHŸÄ8žX[HHEÆŸFî}oOlŠÎrßą͉e³- ú&å῎ü0´oc¢¾ÇòXáhC°Éšë£â—ÕÇ5$ f÷­c}~â9“�uìÈïr`-ŒnÞ¯:Ó·P�Á¼&xNaG~ ¹àLÆTŸhäØÞr'€TІ�ïrMGV�Èq÷Ì·P£™‚óèÿ›W²� pW…B‘†"ÞÍy|æžWûBuøZîÂÇÏ%rÕBcˆÕ 8ïýX�~Ÿ3Ï„"Úm6Ï*à´ï†ú—ƒ1‡»e0s�tŽZZæXBST�cÄ^^æ…Ë€¶q@žƒEVÖûhCÍÔt‡á:jš"LºùÞ±çz ëh ÑÕ³Šníz³Àº­Ò½¢€J5ßžQU7?£ƒðº,‹ ñèí"ÞgF³ºAo§Ó�çhlŸ£Ÿy?_b}ݨèBíœ*éé~̆Räð;#ßuÖ¡÷¯] ú:æ¢áW(CMŒcè­¢"ÝùáÝÎÇ€æäLH¨Hˆsրݬ|ÁǦ7÷D&ïQ‹�çð@šPøa�Màâþ3 º-µ£ä�Mò±Ópão¶ÉÒµlŠ ƒÑË&O £m,¤Y~~°t•ùS_¸÷à²Rß’É5½üñ¼#TâIxÁßÄR¸vjŽ[”ŠPÔ›DéŠú’g[†ªÎU3ÈNZºû¥©°íá7ó ¼+Žz†P€T—:b§sÒ2ãïœEºðæÖú↮ý �YEŽ]|î·t]m³”ú5¤0¶À[\lªæí F/´˜6 ‡qÛ#üŸ²j®›áñÄVœiˆ˜ëó% %~Ø0´°’È®×nŸ�n:¬"ÉGÎ�ËdˆJY7¡œ‚k/+õ[1ß¾åbùOL¯ŒfŠÜ©”òÝ$íê~ùé’ö9s9o�·6—튪±�äÑÉß¡7÷, Z7ßñ÷{?Ð6èÂLpA¸ô�|ì@ÃÅõ‚ôå­ý¡ ¢ÙÊÂy ñýÜ<ݰadf" ×›;t½‡,&ËACº'gy©Ø•þ´à{èŽftFM”(ÅÛ�ÑB Е:PFP]ÕàùÒ¤áHFUq:sÜÛñцšÑ¬ÜÇf‡F1§�;²ÛB9î¾ÜâEÈ�$ÉýãÖWÚ[Ifü½p)Y`ßAñ nfäböEºFçJîc6‘;'Ã�— Éñ_}ßß;ôÀ®�¸ mÙ¤H+½ üõÒñ¡*b7ÆŸŠöÈ8Y[Cic7²Ùø ݆l ÜÙ³±ñßù0¼WþäŸ=©B°›™–úe@Ð̵ã!¨Ö5ã˜Á¨[˜´˜4r¯ìÖþáaå¥þ~ܳ%�?Œ†‚ËÑžTƒQÕ¤4O /Rz§7äu ðpƒ£”Ž·64äBQüÑ¢šH^«H‡wt²ð’%›Ð±³ÌGNŒøØÍÔª^‘ŒúÆŠÆ>–h½ž:Ù÷ pÎÀ�rÜî” <#NQ3Ú‹¾¤'5züs^Å#ëž Î¼ú“¿4h¡†1Í�‰‚ðk#™zn0“)r�$m‚\ŒÞ…ó�B_ÚY½uè�Z¿ÈéÔÝrž˜SyšÈ}Ü~­‘a-ÆØ�ÍU凨ª|#§ ì¢ JüÈ-·Œ¢ n_¼Ø «Úy½0ô�ij�Èï�¥šà±®® tä´0™çl’Ç%dS© S£®P"ùÔóøž“lýŒ¿I<óe<Œž¡n—B¡fLà~üy]tªŒÛ&Èå"«Ñ7Í„¡ñgRÏ[OKtk[ªÍO¶ #µA…„·7kÜ…4Tat“¼¥G!Uÿw6ðú#`78óòÅñtË`vhÚóg,„FäS"4¼°(–AD$¤L¢ïŒá&mæÍwë"Z`�¯w Éæ§O l¼q„³BèÑ#1”@þ´Ù(õì…ç}–÷¶3v©*U*rRõô¥× &Y³•ÏîB_lcRæüË«¨à^Ê¿OLþ9èïs<{o²ogÔÔ8ìÜúéRôo,FM]\ëÈM&x£èO‘ò‹ý¬É]m¨Ùò‚Ši÷›oYއ eAò£^‡,†Ëû¼æ¶¾°šæ"ÂÔ#ØÚjâÜ�™ ¨L&cÍF>;f õ‘î.ÉY²Aí¤MQóÖï�„.kjŠmë‹>ƒ|9ÐÕ4ËÎO(ÜåP›~©cãÙ›àñ�žìƵ¿I_¶â¾gA·‘�Ü®´Ók"F?© eî+w0ºŸíÄÿcÇÅ{ LÂ.ŸÃ·yÙ¥@ÖI2J§4}JÓ/�°Îì²"êpËPdµÇ‘¿NW̓ðìÿ›:utqƒÚ…Üå ,ëê|kྠo· �hZ&íôùQ`Å<_y5eha>u!s]Ïž¦=�NZËÜ÷óK Ý¢K¨ê1¬fqè|ÝÁÝ€ïW’3ühn«Ag¡ÊŒÃ¾ØåSš'·ÿ]ól][2pÉŠ/•#G_›I3y5y~Ô‚©gÛ“ñÁ�•u6nSÚöÂÊ”ieë*¦\dÇŸ>PäÁô¾5]Ž6ñ"ݤ—Àá�pùNCvLù¼=zªd~ %Rø[�B[¥pÒÀ€hþ6æpt�~æÃøúOÜν¨f�=çcGT^ '¨½ª¢oGÅõÂSŽ$rLÀ�ìo—»¥ìŽL†½Ôq¨÷ « o;�7Í]áji°rÜäéKªˆL©ËâÏCˆä5ì¢tó´¹Rw4âîÊЋ.‘Xêø»ÝF:ýä•^gÎxw�§Wþ úZŸÀÌòp–¤}r°k¨18+Ä"ס…ú‡†J ÚB+n<Õ&1¼tŠþ˜Às·á:>k°l£`1[RJx?®%‚}W�FÕŽxêèñ‘¡L!/(–�¯ê0:Ýžbº3çÔå“ââYƈst#â” Å(•¼âB4ЬÀµ|ÜSÀ¿PiYjHQöv0=Ú£ë6ïE`GAø .bŠªaQi¡e&�NdË<²T敼Ù^eÚ ËoºïÞ@8ÆKûÚ*Š‹µ2ŸO½ìОŸÛ_ÈÂdJ¦ûÿôÀ�#™â@ÈmóT#Ï6M`€FsùŒH÷;D¾Ñɰ»¢' �%9_â�¶$Ãí¥‘@ûôR1Ù‰|àæ¸,ò)wŠæï°ñFY{p¤íÁîvåãgŠ#$÷î.(^Fóì*8*%ðÚ!Oçá~^ˆlÇ,AMµït˧5“‘uö/cG]­–x·}l\Ê£;•Œår%r¶K�¾EÓi‚]®§4}¼KYʸêAU(Xþ%› ¬'Å×U«À4Ç.:Dçu–Sê¥Á'Ήy ×TëPE/×|™=傚i Ö. ¨iÅÇú=|¨rÜóCîÙ[§† Pæ½M¢Å¯À¹ Ïâ¥x.F»ûbt‡Í”y[äH*ÖUã@çõ™ IdÉ.ÌÁ�f~ïFšþ/sxô{ÿì+qšÀJ'‘‚kW$³”||ڛѤ…ÔµY ì2Sç’÷�?N,�ÆK)šV×Ia{% .‘�¸o°wðÃzoÏ «ÙøA ïI»£A»Ë�.ø,سš}þ];Ûè@eÁr<8=%¸©é4#IÐ{‘‡ %_}<6pjAy91žc† ý„.c0¾Ú[Ÿ„ŠáG^_/R©Ã`Ë1‰ê¡iÃW¾/S®ßOEA�œÇºžá0ª]e™ÒËšLEýÉ„ŸòÙļBß ac“Œ|öïm$¿´{(qNó‹�;ÛÎt[åhù·™`ÃÚÙ¯�6ÂCÇ7šý ÝÈîç9Ù/|J&£þM+÷µ7�K œ{]{]¾@OåM7?Ž<ò|ø©H�ÒC(šÅÐZ;!|„Ö�›Ö—ûŽ®ÄCµm0þ1†b~lÒæ$J™Ô/ÒH(ȉ¢õ^HãûJ²Ûš1õgc õ6`b¹…i×@l¶)èžxŸ¾ýŠ3Ò,[£M€£= H  !¸©õ^ý&Ècö‰;JKIÊæ¬�à¥íNgñ`\,˜ƒ—БdjêÝèšÚì-�^\är�Ïdë@¤Ü.pkÒèŠàxs5çNÐN£èbé«q[M5~+-ûl”®ýð¹?ÇÓÏÌ“±c®]Û*,¨XõÝ{àÝ–¢Ê�û~: ˆÔ68Àx�ƒ:c|uÓ#%…#òexønw1w½ÀSÑÆ}3“Ò×§ífT• ¤"÷cçF:cü½i¹´æi¸ÈÆ ”¹]Ü‹äèKj¿ ɯaA7ÑÆÃ‚Û›B„Á‚º‘„ ¦Åò_îÙ(ƒ�îtºŸ¡7 D.Bôy"­«¥¿ó%¢Pó~ ‰�l;×’éSjJ»Øð%€lìEd€2-O溦σãÏ£$ëYÚQ½�Zh§&Á´°ºïåg“ûŠ{ÿùÃûóžv Ù/pþ·�øü°ve£ ¥°*/a¼B(Þ®›[T9¾kÁšÈj(NZð7÷BšNìéÿBS©88?qJ$ÍàZk€~À£mBÚxœy/¾öðę̂ä`Š*°sH~°ñ:ëéìÖ»ÕŒÏXRønÃKT2.+š,_TŸ—óPÉ„%m ôË€Nøऽ‚.ý> % åJöRþZ³»ÔÕQ1¹Ò_ÂHÝÍ Ò[T&ùƒn3½é••ï™f }ÇȈ3š6òköiàîý¦ûB‰cÁuCO+Ú¢ÀlJÅ"a·7o9.p /ñ®…§ Of¸ïä_IÝo�—¾žj)Ðvix’M8ôdICéŒ�t˜Üð[c¢@1s}‚þ]\×säºvZZWK]8õt(I�ª<÷�#£aÀ3Ń˄ FßÄiì+]j6EÒçn›7röXK³Å ëJ»šù>*–[Ð2WªõG…ÑܯÀÒ³qZ ð`~˜°ÒÍMÕÞè/O¥¼Áôå5bì0´·üpËΣSkg®lÑzo{ÊÌåËÔ'ZZþVæ)êBÏúå÷É5 `3–– ÿ }O/®ö_tÆð’sæÊ!0İ� 5Ê”±„v° <¦@g�æèjk£Nÿ(oÄáñ»òJ²š2IÕ…TÜ&-êM\SjBñôÙš7cA„P3»†6²›3<Ûã—丒Lðx.+9ûm©Ÿuן<@Q§åšðpZynÆîH+˜7HηQ`Ù£´u˜%Bj8ðhý˜ÎÕ¦f¶A÷MÌÁum}¥KkKzGï™`‰ôyÝ�Y¾W�÷ÝówZ tj[¨NËòóœ wàsóEžÉÃV-ëWZÒx-c†z£•õéë‹=æž��Ç ùæ`¤¿ãš‚‰0ŒE*ØÈÇþ¼G°l]¤E=iEÓ¢n_–ø7¦ �º ƹ!x s(ïžáŸ¸c3xÏÒ³[/˺ܨ¯!tÏÚ‹îM(S¾5èVW¹+q1å7²09|ÅŇB‡Èm5 ?þ»ú²1XÐàaæLÞÈäT°vŸ)zÚwÇ¡`²Â3v{0¨½ÔäBÁЕóXÏž 9—n[ܰí…ìçó’H¿À$’rʦU:ì ¥§¸È¢wvZ)ˆåÚG÷àþ„t†éãÑ¿lqt £f3Ô>÷8µŸóœ�"ÕP‹@Çø¬GdFtU¿21+¤òÝz2ŽR”[~*8¨ßwôäÅ©#k�ÅË—›«!Ñ Ë©®2o¬Éñq�ÜîX±9ö�do'}pze9¿dœ¾¾ó�øgf6…,N(µóú«…=H‰æ)à1mY�³ñœ:ÊAê?½O†�¯75HðÌt–Š ”á‚ßq?Í©�lZûrˆˆÑ�~h^ßÏ—ôTïêK†„̓fFé4D¶Q~“æ÷PŠraïèxyE ñXŠœoü¼½›ºÂHÈ%2v_’ŠvfGÃΕÄ;§(ZQw0�À>NÃ-bLRMe�¹úÁÕÉ +WŽ=pÄ“½°z ·¤-•Õ¬Îdí鮟ÕhËE‘B#F}ô<âc„�xoH­2†�›?¢Pß“C‰n*Ï­èLož·rmÎðž~°Y/=7¥* YÊÜŒò×ït-³ÎõÜóä£Ç‘,A°öÐæ.BH¯š*Äÿ,æ@*’Þfk(8y5åÏyääèG ¯Y¹2²œòä ïè~ˆÚ‡·Á½|Fà3–. FÀѲ®RÅ�M‰w¨´y e°_C*¡ )�Æš€`Œ¦stŒÁÔ³ iEæÏ¬¥¼ÓÙ£örg"ÞÚÊÄ×1g�™¼m¨oú]¥ÓA±0–ˆ¯ÍsIш¡^Xèw º¡œJ‡O:&T÷7¡wõèÎñ !Ë~´„«€.‰Œ¼ÞÜ™è<12bÔøÊ|‡”�ñNi©btq+à �»?dW„ègÓ@¢f ¹XàØ‰‚¾“вnG�¢¦�¹Œ¼Ðб—â˧,›ä{¹møHÖr×=6\R’õPH�$J­X*f8,ø8Æ–Ñ�Ô^## Îí±ŸRŒxÏ3;Ìäµ9Á`�æW¯vÓWÜíôƒÂTà¶v¼öZ„ð©P°¸dC›¹…|¼iú¦ãy Üñ$/ŒR•þñøqç${Q=_à— ¥‹ŠÞ³P}¾Çj Îo]âE=›5l.,äÅž”ÙšªsÜ'`ê qRŽtÜn’u¶Ä´Õ“fŒuFw×V¨•¡PȦ0âS-oôõázÏ*žŸïóýG÷÷¾ä| ½�YV÷-V�è9ŽFˆ(m%ý™ßU:þ¡Ú4¾BføßaÃï‡�—êF&¡ JYËè´Q\ŸÉÓ�zSÓ`9K'ÕÁìxf#â…ÕM˜ƒªWÛóÿgˆÜ òà ZŽ~%ú�ûÔ€Ú¨¦Ì>>¡´G"áJb*ˆsFÌ ÿÉ& 2x›¡Ô ¹í/ ÿ£ 9R0þkÏP.Ï#|âÑ¡Û>öéÕ€±ÜÁÆgÓYã7•NoogtÈ#J¶û‘òp¥3é¯�8¸‘åhÄÀ®Æ :Ùï¹)<1u³?'ФežMAö{⇠«IãWW×ç%È÷H(™ælU;?üòÏ�hçîsÜÏ$ñ�;xç4,ú¡�möÉ¥ù¿Y ykØÔõz‰ûù¦3×=ú]’“5Ò™R&Ï~Tß¿?�¢šŽ±µ•zKkƒÓ·Öù�cæ_v¶•r~×Oì³Y©=©ŒâV5¡Â$QC�Hוwá±6v¢À#x]Ò�VÜÒ»fÍcg_¸HOê3­ÕkÑ�÷"äÁc–¦/€ é-Óx¸Üow�‚Ä‘ UF’q¾£,¿“ì�<|4Â@M3 Ý�/QÖ„­Æ†N&¤ÚÆ£ïÚp�<À£×ö^¯ûRKâ\úàoš›ƒÿÝgßëû¶ßµƒ DnÄPô ïß Ž4Ô“nC-ñÖ©S²ì]�§ñ�Hä?WíõÄ:£�cð`úmH²—¡~ðo¨¡2™ÌÒþM®�`õ1ô“IFyôh´Ô³…Ü‹âÚ}y¯Mp$ÌÛšš²„ûÃï,&lz<'ÛÓsˆçŒ^¯luÌòž¦=mFT%öÆ]óJ]£u r52¼¾]ºatüÞÏ7[1K’�dP0¨æ,gÈ–iO%RÙVäIH8¶ñ�œ%íHÏ¥ X1ê'Èÿá±¢˜( Tý¼âßÁèt·l«èÚÖ ex!ΠÏ̯ÎË2æýQÕúѲe˶#�ÁÿW{¬üØ`{–wxÚYÌ(Hi뙄š„U´¶¨É™€žýï �‡r�¾YŒÃ–.Ëf�žyļ¾ ÀÎÐ#+Vh›ÃOB†àÌÇe�rö§ã{%Q®BY« eŠ‡Ê«Š/ýÂpˆQƒìüÓµ¾z«×DÓ²Á Èa·Ú'?e—­`8�CggÜå0¦ÿžHS¯Í»ö‹OcЈq×F&§ Ä4;J£¸Æ\¸Žïã-ˆÑý(Â9"¤"“Aƒm&†Ð#÷·ñ;úÅ–õ}wÍ™3|‰»8ÛŒ‰¼6q ra•I³×¥8+>ëÄ@QKˆòûÀF¯u³œh Ö„N¢)4¯ÿ,aÓN-¯('�âã¶O«ª2ù¿|òh 9׿7”þ)Ò?ßåx~/B�.‹,øS¬M eüfÝ£ŸÎ×JË£†v-';ä #™?€\²ÿŠåŸÅ�õmtI^Bî(Ûá«h˜€[€öÙ<Õ¢omZá\þeœ�½ë:ÉÂÜÒõH.nˆÆ\¾œð�@5ù�(ßûÈm Xø�Œ(¼É¨“@’R ë&†—\ÛÕ5ÂLÌ{5«ëw2SÒ“é’my jlv�©”£Ãmž@3õŠi> rßÉ Çé 6S¨pì”鱓fªw�çû5P+–Í3šr=Z.Úë与 F�—úÉÒùÅ#1kȦž!çÐDœ‰w /ÕŽTKJUðº#'ÄR¡AÒö~�ï?²ß÷Åø2ÙWVŸ?U°”Nf?‡ŠüÞ0–¸{gÝ@x/5HSöÀ]}xOTé­<¾0lE"¼J”�*5�‚0 ÙÀ@¡ 5F[R¿lR�ŒÄw“Z�ÜrèôÂ/Ïše\ÓÙiï0”OôØfwŽDb�&t3³ªs†K3 Ö†@×À$C ¤Ý–¤6¾ZÙ› öÚŽêûBV§¯Ñˆ;i*xÃ1ß º!óC4�Ü�nº (�•X,u EŒÿô2ž-µg /ÉçV_v™#•ɸ¼Ýh˜gA…î“G�Ë›LW××ç&xâ»—;"Ïj–q5O3aµ™ ¯îŠyCC]�™Œ‘¼0îÔ…x˜ ¤"y#n0¢þ³Çïå�[�70›¡ÄW㯬m~ý +‰‘yÞ�ÇV0í¿�Œ§@>bµÐìzȳnvúÃå˹Žþpo{ÅÚÆJ3¨×œ…,„e¦Ë.K…XÂ+k} è¹=!ph|aÒmñ$¸ëd¾¸õHËQª©)gø± y®ü¥¨ÞÍ·è¬ó+sš~‚4Ðá…••oÉv FyF1 ˆÑF¡D3Ä߆òý9ÊÊCÝ1¯"QÓ�¾.�¡>&Zü \t(j³�hž½0¼“­6e@IDAT¼ŸÈ†¹!%2é€ÁICº^~äÅݺT‚KXƒZ¯}Í{Uæ÷t wr }îÚ¸• ˜ém¸îÄ£ÃqÞ— ·›:JÆXp¨‚@¹œuãg6²ào=ú£Mkð]cr¢Ã‚ž1 2 -äPn²vó<ÿäò¼ÓXòU“â!Ö�žê.rõ?´¯…*=}G÷ûA½î}ùÑìÅ·ÞûFŠ2g{œR6ëÓ†µ(¢›qÝkÞká45~*1¦ mÓ²èy çÛÿA�óÿä8çÝøÒ·~á¾ë0£ËPóiUE³‡£­4Ï›½™p:_ïœ1›zc!ÑbÉÊÆÚ<·;5ÜÙ‰\ºÃk©\!ðšµÀ¸N0,¶�²2°Wà´[Ð!Cåv‚N4�AÙÍ4Ö'3êðʇyMK†¥ì´³LM6�.&-½¦�;Ñt&ÌÎf‚[$ÖûuÅH&ÅN•¤|žãæ"‡ã¹Þ¤üL¥ƒ­ÐEþ!XúÛhSìØqjø¥LÇèþÈòúwœ¦;@ÑI� ¦”ç ;$¿¼°²1¯ø’%•ªBÍhç4PS#‘‹[¯÷eûbonΰÐH�Ðøl㹬ÚÛÓÜ—xOÊD¤[ ¡vq­‰û8ÑhÍ  üTݘce¬+Ñ€ñÇQXØè(ú9{qV5€öдF×J3.ùË«ÇBá·ûƒN6ÅL×ã6!&Á˜ Іþ[o‘wàÜ�ìëp4*ó)sêS½AÉgRÓ�º˜Ä±ú0m%&‰¥Â÷æŒëÒÚ£0n'‹‹‹Bj�¡í23^.{ik^˜û“x‚ vÉ~yÍÌï©íûöSS]-h[mU!é;2d”\TŸ½ð 쯵7îÓ%v sü¹zÉ'}Ë67|Õ4?dpDHɼf—½OÎË£r Æ�^CQíTKË&Ó[‚“¹^£­IeîYl÷®�uÙf{}�KdÒº�ó"òø¼º�ôF P·¢l‹§M¤p´®tÆ×ùÎ%­ÿó¯O.úâW¾Áêæ ˜`ó� ÌŒ*Ö¤.ª*ÿü_^²mÁ�wžeË ÊF´Ùÿ™ßHº­y0º«¯8EúÈNÙ,æñŒ�iyõäIá�¡!D,ù~8·€“«† VZ�v^ÓЃÀŸïŒÆ‡ãÝy7¾k�00œ–ÎÚò_I¤ Ÿͦ™S£H’÷$«1¹ËÈUýÎ¥(F™»4§ÜÐPY‰¬…¶Qj¹ãÃÃxˆØôÇz¤R4|¤Vc˜:à<‰ à À†JÁxPözs]PŒYÊkL8®³1–Ñ—¼‚×¥ïc¸»ð¿EBx»-Ù_ÅȺXâ-ÿ²Ø|ûr@°VSÔÑK{¿DFñÇM�¶ËnÉ4PEfËó,ážg „#+)ŸEúrŒmŽ‹—UM­ã$n)Äû>¨Vy®T‚\  �‡3‘ö°®4Eå‡/Ï>9šÏwû­L†~EÔ€aôãâ';Xößá”䡹,ƒ‰ó¼ßiZ»€²çB¬É.øôÞ„d7¦{¨Ð‰´9Or6Õ>ï�䙸Ãàö-ÑjäBY >zxSý}p¨cmSé»qš„/→K„$@X\–ý½W@93‘7´¹[áÚ½ý{µs¼Hk ágÉp½_vlªt�"c;zMj*ê=ö­f:û7…—ÛLú“‚H¯æë§ÿp‡’„j fïÛ|5æÀèwÈB¼/œ\›ȳÌËÐ÷ƒ°²UÎôl#såõžHœ¤vA&Ôâç��YHŒæpX1óÊFðݤùÁ¤ÚŠ?�Ç‘ù¶hÑ"R°´й+Í0å:dž-S� §vŸË=zNµ×tZ´[F<å_O€µê±¨ P{Ú£'líöÞAY` ø\N4\x ŽÑzûÐáVLã¹PÕ%Ÿœxkä¡åR6#‘a»Íîd °ðš›iª©éoH`]õ¤ÑåbÌoćûº{8ŸXåt\cÚÜš‹¶™qE½ã²Ï|é;8ÁÜ¢lˆŒñ®Å8�‚¾$‡ƒAìÿ÷Îÿ=Ž|Þ�ïõ×_o®?Då†4»ÍçÆ‚S›¡Ù¾Ö!gçÜRÊD)е3Ëvw“s*ÃGš›3Õ@Œ=¹8I^ò#i�%Ï œ›’«9…ÉÔEµ¶nØlŒd @™Vÿ`'ß„w{ÂL÷j”ÄGºvœ¡I´nÝÌð¼°ÛâS¸!‹xå[VÜ[êän�Ñ—B¾,ÊñÔt„å÷°å0¼í+m=¤ ­›bù'ÚÂÍÀ•e9PÒêǵÌÊ|»IŠ|5ee¾ÀœÅUœa^BÙO êS:;€×�ÀÇö ®‡ €5 žUÇš%è±»›—”�¸†ƒ£Äèý¸ ñÆ¡ð3àžEŽ|QÈîX�¡Oýñhd •ñ/Àß´tÔ$djá=ÿZ,kæ'EE‹ìîïÎï){yìp³‹¡>ܯqŠ. &ß €VÅT×MX/Ý5kVîû£Ç%¯Ä0]]Q íæ's†š/±(�2ƲB“ß lÿËETveѬœÇ;þ{«U) ýinyêeãÜBà"xÆ[�_uQ}} �U'@C¡"7|; D÷È'­_;›š¶�"œÓ‚Óâ¢Â+Ù“  £>¥0)Aç1oÉ¢yäíÁ˜Y‹ÛµôPÜ“  ôvǦ„u-1"1úöv¨tZfUaâ,€JF»e(›G9@ˆQóÔÈìçÚ;0‘‚kôhr¢ä�A¢~"4Æ#þbç!ü>� 7~ Ž¿Ô�Ì{“޲ÍwÈ’ ­TÆîn’Ö;î2hñ‘qØÜ™KgÍàkUvÒ¬“+ª-È'×íÎA>m¶Àÿ¡SÙcëBÒÀQR²Þü$“Чi-#JTÓ:§Y÷‚êhqýÐöÐ@Ý$ÿæ”…¦Xw©×¥Y5 Z_ÁYü_“š¾1��`ÔèIŠNQJ¥1½;Žwí¿«©áC•ï%çy^ÐÄ»zÅ—ËàçC•Á€L-˜¬)ÊÇÒ˜˜ÎÒûl¥®ý³^É”À3¼ÒÒŒ!w�s½‹¢„Î�X]Z‹uø í¥‚™´Âã±Z!œZQÊg‚A0Úq/¸—(dןøåêyØÉdÚЛœ&°ú£Ë|-pn߸zµ9íö/ÍÉ� ÖûAƒÍF2ï�ö)J•ú‹Ð ÇÂ�`ãŸÓh5ŒÆ†{á-Ýá�ߎèÔ¯¯*rŽ :O×Í™ãTíÞOÊŒ0A5´.‘bŸ�œoF÷�Ó!™3(VX  «Î×ÃØÙqBk8Í|:¬ˆ{§ÔÊ# 6’J/22Êk-�cÂCš3¼dÁ‘ùRá³gºáÆŸûø÷ä¾�süÑJ»Â½²¬èÚCàÚ}øµ‡J^ý/_½ä6e–®í•8æ¤N1e�žƒŸ�ºé”ÎR™jìA‚€hŽ~"™¶~CzÔ!/ß…!@äÂw3¯_Ê(æ¥GSŠñ;J7þä 8£áþÁ¬­Ø#é ë1¤§~”(󭟕͖„¢ÊgDwØìI�s:…Bîö°èP›<ÔY0Èñ×ðQx¿�a^ÿiŒ�M²3[#CVpòà�[6nÜh^ö…ûžâY+™M«Öøzq“r÷‰<¿—­øê|8O£IacÈŒ¯ sô­]ÇüZqáM(\iýÇ÷þ‘Étÿ,¿°PîWíF—TR)°Ž×Ö"¬ÁSqñèaÎzm`4—ÂØ®*�­ fÒ³0.'º´Lý”¼5­­ôŒã¢�:S1xªë½HbþÖÞ«Ú…Ö'~™ ïf|æKv×ÀáŒR59À»š]§ÀÑîùqždϻђep�Y1ã·s±Ï �4°Ÿd9 c¹W†f±W£Y¦�£¬'»i(c»)ó�-‘Ðé{6±Í?¿ü³�F¬a´Sc:~ø¶ób|S'z�N�g jœv³PÃÎ0ì'XZ]kÐìEL,vtV¾]'|¬К€VAUçŒ6dä´ˆ )ÜsØRÓ¦Í&‡¥tÜÔX·•…¥q ôu |6ÃèNÈcªv&)‡x #žoʺ삉á=õìbÊ5’íK'C ÛÁËåeyeE–dµRÔfÜ@ʤkå´fCõvïpŒÈªÃƒæ¿ƒ¼ò ip¯ »ÛÐ Wo‡óø-¦†]’¹‰E0îY Q,þ&® yêk½ÙÄs«)gbÔðOuc_dÔ7h�í+½²CúÞèåÓ/A®û&ÙÜ,úé6¶i;Øáç� Œ|wî…·ÞU”!ž±ˆ+ˆ~.vÚé§šfÖ8Ѝð¼÷ÎùíÝ{Bw.xÌí”/Âc#£«/Ý4ÅGàj䄦&R‰‰¿i ¾z0Ó­,rßÈÑòCºƒór愾ºòíc=½Q¦+ãE Ö™«Å³vÁ-î]ƒð‡{XÃCªÅÛøG”lèɦúʜѶÖ,'Øßç]6ù°Ù��Çúå6Ž|zïÞèUgÐùÎÍu—º ó)›`Ð-)EÕ‰0^²/£àôðâÞ¹!gâjÄ’'P¨€6£ÑH—‹ý°]€Î+Ô;� R& ®ä,0Ö†"ƒéüþ“A�Ú†à‚>³Æ�3R�;£ÑlžŒ€œ�O™Z¿C`žyËa‹Àó!õjû@ô’>à]¡o7”lÉÛ]‰_;<Ž#qß„ƒ€Š)E$çóS:)Õ%¸à¯Ù;¯NŒÍû8 Õ`à3óèe²F! ¡÷Ahî¨h’ñã¡|8dn£Ž¨¶‚Ûû¯HõmóxY0ä£yê‹‘¬kþþÂøÓÙ Ê=`]aØš¢¤·á½ašã¿óA¿?/Æ—éo2]3@ØõY%[…|cXB�½¡É›Sjl›ÙS<@ÕP–ê›(´q|&}r±>¯7¾¡¹y't´Ž�Z½ó½¯'N8i"5\ÜÑA¯„E]·n=iéRz1UB¾—[Ɉ‘Àû1Ë÷cñ¯¸ó¾™ÀóÞi)Z9cˆŸßv`ósfÌÃsÉ}Ú_y«á¡­ÄªšÉ*mÈ‘Œ ÊîÀº±‚¯$kkSc;÷f)ò³È[?÷ÖLvF·Ê[�‰‹<+55C&ÖžŠÜw× &NŠAâšAþ×8(ŠÒöy…ï@yÆí6÷vãã�t/þÂWŸC=à ImY7k½ñ�fpxÄ*u8™‹YIúr»S †î\è�E /EŒ0‚ˆ¼sw÷ßþ|¦¹ ‘ÁSÀ€ò„Àš (‰P^ï¹B¡ø‹óX-xKEÅž¡8U‘Ò 7ï‹-Ùvmÿp¤Õ�åþÓ,™ ãK*È€8KPá>Ž^r—T{�~v‚;ÇŸÔÜ_ß‘plØy5ñ°Ãñ¶a³§–MòÈŽþ tb ,œƒ†�C—½ñôà+¦Ð %ƒ½LQ‰)±²Ý†î@Ñ;ÉCµpƒˆñ{[7¢P±g(õ"ˆ~ƒåÓm]oät+äc{PJÿ,©èl® �½ T D?>ž˜ˆSŒØÉøfš·ÇHŸ%}…4’ü~(‘É!ü};{¸[Ö�yokëÿ¶ƒŸãK<¼ŠÚìÈô9.úˆm~ å¤[``/ò³¾–ÍS1›š°KÎ2¶H�×oùE?D%ð¾Õo»ÕYÊåSXÏɶfÍšü+ù/oX?yo¸[iK÷‘(¾s†7yFoí'?‰À®ýQÌ—+QÈÃjšvþêÅ‹·%²ÔÆ�ÀK×/ƒA `hYj@;FTÍü³ÇM?µ¼¤€ÄþòòÔIßøÅ©Ÿ.¨¨ ´m�$ž¦¦œ~@‘oðòF´¯ç}·~ºÈ' E‘ÈEˆƒ#vÁý æ¤z)Êó–äÕô¡I,{wßä3š(Ü"R6áeX†°Æ?e*‡Š%¨ÏÒsY š� µÕ±Œ�„{¥&#åÜÓÇ È…³Ul‘{^»Öõ™¦¦,õÀôÆ›oæùPHâÆU^5CJy•XøÃM›Ìy²l!öF£`ì æ6�åH½ïqTŽõ)’$± tÐHã%§Ú?¨›*(”]€N› ri¼`#˜ç2ð,Ç}†Eâ‡o6¼'ÛëŒ�ê/ �ÕŒèm8øpˉEhò»WôLŽ%6ާ_"ÏgTxÏ~ }ì~$`¦bwõù d–¨Ä0XEѵ­ƒ “—˜ŠJJN:rˆU~!qÎh.æÎ÷‘’üà¿X… ïâÑ02±¹ÏNÚ‹ôäT¢wúV¾ç^És?�ÈAÕdÒŃ]tte3ÄéÅÅXñdn�, ÷·vWÍ®Ü-‰ÎO@‡§Tä­õ'¢Ô#óŠ]¾®xÂz…Îá­5©d¨ �­¨8«£s®nT’SûÏ]nÓ|š/<½ÇeØ™ÉRø°Sð.´”é{iŠÛDsV:¿…ªÀü��“Ä[Ïpí�F‹³¨·· ઻¶@Cøî\5óœ‡9WGjÆÒæÙ*Ïñ¸F}ƒcÙŸƒ]j®(pwMP q<¹MZÉl"câ“�÷6>ùäÔ@ELvF�p‘UTµ‚pçNï¶×ƒPÿ½üŽ/-ŒíV‰?ýÒ¥sHx�ì �TÜ141 ˜×Û2.ƒl³O9ð`V£Å< P®;¯Éƒ9B™\£/PO–#7Ú•¹�* <>Ïï'±£¿dxÉî§oà‰±~�0FpLŸ— $úŠËï¸w±)ŠŸ†ñ¼“± È×tM1E[žõò"†ðôƒœþûæ¾Ã]H|œ@« ô4À‘é/â&}ä1´$¿Ãó÷Œ&C¿»¼¬`pÚðnhqmî›ñé‹Þ_ù“ý½ ¡À‚x$ÞM¸,nüç‰^þÌñ�Ò­·~vVÀ_Ö(ç¬ó4�ÿPEØ;[‹Û«Ÿ??lCa™cŠÂqͨs :i[vùHÜ^ äFa†› in?ŠM*¡ü=§s 1\â~ÃÔ~]X$ê(”(]¹ºŽðKƒ†8×÷¯—x4óüX�k/Åb{�ï›Gbÿ.¯[®ÖõüsXÊJŠ5îiOõº7‹Ìûɉé²ÿÞÇ3ÖBÎ ê$±«*C­†c?gÌÁd¶'?éƒH3ô£+z5ïè&(Ó›ÓÅ{òw�®s~ŸÐŒ>_ád•”‡{p!ùkïKDâšh²Õ3Ê«$·elZ@Y…N#ˆ½ûg…œ …R®ªò”.Ä=a[mDi»Û´Ù ¤á¿½Û•%%9¤èÓzöO cÿfy‘¿åÙζ};0•_ gçu¯fØ›9:{k&ï(ý%tóÙ²‡9…#ƒÌÔ‘¡D�ͽ«¥Àêöö¶üo;ú9%SoF®õ‰Ê�á�6Itw9¶9†ÒíP¯Ícp 9ò ¿<¹~Ïî‘�Ì›™„zn¾Sˆá0gäÔ¹(J]Ž*¶ò½©Îš°;ïŸ~Y{F3�&'~‰—˰:©@ìxµ¡Øî ëÞóØCßÕª}€c„iºFqB{edZl‹íâ5X˜@™˜¿ĉŠ^”Q­±±U•!Wæç~a⣧…0Ú♹C‰Te6k›?ü¶ ÂÏ‚8â®>¸Çjn®É·ýá€+VW@!Ê®¾ÙÇqÜÊ^�ÌBx­22 Twàüå¯Dg¿R·2së—\Ä�kÉØ/¾åÎM—\'ax«+™± I§ãÊÀ_0 Vþ=0v�ý'#™Ì£…ñâƒtï ‰¢›¿üïU½ãÃIBrÞ¥«!�?g€ñFcØ„mvìŸnÿ †–TI¾E~Á%ç ëŽB¿´Ksè5¸?U(öë°x»TÓ핸§£Å�P’Y@…]O—…Üž'àµ÷ˆÕÝ!‹LD ŒÊ̵yFz‡ï:<¸rþÌQF0A�ë„›ߪ03ÆXsyJô‹’9žî-÷Hÿ#Š`½WˆÝ�ª¦ù2y5ÂnÅÔ7-««Î'/’Úb�aïGúµ¨®orÏŒb_ú�Ó:Ab”zŠ×rCpÖŒ� ……Ó}ÇK´%ÜÒ¾š]—÷І,”E›é%"#`îÀ„É;/sñù8ôi‡|Ïþ:aj*èÚj �¬*ÞTämÇÅäû*Öã’mÊ?‘Ýü Hߣ rrçkøô)¬ .‚êÙMð$o²?%OïøàŒÚÇo¿{Ø-0Çö>ö³I¬pÿ_�’¿úþ‘•Òþë§žcÝ'÷H¹]¾Ù‡Z^Y°øÂ’ —\Ò=˜Ž›ðÈÐ@rŽÞ½êáŽ||áEuºÃ.DBuȕ̾V^èËçF†¢–*¹Ùh+6B u;êÎB—kºOüÕm{»¾pN�/i$ñên»þzsÃñ#£µZ¨cóì@ªùLOÒ!²ÔÓòå¡¡�Ë¢*šyeþ�‰qnõ¹Ø�a·xFöl¾X&{<®™ÀL?�» t£PŽoW]æñÈþýæ¬e4" îAnS„G4©äÒ*ÒsÃHº\ òg,Š2¥ qz-:‘|µ><õÀNo#éÕ³>�â²k5Æœ;{©U2œÉȉ„!Opâ‡ê–‡"岜°ä{½é\� @Äè ¸x!$ׯPÆ …cvÚÑ c]«k§ à&0šÍR¦¨hžfì‹Æ3î�—mÓ&‚�peé_Y†›…kØ…¶>š¼ßi¼ú¢cr“^·×#é•Z.½8›rG5fB긋\ƒ1ܯ�sò¥ ­å·®å<ÂN�bëÃkÆ„°Uí'3hJ:¡Ë€f¨-}–Ý`Ó~?ø5^Kì~ùÈ¡‹–�¯dPlƒ'€§»Ûûh‰ê:2»fÄ¿ùùQ}½iu5Ï2âÜY3«Šy¡–3¨JèÔ5ÒH•2n9‹ò¢Iž‡YCíÚõÊÀô$‹îqÖ­ùdÿøõÁƒÔŒòò +§�V�®¨œJ¤v§ÔË$–»Æ¢–u‹©›rÉÓŠ »ÆÆŠrñµ¸Æ»�¨ Á¸üaf�œÃ윘ð®ª,NÌÁ;}âþÄä ‰ËLÉù=pâ†hÑÖO½blº Óû½W_£ åm«N³©r/ÙˆKh/;i|þ�ïX3—¬ü ªOW“ð¨fs?æ©GŽ&Û*ü%}ÀH‚p.UÒŠè<ó»o}˨ ys`vÊn} /!R*œ»h¡/ WÕy\ÜS%%ã33ñ¡¸f‚¶KXž™�`Q[™,'³ª||æ•Ú+�’ù4X /°TÌp‚·cñnz®ç$æ;}Ad™»o4ÝøJG$Ì ÒÇßèÞÞ?Qú�»¾<!‚V;-æ]娫ºG�XÈ�›!,q*LNŒj Ñ0}ìŽHjŽ( ïÓ,ã7™Œ²åÂ*ƒëÄxbH'ñÄ+BåÚÞïßËgèóX‰ �ºáÂjD�vcé@åÔ^xGOgèä¿è;P¨ŒÁ€VLŸâŒW„œ&ì2�2wѰäøq�sbð˜/`tzâHLÙ>Ѷg“ˆÙŒÄÑ+é§àm,D³ `\¡/i½Ì%¬NEД+0[3Œ ÁêN<â%ÀòÎÇrh†Ì;‹ѽ“9ë_tÁÎ~'»G¶�N”&·ÑTn‡*ÐíE`%·StÃ;% ¢)HJñXÊô F¤’Áe’†o__lH3ÌzAfc|N[5à¤Ü Î2‡Ün]Ö³¢žG5ý¨‹†t)gÈ’ìE]G6åt€‚Äô’ yËÑür�92¡^ÿ‰Oø#‹.âÜ´Ptt  ýl@·ËÔœ“Ah' åŽm±é‰Hlø@}¶¶¶•Ù°á­§ßdÒ_¦“$ä}»oëÛ ‰úçk+Õ{a Ⱦ?jmöä `œ}\7è”Zo‚ì�“?À±�5ôlÕr àš�ÆÍ']Ž$ç€ÌÇLg²ÃÑL‘سG’ ÀÄ `Z©'rP9†Ý‡ë|Ïo½ µz<–}¢6ɆÃ;ì„ È$3¸ø´ä'É)¼:’ìã�œçà8ydiyE|Uáà¸öž{¢éŒã‚øk¡G£tê¶à)L=ˆ·é.)ÁJŸ,ð ©´2 K®™x=­¶kËÚ•hh/¢·ÇMÕñ¬ûÁI Éuëhê¶»o;¢Õ–Õ�qw…Ì_C�ãÇIòr2hæ^Õt®Bî1¨“¹£·éþ¦Ãž3ãK2û/D瘌Y+zÝê­Ÿ¾âdÖÿô–yAÇÙæõ}=ñH=x $Áê¶¾ œ¸ p³1ð7<FäÑ7�KJ)”ÞZÐdã+$Ψy}<•,!Xì. ½>œô[¼E îÀ‰ÀH =ÚHm�S©£�ßc{%$z>ãrSRt× y¬ðè¼Å€|-^|ÖNEªôZ'ROÁû\‰âÿ6 ð@: ©÷a¦õrÿÏ��_ìž¿ü{¸¶~ÉR7Q¼ø¤Ë—"›7Ó´øÕv(б¦˜BLzÔ.k8H R+¼Ú/€dh%¨(ÌZm8æ2P-Ž`0ŒÛÉÔ«<¿Ìær™‡@¶¬­½ ¢$\ˆœÀZ•lÝ*cEh›b3Vö@²WYcC6xÊ)d§‡€:>aóL•‡cw«º9¬ÓÐÕ‹&wuîÛÞ¸ø2Y}u ·ûÿìöÅFk\e‡““Û=‚2¥³üý€Ís9Ϊ@Ô\²ÿýw1€ Ô×)Ÿ, ŒSfØT39VòuÂzFZ˪™¤ç¢šê1`eíå˜U)j±… 6ùåŒ�•J¾:Uî‰&ÜG&AÊéýG6o>D�Ô�£§Ûd a‘×Á>†Up«Ü�óý´¢ªQÕt¡jY«1É¢Vk–íKdÔü{Ì�Bq®ØÖUWÀ-ûÊÇK ²Ùø�. pÉ�/Ú mä&Ñf×YûÁ ÿA&±=1¥Š•ÙÊQÙ½±¨«ëxÇQêxûÚFsÑtÈádû±:ëGÙzŸIIëøòÓÃHÒ密™Lâ¯(ÉŸ�ý÷2J‚6õ;´KÌKÂËaé Â0¯;rj²»H*¨R$~Í当p£DÀc¥¼cý7ìÍB‚‰m‡QtÂGùí‚B×èîݱ8dZÛ‰øÀôûï¦×sf|e(Ç ¬°ÂDvRä…¬�r ÖSÊýozøÕœ/:h¥2Å~þÚc“ÙãX*.Ã’Q‚Ù‹¿‹M¥&@ !õŒeаü'äÚyøèµú@öÝ¢�Ë!Q@ÃćÈÊ#µÃ°Ž@v®(óÐxûÀ®ö'»¯¸"áª\‡hQXÆ47«Û'"U”¾¶â†‘‘¿'F½,YégÀ2Ö„™w=*7™<³9ñ­ˆkgê:îºØôî¢~avòÄy›ì÷�ÉòF3=úâºâ’c'Ñ0h›ýî’K?]b ´V¢Ÿµéƒ�áÙcÐr ­MûÀÚµyêËæüQaÊ8* 0jŸW‡5¯>ÑŸ0â±ÝÊÞ½{ g¢äC£îÊØ1M¬]tPµ5W‘Äur"}Ô"ãøXwdç5ª DeBN¨ذzC“q{@ §èÙòÜh<^;²ðêY>´�Êè~> §Öë+ Çì5€’_¤åIDH'G†zÆ-¹07œ¦3Ò‰çÕ@~NêG¨Â)ù¥“Í>ëK Å*=ÞëP²½ ¤H%H‚"¬m{4ƒ;ŽóÏáK§”/¦@†ao‘ê¶Äcz  ¿upÝ{á½ÎAׂ×î‚ =r¦fà­»“ɼñ�öu E¥%�*³…âœ:~�THF4—;¡VpÓ¦¤ùý¤(äíuí¦/æm|e,•…Ū*àüBcc ‚ø³o¶±*zƱá6Bó™[§íè€Ûlb½ÛÏ”ÂÎW“žöùÛò+¨ÖÒ=ÙlΣ9+¤ý[<&ävõV^ô„iÖªRy0ƒXè„ ’$ xà�œ­·Ý�^~&r2@mP_C41’¢ì|Ÿ>ÙH{¯oKƒÏÑAáLœ›m|àà¤Éð‡…2 “5Üìò+"cM-¤4ö´­ €NAn~ÔhÓôM�«¹ñØW„ÝQ”Ì„Å}qÌ/eYꞪUW]ÓÒÞî!_/V'ztÝ|+¦ã²ó‘0€=U¬Z ÆìfthM9WÂKýt•¿¤è�ûARŠ›ß]^°O5©±¬ú­lOÛ‰7cPOkÚ¿./ RÐæ|XGVÊáØµš`Í�®%ÂÌúÈ«~CÑs?,Hû‡ñ¥¼çÐÜŒ¢VÊz˜ÈnHX¨Î™V’k.îëóYºÁ0àZAN(4�' KýoÇÍ}ÍÍ¿ØTä> <3Iïç�7ݘE•…£¨¯ß™Ô¢GÎú£“ÝÙ½™jçâ‹/fžîž8ïÅ�èEaŸ|­�"oeÔø!#>ôÛÓKq§÷Ý:kÂ3ÿÄÖ?l$ÍÚÆûï?uœé}ÞŽW4ß6Rô(…)¯EÔbF¦�ÒN*L~V¡ÌTR¥^á!€@ÎO®×fåçLËò3¨JE¯ÙèÙÆ"WAúíhßÛuÌ3 ãßsxWʎû� Œ"Gæz$]HÄ{b¢oºD•ŸÜ8x´GÖ¬±…^ЏÔ^ʦ™ìí«W~*×�ç8à ‘vš…Âê,<²íˆëª}}‰ßÛðêÍ®²EbÙ˜©“0tYU=‚M¯ƒg|±)ËЫÊÃIY°E—z'pbÊù+“]ÛŸ91»ùšõ&e}Z”„å'bifçy¥Òå—úq9SYä“7Î5ÔÛ©Õ¾ }&bºL7órˆ.möò[?ý|On¼½„qZLÍ:±Ï½ÝcFÕúg£nÕÕ¾b‚§’Ý?Ãø’stÅ,{2g –+ëÉ5Â<ª(+» ÃY˜5è&7§ÈX<‚ìVeÅ«¬Píq¹ë-C»hÌSÇ„gˆ%:w œ:¾k( �6�^J¼?•ÖŠxÁ£a°F2ã½î1ÎLÅwΦckŠ�È££§Û6ýНSÔÞ¡ô¬€»àrp÷¹Fb‘f‘ð»úÎ%/�™îY„rr°‚ªŸÀa âg6¥>:f™© Ás1»…´®ŸuàäT.Ά52 ,0ÅJ„ƒ5k›!�„ȺÕ.g‹õûUjXkíëì#qMÒ¦fp4‰ ¤šHIá­|lKÙ”`I.…‘®ƒºT©ác‰BÇ×C¾ÿ^Û.&•ÉRŒ’fQ¹7u9Dcpûpü:ЧÛ1æ’Éò$Þ:Ÿ�œ¾F21ò¼gV®·‚¬æj”Î{-7WBpÓÔâÿy•8}Œ¿÷U¨‰ÂTêèEà"ñ. û’ýXÁ-c5#„¢eAIÉëÓצh9ä�aÂë¡ÎC ±W·� úcãuÇ'�Çn¬+#¡‹óME¼ÚM°ªíá0�c•9ÆyfÖ‹  ­¢&F‚ôî3 /9©ÒÁ:w?nªFÈØ°<†œ¼s¼½ë,^,÷ û—trK« õ¨N_%¼Ìê5<ßöøçòIsÑ#ð Ò‚`v7@Ögsßäìgû'®v{<—Ö•,ÙÍ5Œ„Š|–.ÑõÕØâ@WYšTÒÛñ3foš1ûeN 1­Ï®˜ñÞÞÈsQS™¬$ìB·à[w²o©SÕW(]˜♳Žõg‰Ü“¸‰á¤¯¢Ã~˜<àÖMÔÞ×ûÃø¾C[¢»ºmN’PW�ãõÄÕ*`Û¡=ÎÕ"­–Òzá˜nŠ®´œiŠAÝ ­âBðC'�`®q±ÜÕd+M�«ø*§Ù�wévN<_ÄG}Õ!i Æ÷œRÿŒ�3^7y~6øRE‘(?ø9é¹Ö‘}Ôâ3ؼl’¡ÇOþÖxˆx3ÝÔœ§Ï+ ¶c°Y�q¾ɸÛLF&…Ä{�óÚ ZsšÞ?žXdiõu{Gö^îbnþ…HÚ9³Q‚º CØïŒx©üÚ44^/jZSURôü@rsnÏæ~2L?bøê/½jY ë¹0ãe ®_°¤WSÔmà:||½ŠH·vû(uþôÞôJ »Û±çaÕô £!±Ö…Þ» Ë;r¬u4sà€‰ó彎q ÐXÓs)ˆâ‘+H½À°»&‡ŠÈó›—¥MªHP­ ["9ÅXkXæëŽ´v>dþ R<ª{è{ÀH´%¡béEËucµô=”é)ÒEñ Z9¶wy}fÍÉ6’ùç`"±ÅÐ�J ØÇ¬–Þ±jñTÕ×òÙå1”x'OÝ3gÞŠÔ³ÞƒÜâ’`É| ˆJÀåûÇÒF}™°DíL�á}wcfƒv˜õ4¢Ø¿‚hÀ<‘Z›æþ/Ãçá;Ÿ¦jšZ‹ vX‘ìÝÀg_Oö4g®Çå2u+ÁÖ0ØîÚs¼±mÅIY§é[«+Ê$$±¬vž†í hƒlËìð „à¥J.L~Aã¯c•±ú£˜”òvô8XÔhÆÕfª†C±T<3�öˆ)”šä¶ªD]W]d"]ðÀ�¿3 %É·þé6¼Û_QÐà !€³´}¢œ/^BI>4} p­R,³ºÒï‹!ÝÏÎ/ÄX+UÚ)êbLïVÜÅÙ”ef:»`�ѧމ­¬¬LiŸHgx&lš\µÌXÜ ÙØSzV”E‹ªaf«„ó=…×emEóAdêH{é~jÒ\è¢Å;6ôÆz¶ßö¹¨vß ;Ì+øÎžwâþÚsüÝÆ—Â'Ž .*òú/ÅÂXg5ÐW<±âðÀÄpÐ?Äà Â5VÔT²Ç12ôð³vò´®§<Œu|r³ŽD£“m1·+cSAÁ�Û…H+.ìÔ÷ˆ»{4ƒ²aû–"Þû:X�·a®Ö§e'žlz±Ð_¶á�/æk(ì.Ì//�_u$ª$ƒYÏbõ,ÕG©­âˆË<—ƒÎ²ÌÅñÐ tjÐ/UM±1î?u�Þ‹¿p½½Š^;»�6¬ÆÜdBF?Ë_†ä÷ årú˜ÌZ×óÞ¢¾z£í!U“È]H®%(½½1ñËá0çðþ QêI]i­äü•9`§ÅÞ‰{BœƒÃcñ.¯ –B‰$6®ëÊà@¯.�1Š:ãóP=€ ó ´å�Óí±xa”³í>Ô˜—C߸Îå–P–\‹ä�q††ƒqOGˆ�"ÇŸþÞ»åõ-³áßÒ0:ec§‰úXjü|WKäh²ê¯Ã#âY–Ч'ôS±Ç³�cee¥ŠÛó4(›hÞ{;T„¿‚Qq%ø’Ã<©8#™ïéï²´Õ/ 4¼ÎeÈR-Å*¿R?ߛȎniF¼oz?LÞâ†ÖŽB3@“¬ý’\\TM} F‰ ’ëÓûm‰D OAÿ3²ï*ÅŠ¿!ÇwqîÍ`‹¼B`ŒË¼bM^¦š,gwÆ2M/Ž.%á”éï“WTô¥aÔ_êAÖÔ¾‘©>yaya'‰‡“�w Dk_ՂÐ Ím»v‚Ä'l*=´Ø�Ùï*¶ô ÝY¼íºššÄ†®CÇr3Žâ‡¿Û9ûkîg•ÄDxH\V8<#ÐdtÙ\+Ä_äu\(¶¨!uÓÇ�Ä <Ï$•÷¢ õ};”й�e¬ƒ214ê[ýVâÅw£á%×ðwß\raHˆâD«`òX dÀb«/6¾ráùÃ*›DD×%_ë¶è¡T ª }«Êd‚8µÜ' !ÇÁÃÞc†Ê¡bÜD�ˆ•î¯xÙóL6fÎçúüÙášmHl%Ƶ Ø¿sl$ñ]$ÙŠQH0ä˜Úq³óÈ5¾@b`KJK•‡OœðÌtΑ‹‹çi°¸š£±Ðµ¼ÚÉ©ÝFA]@“³CŽNçLPgÆ` Ä ,Kò�´zU mkë+”Á½HxbŠýj´´»’Ÿö’ú·�²fm‘TÏoüÒ'€ >¹aËrnÄÒ=Ðì*Ë(Ê0ºKë\€d_-GYMMoº¬K˼á&_¯/¬žƒy;–žÕ ®ü…M)ߊM U»«| ïáPÔH�À#ó[‰ð �X�Ü[QîmÀ‹Ê¢z͵Wà…È Ûº~`Û:Ênüd½èg¬ÂãéÑg™š+Ú5( x£àiY+1°Ô~ò×�~-ô:ñt¯iíg ƒ#{ªMø¦W íÏ÷÷Ö]]û;¯ì­)ÅJÛSÇŽi1Ë õÐ�·ìdÎG÷2ˆ®\¨„2ëÇsm�iÊ{ÆJñ܂ȫ%D‹—K`5TÝä oŒ$~­3t3ñø‚Õð–cà1¸|¸€;²ñ¤ŠU— m¬Š¬öd$7yÍ‚óöjÜ9!•ÿ7¢?ïÈä´ÙïO-�)üÄyƒ™õíuîwœø¾É,@†”/—E¡ ËrËSIÕ'£P‹)@žé9ÎRzr†0Œ‡r=Uãbµ¤‹‹¬pù–ž¸ 5Ò§ÞUÛßm|ÉÕ� *A0T­ºê6¶›“™FèhÕ{X½�® ïkF1Xg_,Ó�Ëå>¶;¢þ6Rlǘ:Óp E½ÊŽŽösî‚ĸ–6FöEC‹/,6JÅÖ•…á0)ZhÇñ´äp÷s=²L‡£Q[;bµ'e_ÕcЩ_õQ/‡ÄÐy ÅôºQÏ„ìßżiîçàbÚ’˜u¹Ì#�•ªÈææ7Ë2‚Ð`qAv/׿¸T›j[A¤m"37Sð�cŒl‘º©€¡Sã)eX£Jòï“ÿÖVT ‹ÊkÑí³Rߤ.+ª š^„�g�T £šv'1Qdÿááa£0X•‚­¬²Lêô"‹£Ó¸+›$ÆaÇÒ¯•†Ã棛»2?»½ÆeJ|zé}ÊÁÐö#¡Gž¼óQ]\_\�rf¶õÕA—ôGH?¯†q¼™ÇzÄPA¼Ã$'©.±žzƒͤ­f`šk!Agp¼ñ§I¨ëÚPˆµÌ‘hò'&ëò&¢‘ÕÿmïûbÊÒ8œ ú¦™š�iŒs:„ÕÃD‰âŠ÷aìVùkD©9oñ0.úÛÒ |bèkãí©£7ÇÞò÷‹ ¥3„×Îuj°{�˜ýâ­¼¡¦Ô¶|Šå1Üü©g°O&u&“õ¼È,A"_¼°;†J[c4+‰ËYŒÉ®­mìwf¡«õþ kÂ1 iŽÚ…ï�0Õ{z“GÚ-#\MXç°¨{c[Th�ÇŽÒø—\[ƒDŠSjPU]´PÒÐA�ç>#8ΰåâ†Êlo$“Ë…Êú¥š[žÄDøæqúÆÑÏíoä<ý{0ÒKb¢w%ü±çq‹š» Úr½fqQT3Ö‘²ñ §Æ�­3ÛPlñ]_Øö&ÙûO&Â�_yÆ• GBõ¥Çùßi|É# ‰+ zÞ=p+AP�AEZÏf,¯WœLèî�(ŽýÆ™êéŠ}›ª/È“��ïOoÍÈmr²r }ƒ'$…¨àÒ^V7N€Åþy�¢—Ó uIQSsöÏ�åVÞvb–o‰)1�ë*7 Óº£ìJ£Æ¬È‚BFæùY¢(Fµ »+6Üš�x£z)žÔôÆrÒ mi¨?Ò0çA„E¨U˜s€Ó\Ï¥Õ'_x�­¹hMõâ(²²ÚË3 ùðÆv$Ú¨‘Qê˜�Llq—øžõ¸Ê‹8¾Ü'S5`…`JR”@C[ÌŠèªÖ~Óg”C×@–Çâø¶igaÜ´£îâÜÞžä1ó¸U4œ[»¸a•©8ù…·/†�IÓ?€ZëÝ3gêû‡‡eJ :J:$òª¦ B™}…BîXR¿ ÌK€‰† 2­£<î Þd7Û<}È1œ›à¥6Cm¤JÃAœ#�ëÄ?L.Ààm Äœ¦Ê£Û¾·Î†BùÈ™[ä'÷�¦¥pÜOíIµÜ¬/B»T\ÕâO-+/O¬Ã¾ ´±³K(ð_•²Ódªo¨/ÑãéÙøN#+z¸ØFÁ„àƒúÉ•ùô3´óË�ñÈÈòð¤íðÀ@�}wá'«35X iÃïòîÁdßW´òÒÅZ�°B�{·>9蜌©®Ì4�JÉÃ2:ƒ:ULšOaQTœEÛ:/&jÇ#Ù+ÍFid>‘;µÇ{÷ÿŠ¢�ÏÐi�'Qµ�§ �䂜ÅÁ`r]�|^Ä©@IDAT:Œë>:±;ÂWÕ}–A•Z´Q…ɶYu�Û)µMú<›Í“V…ñµ#KñÖVÀÍŸá$½�wÉ2²Ã ¿E!Þ ¡’ÍL EÝþò¬ Ý'1ù8B[JPáØöÚst|Üy¢~­Õœgê¿Ô¤m…=l%ç Î~7Æ}ωç;}Í ñSºººÚG9¯�Ê1ýûÃQ [›xbç[�¥é ‡£[�Õ0Ž“Ï§¿?ýZZøM×á£óiŽ>ÆÐæ¦ÉÄÑAšP7&Qq–T+Mo/õ¤f! ÆxUÒȽHgé}«QÚ³¶�Êê7�ÚRuAï‰N;}|ƒ7qͬ·âþb#]}î? .âkmÜ ²3a8N¤×-ÕÞê˦Íú5k ”¤æ¢->‹uäb”(ëðc9·Ð©¬ìŸ“uWª´«Nvì™ ÑTræˆËÏO°ªw¢ã…Öôí(é%†—lµó‹Í#ÇûžózC騮Á4µ¶Ñú Y ä�E ¿�ƒQ@ 4®¾ó �£‰&0OIöbI tó†—+йÊÀRVBóÌ£c±Ø«ÐbK!Žº ñàä@.‚|R§­¯ï) Œ’ÉŠ‰fp{镸ñůÎx‚’"x;?ÀÀüoÀÐQÐÍã:ãƒg�oÇÉÿМ)#MÚtýçîÛiFÆ\¼}©$?.å]W¸ÜõÙ�Á—ÀøSC°bßûSë“Ofj›¯ú=f‘‹ëõ#Ö´‘/�¡ôÝX lGyéóˆãf§O8Éù¯CÒ'A¸4 šÐ×Q¹ �2k©)„¨œÒ—ú¼ý$ƒ6†—.mß�{ü&=ƒ‘ù9 pP•„ѹüM8U\ éo65«t^Ýîµ'¯{úœïÕ×;–,1[‡“ýÀ˜{!4È`9zÆ¥œºî@ Qê5~1EQ6u±…KJƒ&e”‚û£1ÔÐHÎa(n eu•P?ã@oóñžž\a}Óë,c¤ax±:Ax±ý½ãꔊ7Êi¨«ý÷üçdõè[Z% ¶�ä€Ý�KÊ”1@ö}ËNÿà7Hðœ7kŽÜs¬ –Ï͘ÎÕP|¸ ÛÕX¢†þŽ�µf‡nZÇ«CgGbé•€7}ÊÔéí¼àx°<ª�uáb=I��lŸæ±%R¥vágÞO(¶zM«ü]®?ù[�B4²äÊ p\‚[>6íj'j é‚{ÆS3ÓHB&¶ËlÝŽnÿy$5¶?‚n»Ö·ìÿõ:h£‘?]{ACºeH©Pe Já_Ç’hŒµ¤A¯Lâ…~‹µolكⲠ#ö–Iæd;àxN4ò7�»¡%¤ÄÔJA–fäý˜Ë-–b§�™tËaéxDAO ‡ûI$T`¹Á*fÜŸ’н›ÞÉNŸ4!þ;pÒw1ñìHúökNBýÚGSËtŽÝ„•I2Éwu�ô®ŸV‰gÆg�Àøš"–ó%yÂû£<€4îä¶/š^ Ï%�îß[0oÙ'Цƒ/RŠPFE+®&‡°E!è b ªãß…Š_¯AH†(ƒ&sŃ|S<+¥T59Ö•üxó‰RÒ‡.¬_ø¸Ì2¾Œmþ\d¹~Às!ô2/¼¤B"Uh ¿€’.ÕÐ&®âL™÷\ V,ŒÁö OÖºÝQôð|''Ô“¨ì:cY>}Mÿ^[㹫yÊhôÄíõµ³ü¿^ú"FÝ‚yYŒDxÉã)BÙî‡NrE<Bhç½gdbóxÙºT�¼K.¿…ðT.gïýŸâ¸ÄÓÝ9’|P4àØÙ\<òÿz?Þ©ýΩç;Ýhbx_îŒÖ†ýe¡pþ± �]À²òèk¾€ÕC'0ÿÙIÛ‰|"—¤ôóŒMŠ�µšá²íÈÅW`)µK0™M¶‹S‡âÒH3âšÓ;�cÉQPG�@œè¨-ØÃÐ{CÂÓº±t¦ß6²Þƒ�›êV¬à•––\%¾DŒ5âÃ$†yjkN7€¶¼^3õQ�–~™å¬-n”ÖÆ2“—TTŒ-,òš=¡¸8çÌü %Ýï+óRV”TÊч].ÎÇÓb'¥Ó± $Oì+ñ$nG¼jéIØÏ©“�ü…$¡DçØeSÃIB!-]z¹7¦ÛŸfeq"3%·x1€B@ÖÓ¢{ <裳ÒD™¶þS””Þ‹ò\xß�1£¸þ®.º´$%'GÓ"ÆÍÒN{íb/žÚ‡îÁ³A�5ˆrð…>`TNnœÀívL›,Å+@Š¿ÖÇ÷>¹;ÛJEvëM+®ô«"µ ìbŸ?ÃXÆëÝï¬'E�¨tÒ×ÂîbáL :&Ù!Äîv»ݡ¨úK5?‘Sé xœ#á„/šóàmÈGIöòŠpÞ@’}ÈvÙܹ²¢Rõ–m­×Gµ�n˜â<*±è!kd$CUÔRøÍOÆ[n¿û˜c±‹\n·˜œL &|Pu´nàÃ=/=Ösn_ì<ÒÞW’Ý«jŠj ëíLמµ�¾·ô¹©3¿wÿZÆXÆ‚|�å(ÅvrrršÉŠ ¢‚ô“�*ñ0—ŠÑK–x1‡žñhþŠ#ÿm»¾Ù𒣬,öŒïOfÁ…â妯ï®÷ˆãOŒv#†áT‚û� hÞuÆ÷\-)è—Ç/Ú9�¾pó‰±-cék�ôE`D¡ˆÊ^øUĤ�ïçåGÊÑC¯©ºõ \#"G}°ˆs•¬[û¦�x¶Y[{ û>�gÈ%Ì÷K}Í5T>Î:½;‰5gs£)k¢ë†JÜñ8�‰݇xæ-€œ4KK�å`Ã"׿dIÐ×°`Åþቆ––)¤Ô‹káãÞƒ2Õû 0wŸÅQMf&~ÔV4©€ó-ØÜÖK�,Û@ …£k¹Ø‰æššü’h9âÖš¡¶&“êà �|ýÅG<'•xƉá�nãé¯d?8šºZ|w¹y×W�ùWÙðÏ­“ˆ…¾Õ­f¤¦‘0¢A/ÂÔ2¶}ƒES5Pà\o0ìh–õGøa›5‡%�?Z ýoñ0Š ˆæömBÂ5ùû‰•#Â&È"Óo8V mi…±‡?Ú‰Ý`3é¹àÚ�Èûdíï|‡þ#ø†sÄÓ¶(î+µõÁÛë/Ys»&²_Bìý«€—­¤x!*.v{½†AÿK¨ÿ²móÛˆ!þ^ù·‘D|J_+ò?raUQ/==‚‰VJ>)³ñ}ò;Iœ½¶‡��h˜Mq\Àƒ@@à~83…Hï Žð™üD²ü0^‚CF…S¾hgI©wrY±¿okEñQâ!•ŠÙÿ!–åWZ6u54ýþ•ÕkˆL®÷Ó6B'T—óÈäÿâênúz·õÇV¼:ž[DVZÓïÇ„ð�ào§9H\E`bĦ?ÿG¾¢mß• ±“àG av ™´iúõÍíËåTØ N�ï/íóæï¼“¿Åèý­'Yq>J‚¿à H÷I&õo¼C}°¤ ¬9Áh?m*ö&à�#(JŽoЍÕ÷$UK î?ûY7üàÄ~c¹×^{¦•»³v„kÒ0†Ã:Î ¥%¨tzKážO�ïÞ<™7:ˆ;Ë�‚ò’k0Y!àm†‰!Å|Áý…P?‚ЧÚ¢odTÔIÜH ]ë).v“Öù±–éE‹œ€t<ÉÈŸò.nh¬;ðdEA;�¬>ÙÿlÛ®ÁhD¹ï¤xæß ´ûAË¢wun†}¼E÷žKV霒±ëg‚|TüCÞ£ 3´Y¡ë%0?Îd�ÿʦíÎ+¡V¬9ÌAŽþÂ29” 5KáÆ#‘@Ëø¸ X«r¸-9Ų‰—pª½déÍÒöQ<|äE­zAòÌîIö�DíÊ r¶®ý†�£mƒðŽ/�BÇW€’øw�Ý kÍŸûˆžÖGŒ?ïÍ’É&VèÝäØÙõ#£?6N<¬t$7/+÷v¢â„ìó–{Ò§ü;'ÊH£ÁW±…ÀW –¿ûb �¸¥…”A Ñ?Ñ|`-¼�e;xáM��x+¾ÏÔŸw&�ù@=G”l6?âDä\HN]ïN]¿XîÑÐNvJ,UŽ•ƒ¬ªß�’\ûߺÀ2¯—Ä×O=볋t{Qä¯c ãÄÊOMB:„KE™Yóê`fÕŸzãó×íéò�íûoç{{J"c•LŽd"ØÔ«xu8½Rl˜WÿÝÏßÒkXHÌ·€ØmÁÕÿòù™;Atå¿|~ÅÙÚ„rþq0€d(Ë(»â“ŸÌ;QgÛïõÞ¹ ;ÿj ÀGXšºÂáí.�¢µMnWÖ±¤w?Œ¸‘¾7;û!�¥1¸w˦>1=PÞ|ÈòãÉ')ð¼ºì…·O-KÉ1R¦]"@�ç½Êî £Äû�þ.æigÓ¨ÐÍjÙŸÛ{'‡g-ùL <&TŒ‘ˆÉ$µÿ|x]½½½bŒ¡.F õeÑ ã2 1Æí¼ÅUcií@(rÔ›“òßW2z1+|£Z‰t*u†WûâØ˜+t§§öõq«¼u,%�§*¦»“%žŽfœ‡œ{×hØôÞfZöÇ€öØg3Âs”n&áI/àýV„ nÈP\0Ö¯¨Z¿~=»ôú�oÌ4 iU¦h±9K <Ǩ9$À¾°TXŽíͬǽ¯Çdw e7�ƒ’À´t£ñ12äÃdw²Ñ( .Qþ„70¿0~#Ü­1ΰ˵•ðè³%ǯ™L[¤™×€wŸGÓ<¸”MTj:]�ï>æ0ÂÞÁj×îá©AÞŒëÜÇq…ÁŠ"'drHÛ‘Ik~™!4€DØP¤…2poŒ3¼Å!*Î_§1À#bóæå›6u=²fM½Fžé«ýñ߃'ô‡<Ç~ êÅ(Ø E„°j\…•·Æ’‰BÈ3]†À|1T7~¤èõðrÎ܈‘ùé‘á«eYâ9‘J¨j‰›ç~ƶçáŸ7ï–O ˆ-·ü¥¤Í™G{wÿõê°Rië9÷¶šPÇÙÆ&c�¨1~@�ŠvERåziiV6¥yˆñÍß;8J€éSˆ;@G•ÒµºÂ¢ßã³4~¦ûÎÛ~ —¼ÔÐùª™%õHUX.¬ÜD–2+N܆¢ã;¢ Ð�pÉÉ LýŸxžC/?Ÿ‹Òß5P„häA$�5”IsîªÖ—£TË¥ETßÉw’5%Ô৺øT§´¦õpˆvRIO‚°Û~¿È˜l úû£ñþ„,>#x„�ñ¿Ò4í�TÙÑÇsóé½+š £�+‹÷ŽUΟŸ÷øH¡hQYäÈïwxê$Ø”™„Æ„`ïÕÚ#ë¨Æ©x¶ä÷¬Æî7bÒqÐé Ó�þÒRk¬wâ°ì‘VB‡jâ¶7¹=eÏa¿ÎÛo¿Ý88–>€àÈ�0¬”ï×µôQßvÁ:T‰çØÞÑøC�T*G‡m‚·z§¦*› 'âmz+HæÉÊo€Åzxïgíº … ÅÛ ¿Ñ ¬bPYOòŸûaT×…¼Õ--}Ëš–@ép³^õpwk<œ^œ]½š&ãôÔ½=yhÌ±Ò ¬þ¯C¸à9T%J]]™‚É]zõ§>ï½æÎ»S¼Í6éoð6µŒ—¥› £]п[Hî–ãoð_TòŽGž%øÈ¬í¦Æþ î[°Jºó*¨”éKAØ´Ü%Ø>¼+€|l•zCë#ëLð3ä›r¡†N]mx`ëÖý(¡•!jºÊЬz@ IšòˆkN…2Ù5ö/(jîYVѼ_zK¿Ëè=ôíd–»]ò%—¥¿wKógûPAÔ-ºÈa©°D ¥ßUbÅU†{XDô<“eFŠÎÁeæû›£s’K@mŒ“’©?<:”¥)ß;gyÑØ?Ö¤çÑ€e‚ä ?Ü~Ú2 „ªÉÓ”–ÕGy?`ð5 $MÜØ©�€`âë§Æ%™xŸ8õ 1R…ž|þ®2¾ç,ì°1N–Ÿ2,û•¥â´`¬X9Þá~�eBKû›àLÞîÖYðq¶ þ`<Ǿ*–—ÚŠsïUw|á:ÄWóK|ܤ·l/ô�Ö(ºuh ?‹%±±ä>0èò:JR'£Ÿz²c² _Ê/YñJ ÃÆ�VÚ§œeg&°0ËtX À<È%EÁêÀòª‚<Öƒð¯Öµ8k8](ZÐôö"{g–ãf±‚œØ¶n]Þã#JË0v·! ñ$ù*a�‡ø êZ%�œ4‡ÅiÉÜ#z%2µÅøbÒ„ËûW§â¹[Qq÷1T”b†'8]ZÀÒ¥-8¾G>Šÿ¦Ms//:q´ÃÉú ËÍœBзoÄINtÉ’êЉ‰Òp)Ê¿%¹=OuìëSëç•ôØŒQ +–÷(n­©á&ØD ’t†ž§é± $e˜´(¦I„]H õ9Z6:NaÂÆÆe³}Ž(MÒŒSŠ,ï\ÁÎjôȾc½¯/û{ÖK×#DðqLMðÀ!d̃l—ÑPXCv£Ü´6ÉÐâCºÁ$}&5¤òÙ„)Ö—,¦ìGZZøÁ†ó+ÄlBYU_2�F8@/ë²;îš6³:]¦çŠŒ8k¦Gü:Õ�Ÿ|;Éq±9¿+ò÷½,1L©Üù¨$|1÷„ 3aȇ:¶ËÓeé ÆL±jj‡B¶Ô™‚ècf&R6ÿt�ËÕÛÓÙzTééѶ—ù³Žãû@]AäŠÊÂÔ@?æ•+whŽZ…‰(ˆv›Ÿ4²©NiìÄPsÅè• Õ=âpŒÎ2�Y†Ãl@ì|Nڴ߇°É',�èÍ¡”Ö2ëÄz·ýo9�›Eø£X}��ºº©¢¤ˆ‘O ­B"*ª­G±k¿âè“ (Ûþ¼Ì å-0¾Í˜lohj2ZÆâ£vÎBŽ6ëÓk½ÝÔ;¼Å¨lg|rÌ öôØ� /Ùª‰LÜ«@ Qv%Þ,úµ�$ùvšçnÆ_¨ˆ¤5¸TV�PÚ‰ª.¢Ó7Ýä\Ú™thÓg£äXãÎn §÷ýG¼’‹9§ç…dÛ×­s¦ 9x ìHêÄ 9 GÕr­†;² ÆDTMg'mëÇPAV�òÒ‹˜uâû6—zψ[ín�ŒÊÒŸ°ÖKwRÖþx ôáH¦–YÔü˜ !]þ… ã¸,Ó¾ c³>Àó:,~‚Ð1ÒúäVBÂBTR›š‘HÃh´õàmà¥+hªs(2YNI҃݉�lg½^�§ù eʼÊ2rÝi½ÄîSË›ƒ™f|åðü ^û.Ͱ/qš34Á)fI®(ÛX“§Á´À}!–Š ÞÁ^Љ]ÈzËècàNxb�»!Q±G'ìXü²ÚZb°ó^\ÆT|4ñ4íV =`˜ï…ç7$;¾ã³ÃtÏîÍ�Þ54¾À%¹¾ˆˆ° žÁ íŠ8ÔÔ1ñJ!ÌIàvpz�ÊÃy/Ï k‹óAÞP¯�ÖãC/p;>b÷ޤÿ?˜»ËpºZ<ø'nUÊ‚l>Ër½-šû´nçé.éë/}ï½kîøÜRQƒœõë´Æ6è,#q¦Ù¯Z™¨�Ír[~ßn Ò‚©ömî+r{\çç@»2�îGÉ9ù�LÀDY! ‹BþG»áRÃOµÿ—'N ç.®äd.N[�Øé«ð”_³áÿ ¸ñŠ Õ¾‰Ç¹aItÀ!±K¸g®¬ðžOÙ;Éû‰f†ex<ù«U‹&Nï#ä|ïö ü~¿›Ùät˄Ѐ‘�p"3Œ.w Öès%{Í ¦¤wlŒ2eLfFS7b’;:±csžh�\ã¶Þäå’�w©9Íå© þ‘¬hßÉk'Ϲù¶Ï\ ˜aqšfwUºØ&Sv^Ù¼9‹<@¾/�„Ã{n7 §ÏÉ/JÌ<ÆbN€\ÿÐÒpààií¥‘¸ãæWÌŠ¥ßª4ÃícëaÄæ�¿Òkgxx©‚@©À03`µ&ÃÚÛóÓŸCI¡K(ÿŽÃ²™sX…w#–y‡iS²Cˆï8E€FÝpU¼ÁE¸#Þ<¨š›)“„Gp¸¼©÷°‘34–Ô÷etÝñÑÒ ÊÊ£’˱ݮ!ù´e*cYýhÓaØä/ø[€™ý*0_-`\ŸU=É[P°pù“==~Bï¸g0‰åÀÑB«ê÷·u|8Säfy±oï3¿þþ@¡'Ü�³ù­ˆ�B÷Ähb o3<"L)tÔ>CýÕ”–¨ƒ!N /9_ÇÑL©Bës{G’K*»Àɱ³×>1uNòù{eÛùèÒ`Œ½ý öë9[yp×àÑõé°ç)Û¶^6MÈ‚,¯¢‚RIÑ)Ä™ÌDÿpºá%×j!-—Je|P$åtH ¾Ó׿âƒwÊ€]ÅŠì èœ��Ø|:˵·¯=—'«<€Ž°RdÏ›ÂÜxV3:ñ:¥$ûF£� ¸_P9“f¬Y[úbçCY…ããìo,²Ðw ¼�‹j»ãÙ™‡FSÔ”�´'Øò2¯ùæ#Yv#* >ÌêëYÊøHÁû�PZÍëf´” 0/ÏÒ!�/ÞWã~#NÙ�/Ó-îc9d Óøõg?t P¤íz&. yGzâ©Û†û8V[¯[¦ôÍl¶÷%‰õt@c­æX‹½¾¢—|büÏ=“K#I}æá´ÆöK)ŒîSúF”ù¥DmPVÕ�&+ú@DBËh_åóm—-l̇Ⱦ¯ Ò]8D(H˜e`s©Y°t+“^Ž·šB¼'^½ ±‹ãdüÌKPKäEØ!‡AÒ&xÇ…ÖV+ãrÖ.[} ÷Hà]3ôÌë¨û-4yîßYB‘Ž=‰xq'Œ`ɱ(C™»vxäɳµ½©©ÉÙþÜÓã—/]8Zî•SeH­�Jæpø/Æ63š†u•ÈscY=Ó!x˜ƒWœ$.�¡vzFc½¢Kh×ñ2oW þ†å­ôÇ*¿tFxˆÜ“î‰À öP<£÷ÕÜ™í›ÄщeâЕ>Ÿë-ôW®A�â‡Qñx¥À2«TÚ™wk"cîñ¹Éõì{áY�+›�·©ZI‚ûµTW“;ÏGí=>r¼�D2ÞãÅžÙ“}ó—¾´Ëíûàé.÷1]$hÞïêÚãZ—À%‚ým×Î�¡Öú݉Yn)Ѽ‚·'·�=X§ ㇀XEǹ5k\ËFÖ6½µ_LçÝøJžÓ@Jõ ¹pt"m¹´ª*µ¢²Ò¬Á$Q¸õÔ-`°ÞÚ½wWMMM~2†çà WϺ¬+‹Õ„¼ùåúX*²y°ÑsãÃÇ늋ßQÏwP™€’˜ëbä‡DU§£_R›;Ôùa¢ù´±z<:¸“+ ò“íe ¼[œ„Ç/—{¤ÞÓŸÏNŒáâ@h9.â<„cXÆÍ­ô!àö.ØþnãK:ÿ=_þ÷jXŸ«]¢¨±_gø¤¢»³ ݑɕEâ¿§}¿Ù„Ž0ƒ#ÄÚW l3¯ÇMìe8ó2Pý] 6« àVõ#ž÷Xa±Ø¿ë”±èià/«/¨Á`ê:¡gFbñ¸½c4ð¤'­œâƒ ýÿ"°eû?ËøÂ�×Í.ÉÀÇ6 ‘õ¼­Ñ/Ä´É-e†:pA ð–AAV ¡y‹¤YuV4£~>8Hw óUùËBû^Ú¨x½�J“_�äjãš^ÝMšUžÜgº�©ªìpÛéîöî çÌɯ,¦ûg5öùù#V¿|ù ¤A¯‰ägþ¹Ê#UræöÚ‹&Ùìâžæ…n2I9M0by£ åbÁç¾Ãd¨Ûc! :Y¶‰áéK–«åcéc([ÅäaoÉ%²4ç�@b)­3Öï¿¿øð»5­W�³ö‚È•X™É›0ˆÐý˜²K`BÒ`³rå||Ž„žöQöL/+o¨+ðdÏléÔ_×Ö‡³õG:Òs |uÈl:ƒúðì�~Ú@?Û÷Þ�ï F“ƒB�ÔyÑÿÏÝ{ÆQÞiãÓg¶¯VZõ.Ù’%wÙ¶i¡Ä&@$BBB Iîøß%w!G: %�jìÐL‘®r—¬ÞÛjWÛËôùžwm9†�Ü÷]À„ÿ$b­ÕîÌ;ï¼åWžßóž2H;1àhö³<Š�b6f÷ Ÿ/;·úƒ±žæ~¨2¾j�H˜Ý¬ž�:*æ‹¥ç¦[Û¬¿ÑoÔýWz½zWLiùΤ¥²ÝÈê¾Gë’‡˜¦ââìÆ�Ï¥ƒŠ"ð&³ –~.ò #œe…KÜŽ©Ó Jòùñ˜V ƒÏ‡Äm§ÞÓÖ=½ù|P÷ð{ÞxñmÜðe'(É}S€¥Ç'Èæ@‹tIá}h·ó)»;\ãuÆwŽv%�‚ׄ�Ñ�t�Éç#†x6Üj  ÚÝcüVŸˆ¶èá¤Øš2Åz¯”Íb~ÿ�ç•ÆÒr †¨UW”fU¦ ~.VŸUààï�ƒGbésP‘é…2Ù½ëêÙE¡?"åò"ýyøRròÑä+ÑqG]^ ²\ŸñL�…º]°Èœ‹¸Œ+q��ô`]ž=Wšî@²KÖåº2:„ Û¿Ú1{¹»`ÊyÓR!e»_ËݧXñVT€ùà//D–ÕË æÓË ý½cie ’òEã¥Ë̤¡ ‹Ç9AÃ-¢§RxEÏ¥}Þ1_šu;Ú@ ¼5PèÜ«ïÙÙ½tÎŒ±j�'âÅ�nßé¯(µf„’² ´D±n]II³ŠrrÊ$F*µ×ϲFRéù¸×r»È6p›Çˆ\î¢Îc}— èâ‘ÑÁ‰ÏÕ”G«ªªˆ5ÉKêôs“ßG#ª °¥Ë°É¹QDÓÒå’º*aY„AGÈç…Zªƒçç¼³É;O[¼î‚å=ÓûÄ’o�ÐaˆàeñÖ+¬¡ˆÄ\È3ì(Ð#ãÃ[K}>UsZ_¢µ&[gð”0.ëlnl4ú¦âŒB³>l^£6Íjï‹)³‹gÏúUï°é¦÷ ÅRí^‘›œÏ†�C#€¢E†a%(‘}¶ÎcÇ‚4}oh'µ=å´QJæì”J‹¨P˜ ´¾1¶Äôg>*¯Ù¯Tº f (ryÁ_¼’ñ$R�€©á²³¶Ã£²>c<¥-†Ä×ZäZ.³QÖ¶n·ÔQ…‰¸á¶o÷ ©)Š- LõèŒ×_1~ÐýÑŒçBazLgă¼!'QRQT–Ëçû�¹Å•^ÛÈÉë[‰t‘óC—-SL™Écò“¿2Júé<Œ±›˜‹Ä£5K×(Aöƒ×‡PP�ÈÜgÄI£ “ѽvI( 𒤙jzn•󪪀Ÿ¤4òlZ!Z¸g,¶0 ̨·pÉw,ò: ™'‹›8³&—�_ÝGÙr‚û\°sÏg„ä*´õl >EUøÑ·'bÍkݥȓ]§TžjX¿£´ðk¢ìU± ºq J¤?®;¼bœ¼ßsÒr„â#Nk‰¶“^Ð…@`<ü’OèëÜ"{™fz7Ít¿pCaS$Qèö ¼M;ùbÌ\,¥5¦ £Óo¤ßÍ£À¸'Æ®&” £mÙ~½ýõ>O�ÏÊ‹+¬žëb]\$�añÑ<*€ªÁXú"Ÿ¯ïk™ˆ¾)Yˆps"èçÅ|ž�a›Paùmlt>$nA)É–ˆ´éòqèÙçû¹NÕjTP­ Lp‚çÝ î½ÇþÕK.‘™JéÆå¨Lœ8F¡Ñ”’+@ß‘»HšrŸËrí•(~˜ý|5l1ºŽ¼»]ËŒ!GG’ò’\ PNïé½û{ôïÿðâûÒÔ”<Ïî¦ÒFßÙ•þHÄs5ÄÐðÁ�4ïÖÒìöÇ«ldB‘›™Ëm¯,¿®ò×a`�Bèâç¶Pâ­ŸtW&6®÷¿Ä™cÏ[�DÙʇ€ü µ�$®]â‹#vù‚ÛQÕŒX%!\7tÄ"#‚åÈ�FŒàY^È�û™÷ÙËAôò)d¸A×M¿@«ñð‹c”M‘9¢5Ç0ýLZ�=ã1B‹®Är·3 uE’Ëß�f*??2V—/Z% * $cý}·ž�ú«ça72zšåæÇ[i³1”dpí&o"ðo®ÃDÚ#6Ê�ué`=’y½ã�ŽÑc¹¹.ŸÍ&2É $*lTèÏ��*�=ݸÈ�¾™mA%½5êDÖ0©LZV¹_,Цդ“Ý6’‰è¥+º™IGZ¡$V´C³™¶Ñœ)0ºàƒ÷�°±u˜Ó8à8I@áðYºJÔLËP\âh^GX:­Q¾y 5�š!pâ·�£íd'E� ã!á ’ðʉŒOD}E˜¬• ñ]"™Ò–±ÖãwýrvGL*% baË@ê;Í•rß0¢¬¹øC¥ÆÐ*¥‘ó_‘¡õ*Àqʪü0Š!d²BAd²g¡sH’$R2gœÐ¢ÃjiÑ……ÁýC�êp…”áƒ't›ÇU&`l<¼w¸-m¦/Ç¢f(––Ÿ\�*0’àØ ¹ô|Úq‰È°—bpÌÁŽ×‘rNµW»ÝÙ$‰±>þÓD'<ŽÁ™ 뻂/¿0üñ+´¡¨’L˜z&ò\ìÙKʆ…šcó<°"‘5c;¼oÜò×΃¬ð—Ñë! üüØ£¥ã7\éJu†ÓEˆ;hRŽE_˜“Ÿ“BÖb™Nq_¹úYÅÞ¼dO4ãÛ;‘ð½v •ZVãýƒ²_NÔtÝŽÊ=� 18 bVq°6 FÄ€ž x)À™±¸Åv„9e„-Êxëà®áø¬Ñ5KݧÂ,Á”>žÈM ¯É‘i~Ûh,UÞ“È,›µx…¿gÿÛ#‹/ÖFF5Š8VÀû mš Âv'~úÖMÎ…()«°8ïïm•�Ü¡ÌḲ–Ô:T>ò¬À`�3—¢/f"<€ˆÙ…²îÍÐS{Ù+WIáÍÈ$^׌—L¸þ”ZSë¶~áksT“iÔ)« ü=€�5"!z'8倞êê ¨6ÈŽ‹çæØ2ˆm*—4�ˆ*žüÁa? �üK´±“Z&Z IòóSyØ8]N‘�7ÀI“&ÙûúøG1ì@b™Ã ¹‘géK1öæ#A n ðt#$Ñä!PQ¼ %•W¡„ýžóÄß¡?I—ëÿ|Ø¥'m*;%S( ‡ñKs’>Õß18ï 'Ýâ�¨¥ ¤Ò!JjÂ`Ž,„€ÛLx°+ ‰e–»¤¾“C„…(!RÝ0j,‹ŽÒ=3|6…@'ɼ"Ÿ{ðwkU V‚(^~ß~Ó×¾—ARýô99}º3úú[¾'[{ú6’ÝQæ–{¢ø±.²‹Êéwuí’Úø¶ÁØ#g5\ÞŽÑèq¸º%"ϯë“ñòCö9‹ËÒª~µGd ó`j*>~ǹ&±êÊ7Ü4O§ULI¶ÃæÌɆ¨OX:bÓL>ð¸$lZZ¬¬±vƒðãéKë²z—VUU•²o0ð�ÆÚ_‡ïlV>¦0òPpϞы/¾8›EÞý1«Ò™ïE©ï¦ŸÞ½âŠë< Ÿÿ¼ ‹L-]ºTXVZš•*Ðâ¡�À uuᚘæü¸IµƒgÁOSé p��s%3²Ü‡¸©*3zÔníZZƒÃ!M€1*>ɤÝ+¨y±ƒ�«oÏZr¹„¿}—-¢`…ãô'Òë?«7ÃÒ “AÞ‚*rðÖm�‚r I.kè#÷àÓº(»5“•¡ä�E‹=¬©úc@a!Q1¡f 1ž6‘ød/kG^3%�T!\Ydšx‘g^T5s…5¬�™êè˜J ÉÕMçÛ(ï_»¸ÃÕdTý‹OvOB‘YÊ0üî‘Éέ ”¥ÚHéD‚§¸Û0ùnɤX' t;¶#|qìÄýž¸GI„� «SHøYC×X»70äã ƒÚ€éWJJXF•ñ¿¿¯'»é#ñ‰•£ðrà¼X¯Ñ¼Ñª+Ö8/ð£J\›´±ÂäXz8�РâŸl0ƒÍH>ŒÄG  }vÜQ.õÕ%îú%•¼à†pM†bs“j¦aAaÍNc";¯ÏD'ôÙ�íÔù%––©ƒ~88bQÆ}2šͽcÑ\*�=}¡aÊÁ/¡LNó¸¨E»ÇcQ^p–£­ÏãÇBÎj¾á”h€gì à ¡¶ýx__“«â;-žyXó�ð<°?’t(šu7øbˆ£<ר�ÓyLÕ­—Q£´[‘»¤^¨³}ç;e¥½»çšf—{Â[áN·f%×Û²žº}s}¯í諮ɳ߈RÓ+ð 9Y±öˆÎʬ5½üK_ò�„©•ˆÎj ¸y“SBxßKAB^Å&usÀ‚pXx^I¤¡ÀÁ}9ƒ‚ò»wv>üm�©“ méÇÏ`ËI—¹÷½žr˜ÔÅŸº½Ê Bx}A$Q/ûÚ·ÆËÌ-G”ÚX¹n¸Ö¢}C“½›ÊóÛá:®�áUD�홉§�(ªhvÃmT ›sT¦E›GS9Ž=n.«¥c um©n=>2b la`Ñ7ƒI34ªµ¨ÚZ±x,TÉÀ•£€aF;Oµ ÿÎK?uK‰,27¢üîr‡†¾%–(n9àº`9í…HÚÞþÉÉl†<2¥wˆn:«Õ Ïâød\æ`]^êœxá ¯G�ÆYpg—&Ó'ÈLxžë„•Ũ]☠¬Ç~?‹¨¶û§y×+¯ÐÜxÛ ÝÔk>xlšØ}º}²,Oé‚´ü³Q 15÷! D`nÖ&ªp;Ó4b2œlÌÍp­ŸE}lš´gúûäµÁï„54æ`œ¥HnÎFÁÊb§×ÆJ�hÕòÅ�==ßɰ^§¤Fåi�¼ÓOðù7ž\/&b ….‡‚#oªs ·…>öaBŸXT݉~A|‡w=ôÓ¾åŸùR¢\,@„À7ikÍM)ä±äI°©ÝaòžÁ…—´ì€ò›Ã‘>0˜É r!?dd’ಡÃ;âÐ,£ÞD1”F%«¦`ò Ñô›1ÙóP?Ð�ïS V2G_é *‚ÝV޼N»›O÷“ë|Øòÿøñ6&ÎÎÑèê]‘Ȝ㉙{Fâ3 ¯S>2•)ÙL`5ˆF½›Zû<szòŠ–˜�BP¾I±u˜í…†n=×ÿ’læCF¤ÿ*Ægg QUÞ7>þ77 ’MŸ-¤]B¡�W"ÞþÔ[JMá¶+æúFš‰µ† "To:xvõQ�7ü²>êÜÔ¬M¤¬ n�­°�X²q”p9àã44—χä‘Á«‘¸ö §‡~c³è_c(¸‘,ûô¬RoÖ7ü?{X� HÑhŒÏ»x„YÈBõŽÉ‚r/��ÀÐV€äe¡ÁR•ø}™]` ÆvqZ�êýü*|Ñ`¸­‘LfK0¦|”Ð#F&+”¸ñ´FÒ(´¤”ß¹ºJ9Êx+ê‹;(ʇªÒ ²%d¯ß�_ì´p–Ói[t<”.ˆbgy×á¶[NXKgp0H*¡ÁTa™Ű~ :Ð(Ñ�?Þ²n]Ö#ð$ ¹nt£îîÞÎÇ™;Nx7V3’£ºnìÅóÔ�b¨ä�B%.Åh‘LŸ®[{�|…áI-àiÇ…>gIé’ bî–�©ºs¿xë`ƒ�Óå>ëóÀvy×6ø¸@"G•Bð²ÔÍ U›N_üº¥ÙSáXÆ2»¦ßr¢'©?N‘½«±i0PëÞ Å4Gßz¥7®ÝËX‚ FW{}LuÆ ájÓn‘·Ã{ýè)YÆÎû;ä>�\4gNô¤ …Až:$Û'§ßñ$%›mm’§?F ÈB…$%¢£|ŦËëÏ<Úí³ìß¹xòZ ‹šÅÚ¼4_ ôwâ$ŸÀ7K^i6>‹z¥pDÓô  ò˜cÖqZg#áü<ÕS· ¿ð{ÛÁl(ôíØ‘˧þø!ýã}Y|m½Í7ì:ÿ}Cb¾ËÛØïò{„2¾kc­ÛÓ©ïMiüíUeù_¯)(»hו5ûçV”GQ-õâwƒØ^�Zû›£#ÛGŽ»|>h=2½¨{ Õ6í9|°§õæiv>‹Ô˜M¬•ds�­TªèœöMíÙ�œ|ì¬BsГcý ºd?„U÷+Úà¢C„Öu ж¾ˆñæÖxCs$[j½19™HÖù"k)~Á‚ë\#z‚@u†3 V´¨°2ˆd$ ·—‚kÝNî+zHù–½hÖ�/÷GWBöÝA®=}˜vw•cýœÁuÓŠÞ‰Û(0¼Ç3‘x\EÙ0o®$B«W¯6ÔÎýoŸrQQ ‰ò¡”ܨª,J«]M1–pË%X|ˆæ¦uoëpMá†5ª�÷?eß*Ðpfï½'L¹}Áx9àað(ÌB,®çä虊ÅèÔ³/´±ãä¿Ruù?�ühEâ³�²kf¢ç­û<ƒ'ËŒ³·B7À pP.T˜Y�6Ú]ˆ?œåXÓv�\IÃTGÕZ€Í–;§./�äÉïuÓêÃ(@èàk"OÝ>ž¡¾ç´ ßóûàb˜ Ö¸cªÍv*ž>Ýw^dTUÃ| ¹€ûA’ã7yö6¾7xÁØÌo(šµr*•nÎáÙTÊøÉáѶc'7?RÇMÏ­œ}­Óß.ûÄ�«ÈBL¨*mL¬›ú›Ûž|¤¿¿S®¥{‰_d†ÕháøÈ&XãÓ×þȽ´…tYþ�—�“ ¾¿ZlO¿2Ò#”—ówéxq1ìLŠ Ã5”M3—æ8a¸g²üµ±0 gyƧŸãƒúwFQÜYu`fä €‰9$v>¹V„ú ‘Y‰*ÎRÔVxE£RñÆJÂNÅPvÃ#ëØþÒ�ååVÀ1ݾ a¡8TR©8w5¬äÿø‡nä&’²™ýë€]E傞u¦&”ŒPD Ô›Så°ZÖ¼4ƒ§Ã[ �òö¿òÜ”³¤ 3ÌÛ´­¯A¸ Á׉EÛuCY¡RÚ¼¯|ã–Ñߺ\Éæw *5ÏN‡&£nÈûÐ#”lœW_îóu‡Æ”G_×¾°¢N�…ë�Þ=ÈWoLŸ¡RHÊ 3Á¯ o“r&U³;ï ¨¦;�b.åxj°°‡`8?|m£+g:̬D5Û°T»é˜òT V‡3ìgQ•vV›j¸ÚõKʤ#¹'쟓»ïÏ›ä‚YÕ"ïž‹� ·§øCBY4ÙH øÅ÷ß§Qy2ùv<ƒ*J€Îƒ�ƒF†¢¼†9i  Ç@õšy(•žc3©õÇ¿€¤Ê\—E »V8&+aé€p™Y…g Ï9‹�.«�‘«~é²²H_ϸIL/O½.±'ðŠcØ[aTúµŸuŠ–ð›y~˜TÍ‘g2}L¤ |ÁËšq[µµwŸàú‰k, —"¼Î´˜’L.g?v,O%9j@RÀòË^HSeˆ£ÏAUà\¼ˆwÃò~&“6ðí7N%»^~øð¨™¯ò±‰¡L{­�Ú� XBÔù(…ž‡ô]±b0—Anä /6:¥ÜñÞ|×`³Ã¡Ük#·IñDgtF‰ŸÍ+‡Y§Ô�F%=ò«ð�ÍÍï¸çé{ÿg%cq�Š—ŒÙ…Îâ^IVP¶»G-*4›9¡Q jšÎ^&¼‹Z8!?)®$Ÿ¬÷zåCÁ°WäE%˜šSÜÈ"˜»7“˜¿¿˜¿Ÿ}Ó—1ªi”z¦-=‰b¤|¬ýceN¡}`lH½×�½_¥ä ›ûðÁ]ƒ=›ž˜0³&}>if.g Ž î£‰LÍK�m#$qûvd‚Êì3áa;$Á-wÙ ÜðC=þ¦+ÿÿÒªh,q$'Ç}@–�?À„z¬±Ã0ˆhî3Qh¾ÀdÍ�a°•p^»Ó)¶,ð¯Bí{½ Ù÷'¸xÚyx| «—êƒ59.Û<à%IBaœ·@, E’’�á¸on…¥56®ß�ÏœZÐÈç±Åµ!‹áY³ ègS•›)ª2+˜Zâ/χ¾7¸(ˆ)³Ì¸o; úÁX�wgq4ú‡€" "}2×2 ÈÂsq é†p„K�u­®­M¬®]Ÿ]ô^„Bgy>îr™%fXÝõÄ_^QÛ�üCGÇ$Ñ«+w%<£µ` <§Ím=�Ü7¹.4�”ecâN;|>:J |e Êö9�2¾evLÒ›êó߸ãä=99³`è +:uNQ8P¶+d\’ûü¨‚ Ô@ÄõêhXß) S”¦çT�³6Gg +¥jE´(‚Uº¶l`i`<3`á]Œ�`cµœ'š�iBÓƒ¬õè6aœÄæÏdd2ÉA—ÍIéI¥?Íè圥'|¾D&ê­`p;„U—‰v,¹@IDAT¬0 Ê$ÅÔÖn[й V38e\aÚSâµé敨–íšSPD»�~®²Ry}hÊÔê¡hÏ™¼—¿u­÷…ÕŒ°…eò*š3òÔ¾7ÊËcO&¾ÁA!P³¯£ø"4%WÂú¸ÀîIXÆ„äSKJ]@/œÚIé»0§1! 3¯¥ë L÷JÓdz•TæuÆ%TCF¦˜ÝOX–v/gú_h~—–ÛÉdˆ›››S 踔ÌR‘³%£„ö!†‰cÄPß&ê9ªp÷êJJÙ“ÛßÞnÛ¥”ÛÎËG SU Ôj)Ô„¦[/`Ÿø÷X*.ÁÚœKü�áîØ�Œv�à`Fì�A®þi@æâ¸“WP�º[ÖÕv‘åï@�E£Xß=UùǺ¦Š€ìX ÒVEìß>AèÑyëE@„ Ý$9F¨!þî„':wwÜAYP¸àn—ç¦. ’hZÜè무r—?×¾Õ…2ðÓ‡,Fbcéá>$°™1ud(ædt{…$5§%˜9â{³¹ù� þ6¸˜Ôor´Uƒà÷Ó‰‰þÏ]ð.ÅçGTŸa:ßÄâ»H6ÌŒný ÃQE]×DAú ’š—@U9Á²•™a™îU�쵚õ4Nï‹8\NMÓ‚bTzSM'ÂS“û�¡ÉjÀ¾>�’ó�³/·±<@Ÿê½I�}úZhæ=ÐñØÚÙ.‰ñ¥ÒÌa¡Á×IB 4Üèg¿pó\”—:Žì:^˜LZ„d©‚zAÕÕ QÕºQ¾Xé{Z¶þé ² ¢_è�›‘9Ø}6`‘Áƒø»Ïü§:ȆS»b�¯gûæ0 «ì /7à `6ëÇXN Ü”Ä:”8F@ OÑà$ѧ·ŸDü^BɳH—vz·±=á¢\�ý´è¥dcöiC˜LŒCtôÃÜ™¸yrOþåk—9³¡D;Ü©¶`lè…é6-Uå­UðD1LÙÞÄñ­®yÍMŽ|…å„—`ɹ™Mo |ž§I›w"sS‰Òþ1F�n&áŠ3q/ëïKØ�¸®î8:¤;&ߌÚüäj¡ÞøI˜?@õPX0©Õ°„Zyƒºo,9‰…³(zç�'¨ +++É@·šñCbP•‹Ï¾ ƒáË0µJ±`—YUŒs´ð’ñ–¦±"i3SÓâo¾»VŸ|7^4ÛqŬ ù?ùQ¼ÜÁ‡Äœ’�$2qÕ@ÊŽ¢gÀ•rƒTºÎ’­ti•}ð°öqÝJ¿_]Y,fbg/PÖ”äu*,Ûîà�ŒnDX-=lÒÒuX"¯–w6¦å^¼~/*¢é�ÛQ.ëFYѰrp´ÏÔª aŒ¹¸KeôGª§d$ÔŒ›à,}ˆ™RˆYúc[¤YfÙ@Hž:œk�l¼D58æ~úæsv“3Þ<Ð7¹¬æ/„&d Ö]\Qß“JJtpy|ãÍÍÙ…êX(Z‡Ðk�Mä€bζ‰Ò~Ð…]Œò‰ã¸v â¸uv§=ÞSŠþUI¦fûâ3‘ ˜Ë(©¡¥³ÿšï`ïp¿ì²;ÎC©ñ[�…¾ü»»ÿc ±hjÝ¿Zé—ÝμÅÈ_^…ø¬&6Õ†®[!¡ûi�Y i?¨É^4 îϲ©½â0é¶e³g8U‰ ¤EJ™Ó‘¾ïg’|Ò->µØ‘¤Ï¨àEŽép0ÌC �Bu–µ’„Tâ%5ÈŒ{AÔnOebíCÏ=¼¹¹Ù„WÁ_Y»V½° ½YŸ—_ z==zó§V¨�¸Þhd¢G¥øJtó%à“®­šÑxúõ—ßBÙr–¡òJÚøQ;d_y.Ð9kìE%ÌîW^Ú*K30d¯‚ÁRd¬Z¤’Ms ã´‹T±©™›�ðÝ·÷eK37ÃØ9‚ñºAª£‘þžƒRpìfä´¢-È*­ ™ÖäÀ»¹O>È~B�™_P¶ÉT ZÌq@Q ÝN~|"�{ÂjÕªro÷˜¬•�>—å¨ZÓŸ{dïÞ­JEYE!mšK ½:¥Yìn¨§p‘¨ó»ÅÌTÁ'”é y›/ˆLo‰µ d!ú�ŽSI—ôú3 «öÜ W*µ¬A¹±«z`Î} XÝj${6*šq÷P�ë5  âäZpãݞƅCòñ8@8€‰¿3ubª¬F騖ü0þâ†eøéX’¹Öc1‚ïÕeÃ;Žso¼-×-ÑŸÝ<«†ËI+ë�ßýlÀlWÉ]iÃÜÅ :U0EÍv?þ®Âœ'eÌgUV’åù»sG•GLûc´��@�39š B ÃNCH Û¢°}Ê+/)g”Çà¦ß“Ñ©ÇD‘>;ö(Èrß4UØWþSЭ¸±&È^›�ƒíK§û`%_‚çk¨®©Â:‡‹_�H4où5Ó0?§z\ÎÓoŽ­_R�ùj<­~Í·*yôŽqªø-‚�‹šh@èê)P­'{¶%Õä(P¿  ‡[ÏÕ Š¹.*ó _‹ÉUHÚÿ㋟Z¡QœâÔ¢7}=bY ½ ¿#”²Š| /ÚH^cðì-”¿Âé¥kX“±ƒ4è,“×aIF¼2z4Ã=2�þ‘ÚýµZè|.°ó�= �ÌÀ…Þq­÷²ô Ïp_ÔŒÆÑ,¾‡³aÓ2rºþ”@1)À„¾ã¬ï¨,»Š´—�’1³­/8£¢ ¼Â›çý²ä.ÍP# /ÎhXŸŸßdó2øÜå¹’èCØh#ˆ•œØ(¾5k᪠¹¹ç¯ÆÒt_|T^yQ7ŠYçù�ÍͶ7Ãá�ŠÁPQô߯Á…ò#0Zý‡ª³ÿ¡a�£÷é?õç{ÞÜ\â=ZÀy †W:ß–Ép™rN†IZeÔb3•Q¦2ôžõëÏœÕKúž ±^·ä¸¦šiŒo·®We4éjŽ™Ɠų£÷¹Œ´Õ¡%“Aä€;¥•õC?² uÉ3-S�Fº$çD½F!B$Û,YÕ>üÐÃû¶ø’›£tý? ð|Fâ~„ÃwÍ¬Š‰á©þV�Ií¢5}KkGèT¹3YÈ9sû4â^œÊ#;Á.]Zšùf�{jïý÷O²ü0vàÿDbè?Q µ TµZ’gbðO¡@Ûiž¯1‡‚ãrzøÏ#ÿ Vîi#s¿CÈñ+†v¥¡S„…ñ^[’zž¯+ÙfòÌó°,-¨:$®A°9¿¢Éê÷ˆø(Š)xä6N¿7Ên¯Ð êBÔ?œN¤]Ø'²¾¥–Šk‡ZŽêŽÔö¡Î}O'â©$º¶æ R Ï¡¤ÃÎNÊÊl b!C)–§Ò«ó2㮓ðµ÷ê“æ÷Ê|¾—Ã@™ÐK(Oœ£ÉdÕ&IOFFþÔWèÜÆ˜‘Al’à¹?®9?Fµ·³wÀ¬üæ7ÄᩌY:Õ@ûý¢¿­M¶x.Žx¾=i³œþ¡Ã5ß>èþ áM–ÑT„LR&Gv¯Ç4Åê$×ïk?|\UÕ-ýTü­æ“ê7?”VCÆ Iêæèª6‰ ?¨ =šD ‘zZÐD8>t¸Ù{N>ÒÐÿ׃Xš#ÉA¸:×aq@¸–Ú¬§Ô§ì¢¾­¡ÔO*ÄNP¤õº:Û �4jË’¦Séð–�ðS@” ¼u…jØÃ€�ƒúÁ<¸ó]Xže%îqq²'aÊSçÛ‹ø#+Ì0ešÀoy ˆ|Ï!{XÄJ‚¹H,°©•þìâF\ÕǺ'šì,»|(;‡ün2g¬îÚ6ܺѲHi4™À!,‘ §óXÝÂ…žƒ�܆ÅE�ù*Ÿey榄¢I,Ï-«àÅþ,‡±Hì¨ó¬ê~OªLFÌNÓ ßþÄÜüì ~2m IvëwˆÂ\ˆi†ä}âEÈdA6ƒ),Éñ^þÊXb+ê‰qœf“ü#Ø%ŸB…Øh"š‚ÅzBÉ™Üé3¼¤©"’?¡Ô—‚/c"– ´õF*¬°À€1Œ½ªyk&Á™±Åpù¼`‰gÏ^�ƒû:©®fn\´h¿«úÑ@GÇü’ºŸ n½ÅR5ª €»oC î™èîÉ�o}Ãã==ëÌó�Q‚Cx0¿¼p x!^°¥w‚<Þ\ZÉ”ËQýà�–•�m“öž~8,àd͸ lúeTŽ)s;¶ïzÃñjñ ”1·/t5¬”\cä ´IÅ}q°Æo:z¶¶Å‚ÖÍ7ÝÖ�Îè5 ØÝ^o¡¤Ä·Như¨slëÖ­ÃØ|³‹/fâR„Ú›“ø·Á±¶Þ Äx+æpšfüÔ`­o„’;íÞœ—©––~ !ŒÓÛøQø÷¾Þ€èÏ…¬¡¬J¦b z=Pn²Çð(®I&"yí d‘ÝjØÚ@>ŽqWëp‰G×_‡·a~þ¿ï«Ã|„ÞŸ à‡Ý? Æóâ‰C.� ëÂ8úÒÄü>ã]Áˆb[ÚÒz¬5IÈ`àv$‡�ïή[—�×ëOð­[‚ÔÀtã6A`áÖÑp‚å9™*}–ÃQþæ½÷l_oxCY™¼m<Fñ–�J'„éñ>ýÝ3ýú¾-¾¤á†žÙÃÑŽ€Îè[’šù‡‰áT÷‚¥¥| ,~}à H?g'CPOÆ}v7⬖åñ„äÅ׳K[Žì_U¿è—¬aÜ}¶9à Ös¥¼lªæö‰žÉõ뛳ç9zt('QàYlŠô¥°@×`â‡EËÜÉÛ�DªßjÆÏi^üÿ„ûK¬¦¦Ï|iµjÑ7 c>ePU×�ERB¼hÝ”Ȩ̂:Æ×2QtãN?¹È¡°€ Z'³áSƒ�'È(a;d~˜Ëâ*�†«ymâ]{Z[û׿V ÛCóNŽê@�‚ûþlk®™�?ùÖ@ô¾¤˜Qwïˆè³æçµ:s%Ö®c`<›”,æRFµâ!Gq1`dupëºz–“ƒÝŸ›?“”l¿ç‘½¿J“/èí3Ò'ÀÁôˆ´Ö,f{!êv£žRžÖrØ"-&WRg­«PnðÊàT›Q×ÉEXû}KË‹5u zAÍV èR.V—}¤oøu€îê� ÏðRv!-g [QuN‘C½þùA*þ¬©½ôìóÎ79º,d°;1ñ‘T¤i¢0{:c\¥ÏÒY«ð¿ŽÄZ†2Ø?ðÔÑûo¹%‹õ}‰ãXÊp§uó6òà°1=íªÈÛrem�üú`Ñ5¥,?öq¯ˆKÇKÁäôÜzQŠª.²N_,àR‹XlXÍ0f7ä7\ßàO‚ÁëÙmð¦ã«;¾{Õüâìg@û(Æi^ÂÎ^�Ð,mYQú¸Î›�¨ý±±í]G›U‰sM·íݯ�’ºz�ãþ°“³Áßð ÷I<÷Z¦í£+ÅsÌžåÊÌüÌþnÛ©RÅ;O;8ŠccV'ÐÝ(}Žkô¸iém:+îºsnm¼#˜žKó\O�K¹­ÖkÏzä•^Iž â�ÒF?7M~aïy°>îÓTú ¯�E¥X,j7Ô5¯G4ï �Éqì™a³ý�îI"v'3¹ uÿŽ[ȳ Œ­cXM9E޾±øíèW0ûsXuônÆ„”ûY~Gnðò/ßZK§%ÊïK~¬(oäíážž55¥=å)ry£SÉäq‚xŠ&r1/í.;JEaV±Ïß³¤À[Ô:nË)/w[ñ,Äiu•Ø̶¶êú¯4Yœû’ñØÔ,(J�n~˜ê «ó `?™.a-”W1·cþI"¢©DŠ’MFTM¹=ë Ýnl<Ïéˆß‰ìLÉ ²ZQÞT• ±n¡kë¯}“Ø(É:MaR–ö‡ÒÅ–Ü$‹MA_™1­…Rý½©´ òÇUŠ™o£­åèì­³ ‡›OnÌäû¥#Ç&)Ÿùº:M÷o}øç¡;îh6£i¼÷ܧ@Ôႉ3 b*|lÏ ˜ë>ÀŽù×KƒÝ+I¢ÏÌ@Â.‡H¿Ä¦Sír)Êá°çs¶™Z‚)MކÇò™îrM2‡É3½óo\|R¡ LSýêñtlk�óáÕ­/?Ÿ9§læ9(šÁ#7ó2œŒ°m’œbÐREK¦Ï…nmïµÿ�Óž‘·ß×Å·YåεÊÄ '²_òè3€¹Ô1åƒa-äA¸½Ã}Ýÿ²r¥íï·9³P8PÁ2öXU7ó7ëN�Œ¥‰–z(�p{Û—-kP‰õtáu·Vñ¢ý"–£¯Ç„À± šz�ü;­]{­y~Þ¸½óŒä¾dx(šø�ß•h~פ"‰�)gÞ o_…©Ú¶­ÿ¾rF^{­[LÏEþº[œÝ`™œ«êPöµ4ú)T�¹]ºY0²wrza¯Î3‡r¤‘JŸóè¾Tp__ϱÖY:/»¸‰&d†¡Ào€ì½Ñ“LŠíQ½¼{*Y½µÿø$ÀßÚ¾‰p=¥3ß@Ñ€!k‰'ŠT¹mí+sìÀˆ¡ ¢�oç™f¸Šà ¦·¤ÀôÈ ¥Ú£ã¦ôØ}÷°ô²‹ÎôßöEÒÐä3TZ?:03¿×�I‰ µ¹�ÄÔq_#ˆA«`OʨÙ‹†Å|R‘å¶Å\Í‹’-“Òó»ãiQpå~åÅQ˜ çÞ“H‹)Yóš8`sÑC©¡j¿¤Æzׯwâ6°ò¼}âï%ˆFôª]ÙŒž„ Îdê›ðz»ßì9ÖA€ïä|ƒ)mf"­ìåOX4È0,Ï�@z¬¨µ˜b��âiEQv6íÍKŠ‹§,ëÎì}�ñ4¯ÙyVÌcJ[¸ §÷æfpÀ£IÕÌ[ž6µ«Ò u!êÖêx‡Ô«(òâ2sÁ!²¬s ‘Ü\Œxòf`öÜÐøÑT® ýG²I§™Å`Ç;9÷ÆT»�ÿÅA>ǤÊc&è¤åøñKfÎTHqÙ Éw¿¾aƒ>÷ŠO;zàÍ=î¹�KrNá¤Ó¥qK%XnÖ¬<)åD¾óÏpܱ/£Ï6 íR {>Ç·ÊÜöQÒ6ÂH×;×�ŸK1J ôîR§-HþÖ1˜$ ÁóÁè�''KÜ^±ÖÅ÷÷hžé Ùi¹�ñO ©t AÀ_€”ænÊ0Ÿ1(cI©#ŸÔå³mpÁ!u o 1ñÌŒ}}}bë`¨þáãeâ¬ÜÒÝŸÀ8«O×ÎÙÙ)ß5[‹÷€‘½¨ˆÙXÔ^læÇiJþÅ9E9»š›³±ekd hGÖýF*ÇyÍòÑÑœwßa²¸Î uc¨CI�gc"ýøLÖå„ocubÞâŽ"ð›ÎM°�ÙŒx‚㽈ÖoˆÕ̽ró±Éšé„‰ 7!f}‚þ¿8É�@®Ù1|¼‹ê/@5‰Ì}9ÒÇß©õwaé­s ~¸Â4�Nç!ìQŒpó._N^ï 89¾n¦FGé‰Öìf÷zDFA.B¤þLp%/|>á ožsÍW>óX×ä¼»6mšæÍ ®®ÊŸ›ê}©(=4@Îi“QU \M¯eô©Ã‡;â/ m RØ& ‰ lPQMgZy²¥Oð(íÔ-öZ*c]ÊÍYë% =Ù´¾ÑXi‰š/·tØç9¸íÀŠú¢0#'Þn±gÀOY~àYᬠбd,ƒëݺSŸÜ eA‚ñe]ËLÎðUToêÌÙ€„›K“ß Í𓚬ß«|³®›WØ%ú;FBù2Óg‡´ÌXgCña�­dÝFr.rœ?ËÎÔ7Ávµuàŧ{É3 ï—\uÝx —BéZøC‚κÙJe–R‰g,û#äÁ·`AïçI;mÿÅèìüì¤$ßý(d®�ñ@~Úe¹üí`t>Id^^c„Sö.ØEÓ ;µ© ±‡w$x~#H�f?ä’ϬÎÏOÑ"#ƒ3Á„Õ© ÿÝßÿ 'óƒp ýp�O(Xú®ñøÚ”i¿¨$‚™ë1Ÿ�Óæ>A`ŠÓí9\Ú¡ú•�ŸIʨÉßFÅd˜ãÄNzÉiеNþÃx}ß_r Ï-ÀyT“3¿Š&‚Ouä:Þrç»7�Ë@1vÎR„I�’–)ý°ÎÐû –VjœygSvñv"\¼jÕ !Jæú"ocF5aü&ÀAß3׿âô‡ç8(Sʽ™DâGžRïK`¦'.Evò®‰³ŠbÕAðzç™× Ô i×ôáp» l[‰6öKzæðWgÌPû�`:nÔýb®26‘›#MU#à ®¤ƒä=â7—VϽ !÷;¿ù—ßCÔ½T╘)ÿX6õ'QiuÄãHFîæsC(‚šDh"Ìn–N¥ÿòmˆQºr!$2 3â('èm5r‰éŽ�C¢à;0–¯„E\§ÓÖy€¼}£aaó¹ˆÕJ'Ï‘M2.òû”‹C)ÁCb<ÒwÍÌ™Sw¬«�û»Ô Õ‚D\Dg^�Lq,ÐA-8¸üm¸�ïëçEµÆ%�Ÿ¬­_¸íÀDÞëçWÇ_¾v]¼jù¬uAUi™š …frÄÆ9ÎâDl©“ãCÇ�ô:±øÆwå¶Fâ©gR²ofúö/¬\³èá-Ï… ¼­½¾  µŸÀSz ;�Ÿá¸|Ы=>|dwè²v¿ë ±½ó± ·Vy�Þp¢b0[B ÝÅO)¦ÇS¤¨D„Gi^nŸSZà‚ÚÆn1aýˆbÔçŠ??ôø=ïuîw]êŸúW y€r´¯ÔY×…S¡ôKá–¯>Á]€8÷Ó¨4}ãn>ÍoÉÎtú¼'Ò¿–ª‡¬Ï›Y^ž5>þÇ/½Ï óc,Ã\üöxt徉øâ�ÝøµÏ";pÿß+ÝUXt ‡ÉÓ0àî†7'Lz²ª,ÙV,)q…=Y(YeAê�óYä×UUá)£ªÇ`«Ê~øCúϲøâ±eÀ…F×H3¬Ab•DÉØÈ%Š. …8’xaíf«¡H‹2â“ `§'[¬î#é�×^{¹sß…ØÁIRÀ†ÑÇP­ôŸ^:÷÷k늻NÒLäÂÞÑè¼DйI£õÛs)×Ëh²Ù×Þž]<§ûØÐ5l]ø=bJÎgÓ#¯zTz(í8ðÚ¥bHöãPP¡Jž� Bdž/ë6~SÏ„ÿwÇzˈEO>wúAîßv°domØxª w#äôƒ�1u3,¹¬+ LíLÓ,3ÌÌ€ááû4±2ñp.”uÝ�j³WcQ#K"S¾…iʼ`´zÈ5=ËXÚo¿|ÐǹÕUZ_ƒï3{€£~²µ+�(ƒ�ö\ 5âÎZÿá5sk²*y� @@Ù­Tùw¦¼Z’›‡Œõúáh4 ¨‘ÕC!XʯÃ{¹Ðâ¬�Y*wQÈÃ-yº'\òtûD㜊ù% e6£h×À2†cÂ]—†òÑ"/ûÁ·›}ÿAyªÑ Ê�)M+ ¨I—õo/;ï²³ˆ¥FÁ½å¾Ê~æ‘lÿ…Eón}"ýçóšVW¾ËöÉô©N½’çF¾7ý†ñb°r™D9š *É 3ªéÙÈ=ƒí™·CñÌ•iŠ[7áIÖ×”ö.‡¢Ê‡•HšnïûñJJ©!IR ßB„t@µlÉvI’ˆÕ˜‘ãcؼ둇¨à³z÷øTýöP¨¨%t’ñE>ó÷Ú8À¼Ý–´øàßýìß;ÏÿöoX|Qa�Q‹ý<³ï£:íÚ Ñì.]5ïC¾æ?SNêžÎ®)ŒùdR‚*ËÉsÔ’lbÜÑ� ÁêÄú[MÝä¢ I‘À•%øšþΙ~}_c¾Ó�IÁ¾¥,(3cÝC}ƒ¤‚lD5jÀg[­[ôö¼üœ.ØûÆà”ÆJ"·Zcá ú=¯ç–å;8ú`į&ܶ=¡T l.b,4æo­žÜãKfS§\¡vÐW:„¼‹@>súöp7Ó*Ï¢Äð÷>Á<°²¢"»ÐM·ïð@œ¢E¥† Ce/ÌÌu'¦ÿFäg¾ý­óñû�BCîåfŒ6¢M¡„zÈ{nă5-íš®ì9üdw¨ä¬’*h}Ê×}Ý™húÍ':SM'ÈÂR}vi¥UÄÉ–ø#ÈL§—–��®&÷7—Y�¡Ê ÍšÅòl‘Á¡þ(0¹ÞÂÙàÓ½�¤âÝ,ûcefÞ&ÔAÄ[‘­^‹ãs �øÍ[w÷±æâ¾²œÌl�c/FEÓÞþ�£; /7éÊ\Z¨E‰Þø�Ãûõ†Þ^ë÷ˆ§Oß+ymÆ}‘{#@ã9ö~9›¸aÎe×@Àå²Ûh»ivÊ4} <¿Ã¥ò lÁúf,„ø(X“¬EÈA ¨B{1xänÕ[?¿‚ÕøR<ÏjCIZ5ß5�KdŸ%ا¼4—wyZS}ð'6Cùp…/ȲÙh•VŒîùSçX•]È÷‘ÐÛýýþÞc£zIMŽÍÅŸÇ&d_ëøÐàéÊÑÙ ’sØŠ!}ù*ÂUýáTÑ0­ÅYS¸ÊÁÓM ͪ°óôo'BúBÓ¨(Ì·%ÆìrjÉ}§Ÿ‡ôÁGù ch0šÊ¡†ކ)%%�¥Æ_¼ÿ¿ä@Šò™ŒùÿÁHXDsÔ<`|ƒ¤©I°„9l~Á¬¯|ûûU#±dIoÒòÆÌ´Ôu|„G-±ET¡IŸLFuŸÄW@P%³v¨'z¦û ‹/5œÚÉп†A‹tâ-sXþ6¹*¼[—8ÅÑ:›M~î÷¿×÷o~aгwx°Û_|6í,ªAu:½ý|ÃíV¸m“Ýñ” ˆ‰cŽÖ¸lÙ¢¯c | ‹ï`$0Å3R\–•÷ïD‰‰äà%vÐ¥O9„lLq‹¦š–�ŒT/eôû Ü]ÓI·éÎ(EaFw*Â^ rüSã‘Ñö‹û³;ÜÀ@Ô{(m,\í:HÉ|a�ă·')óŒ¢mÔJ}‹ìvaËVÔí?r„«-(ÐÍm›å¦²Zà+Y�Ä´Á �¤ÀÜä¾õoÿ½…!ßÂ^P»à�L(þêúâœL ¬aÍF‹Ð|¸Ç÷ê2õ2”äÊùþRÈøœŒñ-`[hj¼ÇVá�H›=2'øú=Vq»�±øV‡# ‰un˜=›"ƒkwH³ } %’„æâ°ðÇ,š»4¡ªŸ@hdnZ³ºVx|ƒÏî ÓÄŒ lb õCJ޵߉ÐFs%euÆÔK`.™ãÙisÓì Hc܆…q9£ë¨ûÊY�råÎ@¹Ø¡-/ßÍTFú¨'  òïn-¼´ª&Œo*¦ê‚Ý2‚«RjŠÏèÌ\‡‡%‘Ò? òœ��í8kùZŸÊ —±´á@Iw*Ã�œK¶ïÉ4ä*¾Òå_Ê" ÷?ãrMÊZ �烕lŽ«:G²‹tc$åê¼eI}zÔȃjHÙyÁƒMÒÏy<“3‘êJ¦V…“© w¦bn^Ú@A#îbÃŽ eMw:Õ´ÇL‰~F¼JÖø‡>;»"HîïÿOÇp:#X³ÀÐ /:ÆÕïf™èFô”¦«ì•(wo€™ n2«÷½Õ}gƒånø@Ά”ø¨½,‚#Ød9M`þšÕ?÷¿Òs¼¯hlÌ óËk$#MË610=6Îdß%{"²Ý³X¡­©¤üKº"ïðJ�.†ñ†M·ðúïüÛÂ*§0ø^ãï#ÍbNœkš ïŒ»Ì-í¯z RDæÂKú�ûG;’X NÁ€Ø/ Àì|åÈ[”é†[P«Ê%:нNÇÅÈ|Ï…*Ÿ6´9ëƒñg7µ¸ûáŠ*o齆bååÛ¹a¬vÈx[Y±¾Þ€'v`ÛÁ׆·{ÝËYZaq~Yâžã.Á?übãM} R Š£ŽPð’¥jopý‰þ͕ىOî�X³[úc v_ )�é}²½=œœ»r“ÍÆ£bíãIZ/z¾/4¤”48€Mý º,ÌÏH¬µ±½©:kÓŽ¬Hä¹²n¤ ÈG�ÈEðnËÓ ÐŒÉÅ6¬«CIK Ô‰¼aOCaÄlEW½¼iS€kÉZ�·,=¿ñ¶ñX)Ͷ_[—o2J�D¶q±ÚàGÙŒÂøV SÂÌ _†Z ²öß»}B®M6 ÷³ìlÇåµ�(y·(ð  �0� È¡¡@@wæÕŒ²”˜…D0àF”X«3Ú¼”¦®–šVÜ}¨ˆï8Íe÷8 ¥Öxo?VËøø‹†.TI�ëL£ÃTR«ñØX5#ÓIlÐ.᪪Â(å:’_MQµë©M}}}ö9g!|þIôíëvF:f…ñúsr”‡;;ŸL5ÐÌ‘sC«æÑ¼|]½ë®Ÿ1ç|î+åvšYˆ$íÏmÈY»Ðâúfû™l!©i×¹û>ý·�Êk‘芅&áªÈ ‰/GÑÌQ´Ý8;77±s,:ŠÐ�ñˆŸA?ZXÌWÐ……x†¯´}5. (BÍzX•À¾ÒnVÚæÌØ±´ÉÞLÆdе9;S&Utæ{„T¨¾=y†Hhumavã$IÆóoøN™[®Ð }õ¦]#{×£"õ=[�”™]8ˆ{ßœö–ÉxÇ8xÏï~Àoþ˯C¢‹AA8 Ù†KU×-X0…6®tu]‰@$ÞúŽn0+uÕ´9�²ë\ð:ÚêV&ïCA¨!-Ò¡„gaËl…ýRot @+ųW?…÷Ag× BòùÓ´Ý®gÌ¥¢˜; ›…4Å- Û³ù¨Á/yèœsKQíU¾—“YxwvªeΚÈ‚ å�uí-‡ócý#JÎì_F3ɵ¨ž«ª¢V§q^ 1ÖߢTúÅÝw”6››AJŽM˜Iº–è0gPp% |   aÍmQ-¦Û!˜}Sý‘Èá®Ê—ø .ÃçZ ê #èf§55uUjîjñ1¹ž«�4XaŠV>Ô=ž]ß%Ýœü��¢„ÞØÖþ§TÆñ*|ê`/‚šk‰“Jè2Ë#žŽø èûѵ–EbÝy¾Š‹A913£©‡ÁœÖscS“þ|_xâx›1ÞÔ2ôÀxàäMærT‰Üé‡PCKûqªj¿8ˆeê'ôâ3'‹I¦»/Û�«ñBµ_¤ý®»¬¢‹¿œôÁÐÂ,uæòS¨$Ì£\<ä–ݯ8¬BS‰§ PŸlÌ’Íñ‰$Ç~R¢Í:—ÅÞÝìÜ~ëIDÈõ ¨àèÀ–µ  PåeÝf>wAAaʸöF¿u”§Ð瓬“?$Œ�£Ë¼§‚ó<§¶+&eƒ× úyŽ×h; © ?Ð:~ŸMÔ�Z<_®fŒ$øvô6ä·ÝAågÝé»°à‚†�Wzz4GCÓYNÓòÐîhoû›Ó¬YÓðQx­Z!�¢ÈSØyÓpÆGÌdc¶vLDw@ü¬Ó`œOHù”lLÄç!™|!6÷› T­õ´Ó-ñnˆf6 |ý*P}Ö`³_„jC²ÔFÔd<@;rl²�+ 8ÁsÆÒLïHtôþ�@×C½OæE{t²“dÓšÚ9Më^E^†²Ö"C�¼}~Ž$}¨ ˜é>ù_¾†ÁpÂ.Ôš§ ‰tò`4ú9‹ïO!G÷˜H%—K¬ã¬vJ#·”#ù»2Ý[:‡s‹ó}y†J盂Ü>4î©ÎŒZΚu¬Å|ñÛ:p ìDíù„�a—Q,ûU ZJ›{&6]X[H¸'ðÿì¬Ö¼š2�µQ¢­ÿd ùDû胢è�Áϰßl8ËPzŒè„ù Ãμ¼âƒOjÁåšf5�ge(cªû�þù™döŸíMx¢�¸/ ÆwM' �Ä8>›ÒÍO:YªrNÍ¢–Í*ix¡�?D i �·yl /úâ—ëƧèŸC«¬4êqPô† íèaò©ìÒø�¿×ù“»ßÆm’F87’TÕÏFÒÚ·õ¹«~‚‚•`Œ³¥s#�Õïj]!äîaø[vo›Ñg!I',sx]ƒÍ'úƒ¼’±rº~ÃM Áé£ÄÿáîM૪Îõá=ï3Ÿ““œÌs€@˜ £EœŠÖY«b[µÖ¡Ã½ýßzï­Wí`oµZmël­Ðjµ( a&@„BÈ|’œ“3O{þžuÈÁ€øÿ¾û§»âINö°öÚk¿ë]ïû¼ÏƒG?œô pãm1¡MdÔ[š~ò û~XW§à™RMኆ¾P6V*ÐX5¦+†$n*ªãŒ$³øòh*øY #rÍoÂïýXTãª\:š-c+-J_÷ÒoN€žšƒŸÉ}h:Ó)'>‘`§UÍ�:TÖ¸ˆÖ´E�ÍšÂк(žUˆ±¯>t8лb…M¬¹;ºˆsìÚ*¨¨´}ú¤¿äN�C lÆLL»P¥ø6<à}šb>b²¨uÀ¦ÿ{áÌù@“úÎÖ,2ö½ÁR•U$Ž'ÂjÖØÅ“8ù�2¼Ûëà<œíØ/ò»ÚøæÊÑ1Ë�›á™¢8Œ)p ÃQ4ÓR$¦Æax‰koðcJ4šEÕ�²OI„X*ÇEe9ÌÙC1åNBù�¥]c¹±P¤™nôÀÉ`÷�)˜XÜÖx!‰Yøf·‹õ´B¸¨À,\,ì�E ºXºÕÖyw‡ÙH“oY4)NއGwbQÅ`f=0t-U›öÚÈ ÅŸIã©Í—›Ë8 õ<�öØ êà0ãemóªìPímñVÄHb`‘ªÑ¥8¼%gÚ@`.º³\3Ô”f 5´kû‰Ë—ÔÊÛúã�þˆÌ¦bY÷šÃ0^�i͸ս€Ñ] Ò›¬¢`y¯;ñgªÄâE#Òƒ<©É“¬,_e.6oÅ  iImmâXMQS­7øßçÕ¢p£‚0ÇZ²5švÚœ ùõ#ê•¢À_R”�1Ï™XS_RÕ—Tdá�°–›ºMJ¼P3;àIß`€ŒÝ`-?ŽÆ"àpÿÓÊÑ·ã´C"ñµyË8ßÐäŠδ¬Û&À0ÖÇ!(Nmì{¬pÍ»2ËuRŒ!ZsçðFí¼ÉHÊ�]^Ú—ßcÍb*ænŒ•ŸEóQg¬¼tÖ,ò¤ãu�öe#92ßÌ‹AZM5£�§rO½ð/íë.|öp&J ¨ô¶qí¹-G¨zª$·|ÜHª,…ã§`¢ì²òÉ#ÍãÑEÃ[JäTR]IûJ�ƒnCìCõ¤ö€Yc-]©è¡+k Ï7ÌœäküIB£D ¡ÏYh+]Pðâ‹­Ôòå:&*£qùÏÛek~ódç·øÃž°ÌÆ�t»la F3Îz…e~+‚Yk^Û•ok»1Ï–^Šƒ!�|öÍ“_Up­û�È£'ÿ Š6ïø»’Û©YÅ2VcúÚ.ÂÊ‹eÈÿä“v~nûxHhiæ�&Âk‹Œ K J˜bÝíå.ÁÆéÆ÷Ÿ;ìíµi‚fÈþ”Ìw&ò"‹“á ¥œ[À½À±tE«× ¯ž ¤ ÿup EA¥›?œ€Ç§‡ƒzÁÄA†ÖÜ@ãêšÑ€õiÿu'�cú¬‘"÷>`üž�Ç¡Ãf¢uÞ°‡€C¹BÒP¬�¢úÞËЖÉõ#’Š$Œà3Ë}LŠÛº© ±ÊO—±é“�üè“)¥Z’­ËìaŸƒC´–:ìAÂE¼p&!^Fº“‘9%kà{×½¶|ÇÑ{®˜–~™%�¾ªSQÑ>‹×Övd· dm…ä/“ºö>`yùP7ù¾‡—ï¬oé/ËœORä(bËN9)OÉò”ž·Íï·/ꉸ²µ¸/í^åbÏ™S¿kío_sÇÔ|_ÇcÆÄ¶jD‰XDÉu RõioqCÛ`<ïK`¬º¬~d-/Wì’¢ÆT5�焘EJ šR;(]û;¤1�ùOÅÁDEeV5Ä\€"°™IÕ„ˆ5Þ(“P•ÇbIß?1Ô´è¹Æõb Hrñì“§ª ümC—Ì•é¬"· ò[3dUI(ÙË<ƒ=¾QªÃ<eŸnJ•|)Ÿº7cÐÉcYÓ(�òõXW]‡þúi¾�»/1!ZI�qË~¸`oO‚�ã÷x£V"NþÄ–�ŽžTÓOU)�W9LLRàYÓÄÍ%†û¤+×zøh‘c÷•U…=™¶¤ø†ý�ˆ”êÀ>¢¢¢“zIù•W äÈÊOaé+µ¸ò½‹îzd¾?Î^@ôri‘ðz°Ü ²’ø¯ÈPç§æÛŽÀ¨�zöÃÝpjüªº�º”‰§^ÐiÆRXlʲ¶kÖõ†n¶ ÂÝðØû˜8•v€>¯}<%„ñNTâF�VÚ¦ëÜ[(‹{‘aù?:ž<ó~?ï4çôûj¶ÛwÄð¸r %ÎÏz¯ÿÍ“ÁñÃF-îí %ÑÎ ë–}·Æ‡3cÊ…‡¥­æ#ò‘CCåÀ�VfY ©sO2t×ÁûY„À:è°íõÜçÿGmá)f4VN>=y,7ìëöö›a¿‹ˆÄQö.àee*™üÃê[P^x ±Yö%w�äC¨¶Z4µÑ ]¼ù…'Ÿ” Nòl=J +³mýBD=zÝ6Éu‰b��êm3‡"–ÚH2–ª¼øqVÏqp?x ˆãRÛ"ñ%è'¦X,1âu�s¥®%Ìgˆ+v…(ýå{&[Ò�³†�L\�tÂÄC5 ·åÆ!€cªŠwîɲ„̱`ˆ¦,QØFÖíÕ˜GRÀñV÷'uÿÍãGÇ6wJ¹ P³òô’òÏÇú¨|‡I²ÐlÚNéT4GךçÜ÷£K ž¹�à[6ö†åø³RÏ IɶéŠÌÊ ‰Ÿl¸ê,Ú<¾ùžá÷¶ŒÍ¬Ãs$ýƒiÈ�¥4”›î‡ËFõÚmûå¸QB‰zã–’Âô¾ýU@}ü�ãéõtŠúñf€Ûõq¸÷KÌ&îÐÊÀ‰ÃS,î ¾·³úó»Çö’k ¸È,«�;\Ëc@\ïGUÖÁíãrûÉßÐ}ÔêF¯E˜;å�;»ÃÄsç™ûW%ÍVzÞùÇÿü›Û‡Ü%9~—gJc4Ô³£7PÞüäõ£Ó�9r<Ùò]&É ^ Uzýû7ïÝ„ñsKÍèƪN �xÞÌÙàJ'�þfü¿[VÝŒ"ù>ŠòÝSj±¤�Jo(µ$ÿ7ÁH�¯„Z pH!ÆT7*À>ñÁKðßNq­›îùÒ½¿ÿIÏ~ Esiá zfa,,ÂÄÕ¸jõGa mcQQ‘Aà›gÛŽ÷¦“˜mbØU�<Û�Ù6a°Ð.Æó-|Œpbœí˜/ú»Úó%„R2r˜yÍ¥ª£=Ÿ–òŽBÌP¤ä75UZ)‰16ÎúJkB±ÜmLB¹Ï=qÚ$žˆ›4.*pué‚. 2¤ìál‹0”ê ¬u£§<×ÜL2­§6"-¿5 Úh†¾KÇ8e¿yôSœNÙtÁ:Si$ç @a�I"\ÓSžüÁ˜<ºxB,Éܶôᇡ¤û©§~Æ~”­­�Sv¯‹&Gål1ÆŸ[èhŸ½ ‘ÈÚÀ "Jb…å8TÞºÆEÅÙ}sPM†s�2ê(µ$î>Ê=Kl4;ù™5kÈ=§«¾b»Ö5¡^a3ÆDNôl¬oG|ô~wo´jë±ca2òha Â,AH¾LÅx§må³l¿C²¯{Lþ:Zñÿ™®ŸùaÂbf, sÈØbYÅ!zUÉbGñy¢jº’â­ï�œäiP:–Nð#‹hwûÖ¿sbÅˆÕÆbô·&¨�L¨ ”x¶qª 8ÙÑÞ×Ñvj_ÚVC;‘‹)Çd· ¥YkšöóÊO¶ö�㈷¼ò†; °¬â9Á‘Ò&­ÔÝzꜸO[‘9·?!ß¶¦Í©ÁÈ&øÙ>šF–l™¬Þú‹ç®·WM�D5®ŠÝ;cìЊºOó™n"�9š 2£Èzð²Ë.“—TW �ÁTIóPdÆîÁеUs/YP_ÿÏC/3×û*>shiÓ‡]ëõˆ¾m=4£3m`ŠäV²àù×áÓ�Õχï=Þ]çû磰©î,}†}¿VÛò‚ð…Ð}ÈûÝxá*ùH ';cƒ¯§ Ê*ëû"sÉêpÇ@|bƒ7\K’«™˜7©,ÄÂ.€·tÁ¦ÇÓ×·íxþ¥ø»‘ÙçËþü§�/1r0oƒ© ˜ífŸµ¯#obbnî Ls~/€…ÌXŠ ü$Tžvá1ý[EyyåÙJ#,›v£ùƒUQGÃÓ»$4sVš™-ÚËÏ|1ˆ’Àãˆ6ÐÉØÐÞ¼D`3¼ºyF[fÑL÷㽋¤\‡fR?3«Á`�ƒá^�Wl<•ÙnòsŽ;TþÜE“ ãÿÜRgðAÄ�Û[â²Âj-à €¶MÓµ™î"G.1"ÃÇæaJrÐb½pÚó!ÿ½ñ53ædäk¡8HeÈàÆðB¼Ï�v¤p7 tñ�YÀŸÂóTÕ�`³‰êñ7VÙ#�œöÙ7-u) \—•»šÜSK¶12=à²ðEãžOA¼‘ŸT˜A ˜ | (ƒû\jX7‘¿ ôþ6SKIß-BÄàô-ì0¾I'Ôò¡õèÎ�„ØKJ”3{²î’¦ “°dêÑ£Ö:´å„·e `M›q~§�£ho±cÏ¢x½ËÊ—h²´�¬ª¾‚ûH_2c0þ©ë#Ì ˆÚ@tÓ#GÛHðQ€“,‘ab9Ä—4Àzô7‘m} !ÒnJ䌢cÀaž_9wYÚ¼n”ÍNh*­ ±â=-´Ùhc¼ÀYí­öÆSç$g&ƒ ‰°O€·™œÎbxÙª!ªo~¤ ²îfÄwN€Îó»Ën µP¶Íݾ °ÌºñP¬Õµ«Rq-½�@IDAT?íÜ™ý“�$ÂÙÌÂ>§ø±á¸.sÛŽeoyá· ¥<ŽA½/ÞëcHF™ld°6´dàf rLÔ›Pdò6<²]‹‚ðÈÄÉž›·÷†/²ˆ97bé´@6ÔN3+üV‘Õ7#‰©›LÓ’À"‰Æc¾ã�º*íNl·ÑOó¬3í%�z¨?¯ÂŒ�f¤áa„8¼éÄÁÍΨö<(Lÿ3$ë)F‚ >ˆÐD^”¢oÔÍÙcÌp Ñï,ŒdŽ•+?­?>€ÄR‘»òú}±˜dúo·#w‰ƒ”Òè¾´cæ=„¾]ÎYroZ…¶ÌÎUEÐ,s›+‹AJ�ݰkz‚²*b…ʙʨT¢JÈ^‚4îåNc� ~ù‰–þòWðÏœ øÿ´)'gÈÆ7€:}0ª…ê�Û„Éï(ÿ0{ô½ÃçÎtÇ©ÏÝ�d‰}ìÔ«½áë.«»ô¦-�HT²%œÀ‡àëîÂ<9û�-;û4ÇáÔ ¾á?¨F´ŸÒˆ/Eéøï:7}´kVq:É™~g»=)Á,ì�^µÇ0Ò“ÕÙöù*¾3Q‰fTŠüU0dm’4+£§LX|σ7«êåþ"ަnÆ}¤ڰ✕d]Ó3í‡A8 ,å­Af ³•Å¥Ö¢Ìß¿ìÏ:æKìM(c�€=rkÑŇþºoO°n8IþÞJ^Îs\lÊ£À¶«»b}{X�›ˆˆ Tñ]… û’Øé‰84…’­.š+ >l·àt´ú{‚˜ÝOÍÔÞ°<k75;#²(0.Ça”5ùW"%î�uÃ{±º¶Zp°W‰ '°Žs�Ø9ˆ×z‹¬î)uÛäÚ#7bÈØÜüx£eœEÌΊ€ =mÈËË)½Ü.&oýå“Éh–��å……òÇGüßýé¿.”yæ²D,5¯ô¼™Å�T$¼³>�UVu Aä6˜R@³P¢lLÄà˜Œ%àb«< ^Š%äáÎ7#DQ‹Ôe bÄÝÀ^íB™®Dúeͱ#ƒMý]Ýwž?! #†þ¢{7˜’kŽu¶ %²+& fú];Åuݺä¿|¤�äžÈ³ q­wžs…²wŸøþ¼Irs,æ…jò3ì$ 3&' c¼Öd\b^‰†ŽÊðí6÷Dܪv/Lî]Ý!Ùr×þ£c²\ñÇ£é•G½Ó#JòF˜ÖYðtБ£2eò�qj J¾ç'roÇb:³jÁ3OE®ž8±ð|� œ«=Ô4$­"¦©:„@�—9ö= ãŒ%¡“·Ø—µÌÂu1�ÏI%R‡³EAVTíaUËsRÚL†gþ‘h}ãæ ™ç·ÐEoJ½´3$÷x '~¸Ðô{Äv{�^„Êœ@nG£B+Œ¾y3N)ï.•æ#Žné µ·:'©H’� Œ\â“~"MØØZ~„K„ìâ�Àm<Buy†,ÁºÏ8ç0Æuiƒ¼ïOϵϽþ¡ßõóñ]WÃkíÕ¦U¦„�G^'™ã°1Ý>ˆÁê'0Ý‘Ò-ô�hìùOåQ9õÔb'ø‚ç]š§hÎ:ó1Ä*5•Õ!¡ãâ a0‹SÖ<Óàö¶Ü{/xÌSž]ºôÈš5kz¥qãº,º­4¤¦•Vó¡%èOˆÊ±Ú” .Õ$Q ÀýÀ.„'3 BHMýúP“E³~€.ù<÷›{ß `âGwΙûé�“�RðËÀçð=äzÅ„½ ýs ŒëO r�[»}QE÷Giù÷ó*+ÃûÁ TI‘ʺc¤«,3'ú¦}vËìx‡ÍXPßÞf§Ûk�a'÷@ðÚ{{#ƒ(¹™¹dÕ�¯Òc€ZûäÉV†6»ÔhYºS¦è (ý÷£Ê ozÐŽh*±piý©7x’Mß�݉Á�Ü :Š2èi`‹¼•bĬDQäÙ¡ÀÀ~�àyCS­{t55Q8g\%åÕÌöÆWw/�üËêVoŽÃb-�{�äÓ®ˆ'3±m%öì²|2±4œ¾o4ý@mèð÷È,U†éðk–¦³�'Sj[f’ÙÖ‘?c‡«·¯£ì�›Jl662K�4¼dÿÁž¶�`Ëùc0’†š½Çm­)½Ì㌪Œü2ûðH²×€=êL.¬�Šo³š«S(UiÕDóªÃÒ0£�ª§ ÷Cö%^ÞÖ˜ÌÒva%—,ög©i€nE1ç¿«KÒ‡V#¾ã½òòÈŠƒˆ$uÊùbÁæ²L‘Ym<®n쀹(Pøñ'©ËFåÃÐå§ÛD®5r#ƒ~CWph0çËvþŸð¯<ðê«‘š+hOo²C¦~”±Þ œÀíª�àØ&«á$ü¯—W—G——ST ¼Z¾k¨NaØ…,„A} ]ý…pŠ9¸KqKæ­ÌÄ€sÎÒäG¤®î$" ÔßÃqNò‚ÊjÀ&ò ÿ³ŒÑî"gþk@ԛ̀ ˆwE­ú¶�õÔGÔðqä> ‡§(Á0óÇÙi~Õ*jw¦4xä=ÿ ”Ã2 Þþ¼–�_ªfö~L6ËÀa·ÊŠýƲҎ×Þ–‚4*œ�ÿ0‹t»¤› Ã^7âd£YƒÁoå ʘ¤”›âõÇö Êœ¥¤uFTñ¶Û<(þ£Ñ¥ôCGuÂ<%Ú©Žöþ�ëiƒš‡DíËÞ%µ©ßŽxŽ™K�°‘ÁQc¨ípxfÆy¢®MCòò­ø z²#$Óн#Ù´l™+=)è2Õ@ j«T\|Zè+sÎoÊ' I Vr(ƒC�´Öa2ô]¨åRÒ©~Ü„(˜Iù¸Ô«µE±ìEZª Fy½ÉÖ>ô“B‚‚Ãâ Ij˜£•ш›ŽÇ”ôN‘ª?ÌAo¿’SrÊ7&ð›ÍGŠ,ÙÞ%��¬:wÑRØžk’bµ8z׆N魋˳Q|súj¦ˆ%¿?ž’áÞ3"sýVÅŠh©Æ/ûÞΉçËX²£t؇:ck�rÄðcE·Q”O:3?*Ïæ[éK&&UÚó¯ŠWªEåãÑÈXÄï¦a÷5¡f#¦ÚI )éU¬Xö_j&8£µ ” �]hSdзÚœÞ[öâ‹ý'´†ì¯ò‰>Ú0«?ˆƒE”ÖnA”ùïaFû$÷õ‘j©)dÇá ,iÖ1ù£¡~Ë:T]Ëg5n G±[-±×õôôŒ¢‚9��ôv¶°T÷["s':C8ô”&†w›7< þÀýXjO‡Ñds%VÀ‡'½„"sïj¯ú’ •ŸXÚ>ÄrÂbÄA«AòûrWÎ)O…#T7EÕÀÍǹ:P€¿Z’ä•K«scÛŽúí¼�!,o•"¤½ã¼KÔ}Ú¶ÀªÑ íÜò.CS ®aÜŽÚ÷…Hæå9uëÄPL×Ežþ6«Q´:\hxÛíóåk†0 °F^,ô>™ÆFldrÊÎ*œlñ‰­Õç»éóÛ}sŒ\~«=Åÿ*” œ'ˆ£‡W÷=oÚ»ù„cÚE :Ë|�»ç‡¦5¯8‹�$ÞÈÚ¦¦¶üŠðа‰4ýÚ‘�ËDZ½›¦óßµÐÔ{½I­Dè—Xi&è´2“‚£˜ê«æx;)Ä�‘ó6")G›™QI^iÆUSƒN'“OÓ€ô±ƒˆ©¡ð2hc8ƒ)8)ÌXu8væ²O½¸=ýdzÌfz™§æ+5.#ºýü#10õý¡]Šá]"|Ê{P¨oY‹·vØh®>Úº´zZ7&'Þ‹WSšÒ$ÒT�J1r4éë÷©1Ϭb10ƒ°×=úè cW¬ �в¯—UPð?n×¹<€0� Æ�#ãz�”_‘· $V4Â!›iÛDdç†I“ÊYU�=ä`xú†-��GІÏT¾ER‰¿')iÈfí¢ÅL<ÅS˜èsÙæÿ·s�´Ã»½mT>o¯�·È{œŽ¡v_�Òuà€J™³–ö¥TO,™òÜKÎpS¦KI©#Ö8Wi = ³°á¸NÄaƒ™ng$ÐB²™¤ÑzpP¤,¥(œØâ *[²z¾BÑÙ€ýM‡|y’bgÙrøÃcó]ée9©.ë ö †3K ¨µkÿæ§“^^˜ë_ù»ßi!ÏÓŽ�o‹$hTˆ•š ÞŽXòU�âÉ‚GÚƒzooJŠ+-Í;<:›ŽV`J,ª»ìbо¨ôÐo{øá­±ŒIùÀœ ×Ì­Ì Ð¸Fñù³gƒÉc!Ë£1J£g «cAa¡J&XÎcD·š'›»Ýyg‡î·Œ½�Ü;ÕKÉ0 IÍ# '¿8ßj �ëu¡/¬°ñp«S祆öΙ3ç3žß`Âd“à�š]ta�?9ÞEM(ÙpðuOßèZk0)çYþ ÜU–œ[™ãàé ôHvă»oõœ$¼'×Ël×K¹«òò”<‹„=d¾ýÔÓqå,U†ZíhQ ‰$Û_`å¥0ÆI2=öhOo7 ɺeb™ÿ:$_¸ÜìÑà)Æ,�'C%GòÏÏ݈CqÍØQþˆD�`ëF8;n `”ãþhW™Ëò¥&ω狸 ÕÒh‰BÎKž¨ZÜÉöÖõ‰Úq3Fã‹› é »‚p ¹/º°ª>�UWè´/©Ð'ì"û-,Ù/Suq-º)�¡-GÌ7ÚþÛN.¯-O}Üè ^|(¡M‚xdAN6â·¥q§éjfy~ç¶Áø«†®|†âØù¨ˆÁ†¸¨#Åp|YK–NI)]¹Ðl¦ö'“¬+³0­ŽAXq�<UY€wÚ Ë5ÁðÜjÂ2…µQ¸áÛ`,ü¤è�'ǵÆdÃC³úwÀV6eÈ?€±ã×ôÇV.&Hlkºx¾T6öïFŠziS¹¹oáwΩïè`ªE1‚“%çSÔA¬{šÛWQúôéü²Uë™ —?4È™ ÖGª!o—öï:1í²ËÔ =“íŒm>e(£ŒD()¦mæ:ß¶:O<W{ƒÏŠšÞ*@¶)©ékØÜÜNÒì]½½Ù”Õ6™’µJ�?ll•ƒP9JOä¾FnDËHAà‰îÔl¹Ç´uÑ+ŠQ™öë¶Šëïý5²Ïà¤nÀøuD8ngÖ±Cíˆû(s®_65Y‹Ä‚y²ÎŒûC°kíºGZ–=8KˆçW/þø[Vž+�RÆ™‹lm·; •G7Öob³«Ã<#L'UwÌ º=*½þÊó‹¼ ½Q·ZPRC«T)–Í4JÑÇzjŠ÷]­#4B8õÌ7€?~Óîaã×a–=}ñ™iÙÿªO€Ž€<˜2ÈO±àÐf4�ã<õõ4ñøÉfpl3>&Ø¥HäoÆ ôÖY6Ä�«E“0edãë?ºysjÜÂ%q‹H�ºéþû¢Ù ¼¦Æá–Õo¯'Bª$$QT9¶K9ðFÙ#o� ;¦�;‹j'VFÓ0\X]¦·Ñ<åâL< =xGîÿEÿÌœ‹ ôÔ×kil-gHI�ñêH‹y½^LFê^X*�¥Ð&$9v‚ÐãE¡dZße‚ÌŽ{ëæÚ[e5E˜²'0,$cNÓt³&¢ª 3vÚ0°Nz¥q´@$HŠwq"-@(pî:RT«:|HrkQ1öGˆG �€… ¶`¸¯Ë’‰¨€9Zò0Ç6àY‹¢²²žO2;±é˜Uãd +9&q1Ä™@9ç̼“S´ä&|߆òÌLñ„¯Ü½»-ÏZßíz'E¥~�DÿZ�唣˜äQÑnÛ<.¯ïN°³D«Óx•bÌÏûv¯ëÆpבÑ+wØ<÷Q¶óŸTWÖ1*w=JgÕÜ>mn1oès 00Ç<˜ŒûÀ 'mêè/Yñˆ"~BT— ßï5C ØH 7-344™YÝš|#x<�L=åò:ëë°ü$¥—ÞVËÜÖBí‰õ‡�.ó¤ám8ì³Ûª;d‹`ò!T‚® +cñduÉEÕ$²’­”æsƒ�þüñ“ƒfŽû[OS¨sd’ývµôfW9 oQ íÇ Mý‘È™o^pk¥èÊ‹ºU�££ÏCŒ`ŽÒÇ¥(ƒÐ\[Y°·óÈ®—>úŒ9ÕûÖÒꜾ•˜ñ¢TY11Eé Í´ ¹Ôðæªæ0™äÒíáÝ.¿0á&ç$#»ÖÖh4g¸¬ýä—ÿ[þ¯šP‚®mFâøoÑxê�¼Ü5òy[¿رÉžUóyü·dÛÖç¯Fâó^„zŠ ƒ²K(N;_‡nRSR@¥Ô�±¦sè"LnÀ=)ƒ’4iÓÒŠÜAÒ/[û#÷#þ=$?F¸æŒO-Š#ê›KáZ+u¨:P)•+pÒ-oºÝ1bèÉý�©abfF7 À‘þ¶OæNœø™xÙ—l$ÖÜwËŒ÷ˆ€’Å5êJDãËACø‡D$ù– Tu’.!cÌ4%!=£tÇ7Þ^wºýJ}‡IËïÉ3  %µã`p³¹MôÑÞÁÏTŒËSÃÊX G9“*õÝ”®Ûú…xDuRy¾Üîõi¤:ïdkðP±òEëR] RiÔ/ÑŸDv|œF-Œ¯«óP>_àóT *ÅiΚ Â�Z¼o9’®�PÚÚÚRE{öñ–¹ö7å³ÊÄ1‰¿«¯½l¼ëÌŠ pŠ|zoÍ‘ˆ;œÔ߇Êñ Z“_€zúÑCtE2µ²$ð(z¶í(¿í“¨›Ìy]¤[†Žx·Z<–T��Ëê¥P!ÉÕå,LÕÉ„ñÖñ�æÃ?¬›’ÆÍŽ<_KÍJª÷Ûî-·‰‚Ô“¸Ímh�·L™ø«'#ÁlˉÁãÍG­9%^ôëxœo!Ê}yIJÕ8 DïO<‚ç–öÈ�ÔË‘ˆ£©º_6’[æå €‰Œb ÊBðVU°ò=6S{áp_fÚ’Fµä•| Ïã&Tæišz* ÑPvHÕ›sLÜ©ê¸Ì1ßÄOp&fÇÚ·¡"¥XaÔw‰è�Ö}$Õ„ä~rMbêîG0"ý:@ÍÆArª:Ëàk-9O‘ ˜Ë Ís œ†ùàž>¦iÌ/¬…9 èÏôÿ:ôI®‰NÝyÏ#þ/û ky×{äX;«V�C²nìFÜðý¤¯km¹ÝV 3Ü�Q »¹È!&Óc(¦„e]©Àb™ý0V¤yxo@aË›C^0,žÊë|Ñ÷{NŒ/iä‰H´\²N;%ªóe�ãŸ�ÄÐþMë;N�ÍÜÈ�jxÁ“;L—�cñB!ö«¦¾gLU�Á!ɰtuæ˜Ìg9’õÑ⩳…%ˆbϱP”ÛÛÛ1t¶ä ªWô��ì9Ë …W:çæç‡^PBêTĈGƒG8‹çøÂ°ÎUÊ197Ë]É{«ävSš=;JÇ­¨Y¾2+((7&Â{Ï/ðòõôŒv»Óƒ“$+f¯=ž*Å3D6› Eö뀯çÈÈ8èîD‚s˜�ˆ29F\ß;Çe �è�U8w•f˜¬¨Ê¹TÏ1UN¶ÄRÇ¿ï¡Ô.Î9…„!6> Õj#!ï¼jtQâOOˆ¡Ü*„S 1 �3™Ñ�]‘”¾eœ-k¬™Ë£‘Àü,xÆ$>©ÍÏö>Š‹gúuä'a‚ÍäwŠv4dJ²á4˜VÙ—å´uÚcRWPR  „;‘c)>¥h)Ÿ¿ÿöòòt•P!^„̹Hh&bí�3¤¢Êà/¿5ªlpb–)2*Ûž@6Ç Fµϳ™Î57n}ïXiIM?Ç1 ‘”œ‰Mw$$í¼ï[KB™ó‘ÏþD¼ªØ€OkGfäyHüRGåoXÿƸ›‘´6u}4©Ø-�<.Ï™3àŒ'(†jDü-LÚMà™ƒåælUWÃG½{HÂvä1ß´Ÿu‡«IíbL.㓆<Èü%H6–M˯Ú^î:™üÅüEµõù»9¨Q¢ë¦1} ƒfR 3�ÑQ-Æ•È? Ânå(ê÷�íkæUå}®3ôUô1 …Yf)C—IÚ@~>î“dA£�éfu7° çÈš‰„Û-�>¼:Æ0ïVX…²o¡MHÞèhs ôAM(Mž�>C5«õ[d7µ‘k|Û9‰ù’† ¢9fæ™ü¥'cŒê­6n$NIÜ}²‘ýÞ›~4&=‡£§8E~„�dz-/‡2¨|ù[xSÖUÍC©‹v%g‹KݨªÕT*ƒ|§ q/XÒ&€l&Åf=6L€C®5r#a"ÓC”-F.Å*ívORãtŽ @¤¡€‡ëV½§ŒS dÎçB5ø¯’®?ƒŒq+R=  ´ùˆÅ’½èx«™k¬Ü:ÓÄÁÇfMýµÜ*¹1¼SóªUª˜K�˜“1tŒ1¡xÌDÌÄ—¢D}bâ ðŒ]P…>¼¬†’I? Ž·È�¹H/æ‰õmÑÉ_ô@ò%Æ%ˆW.µ<¹ Bê•hü% ¢ŠX†�†ÈdÄE=W¯Q?´ukšqêÞ2í&ŸÄ`&ÊŒ€¯ó.pµ³B@;L?šKgøQMv±'ð’þ’•é_÷wù>ªÃü·ZXt^iÙu#´Ù.\ö}‹¢S3­f£¯ÊR“N¢â$4{ÚµËÌôEK]3ÛLù¶˜Y£ chsDÑ_gÇæ“,õiû¢"r .½GsÀ|“y p˜ÙôÝ.L:Í•E¦S¡ òw2î"ªºyÂ?òWðgœ R ˜X²_/<ÀÓ®AŽù&nxÉr ®ZF1´MS8B{G„BI¿w™{ªSàO"ß‚2Ü'‘ƒyÄôð}ŠÆ¿¢ÿþ„™íI&™øOÁc{oѰLæØ¯ó' 4K)û=Mï4™…«*õ/°  Ú+¨ò"²Ú$íÇÃ&“~ª¹ ¸o¬àZ°zþ�7'°cΟ_ä=§t..À$¢IÖdåcR\d1÷Ÿ<+IòìˆL°;…At@'0°°·ÔÀJ`ôÊ¢Q— ¿ê‹]AÛÌ@0>ÅJ<¶@·H✟Yòîß­ѰC½ÁD‹€Î€™T£Á|%:&ïïp¬±©IRÏ~æ}‘ŽÏ|G.dŸ+KÝ-�=VÏ̹e²UG¹„Ã:ˆÊ­XI`Ò©ã_|1uÑ=÷ÿ%ìãî4èðÞÙÅ¥©Lƒ1! ‰¦á¥¡Ç w1T4r¶‚úßß ~Ö¯ò¯ï�bqmÕ˜¢8äÐó°4²"“C¥8k¤n½Àíè=ÿ¤Âo¦N}®†�vû}çk’´Ùd/x›õ‚ãÝb¢tþ™S.hõ¾`̱²Rs÷šîî#Äè=R�“~6¼AOˆpbÎ#ó/w�³á ™p]…<×Õ/[†í õZëÊ‹ÎêAÁh\ª(jIqnù=Áhâï¬Â6÷<\7&u)iqÒ×™*Êágº…üм>óº¬HÀ9‹­v³«ü¨?ªWçØ�áoiOmãp2z¢³ÌS,òÀ›Pÿ ȱêíN'æ3Pùƒ·‡ä’n~7¥È­P„Ù�@@ á(%ÔÔ¡JKO»³º1v=E½'�má9‡6ÙQé•’°ô™r,�U9uÅ¡›ÓNð5ü…KÈþ(Ç]‰ø-ꤌHäîäY~›¬(e¨Æ¼#X2n š}hdÓ%Vê2«&Œ ¹‚´ÓMZOQÖ‘»|¡?Ÿ3Ï×"Z#ÐOKPYÕ”Ú÷ † ªù[¬¡×…BÒ Ùí&ƒ<=ûŒî•)j9û~ cü|»˜&R‡‰ãÀû^ó°òYzÀ˜÷Îo òéŠ! °²¤]P÷$iµF³YoJž¢³yÍ™ólˆåÚÍYׯïŒ!NJÑ+VÔ© 1½`\q@Ú¶®iûŸž> Ka3uûÚú=‡kjèyùî–%cÆø—c?o°ï]x¨¿ÂD±UeÓEÑôåäïs󃌮^Ù“.®t4Ö�ñBïùŽ“7Ïp[ô*Ä¥œ½„-†¹KV¡¶€…©#E«û.Gr}atÀ«6Ï�+&xÇ?´�÷9žw=¡èòsð>UÄ«¾ œëC¼C¸�‹PÆ8é•’XöæQ`r#þô|÷:Ô@çĶ Üî¾ÌµðyÚ¶ª½Ý‰0ÅmèÿŸÉ,{[ÊåïžY9èV´—@Ûþ<¢…n†û‰F©·úúu't½âá““ì¨Ð9Þ‚Åj%¤ëç “æ¢P ‚D”a‡·y^y®e!¡Æ$ß�Ü>nîqÃNô°´¯7@›\쪗÷­k"zB¤Ò4�×ì�^Žc¤�lÄè’§m5Y¦n·èÜ#ˆòX¸í?–üÞ–ÞÞ,²ñdPñDÈÎ}/j.Æ€1o-¾¿l|»ÂL…yŠ¿‰(Ùbw<‚oævA~¾^�s¨7ù×:ðô^VQе¨ºÄK*ÏvGuX9Ì®² ¾�q=ÕÅ\ýì¯[gäÚj X©ª]ƒÑY ƒñ¼Ž…á×ûljƒÇÀ³@úã((zb �·x�¾¦�£Hj;-œPvæ]lzî¹A0,¶!{ú�/½Ê–3÷ù"?'hÒÀmÁ`i4b|+¥ª(÷gZ­¼í€�M^€€÷T>²ËCјÿ·pùC4–·»¿ûÐàÅþ6ÈrÖ#¸¿-&¥ŽÛ8!†Š›'Q¡òxÿ[ÃâÅ'ˇGv@ ^ûvìP–Íš•Ä ms ù¥^÷`Ê ËòqHªÄDU{viuá)ÏgäñÀ¥8ü��GÁIx- +ÚÁ ôDa"wc –÷™ý¶ô N’ þJ&À�„Ùl1›^ûè™§N‰Q2ô‰ãrÆ™Mì¿‚¥ú2¼Û)û·)’ô«¡¤o癡âÅNMâ]À;È*�m6”×z<®ƒuxðÖÓ;:Äîˆæ0Mª"î9ŽÂ%(>XŠCMº¡äEþï;ß|¹-ÿâoóùn±†6tL\LØ”ê!Ó¯y¬‡ñ¢ ަ"¿GìvT*©þ„Jô�S]�€‘0.ˆ]Ư½�E9Ç3Þé,rÌúßhIånÑ4Ùk8˜×—‚¹Ÿ–Ù=ÁK�÷[f¯w”¤E)f:šì\¼øôÐñ†=³|/©?‘r!Œ<¸v¨ý¯ý¼íHx=�J‘°€jX~ö«K0cf¡`ö�F¹ª¶åÇOôlýèXæa§Ï7û¢[ÀŽô+ÄdJ©Ôc‰²œí‹áyŽ|ÀýÐêˆ #Oå˜5Ô�Tð».D_?öñûí™ó�¼O˜RÄÖvúo²Šü]�×Aþ�Éâ)Î!©êé\_˜s ê´ìä¥7Þ��Ë¿£‰Ïêrè÷˜ØÐ&¦rÁÛêßf¡âSCBð¶=¢é‡@ Тh¬q´'놡a$“¢íNŽ7<ŠÆý}³(&«PŸ Þwòô,h¨2ÞtW_ư"ÌbC«~ˆÙ`BÕ9—Ê×w[RÝä™dîý™†6GNŽãn<—a\(Y"Ódåù݈C‡C)yh#çB fͱ�_[îéÏ úNȼÇþ'� ‚#†*6š[©„î�‚‰;snòyh0\ECœ“Õõ"oJêz‘¬P•Ã? DH�Ù¾=:[hiöù¬õý ÒøÍ'Àeð7�⼘ñΉQV_ïÁaø÷*›�„¹¾q‡c.\t1âØY 2ÁäH¥�*#h„L¤€5q#)­$ªª³¶¶EïxbAåç"<â7Ò ¥‚èk·¦(Û£ùY×}�ŒoÓPø¢8à¦àÅÉÉbµ×ªóœ'È=“qìp¹„L¹9ùŽ8휂 þ—(¼xí‚Ò¬È÷gÛpßÁ³ õ#””O.(t“¼Ã¶�3ãK*K¼öBg_´O1ó¼zˤI‰¶d²(–PÀ;ËS4uo,ܑޭ5¥+R²¼Ü©Q�ü¥Ð~ì¶ŽÁk·gãÅgóâq!Ày×Äøní Ý‰ä ’áBØî6qFìr¾µ]áZÄY+Pu8ªH·Åa‰sz§$3›´OCÐȹÒçè^�е,ŠVaøïóYFSj3@€OÈ)çj]ˆÜ…ìÙÿ�ÊܳLÃD|. P)Þ¬“XíqêðþCdiLη±;ô0g%Rêϰñz"54²Ýäç¦@ìZ,‰ 9�.ó1Ê£ÕÀ|äTôÈÙ<Õ†@² ׯ€‰k®·&t<C~™÷� �øÓd2é#F°÷-sÆå Žÿ3%²±„ÏG¹Ý.xÉúBˆ;mî+´§©%ÉË©N©û) 2¾ƒÃAÇ­+žT\áÝÙyÕ¬F€:ôQpÞÑ? O÷em‰‹ç �Gõ&ý—õ›º>ya˜GƒÜÁÓNîa¬=œ¡®nò¦öÝ3­0¹Ó'²ÚxM0í­uŠí«š;ò@,yBC·è4=ÁÊR’Ò @8ã�çþxâõßµ�âÃÀ¹Q† äràyÝ(¼y‰¿<(*/÷çÚÞŸ�Yp¯ÏW`e,—A0õ*UÓ D…zdí¾þí³sÃzvU•5ª±Ï`^½YÒß…Á¤��„Re@Ùü nóZŒªI`8#Å*”u¿5Î#bQõÙ�¾ûÚoäù–]xé[x1à†ª`¬“ˆKú~Ò¡E|…ïs_$Ñ%XôÅ‹F0žyƒÛº‚sía)BkA)Iÿõ#„áF>Ÿ3÷ÿ²ßí‹]`M¨€@Ú{žÇAž]z#4Þ˜Ïëê¡€ÆLæz�Cঠ!?+Ôç8Ìì;ò³ÑÍ¡xí6§º”Œ1ŒnŽù“'’L#÷;—?Ÿ3ãK…)ïɧ˜,MÝã§]…¥ûXÈ()Vyžˆ_’}÷wt¸b&×”íjÎ¥B³Pz²˜Ñ˜bˆ€Ið`>V‡Ro‘'|Ù2Wí,óö÷E‘½Ô¦KŒ±œ±‚1¹Íµð¤Á¨eÍ�ãʾP,B$Beå £èÚümG\ìÐ|îN^_Œ‘�–‹–û ¤6ktZ'õ<–'7jºn£yHž+Z3¤Þ=:Ï߉Rè[Ê6âà�¥’ÒÏÍ.öp_H5åÙÌóÁ¢Ó#ÔæºEäwõF³íºô”KPá5ÙÄóSTÝ f³­‰};½0àgMò@üo oÏAýá;œÍ8 QO‰œ> AËŸ™ºZ’ù£îwng¿ÝSªŠÂê‡|Ž>?ß8ãí;¥±Trõ¢²¼N46�¦_=2°ÅØ?âæ À­Šâª>ÅÊrþ�¤í…¨å‡£lò¦ó òýÄ�½©»BÒMÓÜ"kÇ x¤U4ö?rÆËJ–û“ ÇMBˆ,ËT‰™·„Bå’ÆÏcÔ$¼üGvìÝúæ@ûøfW—K‹YÇP¬:Á!0ùpæ¢Q[Ì’Ðtåw/i#鯑ÛcXÑÔ47s +¾§jÚ{låôÄ-‡ëë}Õ/þ70Ð]�ñ6ñÜ_&8êéYGš·‚œã â•(³ý3‹�GÓÒ-‡Ö¹¶aIÉA•£ùÈÙseH\BÂN>�*£è?2™’c¿*bmríÿ¿}Ý*vÝo}z¤ý3ÓÇá3q^ÒÁ’ªM IÆô~=xÙíg¬"2×&ÏuZyõrXðR8ÍDiܫԮ¡ÈÀšÌª'³ïWõIôå [q 4ç):Ӻطk ì~Òž÷á ÊâU�Á»áƒyRË2QÛáütë:ó-ŠÕ¿?ÛãÚfÛ÷ Ä&˜hz*�6Ä��a•ze*©o„vcû™ûž«ßÏYÂ�4h¤á%¿“ Ôz/(óH&è§‘Èy®äÔE²ÀýÜPÄgóÌæåvPG {6Ο'÷•ÙÒÉ5;3]Ö´[’O åhO© Ë‰`ÿç^r<âV›¬I“Œs·•7&ªö\°+¶7š��i™½VSŒ«-†Ý˜V–Ó?Ôyœ’(JЉã´*°¢u¥R!2ÓF�,ç&úï³ Á S «ù@ÌÅ=¨Ñ¥à3}¬9B?û‡Ã}·�¾q°¦pŒ¨–éïJºÒBŠ€MÍ+2yÈ!_‡^?x~=¹é>Œá €W’¶¥·}ù]\gt(çcE}…å”Gâqê¿™û*fWªUï8Û}ŽÒ8c'Ð¥"(1‚’xÊ·™ó~ŸçÔøž­�v·�d¡ šB‚…Ÿ9zÔ&Í+u•;DQ®Æjª"×:v5?IÆ‘‰ÉFé6¨‡Ö¢Šz ¢­õõ_#CúíÿËFî! ÒóÆÓí‹Ï!+°³ý øÃÅ3yÃ'ÝÐî³ Iª(ÙÍ\g”ŽÖ˜À$½ˆ…hÒ¶1L'Ÿcf¿¯úä™m¢Á6á}ÜlŒ±b�ô×{X½‚Õã+[_<¼òA�g¢pbÐÌAÂ÷3“ˆ£È£eè¶äæ‚ï�ÛŸó£ÎN´_D‡Ž"É'Qìƒq  ªH/(˜ÍNîGý6ŠeJàÅ}`¨± .¸W£™Áˆ÷á%Åó¢o.½¬Ü3ÀšL»¬¬ùížÃ�;‘�µó<µzŠÛ@M9&!²EQ!¦ËýmŠªìT5%Oö<âÀˆ¼qXè‹}ƒI15‘b2°¥ôí’x 'æß ßÇ¢ŠþJ/5“ÀÝ^P�õ]’¤î㲂‚®¥üa´4>Ì¿‘’ñÍ£ îˆ�@ÃHñHq�Ð`. ¼¡�>EWFII“#V�C°»iƒDÂ2[�¨ÄáÌcÈàn}zÑ];ÂEØ®”¢¾ŸPµ"Ìà¥@«1¦v«7º UIÿ*iúPXR×ô�—@¸ú·¬ ˜Mb NkQUn‡)Øî]1ìõfšFŒ§2o �È`PÞîK8õÔõ<ÍöÌ0ŠÛÿž¢ÅOÕà,Úîo<ÍxgÎC>µÀp �_8ߟç +V ÆCÚƒçXFßhÃMжº¢þnôÅ©ÉÖãùÛ˜óœ$¾¶§'2Ãfæo0Y„Z€a*í‚Ç2ò#~6úƒÒVÄäßÇP�%õ­1‹;ci.~Ï…‘øå ÷?ü¯m¡ÄT“^U‘l8GkWâ÷8Ü»·ë{<¥HoC)%“áO÷æC'ñœ6`¢ô�¥»¿®î$Œ.ó÷oÂç²ÚÊðȸ,�ñmï‹þøÀPì¿wû"¿Ù=yvBEñSe~i�'7Ý× Ê‘U–Ÿ®†C¬é ã>é@Ã$âêäôB¾éXG*Êfs_°—¬¤Në·¯²_d*Þ3Œ\¸c‘¼®-,µ¢ úä쑚 ,ÞžßIsnŸ8zCOø.Îjù’�“±b*¾àŽûÇeöÍ|v´õÚP¢¾ù“ùQ]øÓ �_�ùûñyÎÊ‹ÓTnEE¨}ô3íìJF#vÞ4 ¬øãð�½%àë$%ÄÝF¬çÜU4ωÒê.�pðz¯…á<8ËIJ¬,qZ ù‹QåS²s‘ÉUyò‰Hœ‚êdA\ejFŸÁIêÆçÛþuÞ<åÿ¬ÿ0<³¬z"ªÇDIÑWàc?íZŠa;Q=õþá}Ûº?|ã�´7G2¡ûRlvÏÎ-±Þƒl¿©ÊÖ™ûlÄ|§!Z8^á ( åÛ„îʈ)…ð’/V îy}0ùÎ% O“å!‰"Éä¬ÃS¼VFºàðЂl‹¬)!he‰F(Øžá,îâsê@ù£Pœ,2;ÎÃÐ~3ö9¨‰®ÿ‡º÷€�ªLÛ‡OŸš™LzO „^¤Cl Š}a-kwíºÖ]]õµ·µ¡ØËÚØP¤÷J€��^'™–é3§×301 ¾ï÷Ûÿ/œŸ2“™3ç<§ÝÏ]®ûºªEtN›8º?zƒ 1™ãé3�‹ËˆIúÜP¬kõ¨ÌLJ÷î�(X@IDATiù¦þýru‰)Cº¦ ܸ;¦-=f¸´´»­;•³äAjæâ£0u ¼á8X±:ÝžßÍqôi˼“½?]qÊ)Áß\ÈÃ4b'F#7‚×¹ ªºEᘑ& Wˆp5ÝÀP�>—T)5ìí6åô/‚~Ý$÷Á}-+=¬�YP0é—1à@îËr†<¤˜Ð̧7C |×þEuîç6Ò£ŠËGDu¢3R�Œt‘Ýâ‡oÜ�TÆ60½H õA˜ƒI\ÝNèS€F •†‰Qè�*ãM©ŽˆSøî'ÚÌ}µfúʋONRÇ4±h,Ü®£åX¸fa¡£¶ü¨‰ë÷ÎÉüyS\¹ÔÀ±7Ç‘êZd Î�¡zÿ°¬7 ”÷›ÊÕ«USqÿià1@¨ußÈ4Ác7â÷eõçÊ…ˆÖŠ€ëÎàb즟ßyÉsÃÅÿ&ú¿<~b?:‘B8C�Ým£ÕÄŽo¯Ú·Ÿ´¸¥îNIÊ�kÔ³p6ε Ìix® z­†nwè ¦5úœK{·”w2BšW24Uq¢Ðk•5å Âpb®Yhþ£Žó¸ÜV¶{úk*{ªÈÇ—ž—­°C•ÖĸI˜¿Ýå½Ä@› ¡zqhâϼ¾·ë±ÇNCå:ô‰‰Ñ˜t/m3‡�²¼u#àdoø:¥Ï¦ÿU×-yHŽQ�¥¨ú5ñF#<›b`„»Á@¶lj®õà†WqTãÏDø” ¸T}6˿ճ¼¾#©|±¡-8½0³ahû¢Ãê3-ÐþK ¦&ž?þÌA2Cÿª‹1OøU]™¯èÊÛtœJ¡2Rü\Dì×ê�ïºlè!–±ä˜ˆ!7™R¯U¤_2ðÍZ\¾Ô,íš_*©”´�§BÒ±jP¶µ¿IÜÈ«À^%�×�æP$[A‡ ¯žyzB¦­9jjÌåw•d¤pÓ]>ý°Y˜˜ü~vIüÒóûX\ ÝÝ6WŒ?ßGx�-–cúš°ýß°’ã\tÐ?¯ǿ+œÍ 7Awxé,¨Ü®�·�›&evÓil¸~¤fþÛ…i�ºQ0p+Á£6CA¬F3Æl,Ê«¾•îvµŽ†*ðÚvßË¥¢ø—‰œ€|Zœ¥µÎ"H¥xB²Ò¢Jþ.žI¡X„ì¹=(³· ª¢ÓÈ›à‰7qº6wPº�Í>{Ä)Ï|&p “ãb<¶Æj0WhÊúX$"‚¨øV),õekqFcÛ8Šc 9:roV»# §T¯ÖhyuMZÚv¤‘u‚ßýÉóÉFgð-ˆ¹ê]ÑØÒ\›õaËþŧ,0Ë])½k ª hÖ9¨µ&‹øãüZ}ÍRR&G#(Xê\.gÈ=Úý¢Ãžbæ‰t¶…¨ô¯_Í·²SÐÕ7‘§âO“¶a2ÆÊúpV'­ƒÖ ጯô3¦6^Œ°ÿ“•e.›cÛž<ž­ííFƒý:h0~‹EM(V ½nT†}Gr�ãýz\Ò$ßÃ?#ß:„�r©¯ÕÖÑÍDd�5m�‰¡-àbMíP ·\xû°lò|ñ=ÈB¼‘ï/u4x¡/ÿ4¥Äï¦MÜ·0¼É0ñˆã&Æ¢�ã h(—_a¦Y2æòÞx<<¤Âͪ[UÕÁ¨(g§B ó;“/ôË%}R[‰á%E·í¾9Àó=,ïÕ¨þŸ�Ö?u{ñ %ñå…Ž*ä’Ÿ�iÌWm¡xL_ÁÓ«Œ�{ŽîdÚÛúˇes4æû 2ž+à-"ý8V"!ä¹eöÆXÜý=ųO‚vóÐiz¬Uôý@ /Ùÿº@@„úƒ�µ lm5ÛÛCN|þ»†ßQ�Œ§¹íï�,†ŠˆnŠDØÜïêÚ vt³¸’äcÉå"ëö^oÃ𥵌ͪQUxYŽ~ͱühƒ³¸lìÖŸ�-‰á%¿¢ "!Tàý"-C-Ä™¥]¾hx™`e×wháý¨ž·” +ë—¹ûA°z:ä_~€Ö0!iã7UgøG[Âák×®•DÅð1&ª\óŠ¥ùŠ ©EUËa@Úð¹ í/PÕ~Ò îY�klƒá%cÑW‚Št›;tõgð ¢Òû¸~ÙÓiÑþéèŒâÁC}rÚzÈoÞƒÔ”ÕMkÜ ´ˆòSƒÃ†µ°zYAj®‘ Óf#Ï"®Úcž°†7q_ÆÂ¡Lz« ‚²[–ã ¨ˆ3ŠúÍîþ>g£}ým\>è%ÿŒ‰ÞR„‰ÉaŒ(µº!}*)ÒûT@ünY^戸- â—$×%¯°>†g~nÞ¸ªÕò„#×/àWÑÿã–ÿµñ%ÆLKg¯RT…Cù�ÎòJ1ïøë2ãÀ{ëÙ~ÓÊE&£5›b”vžÒö‡"bXu¿×{Pe”'U?·-ÕœÇqÀ{±‚i<§ÿ¶Ó¾ˆàI�uø$—|ù€×�iM�qª~íSÿ¤T¶ºoQ{»[ ©s77U¯íŸ››T¶¦Ö·ù.±„Wá?„Éë^¦‘\{ò:8¯,Ç´FT~};cN(?$¿<‰_C’¶ÜÄðƒŠl¦bQÕ¿ †ëí<ów”g›››ÛcÑtAôÅÄ�oÓiEYl=t°²ÎO@çá9PpΡëfHAÁ �çbÍ/�•ØfœSPO¸Ýšˆ �¸ô™‰ç²-hƒ=Íž¼º½ûi†ž½Ã,%T=z®0F–”ñ£'œë³d]‰ú< p=+üozŒãºmSßÉ+ €¯ô£ÏßQU¥é˜sÓX¯ÂÃ}ëÙ,éâEõ@Œ1¬(þ—•%<$‚—åØg)4Ñ”™ý/„©�€èü:Ô¯/¢eê1k†pMeׯl¤˜‚æ�ò‘“Ï)ÑõxŠín›åvxsý¡¾B›ªJ<ª’€gß5%T�Aªƒˆ+l¯B(+&És)£ðž¦ŸG±îÚÚJ2Ë%ŒÁc€T”ä”)e·¢1pŒ¨Ït]úPÜòsY‡¾êPˆ㘲RÇ8„Ò)Š¢—ºý*rÀPm\å÷tî�ÿ‚™¬¯­bžìo.$¼9pŸ3MÀ¯ý Lhw#Ü®„|¦$3ÏK…ÑëÖ·�n€T;£Oç,×SPA{®Æð6V5%‹¢…·Û 3ŽöÆÉP 1¼Ëš}¡'ôbFiÞÀ&6P‡^–õh)6¸R˜i˜3gvbrJþ&ùºÎé”janCÛ(KÚÏhÞXˆ�HIº$Ï–â�‘õÒMX?qî’¿k‹v5Ä»µmùº´gIÀs`2P å‡BB\µ#×…„ù( ²8ÖÜ©ÕÔø‡Yœc=€¢ÉëðžÑÞM]8rðà\¿ Í 3¢†¢»o$>Q -¯ìhkþyx±½©7¦{E³wÇqöa´º Mó ðÛ@T•“ãœ3dˆ o¨ W<#=Ÿ'¿?Y_Û#]kQ}P©µ´AZæ©7½úØ#Å÷íÍ÷ƒ× ZÅfàê™p©­²¾’m•Dqµ�ávA�ÞG^«í|Ô¬[â5#‰­±†~€� `g¬�Œ3²cCx®ûZô�™Ö^úé4f4 ‰øžýš§¾÷±ÄÜêLº®0ð�Y–IR¸#"øÞë�÷ÿkã[ª‘¢ª²Àú ›5ÿjÏ™� ³¬ÀÁ¸;ê™ý­�“3ÌÎÁ6ìrÏ�Nò›r8t%È~Ó{D�}ZÖ•"1¾B%²ò¬§›¸;Ò‹ŠR#!í®´A£ Égîhwƒ*+ÛzoS9í-6êjÆç‰mƒÇ7�gÄ߉Åí�!—È�A·"K»§`,奶úòc3‘®!+A÷˶Ö˜9­© œã”]QŸóŠöæê‚ÌF2�t†¥Á‚A½V‘Ù§÷vwAøÝ i–ÃÑNV‹¼Î[ÔHlíÄÒRwïc<4‚CÿnÞÜfZç L?ûÖJã‘À·ðì¾/QK�sFþ›ù7ÉPxŠà ^D×Ü0Ûä®HÏ$B¶"*âXÈŽæ¾Wèúò^éŒC{9ô/1¼+ZëÓĆ4XL‡ÁÆÏŽ˜R­Æ±(2 ‰+j],.¼Ô%(ó.kª'Û&û ™À£¬ÅñÙT<•À¦±�Ck�|ÿ^?x°oiãîõ#úYÝ@êx²>—Ë3ßRû®0UÈDÎ s*KgÓ‚„H@§Hk4™‡s7)¦kCe†ó†Ö¬9¡Ïy&ÂÂsCm�¡hª&jv'Œ/ùh¡u¸¬Co_dÕý%?`£KÀ`¬9èÌH^Ù†  j¡}‰f¯ÕO|'è�‡í?pù__b´°¬ $"•6F�¯Õ9ýÔ5ù¬¬h°õ{WyÔB¾Ÿ5k ÓÌ´?nåxfž¯aï7á}»—"A´‰2¢$òù[}áA;Ýá9-aæ1€¨/E«í¤&x²ä!ÿê¡Û›ã!}ÓŽ�+w~{ï¶ç=ªÙfUU­È­fYmötr³áKrsõÜ`v‹è@Î÷~–u\2ô5oz™s7Ö‚ìV«A ‚ÔY’nYM:ù!�*tòœP •à MTq-¸¥iûwïmö o¶ÅË3ݯþ©˜{6=#SÛò�ÍOÚ©×¼óÊ®8Çÿ dáÏBé£ðª( so†j٬ѹ‰Ü["gÝÕ•ÂÖt]’œQQÚI¼˜ä>º¶ÈéøóÞ€ŸÑµº.@Âç2›0½Yõñ›®„Á‰†c¡÷Öðº9 j3~³Ó�E”4rEËÈŸ†”Æ,AÑ.ÐÕØ'WÇ[Uþâôìl}á+ÏÕ�ÂÄ…íëUÝñ¢½¾ÐµÍðZ<.¾E�öx<È %&Ëžóº½3< …W�ܸжÛà‘<.ÐÛ‚b¿`\¹9„SiýÜ‚üACðÛÄ}ÙœÄhÙ�ßÁÕ 8§‘;¾�ÑÂo�ÌrlIvL’ïŽ^P_Y?¨1psqF­�4×$ ®G¬ < §Ða„EüšCã>âë“å�ï뻇~}Ð5âqœ?¢ÃvÞ°QS·#zÚÙå?£ÔL��nŸ–nå.²ð&ãjà«áíyP4ˆ…%6ŠÒ �Ó$Ÿ"éš+¢]�{1�°i‡äuùÿnDŸ!`ï‹Pc×ÍJ~ÇÊÝP¢ø¼+¦¿®Åµ}ÝÁMÒ"k¾‚O1¿³×èKÖ%†zÚ”OÍ´t�=(£3Ñæ7A–*¹�?âõ¸ ðìÒ¯ÕúRr¤(J³)¬ÊOŠb­ÁL/›fÐ]y¹PîeàŽ>’>ïÖ»¿Ð4:ŒÐáÒ*2+%ñýÐi0””qp¶€jH¬^ŠÖ²:‚Np7üJžCœ'bXŽÞ~òo( g‚à|`4ƒ�y3®ŸšqT%w]{`Ž‘£_Ghý²ÆŠo€¤ƒTOuÒv9­dÐ9¨Š_¥èÌ�(¨õ쫘ÖÙÆ˜&» ¼±UcûüÕ©måå¿Õ’û&¯Äp:Æžug¤g€[s N(Üš/£÷ÏžÁÛ;#./!Z'ÊÆ~¨ašáÓÒ[§˜©Dú‚hÑ)ñX¡ wÐh¦i&v[gÞ“Ô’û Æyv Ã^u°‚¡Qƒeßi–¼;R5ËXJ’¼Æ¡Å“•||�ÿE¯éŽ GWÂP: HͶ½{°®.žkÏyFg¯¢ˆµ0ÎûX•ž¸Òæï÷WlNF Õþp9<Ò»@¿>Q &Â6~¢=s®,…Ÿ®–x£ÔNo3êM(ï5™e˜ÔìÀ[Þ ÔÂ`dÞ¿ÇÍñ°Q ômfbRù,iÿÃñ^ô}îð8ŽscZdƘŒŒƒË*+Í'2h²C“&iÐHOÍ:Fy< gq=¿;߯ßYèv™Ï3:ØÑÍÕµó{§%ÈØÈB�*‹ão ʯ‘žò>úMŠ(±â üÏœUÖÇç|ŒóšYÕ¾tt_ûläÂ/× I.4 VèS¢ÌíÑXç­­³/.,¹ÄŒê0Èñ³ëbÎWn8ÐKRyæÁ£ïD~< H5¾ -á;W-[ãô‡z�ÿ›SKîåݾðlU¦<«[k“íHp÷Òxæ�Ÿ^bçè °þï6Eðã Š-,ý'ܵ�ŽõË'Æ÷¨1�Óö»¶ä¨uÿ£?{ ÿѯÿèÐC@8Ò¨EÍ!—HÇB�×¢’ú×j†¯Y]Ù¾êñáù�ö ÷zï�„@;<Ñ/Ð{\Ñûv{#P£5bÍ,tà ™Vù÷@u·©˜ƒ‚Âu!I¶7D ÛC´0¨ºÚ}Û õÞvï÷ÛÖ·¦NÊ_Ž[?ä/ ÅL¾E½Î꟞ÈY&mtýJ\Äzx7i…ÄÛL\<´ ϶˜Ñõ þ‡ËdT~(–[…¶™ÁÈËÚÚƒ¡ŠÅ]Tί!mï}“÷ÖQSÒ 3G•µ•û:¡c‚5å“M¿²Þ%Ù\þnÓõ·¿µ<ÛŠ& k¢Wýð|’CÐK1›ÙA¬‘JSiµF Æ�6ÃÙ¤(Ò©Ã{ËQM­`ñ™ ­>Û—³=½_d6ý}hnbIŽ ¿Il“LX€ûý‰„=”]õ Ö/ËJV - ¨QÁÔ“åŠé_3Ö¸7ixk<�1q†} N)š,”à4’›½¡Ýõš¤" …© Ùþnwè2¤ J‚ªòOÅŽJ>w(ŸšiòÀhÃàRX)ú;à±çé2=Ç5Ký€/´ÏGà€�¸QÞñut´Qz2²IN±Äð¢玘H •‡ñ´ &qËʺ¦ÀÏUÄíÊ&4’LÇ}uGYnñŽo» V†šbs°_&ù ͦ}Æy­T\²ÀóçÊ!‹”^gßRâê<‰RŸ„gi�ç÷ðž:ï‹zOùã ü¦€@¶¤…òôÀϾˆ«ë.þrí«; Èt[Œæ*OTïpÁ,!¿.ÑÜ]¸˜¥*%ýËh`?ä0¯°„<_Etåoš�ÿhÔ?ÈñÿDLžÀ:\cEÒÖo ®SÙ|xª6xË—„à Ч”G½Û/)Z9<¶ÛáL�BÒîŽâ>�ÑíI­�í&“%{¬™ånÂŽŠ€]~Gó�7BUÃÃ×&@^ ÞøË˜¼›á7Ðü“éý*˜®nQýzŸÇ“é�¾} ùÊ$�p9î“o©»óØVñ-Jcÿµ¸vy("I[]1ùM%À}æwÓ_�ÿÛBÑÏÝ1yU4 FU.¶¹3еˆ½•c§´¶Åb Ib+¯œŽŽIKT�­ÇAÐqqÒþȳªßÕŒ®ù|A Ì@â ç…ôâäþ\&1)Há}ªÐêÓ@ëlÿNZU§Cêj †þÿ`9î'õ ô–#�ξúoV�:­iÝqQ"Œ~]êøÀ¢yK6í½23G«{ûö�ýt@n)ùcñ´‘Ö�œ¥ËÀʾ†¬ì8y˜({‘NaŽÊ:åî4¼²ØR.ZgÃ@ ‰Ä6ÅdcæìÙ4É{fÍ·±¼a†Š…éÖ:¾+0·Êkßp ÖX[šUôä‡bð’¦Jv>ø&"Æ@¿xy(öC#„!×+ œ{ä 1�;qZd±˜¬ì2´GtêÀÔ[;U{�…ì;Çdº)š1>DQ¯E¡§Ø½´èß Odr ö5š`Z7[÷‡&�£¿ ²�árª‘ÉDÈÞ{¤_…°zÝQè6uWI¨ôy�óT@þÉQ꤈¬|bŒ)Î9£r=¯mõu÷Íg$´)?��ÿ!%®¿]œ™¹¹÷ö’ï»ã�ïÓŒéU ƒ…ÎiænV¤›º¹A2ÑÁ³Ž &¤O@àm#ðçéÈÑnSŒü ðà’‰j5EÒÖÔÀ¥µaàÚHn×͆š7ý©k(ª­üp¡®Ùý:¬ÑW¢áyRÐ&U–#Ze6÷áAÒËQt#$ɼò¸_ïµ½ävÉ«…QO����˜±çó#w°½uRhMì?ÂD7sªå'�-ÝÊpì n<¨Ú½ P±æG){âš-Z´H=ó¶Û6¦ÉFËÚ¦¦0<&²é“j9<É5’AOž=[_ØàËFX5”Z»'‚.„|þiuØ’j`òg Ï¢Ï-Ê �£dîFªîйº)//¶³;´ óP6¦ø çIɶÖF»�¤�nì ¼`RW¶zÂn£¬@�¨�‘•{�99QÐÑ~ÆèÔ@^ãtÉr‡�½Ïý4X•z™WºÿׇwÜ=_r$�àw¨ Çø•hÉŠR§ñõFVÏŸ8ræ1½ß9sæàAÐ×€8àËTÞ±øÙÕËv�š—ÖFò†0¼$“¸øÄ³4®8W,;ýÁË "=ŠFÖÕ²j³ÒMY2 ÆÐõ42`У³ÁýyÔŽŸò¥‡¦ÚG3ÔPx™÷_HÐ/A‹ë�¨¬.…ºï#A*üöDxy�ò<©}±tw—ÿ‚º€<²àôY9#ثĎ0<Cýôã®�+ùØä…ËáM…,«_¢¯[)Q<¥ªêPõž|?>�ñ¼ð<­Rÿf8<½ÌÓ!9ú/ÑÕ°˜F1E™•µ&‹É~J#¹mòJŠ–crS¼ä•ü 6ÿ¨æÃ‹_�0ýìN^ÿëëÕíiwŒK 2qÏz( {@“· õ¤e×Àë$¿9z)ÏÏoïl?¸%.Óeš¤ à ÂÊOXý^Zû™`�‰?âÁ‹kâ©@`8 ò~ àñ‘æ�áí¹VÉýÀ°nÆäÑ`þê —+=ùyGØW "xn†1Yt�°U5n^Uq°³ñg[È¿h€Ý¼s°�òÁð&Œ$Ia$›|%ê™H��ªš]”x‘`ñ]b¼d};m9 �Þ‘›�9×ÀÖ¥qPØÞVž›ÛSL!÷+rûÎùo>WŸL«$·’½ê40à¤`jA[zªÙ\Ö^Sc$õ ÂÂòTq¶Å05›eìÉãJ^ò÷6 ¨q}¨KóXUžbJ‘�k¿þ¤íh|zò·'Ú«ßå߃”•‹a)7ÏÎß$2F21y¢‘O19hŽ~ÊÂ2å yÝÆù·!#w~&’ÿ¼ü!Æ—‘ù©ýbn jصŒ.ïCWÉêZÜÚã.Ò4Až ú«ê¦’¯Z£yÄ ’õHßvpÔ–~iTˆ°`%z².ÙfS“?µìô³gA9a€…¡bÖB6‹ÙŵÛõèäBÞ«Pv‹6Ü·b!ñ!´²¾€½Eq £#¢ô„`ð]hâúÑ„ 1Æ„>V Êc’zHRâ/Gxiá¹ïæ¶bW‰‡�ì²ftYÝ£c y9&³|�°kWdxš©ã‡7 uG"ÐÛ ¾òù¥²Q­�©¡1«;üçZY‰Ü¨®ïùî³J�m½Õä—åx`] c8EÈê;“æõ"ÄnXy‰;ó¿ï*"ç…‰”V#R þ¹®²^¯6þ�‘ïõÙI÷ö•óo§õÛ&À³cô‹sSsß]PöQqzÁGý,æ;€fU)rbâ>úàÑPd§¨Ä—0¼Pað³±o¼ñw�£ÿý÷ ÐÄ"ZæÑ1Ñ �Rz®ñ¥ympð;AJeàY¨jéñ@­ü£Ó Ò·ÇuSùfÐÓ"v‡üuÇ�XçXF( ­ 8#™zZ“¸k™ÛÒ§/—ÊPÂöv9忇>#¨PÁ­ ,©`6KEæp™ÉÔã�£,fæ »ûÁGFtc%QZKÕY®/€H%ÀÂB¡4¶5ÀªõSr2|�áAu œ*°O@|?¥Èm.Ë®ÏóK¹È÷�M3:àùl´\fx9xŠ/ÞÑŒ¹‘€Òúï]ëÛœ4Î�ðYz¬üWµÙìgd�Y¸»À°6>]úÄ»S¨‘î¼¼)éý‘ºé�¯ŽußοÍÔhªšSÙ·[ýΊ²´´#ò� ÝÙ)Fþ 0Èuí&Ÿy(†3m‰BëÉXl¬+8¶xf#Š-|<Ô�by]H•ÅÌAý«òzE�'ÙظhQ˜Oíß>© µ‹8S½Ç¼4ê÷ÊtÍIJbÓìèŒIWªw»ÎDA³ŒÞ›ð]ÓžŠê¤£Ñû÷ÇûýqÁù’A!×Ä�Ü1\óg6Eü¦á%BkÀ=ɶîÊ�ëNËÌÔ|ùCçh$(.ád‰‰?-mή:œ+<âø*HŠÝ˜‹p†ÙbÚ�„>îß³Íë� ¯ùK}°ŸÝB?¤èj}<æ›g6¥Ÿ ±Fûùg5OüÇ€�)7ÒúFp£Õ4‚|áà ~~|¶5Ñx�Ü9Ú‡áS· åêgF¡ƒ¨ü\QÆ7‚�M¡æ½{ý$4N®û{¯ä<¬jé¾Ö·IÉ*î×�Nb8¼ÅJ ^Wý«5söé@÷·ªœ©cm¦…ðô ÝsX�+SC�7ˆÆZr?‹›=÷û$ØbÙø½(¶Ë9™BqýQä½Û¿¸A±ØâZ8ü·8cZÈFÛv�5›™.Kú˜ƒÎÆ] ß›yË=ãüІ‰E†‰í+ÃŒ•UU1 Ò7£32zŠƒdüëš"9AΗT:C0Óù†¨´tDAzG]8œ�‚çBœçq(�.4ÈÚÍ¥8‡É1ö~=… .½$«ò]ZDØÎ§ÐÅ<Ǥ„ÔÖÊe Ú{§nÈäÐ÷ÌsäHðƒ‹‘̥áT«yº&Q^]`*"•;jH�%¹`-–Ìâ—àÕ^”IJTk²þq®·FONš|³Ñž‚bè‡Huà,~aЩ{ (¾bº ‚‰mØf�÷�Üþ‰üJ0òg^w[8˜ Ü´Ò1ê+d¼_Õ:o*J±žÑà ½;hhÞšÑ(<ŽÏq�õLäGמP|²ÏA«;.+ÝhvY?ì­ê}ôú'Ãß¿@™;ƒáÍ£ íõd¼‰{ÀíN�—ÐWŠe#ÎÈ 4w9 :Ó×Ï+W÷�ý#�©ÐÿýB—_sçLè)KÛ=ghQuŠTj¿0ô\*@¬ÒlÎ�0iV6á20òáU6Í´Và©|QŽw²¬Ùnç»è—æh.F‚sóë2º®ot¶£ ‚�àµkaÏ.ÈzÞ�­ž¼ íþ+·uE.S¢~/ êsZ4ú1׈aE?9f¬Ô‘`‚7¹s0ÚßœÃMPÕ]ÝØ1ÀhV‘:©ïTì'ðu?ŒâÞ"tŠÁ�q™-ë†gï5±¦´,w³/ixI˜þiµëbQ¡ïeùÈn‡R8ÁS1ÞaL£ê¾aÔ ¿È,ƇÀËÒ¥qÖXÐÙZ2moÕloÛEx ‚¶´‘`o»mPvaŸâiW hW>¾Í0NµP$àö{~Pcâ’E.—»é1<$_:¥ØÜÙÜTµŸ±²�lD[³µK"… ýó—^òâ¦}ö9 xÜéQ–º�`­“ãL¾‚&2UÔ™ÙˆryÁhoØö³·få¢íMW­¬ÎLÝ®«h+œ<}è.\» w`öV´v—–ŸsŒìéݼ€øoÀ æbRì§é¢¼DøUM�-µ½ /1>BFñ¥ˆnÊeY#¯Î:5®*Ý„Ô(9òŠƒÓÛUÿv@ŒÞÃä+Xý|‰Ñ Eé¹hÞ¹yQE�ø�T òÔ:ÔÝóã&:DÏvXò é…)&¨�Ð¥³)öü‚Äàj¹ìÂz_!a¨;ÖAÊQ¹?ËòÐ'èÈ8DL¬õNÄÏ44Ø“$\ ‚Ú„ZÆvW…oþÑZQ*OŽ™ÜŒDŸ ®¨–h¯ ñç~‘ßGdúžª²îÌ8.‡—5 ý'möÉ¿�÷ëqI;ÔøýF�FÁõCžÄn€:IÐ�¦šY.%#·{+o¼üÂð©©‘~•›ÄŒÔ#§é]èÉ�ç =,iW»Ôöq™Æx/Bvb ôæxØÇ´6v�:4’““@<³q×Ü> Äv¶¢©å&‹‘ w7œ9 ßõg›fæê4‰ƒ ÷ Bk3¬Ö¢ Ž.YþšÓÄÏ¡ÏÖ€p¹Ç‹?n*Ï@ÚÙf…Y¥ß :·þd¨m‚$Ôg ŸKöÝÈä1Ï} §1{ÃæºBÛ¯ëÊfÿi(> Ù xmæý]ñ–†óûô‰?óÙ×€¡)UÀ²iP÷>Þï`ô!, FBKÆèÛãÙÖëæ¡.Y¿F-7ÄÀ4£Ù[7WõO5Ø{ïQ9#N S›ŪsZRZ¯jŠ5ø£>U×fP1z±qDiëù)|@ÊÐŒwÝs×1E!ëƒ(Ÿ™>xHvE(™‚H ÃÄ3~†Â9‘ŒcX<Ôþý´jÊÈøù<-ÉùíÛÓ�:8¯k4HªÉñÃøé>9Þª©äµ±¸èŽã�Á°3n5…RQ§¬ò…H÷w¹óe�ÚkóÑãFøHz§¬¬L�´‡Ò é™7˰âàKvá!™F9hÀ‰]( dá³AHSAX‹úÍ~o㨢<ÿ�ÌÌžë•KK 6Æ×„ àÛˆ3ËÐÍU±Ó”}]ÝëÊÒ¬G!ñ�Ð-èr:…¸Bœ™¾P@j*ka`PiÝï¥OȾNÔ¥>¦÷cB1ƒÏ,´þ5Å 㬠(§ ÚÈÆ" î9X­u‰&£9e"½[,¬}㸂´žÚJò¸ÜQ1KÓå€üFiŠº'Pj¬.ùo<åäïN„×8eʆÎÔ=íaù,‰×/�hÔˆ\ À–*´kÙ¢ÉTèü†ã�©ûO+²W—Z-‘1ý>dõ-k7õ0Á퇼U“�}=ÀZ´»)Ñ-w¼�ó7^Û²)??Œ§²ARÐ2ÛÍïDþd©,{SQãz6à_¸=Òä!³ùŸÀy$¯w *üÛ �fDbáA*C Œ±1ßô÷YpìƒôïM’áƒrè,=†‚ÑpIÓ½°/v4d¡'+œŸmë´z2ȱS7³Ã\Y//¥Å�[¾ú£l‚j8âAéüxäŠu™õKFÍ"´]0�ßc{Df,º©ãɲ Ðô±Å£!É;s%Ò,ؽ¦É=’çô{±û6ÿY ugºk¤ZŸ×?¤ox#£E�E ÐA˜¾mÎvnìÀo±‹C‹�¦]º&½�öeº3»$†ºM=!ò¤�£ûU{ÒžûXºÍó§åmÁ2–g§˜X®‚gõV˃÷ž‘¢þ.Ó–—ò,ú…š"µÒårw…oà)óOPÅ  ÃägèðF„•°4•6lìå1Ž~ñ@4šC´äª}¡ .QìK#Eƒík`itå} ùß ,)~ª¬ûú!õ�3û�zæ´[ƒ¥@@PûŠÑç¶v[ë“?)OçÐÂ�¬â]¯2üã1UÙï�«wtt³W¾ˆÜ&¯°ë¡yÍÄ P}aôDâó‚’r*J¼_äÈÀóBHb Ç0nä—Ç@Í gÎ@Ñ2N:ôc¡–±Øb°m9õÍÌ %I10 g� ž K7í뜿8û’'J�9ëÑG!*™tøãS�:ÿ"îÖ§ÒY•Ö”ˆf>Çc"ÀW CÜ\»³Ò�†‚œüOƒ<`îðlkÕцmAÝÎ k4|­Išƒ31c<;6@:¦…CŒo%†ÿR6éëïÄuL²Ã-¬ë¬õõ¤Ébz�Æ+àÓx í¯ˆ”­OõOMug‚ëX¦¢?€úb!î‹�£nËË �JòÈä¼�…kLãÊÑå¶ è‰û0î<›²ö-¿Ckœ|ÿ†e•e.;kéÉç‚<§4!+Âó Ô aèƒÐ “Œ…{Îᵨ]%Hæ‡Ú»'CÝ'ˆp2�‰¿Œ/ ¡›ó[4XÌ3‹ü|ðR€’XG<Úå/L¾™£Hwibùå�W[0±ÿÂ_iOßîö�\Óá»�Qé7aSÆ5ílg}È)iü]±°¶8ù»ãýz\ näæ.˜0ÃNØbi:NnpòÙÀÉg—ɬ:XÔ˜\„vhš‘e#½=õÝu;¨È)‰þ1Z©‡4Íœ‘á²FZ ‰”dïÂC{DÅ’œ€Í`È‚’ð^QüP«ÿºh`¾wN"‰Lž ’?T‡æÂpè M�1»Ñ ³±.ÖYqE¿„'—X•è®Y�¶‹UVõ˜"Â�¨Q™Òžx4ßÑóToûüsùê«ÿf[æ­�€ ¯¬°W ¼¾.*2ã Œ¶ ý˜»göËñã{ú�wGw4EyêÅàð¡ÿ 7n)`8Í,©Ì·1&úŒ׌ÑmÝ0¼‡%þ]åœl3³C…ªá´x­µ£®‹ä5ix�ïþù¶~fFyÐÊó0Ѽ[×ü`\:t�ië(“ÎŒ*r­’Û8&X}¸Á ïŠ tØ-Æ+á�âíyQ”'ƒx_8;åçò^Å=r"êBÑóE™úEˆ0"ˆtÿ,„ìÁŸ>÷\Ý´[o5§QÆr OõCMÿ¨�OúÙí ø©Nr»-a½zW9ϛ㢃ñv—Iþú[Ý¡ÉðPnŽË Òä´|†æ¾ „]>t<ʽ'¦ÍmÞSŽ‚Ñt¤Kô*Fg hp>ƒ;�oz#i·-/I­|žÌäûà?†tÓPE£—+bäÅc®§ì΂ôyž~^eº"]õÉsFÎÁɺ¬#�Aç&y�Ñ/Á~—H³|_ï½’A†~f £!‡%,±EUšµn�ûñçÏ?Tˆ$òQ6Þò î“N†b#1QR‘š°› Ì'Cn÷ôýüüÜÒâ8·¨ˆÔ%¨UmÞ|CDÊÎ}1¹hH—Ø6{¯,ÔlÆ£3öY(%µ³>-íiøY œ¢¥I[:£(mÿ}¼ÇÅø=H‚�EÅêjô!·Ieàš¢k*$‚ñ ’lê£Æ†}ËÊ¢”tÖr°£Æ?¬x@~TbFäxZv‡«WÓ )æÜzô²¦%t5ä¢_†¥ý4qCº'gšË&g`:ŒÕù�êy#wûò… åëÚŠ@ï&¢G–aÒ¡èð7d¥ßa 7×&�0§¢rYªð‡Œã¤ $Áz–…1 ˆÅÕZ™Q'Bìs-—Îo_…âÓ¬knËn#YvjþÝF†¹—†mD˜/ÄTݤǷ¨]ìàýVxv}"RôœéÅÙ�dœ$^Òäß—ŸA»c_U¥P#¤<0à‘£Ý œn¥=¨i5˜­iw¯_RNƒû Ç ¯ww7D³ÒÓ-­]ŠvǸ~¤¢ßÒ'ÏÁ2Æ´v¶›*Óü×j¼«Åd³E�¦al úúæUÐ<ž…ßB�ƒúEÅGç:Hõ_#E9«ä5–AûµÌ|…]íýþí—<$ ’Üy%)�,Èncâ%äñG|×{½Þï‰H~qÊ‹ÈÙï•EjYû–eM½�.Y—(i yð9ŒCP ãÏÃuí±Fsš‰>]}O Ç›ŠVﯣý6B¢O&þœ‰g]‰¼ú«+2ô&ÜwFìc�&ûŸ> |ÆÄAHŽãÝ*g‘™ÖÔwå­~´ü׈(ùý‰þºÑ/Ò¨èt–~œ©™¾H¡ Á¤<ÖЇ�u΂ͦ‘}‹ÆoØT¹iñ‡ZüÉ3 ÒútÎÀºId%Öd¥uu EGç�ÉÊ"÷iÏy:ÑÏß4b™ÌzÑžÖ†rú]ȪTÅ”âÔÆä9HÃG»›RK²R„ k?@òHb¥—÷`òï\ùáÜ#” “¿9Þ¯ÇÝøVz¢§Ä5õä\&ÁûHÇØæ%hílD{)8oõ™*ºLÀÿZ�«å3ÒÍDŠäϾ>Õ`´L0Ôpü\�&C4íóBÍ”9·ÈNŒ¬¾¦5x)èò^âÊí‘#W¨Ûr÷ UÛÔøÂ`+*7¦úË�†ÌÔ…©L1ºj>àI³c2]È0j+ÂÐͱzó>BÓHÈ–­©f[Œâ¬áhìZPy7#6¯Š"ÖŽ3¡Š‹ú”Ö´ùË‘›½ -¼õÀý`âMå¸yG`;õQþrÔ5©�ƒ¡œ²wQ(+çñ•gf�¶jÚ¼¥Ùn<ÿíñ©Ý?Lg kÊÞI«N†ð-èæ §"i�(ä@·4TÕ¥;`ô›4�½¡—ÄRÊåûÓík§ è*w°ÔÏkrûòåm½Œ#òÜMP¿¬é�£oprŒ½¢¥FþÞ™¦G{TÄ`§ 30$Å}+þýF籌ö–¶àL…¡>G:á!{¾ã}À¦!qE7e�Dº†‘½lí1p&^î)vüBÆŒü{¤sÖ¢hY†I¸øcÔ}¿ü%×Z“Ü÷w�þb;­Lð**D=™‰žZ²¹yÿ¦ÞtïñŸ¨ï«�÷ösÚѨ¾“OOný—vÿˆ –J‹€+ÔÈ¡�¢i9.I?¯)ÈÞzË0{ÂC$ç,s¦Õ%‚”žµíE<õõJ¤=ßäÈ G"ý8�qM@G(Žÿÿׄúuž )Óž÷ Zc"•ùž)'Þ§M͵}~¬1­h ÜWÕS}¯,\å`A[€4L&îgòþX¿9žŸ—œor@»;ý%2 /Šg!Ì„·ñ‹¬èw©<ÿ¼Ûü^Ý«²¤Þ‹\ òOÚ}Å™E3‰WI~OºÙh´Àn%‰ëT�='%”Éòãó RPÃ+�Î$ë2š¼'Õ�ÄU_;c¼?{´»/ 6=ÇBàg@]Ì‚—Ö/¾yÁàßz3Šž 4ƒÊsÛtQßåŒ�¼¦¯ €o É�Ë[,ý" õ  }: &|&†I¥8)Þ!ëÛ.,)ñ×z)�u3w!·:4“s�ذCìò)&=ŠÞ—¡f°1ê%oÀµ¨®�‚¡ÿÌO"4!Çካ!–Ó߇·|'¼•€Œ™‘NÙ$éÊ=`“z\3?²<ÒUf  ¤‹Q(€Ë|‚ôÇà<“í�…xÑdB!T�œrX)¿p  [¯´Æƒ ÄðuŠ*o䙀…ýJ–Ù/zcwð¼Ô…¨äÙˆ›z¶ü0‹¥é×Ä.$}0ˆ¦'¦¦6÷2¼Ôn(›„´ëpÃÿ¥Â¼:ÙÚ{ø7G¼¬íôŸfæ¸W <óÔ„ú0¢¡C I¯@ æÀ$LHIÃKÒ-¾ˆ5LJָáÈÛ›Œ,›…ÞÁžë<dë<¥¼‹P .ƒ“�?ÏTQa ç0‰iÈaƒ—–Z“•¿!ŠzcB®5aôɤº²½=]Ñä €£ÅnBÐj`¶zr¡Éñ� ¯16Ñš­›Œœ ÏAâœýØèŸêàÙ·Á*ø†ÕÀ¼Gèu@Ÿ‚£�kïUB"矰̑èyo ÞXé L[#ásè F-À·ç!½cC ¦ç^;QÏI&0혈3ŠþÏâhÌJQ{8…8vdz$É ½ünR®£Å`H)YÕx*—µ}k2¥µ¹+p=�vëwÇë³cê?Ù8 õšý+Bîr Ë�@zÂêZ>.ÍÔvî°¢îqY–®Ý�uë᫽‰|œWó6U6ça=víûó:$ʰ‘cÔÆl³y ÑtxöB8„¢Obh9½‚�ó1»‰ÏÏþó…3;B³Hžg 4ÇÀŠ÷ò’ƒÐññn»·¥ Õ }G‡TU0r6öȃ�öžˆWž†Dà0ôx´Ø�/÷VÔ% /äáG¡ <îúqE�@IDAT>ñ3�(€Zrì¨ýî¾n@F˜<à~: hšVŒIô�ÚýŒfÚ¾ìõ9§öͬ5ˆb Fi— Ú:Tm·ÂgøÊ¤+‹çüª)×øõûÝ;>|c—Ð'c8khº‚KÐv[ä÷„~‰Ó湬"ß‹\ù­¨þ?]-„þÌ‚ª¿`º;’\•O\í þ=.E>•Yö}�H½l)%('Æ'Øê®ÁLÞ2‚a^MÎx3&ÉR�ÑïG.üÖÌl{×h(CÊçu„`­°Úå~…ý3I9�bÛnw𜣱¼~¿?ŽÜ¶ÜDšI’YZtô�Nî‹�ÎÀ 6Ž{ü7Ó>™a0*áP ¡·A')†Í7ß{»;Êÿ q # ô‚Lü{Éó-f_n–žû¥móšF|¿¼a}èÀ>£4Îy!�*ÿFÉý’ؽ„Ü{Ø„¾Þ·j1^"h–)ŽQxž-À½µScº;Ýþ]'›×KW$è6 .'Æ€{"±8 ôyˆ0»Ý’øM ðú»¢Êü„~§Cr#ˆ:r©pv9�N”Y*÷p*(Ú*Hn^£j5Žé\0æ÷_Ö~ü1¢/m]œÒ/ÇøÇbvÃÐ’“úÑÃ7·ìkˆF¢K7v„hÿjN7 zP÷hSÞYe9¥—ý›ãù÷q3¾E“ω |haQãšòœ1?­¢·Ò8i�P£Ú¤ ¼Ð>;Õj3 «:¬ÛE¾''jNiZÐÂÃãc>©Æo£B´ú¦1‡ä­gö£ÄhœzÓgByv:r¬/¢˜ô¹NqŸ"µq â|*¦~‡=‚Js<û' ®‚'ùD¥7tËÊ Ú + °ØGÒåz­Sï<­Ï°²m�xŠyãü…¨Ð 8¡·nX ö5¹ÚjföK ÂîRë[»/”âÚ¨®"mDmvÒ�§õ±v%/4!è–ùÀª€A©²ðá}²\Ä�ã$ Y—ü?’l²äÛ¢š!æØ¢jôýY9©ênnŽÌ�WCïÞ°‘1H D“ðš¢K�ç"R4$`ùôQßRhæ:lØ O…]šä­§_ÔÕeJ¦ˆ JÌݸ©²qåØ�§áI?ŽC "€[‚¾àT ‰�6×í…QþŸC‰ºGÎî{�PÜÿ9xNqŒ%µ·á#E1餸N¯‚·Q¬°ê´Ó¯¿…õq_!EFW@˜ºq˜÷æö‹Ùƒ#×Ûæ Œ±Z¸— u~¿�¡öÛSP­Ç‚4Ön¤$C1±ü‹ù#Ÿ“…nEÖÖc²�böµ4�cÔEšúuX–_žZàØ×û¤�)ƒQdŠcÒ´}šQYáëÐ6Ïî×Ï}M¯NÂC[?9þÝ&�c�¡<´ªõ�·dIÂ[š##¦èÍ�À{èDôú%z[‡/ð%Ô|KLTš­÷‘¥N`ïmg¬—1´qD,NÏÜ� ¹PœQI6EpßšÙ3áõþí‰ôžÍ"ÎÎRH¬´<Žñ<5¯ÑÂûýc+âiíox²ç`�墯�úëäA Ÿ_‹,êb%P+2¼žá  ô„ïdX»ù '¾,+Ê?‘.ªÇMWdâøû€]&fŠÝÖwoÇù~&'«Æ¿ž‘��0¾šÈTÀ×ÝÖ6R?·ä¤fÏX³æ×,L´éØ 1{³3¸â­Â­PIèÌ‘}$—$1 ¦Ø²ùõ^·ç쬬úë&e„’ߟ”¯(p¦¨t%¨øfÎLäǬ2óÜùÃ2Rse•Ú™ÂRÏ䦦<Z× Ù`ÄóðëÂÑRˆ\WE¡ì1I)EÖÔ€gF�Ö(" †5æ?úëê'ô;�Fר9~T¹ë d±‚ávô<þ%¤êvÜ#Ž9xZÏGtý¹;(}=¥(­ŠÜ;F&~SO)ȼò�ù›ãðáq1¾°•0:ôXè¯ALWoF�øÓ¸~i‰BÚ±ÆÈ1V@Šh+žØAÑ’›<8ÀïúS·v…ØäôO]²¤Î@<\G{Míĉ…q3©Ì(�&ë¹ÖB¬ÎNr8ZBÑè‡qJº½«r\%iÒMq�{gý[Ï%Ô†“û¨è/8ÁP9¥û±:ýÞ×b–¨�ÄSZÓRf XZŒ)4=ž/’¨P�|ÚÔé³øÊ{%àE Pœ-õâáíØ&Éù¾xÊÈ´+—UvYßZgû´¾+ëM·[Ÿ‚ê3Œvˆ�±˜­zĪg±õOH‘ ›8vò1î|ý®Ý&–yÀðZUWo ˜M/ቔOÎsϺ;vìà`@®/éPÄÙŸÒý§!©–=е[ê±¹íÝ®ÝÉÜ5Ù¶Ñ$ Óz[Z/+Í(|-o/c{ô{%F~Â"†šnt:ùw¢Ÿ—eJõL,Ÿ¡hõ¨±Þœ™!Ûê½€×e¯@««ú‹†)’³½ç-n4¹Þééíà�pt7le4u.Ž"5�éé”é Òa-Ât=C¬‚÷Ær±OngÒк!_AÎÜ)0tAPTŸ2 _¿¦Ý=rKG÷yF�»+¦h¨l~Õ†¼er¿Çz%“ޝm©g�>„ c%“Á¼ë7'úgÄã« ¶5ïw67ÀH& kk4ò…®«_šyM”Ú{ Éï€'kFqôÕözW—196¹±±KRÙ)Z¬CCL­¬Ñ-ÑØ® °a¬²·L�àÎI°ÙàGi_8bÏÅ�HÕ ]ß!èÝN]Ò¿Ç!üæ8ðA�I)™æˆBjP¤HÑLO"JËCÓØoîùãuŽ Ú�È­gÙøEìx_ß}¡;ÊûçzŽ5Hr“ïpúÿNsìƒ8h;rÃ× '7ŸäZoçU„Pƒ™þH’}_O>ŒÛ[¹õ¢²4ôc¡û °*Û»šiäf#Û|­¶V˜hGwÂ%Ç@ÖÙã _…¢Ö™(ú½n0 ®¦vOv7ÖÑ;�ű¨pž…£:X¨ðá… ÝüøÓÌ™¿â‡I1hÃ¥·¡C“Kø˜ º‹SŒÜÍí.ÇLæ�Éú7àv!…ªZÒ_%†#9òúC] ÔnÒ>CäsÔÅFUûwkqz�°%Y‡¤=”’ácb´þ¡7ª ;X–W¤¯HˆD¾'Ë‚*·u@¶yŒþÀùî°EÍçæ‚=ùw–zÀÎ",½³y!Îo=&žO�û]BI‘FBX¿’îH7 ´:̯PhTÆRÙìO•̼‘’�ш<~gÓ‰� X=3%½ ‰Çs%�k�  6¡ u¾<"¿—§­sÍ{)¼àf4:\q ½i×ðüÒüˆÂL6±Ú»à*¸4¼oÛb&÷I¼ck¦ùJàOÁåÊŒ±O7 µ (EÑ—¼üER´3ù»ÿéusÆ“šr«³Mº&³ºø¾÷Dû?ýþDûþ•=mù“rí§‚ž�m÷…«$›Ò¸)°ëO)ïc6pfZcZ'B:ô;½Ã¸ÑO?D’H5ìb²lóY¿??a¦„bñMÛ>«‘ù^¿9aß.kõý RlXVöX M¨r÷tD¶¤~áwäŸ×-S«€œêy–H�^b¸gÌ,k6š3s €TRÿnõ¶=I&ì?â`�‹ç›jŒ¢‡�†-æZŠª5!Aù{ƒÝÑâï ‘Å BZàÙ8½Ú×¶ù�·Þµ_(õêÔ °î?h68^Úê Œ^”Ãx¿Z­¯J”ô�¤º �SÏöùN=è¤2ÀA“‹ß3¼d,ðòtE@ø:gb›wmq’ÔÆãkÖ°ÀŽL¥-YP<0£åô⎇eé;�ßÕ˜4¼¤’¿¤ÑsÞú›îy–¶ñwFeª¨�²…7~ôê—?òœÒçg2¢¥ój¸ÿSQ7¯”5Çp• --«QûÚŠÁÔ¦¿†B’¦ü ¹mþ)óàé'Ï1:@|Ô]Ñl3ò¯êzüû+†ù“ß“×!”[B)"!ËCýÕo _½Ã.¯BÓBUP<›Ó(ºV…âSjjjRv®[âÅ)XŽDœv¼FÚÊák1V;ÜþQpþ�³4¥«¡!‘:!çknÐÑ�ˆ?–á%mÆû|>�$< ’WE3HT‚]Œ®ŸlOê¡÷¸1Éú9|Ê!°¿ˆ¢ÞWš“cŸ“ÚD<Ü@ÿS™m4vZoO4Ñ<`Ó¿–Tê6´B~¥ÑªžryùŪ¬ß&[èù¿gx‰ö~op,JF¿“Æe|¶žÂÒªÓ]R’Û{�ùdyÿ´ Çe¥¼Ž ü£4³nJ�£ò†—Ï#sÅfŠªº]�kÁ?�H;øÂhEátCÃÿÓ}"Ÿ#KmB¢º²ÙÝx“<¯Ù…i+Úº¦¼ŠšÇÍv–Éì=þÉE™N¨9¼„çq>G�ˆZ¤Ä½ñ˜øFIff`«ê½þñz\Œ/©¨ã‘Ù‡™BÊÀÊ¥§ýƵ'&!9%°wàà†€mžƒ¡þ!苾tÈ;ê7eæ<óÓ€›2#DjÏšÈØçÍÁÙ›Z[�С¢5îj É¡< ÕšëÐc&ÌÈGœ�y»ZV園Ø_¯oö­þÙ%Rê<�±T4¯]›˜ ¦ä ËVyz6ðF fQC=­ ’c»µ]9=c¤ÚŸçÈ»Õfþ Ü‚}¸ 7é¶À=ƒg]™rÞ€ìF_<þf í¸ñ¡q©¿£"ëIÚäÿãî=पÎ÷ñÛïôí}ÙÆR—^¤ ‹ E± Æ®±'±÷¨Xcïݨ±EeAé½-m{›-³ÓgîÜþÎÀ¬ ¢¿DÉ÷/Þ�rggn9çÜsßó–ç}ÞÄí‘чÉq3°Ç(Ú§¾„ ÓÇm­q(^UÔ4îë]–ZŠc»ž‡/Æ€j�~”6Ôw®è_�Ô'®Eö õõ&>6!a¹ ¾ö{Ý}NƒßTSµ'±>f0Ì#¼n¼(§å?�1r¤¢çEL*L03Wc¸ù{<Á›«|Ñ?ó ÿ´Ð dâ½F•”ü¤†Kî�*NFçoVu~ÒRmä;²•efFbQå˜ôÏÜ2µ _áÿp3önÚ‹ø1 IÂÈ.šnaì—Í‹ÍÉ Ä�‡�:Q³g(Öùì·M�óHuL(º£¥%ª+�_Åúþ`”»&,Enà<Ôk¶­"åŽ~x'œ„ÍdÄ~ Z¿­—ßÿæµ<jQ£EŒmˆÝÚJº=ƒc]ï×úégïtç5ˆî�“s{X3¶Œ9¶G²íò‰—_Þ‹8Vû‡ (‹ø‡…çOB àL™ä¸D?¿tz}ÈuÿcØÕ±Úõs¿‹¡î#´ž€Vx5¬Q�"°|.ä XÙ:Ì­G¢\”Ä‚º6¢hL쑺[ K¯º2GS”¿·Ö?ƒ»Â ÉÜÜC’7ºN8Nº^ö_r=ÒxØéŸàm@(”G‚îÜyGMâ:øR’²m7 0s>´^ï.D�ߦZrâñ»�}þFhs(Bµšñ8´é ¨÷r<}·Õ–<“øcˆV89/¯³ÝŒî°ÐÆÚºúNâÞè2‡>é X¸‡¥Ø„T•‰ßºúíÚº­ÔAu€¨…^ÖÔ9J™³�͵øÚ³Qñ3ÉÆª_¿´~²¬¾ÑœÞ'¡-bŠ~ÓõpJ|‰¹^‚`›5·°$~oyó*úå‡Fû/:j.˜Ü7¯Ë¯FàWõÍ�ËÁc{9Ú3 n³È™É¡”;�(Áì³….(²‡¶š…/IR̳Yqþ…Ä÷‰=rÆŒ~��¿jýç0¿Cðÿ¦Âï 4œ?0)ð,ì�~`ü¢.ËHʹ(ºW ü­x4æ^h÷án¿©^7²!Yn#Á³£ƒ¤dÌw¶‡ÏÛã�ÎÛÚœ»£#:ÔÎY{¢^?`g'G ®mŠ/¶¤M'#ÈÜ¿mGw³Ž -67JÖ{Ã**=CT|þ�]€>ÃÇËþ19×z ȆúaXVõ û‚cu6„ɣȵki£ï™œÔÜy!§påKOÕO)´m›R’µk´–ì^ÓºCÜÈbYZÏ� ÚÞ|Ød8T,ff ¹?¿KŒ!ÙS:¦ÑÍÁ¨6AÛ¬öªª®ùÒý¸_ûç_®w‚HþdO4ö2§ï«ò}I}K£ôQ8Ó°�xe éW{sˆë¬{¶mÛ¦ÁzZƒ²;1¯?UTµ>_ÌyL{ÌT€H ? óì~Þ¯õóº—^"ý(&åvàëŸFñ„§A¦ó&/Ÿ·­ž kïè¶“¹ë¥…Þ,Åç‚ *7·Èy³h߇%‹ä#ª�C Š�>ç—þ}\(%I#ê}޳ˆ�DS^ÌΉh5Ëß{Ë;bÄóìëo) ‰Ž›�l¸*Ñìf¾¼¿­1úíÔ±N™¬Ú7Þþ× ¡ ^‡÷؉@ÏßyÉûɰܬf·ª4B$]„¢“CtŠ]Yà²Å…)R±hQxÖ´1G¸8#±R+Í ƒÐñYyÞ÷öW‹:»—L)B ÒÞíbŽæÇ…ˆžú$TÁäètÅÌ]_d´=ß­ªE¼oaéZÐ@æ@ý› hn�a/G£ øµ§-Þ©N3ϰ» :U–ÙƒŠßL,JoÄyÆÛUí=«;ý ‡�9è…Ûá=A½1m1²û.v8©]uo§EÞ­ëÌæpQšû£¹ß/"XdLTwˆkŽËÁ–¶# Ñ%Û¢å�œ¤MdKGé¥Ö ¯FļËtC]¬QüB–Ö>FZÉgŠi,…l‡/ÕÌ´ÌìŒ�;BþŒ)làþÚ°‹Éç¾�þFºÜ¹~Pnn”øÅZ%Á•�d‘çbá j,˜Aòë°q}ð,aÙXiá™<#´6Ùø]¹ÝÀÄ�h/q²=¯øV”ÿéùJ2ÚüUÝÞ3ÆD÷ù¥Zh$‹ê…[¡ �FÖMrÌ€Â7 ØW/Q/x0ŠÐݾË�cÚŠ+ÏžÞ¥åÎÐæšÑcnì?zL¯˜n¦´FÔ™FRÚµË^ÿÅ90§G´)ÁâhnÛÓÓ&ìèÞV\—:è g,W,Òھ΢üÚ²ncL~?¶z¯šŒòT—ûUù±­…é•W'Ù½Ëý¢SäFkŠù©“æ¤{#Yæ–~¹½Ö”w£”¤“z¤è=cdAÍUk¾Ëj�…-² Íh3&�o:QÆ…¼;-¡X+H‰÷Ь0 vÊUÇ%W&ææ±žiM 6Ö&²Cì㌠bŒF©—&È«wàN "°4ªýÀî/Žgy¡øŠx¬†ü·ß‘€Ùçû=oå¥Z�†ÿ>™Ñû?û¢­›Ï¹t®fÜà!� ¬FÆ“ÁˆùU‚dm³oMSDG³a>‚"£~ööÛÁ!p'ˆ!½Y²Ò!”â ˜Øà·*ê† L¨Ê<©ºÐrt;�½dd l'…}fvÿ±æ¼ jÏœÃ9ûD¸t&å�\d£ŠÂ:“0kX`µ¤fÙ¿jÃûïÓ·…\64±tVÖReŽ¿Ä9`Ucî<Øì800Wî n�Û�T�QÛ¤EıŸh�§6‹•»ÑÉq£¡¶†äÈ“6Jl‹¨j /0+}TItv1$«n✣ö4Òd³T–mÕé½%•M ÔÎî!‡ýœUDÐmžœ�>Ư7sæLÖìQj˜;~Í|Sá,xN�8vËå7ß|p¯ŸãK’c± …juȪ¢s o…^¼y‹û¶ªâäTȶ Çû‹�V¾Sea7Üg÷9Ä–Ÿò†ÞÜF³‹ì �ÝYwðóA€Àuk?;éê[®FôüzÝdø‚í¨K”Ž…înçˆÌ 8î@`ýн–±ÓŸÀ"ñ4V@²(íîtׂ>²²¥¥V aöÒô½0ÿjÎ|ÙÊX¿hÞü¹›*žÕ5^&4×½¾(ÊD÷Àu •Ä1 ”4(ÜV±IÒ™vJËC æT°UpTþ¡ôäD[EQ,c Åfrb1o JDǽÄï'¾^òêãñzõIvÝÕ«-Ù„¨Ù tæ7=À’Á=¿ÍR²íö¼€¡;,!¼iݶ£A5æ]ÐJ,*G;d]ÚI~ŽBSZ–ºát,©â¨¨Àu‚lö¶š}áì^÷(º9ÉN$³ \j*ûǺ ˜4 0šÔ‚1 ¶’�i­Q µÅ0™ÕÜ"âD¬âølG˜¿ô’göIoa¬ÔËÀ»ÿ öfT"(‡�ñ|3ú‚â°Í�~“Ûçþ,A†CÀÏ¢À߆ˆý­ü´¦¼ÁfYšˆ9HÚ£ðÔHªd#:š`!}h_WOäáÏgXí…‚qSO!Qöîí&Iþh ¤4ûÕoA’Ã)#zxO"3bþz\9çÃ,+@ú2'+(£fÄ>×dmMMϞǼäÚÐȑޚ-ðÜ£ÐÆ¦@É{2Ò,mºüÈ™††ÓšpYGkX¥­à5h^µX�öÓšYCÕmo’Mõ±ÀšÖÍE|›°¶%0muKàªU��#‘¿È'úDƒ]!¤ì”Þžà�dŠøç�±‘ñ*+/Ïè3a‚Ÿ!“æÄ‘H"AÓ üoÖÔ�Õ‡œJ~+JNöÏ(utôIÿãºÅ+.˜^z&ôÀb» ØÃÌ�ÖkqïN—$‹Ò†…SAÌÎOƒ…pq$A0vàÙЩ©ƒâÏ+Ñ´¯ê}Ó‘J|;îÝŠÔí[QÃí ˜ÂW¿2Ía À؉'HØmÎOÁ(õÁ CHÈOÉ÷üû“^yÅ‹6»¶[¨‚±Ì+ÓoÉÂut„¥CîGëêzÄH]†©ïGvåc¦fÜ|²×´hÒb ·b’œ¨oŒk…÷áÈ n—&ð¡:LE÷fôïD?Ž<ò×û×?«¡z‡2JDÉ‚–qðL&êÙµ!í«LS©!Vž„kJÉ‘ýp‡ âú;ˆqËDF›× ŽmümêP ðκ�uYÿæ›Ûº�'Gžýëü«ÆjEdÙ@ÜÞ|ÐÐÕ4‚`køS­íܼ¼ ŠÆ x_ÿŠ´}1.×¹8ÐJpZ±eÒ£éømÇMóÝ"“ºh‡°ŒŽe‘¶ÏÒiÛFÓõ0MK*x ‚RTêÔmfKÇ·Ä÷Jº@´5Ô» ¾ß©ð·ñÈTr¡ºÂe±æ€¶Ià*ÁÖÙWw ´™ º_ÕÁÃß Â1ЂyœÓ3Þù*òôÿIüÀ‰a!Ð�5¾5):=d)^Ô£*±XRUnèÐ�Hù­’u„ £Ô7’äÛvÈ}¦&N>Æ^UåE‚År>¸~Ç" V�1�TH%­iñO�Y{5¼Ê›_l×láv!-9B[\ Mõ—B„Ð_ËÚ3îâ9¾œQ9&é{ý]t¡zæ=1ÜŒkn4ivˆ§Þ×k±û”$”�fd´ï‰IÅCÖù¢Úh§•­ã4­–M*æ>®ò–JëÁî˜Ý�À޵ß˲Æ ^`7@1¶að¡Þ 0JÑQoZ;ÀþP0œBÖN‰W—#g"ã·éÒ"Á�9»Ý¸2†gb°~ÿ;tz�Oô¼1m@¨¬éÈTÛˆR¸ßº#¦ââ¨Çñ,Ú"1Ó[†´éÄ…È‚h³e\�ëd˲v“¤ùV6Ø3£Ç¾R…ß~æ­CÚ>ágX­�ëš2‚¢Ù>~•yè$Þµ¿âê?¾îQ̯² êë}žª†ëFŒÐö´J N­†Ãß íñ}RÑ",¿ŒÖÙgN©âXn«3™`˹:“›À_ÑŒ<ôY0§‹ð~0çyS'‹ÞgÈ\‰Lïæ>!ÏàDÙÆˆ O 4Û—RX³ƒAø^…*€šJÐi0àÑB¦ VHãL_ zuXW R�iŸ`„åƒ1‘Þb¡˜Ü¯OÂ…‘/‰Ä !ÿ‚³N¬EiÏ�›Ô4Œ¶lhfƒªàáŸzžD.�¢à-({ú¤Ô®w‡Áÿ¡Í@ü„@T[ îÿ§ÎÿoûÁDüo/@´É‚±ÓFÖÒ·Ÿ³tÃæÆ66¿GV]MWôÑGµŽ¡C9kS“¡shåD$+±�~Ã-“¡=]‰÷?å[Ð~:À•pŽ]àãðaÖ¤ñ,[‚é#Àßøo  ¼€‹�oG¡2C ‚¾ÙÅ:úmïð¿àß“¼»ü°{aV¬…ûš×GQ2!Y5.2Yñ$TÂÔ­Ï^Ípi¬Óz%G)IIZZBf$šEUy)—;ìh°f. ÷6§¸¸=!ɘ� Õý@ô yúoª"øì • “J)ÿ¢Ÿ�Ä“Ö+–"›ƒË‚´'‚M!‹ÌŽS®øck«IꤥÇï‡,«ó±€`Fà ÙÖÁ æÝ,m?œï62^äï=a#=è²1§Z;Y~Œ¢*ƒ8!õ-çKŒ+‡œøuË0QƱ¦eD*) _[9¦ç9ýËÁHâ n>%\¶h+›š>éèéx¦’’;™VÕ×@¤>ëL:³GÓŸÑ9f´J‡Áð[JÝþÁóÿh�»822Ö ;ü\»b’=?;¦EG@àHó­ �h«äòÚ“œàø.ñA?è}†,gMªT„ö ¢¢øk;w'7ž‹‰�l)pìñJ£A„SÂ@ æ,Â`�ž�䕸¥QššÚÕz„mo¬±egà%»Sg €L-óðÒ‚¤ÅP¶÷}ͧ'î|h¯2T.ˆñûДŽú|P.?ä�>òŒ_ß_Ua%0T£îÿê³5ž9Lyt+á<(óì–Øšw"Ô”9ñŸEYÌ6iãÊÆ{­ºáAæf@ŽÂIäöJÒòöì©.öƒw=º�Ção"HW6u®Á{æ”c,‚2“æ÷ÿ¿®�¬Ô¶H¯!3pÞ•ˆ7¤aÝʼ©Æÿó«;ª'Cy9^Û/N² /s45ˆSÀKÃrÌ_/H;Uí©;IÉ·¨&jÀ”$PɆÎþKòxd›ííäõ§4; Ë(á‹á£ÝÈôCÌS‚¡?ѡӵ}œ©2͆ò!I2�☻QmlŒªª½"ùÔ…åý3‰"ÚõЋÿ0ºÍPÖ`‹9Þ\l´)»è ñt«É¢r…ïßå`(K¿€k îE„s<ì{dm2À]«ëà·~ånìŒ@]a6B]f÷GMóU]ò|‘‹IWV†(ZNexð‹oß¼‚ñœoâ¤Mc¯ºùÀ{ Ådï=£0©nu‹”ÏP±¥€{Íu1æÆ0/(­€Æ‘IDާ&NdÊ{ 9ÙSÆÃ“ÁÊ@©¥·2Zj�´Llª=•›Í«`ˆÃå‰Tj“ÞÛ%(1³e¨ÃÑAã:ß]sãdh˜#€5^o¥#ØØm^Tl6¨÷�Y÷¦ÁÐ'á1žÒò·ô 6/â°Z×­lˆ�!s1˜èÇ�í ¹« l¡^�~Nk^ÊRˆÛ#ü…˜Ôôš¦ÎÑ^FÞ‘Á²N]GU Ô}„;èeZ /ª1åÚ+g’ˆ©â¨ìØ�5´÷ÛCfÇ)ÅÉ ¸—Ì'lm–» )Ì=‘39 ¾ˆÇe *�fúGÕ¤OÆÂTƒçù�Ž�›¾N,XDq(>Ũ±vc‡�To8å­Qø’gAÆ”(º?ö\Žu AeÑp�Ð~�ã4ÕôŽ.ʮDZæ `¹…ˆ>lr íTlKÂ"û©ëÿš~#H“qõÔ(æ¸5 þÕÇêôü …IµÇ‹Äwß4ù¯·ÈS ¶øx—ò†¾ÞÇËq¦·ãÙ¿_,|É_ÙÞn·«¨’fáòÙÌCaINg”€]Ó²uÙ(g’˜k[BgYú6@É�ÔÀ&ƒoïF)ÇõïrÀ°ê0`n{j¾Å4‹ ¶¤Aó@ Ô†+·5�—¦ xÙ^ ùÂõ ÂÙê´Ûþ žördæhÀźUÚX„)ôRMNÚÁÄ ÿl…HŠ{¢(ÚJ ©¢}€öË;-÷HoKGúòM“÷TÓ@ðÏ™ ¨/Ms¢žI8m¢:sQNÈ�:l),á>ó6x Ý†‰¶ì•¼e€§”†ehêšð ~ÔT[Zç}ZAçý±ØƒeY6-¦þ‚òeçÁy¡Áñie^ ÿ� Òª1©:à—…ñmžÄ3,,hvʤì:­´4ž›O(=m<;Tç„PÀòZÁäu`q-Ü<ýQE„¦·€ÖÀã“àÓm@bÂ’Š™x.ÛCc uþ �¯Õ0µöË|Düçuu{çÏì–Ý}òm@ý8‹•Õ!iVE�.;­8'. A·Â˜ßÝÒ’àÝP»tC÷@$¹Fª_pQæßð§}Ô }•b±Ý£ñôbQv­•2zl‰{‘ÅçÂ;IHzE1lËÆ¶më*B”·»=’epæR¤ƒöB…Ò/Ñè=î «êžzjEf²H (R ž†×%û8ï¯`ç¦ ×EFG ñýîîœîÇþÚ?o;—(ŽÎ°©;;«÷tO:" Mé©gäÕ{ZºOúõ°Û��ÒY,°Á»:ÓU:!ܶ´m0HѼϬ]YýÑawá¯}º·ïë&ߣà00‡ùøËiXSZ&÷Hyny£w²ý²&$­è~<ù zƒÂ�j>ङÂB¬•@ÓÈøeddÐÝçÜÑçý·3ÿí GO^’òŒŒð—¯>ÛܪJ»Â¡è*˜ëÛECP‘šx Á[ˆCºîcêò&š¨/Ë„\¥+aF®žDç‹­ÚÎpÇ*·UD$ßgO®^V1"3©&ÑéR0€áz2Ó8›C¼p©¡À#0k> §ú&øQfѼø|©'1Ñjú·lBð’ã¾­¯OÆü;Í ô�ÐÆûtE)¼~-|—k…)ÉÆ}Qä´.ãï[Fçf®•�Úd‡ÉàÉù€B!e˜F2£›˜òqÁK¢q’ß»o W|•ø|z8·A𒤋é0�ž÷ª:Ò\�é°&ÂÅ‘,¥¸±¦ÐX´)Šð(¨³)“Á`Õþ…1”-mâ5¾({n’�\/„:—Á4º0}ë`«µ9û䂤�¨ý¶�=�aŠ•nÿ |Ý%xɵ3}¯¤ê7+ óš'ä]„‰úˆ“K†b’#Ç$6³ñ�;x3x™á21°2Ô_,¼å)°Gáº~íR\ÛX—¡‚œéL/ØrT0‘ Ó³žešZMáQ7Ô|lû¦ö'åQqß=¸‰û‘aµÛ½ÏJÊ�Ï|°Þ¿?1 ããBŽ[۸ϋ¤�FhÏ•œN?ôuA&°Ú³ô¾ÉÉuƒ³“6Osî;Zð®kê(e9n „’ÀçâЂõïß%ô÷?öñ±àÌ“$�šÌóLy(- Æã÷[Êð)y‘¨23–[rÕÚúИîÙ~¨O2óm+‚©5x/y'¡1–c8-:ÃâÀž•Ÿ�5ÿÄÛàíFh‚©¥¸²@œç¼ÕM�'›ëpx÷9”è�²{38¹çŽí‘T�À—L™âû ›œ8æxì±æ{¬F�»V\luröLÖ]×Ö·OŸ0ftפÞš6°Ë A’Ÿðú ‡¾+ñ|^Þ�¼æX×íþݺ6ßLðÛ>[ ì\Ô+zX}ÄêdhÀO <3œ¸íøÿ�õ¾¦• MæSތŒQø²Å3»ŒPM~ߦЦ`)Ê䜃À×ùE¾>¼1c'I´ (‰lWN_˜²OÁ„‹ÿªœòqyÑ÷¼¼¤]Û<ž\C³œ ¼�ªß»‹h„ÂN2�KàØÒÔÐþ®³eg”réåЖû`/âV¨Ú‹´¯á¯„uí»öÈ`]U>€ÉŠßéj¸ž¡5q¡ÎQäÚvÇÆ÷0õk–‚ö1ÑnÂk,ø; eÆ€owa° meb�§p¯$�"úJÙŒr´ôO�ýt¹˜�R}#^&vÙþ�/�/ì“_Q†�“½ÿîc¥HolöôV¦D6xdÊîF´ÖûßIÌ—DŸO”ýÖŽàt¤\ ˜`é ��¤J ñµÓpÛ8˜T®TS˜rX§Àñë¥ÊMßVhèÞðýp3u"¾‚ª$Òžhe*\3”VƒJ!µÁŽó9ÞÖݱzõŒ‡J�(ãAÚYÑè?ñ�?¾'¢j’ƒgýPzH2ISX‘ª¦e×ýTˆâ´‚é¶Œ¤°®Oqùš®¤ýS§ýG¿—$‹�OïÎ�‘Ó,É}|@3() NµZc/<:ßÞyçÉs�jJV’%¶®¡ ZŒk³•ÇÐèT0ÌSû¶64Ð1k¿mî½_ºë˜þRr©µ��žŽˆf¨!pnnÖ%é�æâ´ªSïÕwÜÛè«k Y}ÇêJ.k ÉiŸ|·²óò‘##3¬��ž^™Û­Q„¤F´°÷[Xæb¬¤ü|LO‰ìé�f —edèw~µØsR~q2²]&Â¥²�ƒ› �à�èÑnM{êP(å Q»Çõ)Š,k‘z(šr=²À¢€[MæíÉTN�sP%9¹´†W¦öþæûïjð…ä]¼ªv1øHB¹û휬>ùmÏôúâXl,¨Çîà)®¾ÚÓZ ˆ˜VbµÊzŠÅßé�Ôñ— DZÁŠÖìÁž°´=¤ôßài‰ NM�ºCa§Î £±â—ò,×äøWœR�ÓŠ®Cöbb6øÆƒ¸ÇöªSô–w[Éo9!šaºùn ˆÎ «ð ,�3P-y€ŸF•è:dÊ�¤Ýì ’¤>ޤ¤=Õ›,Uûb»<Ù®ôNhT'ñUˆq„ÂÄ“J<Çž—J½nªýg[ÝÖÇss»üÁ{5�7ËE¨8K\3¬~Iù¦ÚV9üRÍûv‡¨¤Œžf.Ou²�­k Žœ·`�pë˜1äæB�°ˆÄ70±\ч6Òž€-u"‚}¬óµ¤^~1Ežâ“cæë!Õ�Q;8P•r–1R­\)±vâÁ‘áà$@õãŽrhýà)Lµòy¨¶üïˆh8¹ª½—EZ æ´^�ñDóœH)$ýY¥™ÕtLYmµqd®^ž)©ù°,TɦёäÜô:]�~ŠfWú娒™½óã/y!×5f‰"÷DH3æLnhˆ»g}ú±½æ áÇ=n'²v–ç8;ñÌ|x%5ÔË¢fO74önª´£H-Ñá�ÜŒ*ΟÁUvNå�@?Ê€è^Sîp´Ö÷òaÄB|@ðO¼œa¾ ì�H;þ–Ã-EéÞ‘hC¬ =¬mh�É‹"šáÐX¶¯³ÞCX¾ñE €�ÛÊXR€)¿ð:ASxDhdùéa«�¿´®Ž:NÜ„¸? Îs@‰<‹ØÞ±`Á '`Hß]öü,(1&¢°�Q%f�C¾ÄF,-Ý`«Pz«±Ž xtn[¸¯�Œ÷s�Â<À>?Ìp!&LíÆiñÅ0…J ¼Á�]půw¢ý£‹Z�`jOnnØUqAϬ݄«—äè¢@Û&ûÁÄ+¯ÿæëo* 9Ò.Í7õ|‡�9Ÿg̈{ô`8jV¯ɉ%çÝIALš- HJ¯Ë[AWâ8dD½§húm ©üš|ï ÆÁï#�úgGf�y[ë;ûu÷w‘ób6Ž”©ûtSþ³¦2’tc4³éØ¿0~ìÔ'c”ã9ˆì³ð÷˜"}»ìõü.œâä|€ö5æÓ¢OµA@Ä3èaPÀ™¡¢²»‚�º< U0¦™[W½=Oùc°Ñœ¶¡6’…&Ä…8*A^^�œ…™.[êÙx+@Þ®jêã-ÞÖƒœÕ:ž­óOvû{�sÈBB·"(kaö{M�.4Ã&YuqîÜy4ÐÙùYÛÛÜ‹ÊóR „ÈŒ“�Ÿz'] “g¸MdNgؤß÷ ~ÿÉMÔŒC2È*ì Û¹³ÍŠîGŠðIXz–BË”au _ñ�¯lq[I‹[ƒhǼ «Áe 9¼ $ß×Ç¢±''f€oøTl9ø8>©ñ<¸¨Þûð¢ê¶‹0^œ�‘ÒŠñ1”x7ž\·..8 ÓZAÌçá4³T`X#Ùe²²[™H±‘‘Ø.êýxwÂûþ*(#G˜¶ö,2€CB)7AO稊Zà+)ùÁœûÉAùµü û𢠓¤ªJ~{kÀöDD\›Á–¾4›®»nD—òP0qb:, 'PJh¨Eî&ºŸ�>Ê/cYI�ß=.¬-ÝýOÛAâQ'å¥5�¼Š+aºÙ ©?b¿3LnLYä—ÀÁ§›Ä ¨d4jCëï‡TíK¹aFY˸k[ÿÓ{ÿ¿Ž;.-Oä½G'‰œÚDÜ¿Ã࡚uEqWΫ göJ8SŸEvÙ3¨ŸööšÖà´îÙiŒKÌ�ðÎ<ÊÊKÚV¯NWö?\¥6щ­žèpL”?!�7 +¡á¾ ²ò#L{ïSÿW“ŠŠâùÿ©.Þ)•ŠU D8ôº�{©¸#:µû½ #VÛÆ¥Æå¦ïÏ+p.3åðИ@›ëmaÌKPBgø ^‘dm¾· ³YÏ](oCeÀÉÓëÛZ‚›_øî;ÚŸ¤2+¯@)êfôï9&-}ø¼šI²÷LB†CPõã]±Tœm ÄÆdÊÀlP öxF0:÷´ê©ßà²-"ÏÂl7fÿÐ*wh|B`ž••Õ�Â;à;¯‚�`ÒÂ$HEÆ�àsõìzkjj øÚed²½�4â xÚÀÃssIJÞè6]óajzmj �±¢¡cö¦¶Èä øË‡ +ู’Ýóy{�'œi¹Ì.З Ë‡#ÚŸá{_ªn ˜ÌÅÅ–K׺Ãç¶âeù®š%/>±alžkÝdЖe´¢’‡‹hà¤�,Ëf ÑäB�õ\�’D×kúzå|¯UUŸÑ(¿1B>9ððöÞ{ï©v][ˆÄ4ˆ“’;"ðS¼íÀô‚Ð˨¥Ôè> |¿a8v´Á³‘" ËŒ¡Gm³‘Š úÁï@Rê‡óÆ �½ëÙuè¯úO‹�GÕNâiTvâ.Š÷Õài'¯A ¨R9Q°Š[·‘În‹A.³T „NoT5ȲIÑC’ˆ¶ëÏ œ\]…9Õƒð#�‹ Úcü†®yÁ«ˆ=u�âèM‘ˆ±P£¢KBZhY�–×P-ªk+–ï$ņ@[‡lЄÂpŒËý×_u½lÿõ™ÝOàA<§¡ÌeÍl>Ó,D”Ñ�¬¶ðÔ? +Ò Í�0g9j$½ ÁÖ2•kzØRãT�ÄtGlš`a0Ô¶­û×·‘$ \Ë•[Á"îF昬j·C›)ƒ†EÈ–žNÊqÖà˜#^"tˆÆ‹™gB›v ùFP—¦ý*àS€ˆƒó»w!#Âê”’ü�!&ú(kÐ�(&Ä‚�ƒ„Ž�™.x_a¦‘ö&*›ç{ „7x*<г¸ÆW°¤¾ó>#Æ?»f–w¯'ŠÄW˜Ñÿ2”ðF‚!EŸ ¡¥”i:Û¦HLÑŽN)›ŒqÅìk©^gDõW E'`_u1-´>‹�Ê€³ÖÐÁ�A? „�Ù~s3íÙÃ+'1�Û/ß‹bßR¦¸8ºaÃ�bqI?YY}_‹F_Dåýð™7ƒõ¬§B±·�8ì~Xß:ÓA±¯Á·û�$‹ð0òœžåÅÔg¿q{†¹9Ÿ—~`ª+£‚ûO@™ÄÀ=ñ Hãwƒòq 5Ú�‘„÷„ù3<÷ëW<±¢‚!Ï%ñ,G¥À”OSGMu‘g`èôŒÇëܪ¥@J¼�M±±˜ Ÿ¡vл•cÆtYäZj¨£‹”ŽÅªIsÅu~üZ3¶ýÉ´;’‘åÆ®Ã¢W†d–�›¿hkÍ®#|¾Qñݵ·þnŠÖµÁè¥XÐŽ@ �ö�m3Ñ•‚ÒkHn$á1fXq4Ò¿‚t¿Î0¥†U«Êãï ÉlÛžZ˜E]5–™›AÔÐÜ^ï'ï9ß•ŠFÔÚ´Âøþ nASÙO±±%€0~ÑVÚ´ò­§êOÎÎî Ù²Dc&‚6!þÝ?.ƒC�¬?"êž™[ÐJò’mj €g–�î2ÅUÅõB‘Êí¬D½q%{MŸçK§C˜¦ÙŒ�@ŽÃib’&²CtE³wàŒqŸ¸bí`¦¹ËbZS£#°¬> f/ 3ü5°m­ƒ ²Ÿ!År!P§þ…Ê|RŽ? x^ Ù™ðŒÜxÖÕ1O?,$ê—Tµ¾fmu`¾ sô4EÕÓóÆM{’{L¿\çjá/»Ã—ç§Æh×£ÔXCq�h©ºhjª¤¿Ú�µ5ƒzƵô ¬u¢Øçw*/÷D¡«ljy;ARã y÷Có"oNóšϳ¸Î‰±Œ e÷êoÑõóÁ;ûV„gþÅi1§f c•›ÝïÙM¡ô÷á6Ç“EèâÙôW Þ­‘6÷e}ûØ[üeJtv$4OòÚì‹�òجæ ùd¦SOúfMk¸ÆÂ©@‘ÀõÒ—êV%0à �ËO?ãZû§]wÛ8 ?àïaê5¯¿•Êv˜Á ¼#=‰÷‚«A",™ԫ -mš4©o0L‰¦&K&“4EÔÔ'å‹‚^yöë•M ®©=ÆúU“gsáf˜Ž t·“¾f ¨A+5­ú°iªý´ä';)k>ì ‹Ñ~wÓ½ß2t¬Œá„�aMS 8—S{ö¸ËN�±Å Î©ÄC¹êÑÑL»ßŒ.�5fL—{ŠôÆÕ·¥ÀM“…ñBí ˆ³+£Ü0¹N´M–˜fV0Zá*cö ê]@Qa…ˆ5¥ŠÝ¶•פˆV›�âÁ¸Äôb­úŠv�g§Ë–´ æ �úóðÖQQ!‰#&~#KtdNÖ‘JNâ˜iOÊ”¹RÅ+PI`A>`˜DÀ¢ý‡‚Êù.jæð9ÿ§ÝùÅš/I.ð+(ä‡)&yÉwñ•ºå2|t€°ä1T¸�CbD�Ñdj{E˜í.++”&!Pfz´€¿�„Ø Ú¿PÇaa¡65Añ¤#£Ë Sùä\'!¦° çï¯Ïwue¥kÄ6!XR‘ë 4²Q±øÃhLÿh\INã8`{g¤Æ³´â‰héÇ:Ÿ|Gü©`S†Ï˜º�¶-vã¼{úÀ¬B[[—ïŒ�J”£½BÅ`îcY ¾Š€Ïà UQÿ�\ �6>6 yÚBÕ\ �º”�Q\¿ÈfMqxð$Rd³eÍÊ*›J} Z/…?/€‰½S“õS³¨û-"}ßL�V'[’þ:j#‚¥|'¸rýzûÀ£}܉É…`°±7Ñ$'P6$½ÜF›úc  ±Á¿}?–¹ËQhórXïâ’&c-´~çÄY³l1CåP닆Ù_ :�¿_ß•êào‰ ©ÛÀqy8æyo,\"¼ægU­ýxÕzžª))†`í�LµïXÎHî‘–Œ©AÓd|‘'š³Qå‡-vûÕºm¦=Ѷ’Óο"l8ßAý¥#ðÆÂêŽé$ãÏh9€±¶ÃgW ¸¶D’L¢©Âòë›×DÍE9ª7CÛe3Å›vû#—tw7 ÆaSC ä„e-×YÞeu'†é„Øw²‘ÝõÕ�í!ÌT_Qd�µ‰aX-Ù(ï<$j0­(FÐe= 0êZh—žœ!UHZÖ,­2 MÊÏÉézÈX’x̬n£'Ä`£‘ôl¸¸œìµ²fÞ‘Í'W"&uŒÃâ_튫ý‘˶{B�íò„çUû£g×ÞùcÇÿÜï±æÛöæ‹6ËP×ñ’®¤WÂ…ÅAo^¿¬®`´ùÈ ý#̾Ið©Mt â8³ÿ0�ªè2Ñ[Œ�ÒAåPyøðÿUçšä²�ʬxõyŠ"j?´Ë•šb«·Ø…ßAÀ_S“… þ“‹Æ·­­0L@IDATÖv|šý‘n»CSèç ›UT¸d°Ö5uæ ‚0rC{°Ï–ŽP~Ãù·KÖ˜ïâ ‡’ª �j]FRþ<¤=ÿÆ)ð£Ì#¾6ÏÎ ‡F‰ÁhVØ4óu-žÕ¿¨A¤f@é:8ú�€0¼dOÕ¤/‘JK¢a¿·)Ôg~cà£&xpº#ýF‡•žç�²WŠ=&ôÈ‚Ôâÿ^�ÿu™Sý°¾î$T⨣mEùÔÅøþøeïwuÿþ!žú ÿ¶xŸ­ÏÐ!åLaY~/,Ó°tÕÿÒ5¯kÐ�ñ¥6‡ÒAùæ)œ0­ ¶T ¨ÀNÿÏÛû}ƒª×N;Iò7ä΅ºrzºhI®ì þ¥škc¢-D ``™·ë(–W� —ò+M [aš>=(͵þ°•™8ígï!˺äÑϺñ«æe‰e&Ë�ÃK%ušÊ{£i–yq™ÒaÅvÎ:5šq_,«àO1hªþƘüÈêC°–ÄÍI&mð)ÙU¡ºv˜à]¾Yâó ê–Sqܵààý0ì•ßîÎáÐýüÒS¦ß WÃmðY‚Q»±¾.²0Á�K¸(¤Ô‚³àï¼ ÑþÞð÷9át±WàgE0‚Þ†"/[Û“×$Òzɵ7·Jd�F óSV’Ÿ F0ŒÜƒ‡:QQ›ìÕ¨fîCJcµ“gz÷ý±ƒ -%Zf¢�ÇÚ¿ƒK%Ii¨N‘oU˜Ê¡EÉ�ÄõIrˆ L€à³½(ŠS€Yñ*ý‹ÅÜÂ�{¡�E„_VM½Å®ÑsŒqªHÓW#Tý´Åä>8VÝ®îmÀâ"ä%å\…l¯¿AG6µy×öížnìV8t}K` ¸|ßEVbÜ!¯*¾ÆÛÙ¤Üsq�?Õ‘‡qi� õ}§bYpR‘ýe�Ö´xO�èÌ´±ÃŒ K[BâŽÉîϘ´‰Ì™P~¿`4 f:¬î¦PtI²È¢¾}êµíHž74á…Ö†šuɹýâìfÝ»ÿ¼ÓyœbôÆ”Í)pÊ¥cÞWK1éÓñ™µè÷óúD‡D»‰ï¢>E‰ªa@2´yÓT›ÜŽšUsz¦Ú—’c÷{B}àFû–Wþä@{Ág;ù $M`“û, ×Âñ_&æ ùíçn¿Xø’×uD²u ;”�à{Qj¸·§4ÛáÁOñ‡¸=ì…ºº½QúÉO«†oÙëϵ!pr„ÙN®?¤%Â&�틪‚Û=ÃU7�—16’ ‘—Ä´0‚?˜ã8XN0^GmëÜÁéÐrHyòR¬pÏXlô£ƒ�Î8…!2¨ÅÆ üUÈ$í�w�ü\ k¥¦�E�CÉš9A¨¨*ËeôH[Bpä$+/À1gkLdeyvv¾êÒ"VûÄ0‹%=Ð�+¤MB¼øT dÁðë®q(âŠéý]> ÄÿsBapæ}/u�±uïY”rÆœŒ7G6�º¨¢Ì…%q´í‹C{Zù„§©&SU´ÁèÇÛ¼M}yZVVÇú*¯S· CxY:`çD¥,ÿ‡í�…‘ªó®ç€e„¢ÌT³”|ñÒœôms÷s‹Ûm ›¶÷�u6I#{b²YW‰ ß}!˜i¨{Õ™lòõ„ÿ´{»ŸIñP·a)Ÿ®Ïšíù©ñ }%‚Ï;›¶;æ­P¸ùBÑI®·²Á7.dè 5;7´aźîB%q¿��¡ù†mqúG¬Lm Ò­Àòæ„tc< ã\;ü'*='®q¢î¿yU²Å:[QTÍ1Ù ß›œçìB%Æ—¼kÉÉÉÙ±gÕ™X`ÅÏ·’`æ‰Úïk7�¹ž3pøÅ@| ȨhH"êhµÐͽ°K”áøª›ÏÇr|7,ÀVX–/4u7OýpÝ‹\Hæ,¬éÝР¯˜n#á­_´ý¤ùþŸ^™uaÑdÅ$ÅV DÕx Œ<ä mþß› ûÀÝG4ùA‘æJe-Ôy,ÁKî·êí·=ª¯æE®:�¦”º.s[mÜßB|OÍk¿ÞXýÝ’å‡/¹q¤“úT„™ ¦ï�aÅ �Y§™ÚƒdD°õHÏŸ3ìz 3€ícð;Ï×Yê¯ÞPû»™9®OAéYÔ ¿œ`É¢U¸Ï×D4Ò®OÞ|*²¨¯zùå#/‰˜ƒŒär'¼˜#dÞàŒ=t,ú²Ój}ËF–N/û¡ #×#i;aìZ®ò™´“{²?tÄ÷ÿS‰l0™–ïå5ö­²Ì,Âä,¸oVÁ“þ¡XµsÌÆMˆ,ŽÿÁW2%ÿµ¬âB.¬I< Ʊ6ËË”/ÞÕ�üý•}BàÐ«Êæ? Ô@»-…~[ý@Ž é¹HQ®‡ŸÐA=@ A‡À¨LûÎöµËWO.ÎÜ™¼¤/ xìK m&î3¾ À7 �¾YCáGï¶äÉœ#Ï'qlB˜"½9‡°›ÜÓPâwÉ0{ãep àg?šž2q wN‚ßüÏðë&k´ÖM£†ã¹7Bû+~2P›¸Æ‰¸'ó1‰ãOVa0xs32Bð’ k|#ûÂñÓ¦žUEE àƒu£ {Ißs¯¼%é«FßÉKë½Gp?$Î=Q÷ã2åOZ´ð'Ÿ<ûĸ®æ Îñ8Þ2‚Š*ƒvû |»÷@>S3zÛ»TF}ù`[ã*¢¿‚²8×#ÖT�cAž­˜1îî;ø¹ãr\4ß8¤…¶LB„»Å£Çþãë¯#vE´P˜Ý×XºŸ$I§ÍñH#¨ˆ«ç8Ìé¦=vïxz¢ú4x 8Ä”ª;¼æPUŽ™Y²P4ÎÊÿÜâÑýð�ÆzÔiêOi™ŽÅ Í•¤$ó"ó6¼Å£0ˆQ°ªÍGÈè]`÷ܸ7¾:´-††—ÁÙÁË{9)ð‘Œ9‰ª‰cŽÞ/®÷>€‡r ¤´·YR¢NŽ‘’-Â^3ôIâ>¸îç�×#5g¢ªSáï©‹ˆ®E6Ñ MZÚÿ'äåºxÎþà†*‘ËK[†¾î‡`½×B³éðá=�þû‘5p¨„ÛÒž»i84z„Ûµœ©ŠX Û`†ûy�ò«"“Ã4ì_G–ò~9½®¯Â#°@ea>ÙdŸ;§¯³sM«¦÷*þ(8 jÚk®Jh¢ÝûFæ’a]°g¨Štûˆì´Íß¶zûÚi~ Yá´oà–Š'|W†Ed&t"ÌÁ†h¥ç­òòïÝ3¤¯Ã¯¼ùFÎ4~íd>b { P89á^YÓSA²?F€”EÉBнû!øÚv3ã©ÈvÙÜ*SóóEY©ñûcè~ìoéó7�í=EA,gt16j,cš» UÿnEAê^ò¾UÔyG³VÖîD]2÷¶ö�ÎÅÇâ-hÁE|òï€p€1û¦3bÚT¹iɱžñoa¼µmv’}6¬Èû°�g@¢J �«î¯)Ý ï*Q&úN:}(�Þ„¥94Œ!PTŽí—âØýKÆâ¸p;¼ðøãÚ°1#[¬ko)”�UVfߪls^ ?É2YÓ>EåuvÁòJSO¤h­%ÏcOÁÝè ]”ss.‰hz²¨Å˜ßYÞM@vï°bJšÒùxÏF2Ä`˜H?fž� Mutá]›£ò àL8—·!9ãŸZDa\^Z\t¿^o§Sm•ÔdLÚéP—s ‘ù¼À&viÝ�M|>è ƒ0ŒÆÐês,Å倿¹¢É5nk®k&ü‰c}ˆšš–Â/z ú8Qö]8ôé¾hû ÓnÉìŒödØlq;q^bŸ¤‰¬ ý_¯?«„ð™3j"²6~‡óEWdòSv�^VÖuÏÖhÄ´]'î“*r”äá%¼N'#™¡L³`´t£¯l(;Ï8Pªu{khAè þâ�H©.³‰ÆÀƒ~©ü¿×ñ,. º©ÂH½ «!�»-Ò>B%©ÓÜÝàTè�({ÖäÞq«j6­›�ò<‡ûS6$ÚÌn D¶º£zÏÑs° o¡4z£Èa¯ªM %×#}­îð×p—�Ôä‹‘,q^’ÀŸŽç’ìÚ|‹ TaAyÎSÎÜã�DîÚ\ßz\C&ÅE›¢�ƒ†B¯e y¦bј™nw–¸CjC–]ˆÎ%7ù�m�¡h:²C§ qŽùjàç3†®½ÜiiCWÍöˆ&¡:¯)ZogaRZk‹7œ›ê�†>ý´Ñc؈dp –�ÇÀ¥)ÛasÙ½‘²Í>¹µ,Åú›Z° “má–öèVdª�‚$øypI›,²+Ý6SX‘aãAÐ0he�g/ýýá¶�£Æ‘ù~:„´È#m`ö¿dú·ñ‘DƒÄ*JTÅfŠ0ös@Û¶§mÍ×-SQ}D¶c7ü,[a¶Z�q«áëÝÑ¡ŠF ‚�ç´Õ�nÓMVìıdJ|&ûwžy&¥#ï`µ¾ ‚}92–‡¨Îź‚uë*›RYŽ� Ák‡P¨uë[jCÊ�…¬Ço!ë'Ï¥+)  š‘{‘{“E…|î¾yCÆVäÅû#:ŸŸ’¸ÙW�Ý= ›L–5%%î¶ Ç“Ô^·!üÄ2ׂýÁ†´ìGc²v3„æÝ­ß„€ä×øí�µÜ?:ô¿7ʼnOvYƒg8"”ñû“1o•;– Éë~4®ΫžÈìz®ÅŒn-?Êž ð‘-¬¬|ð†×Jº¾Xaô•�áu�Tê2›[vêñ—«¼wއ3µ¿7[¤J&äùD �‚@Eó# I+ÞÖªõp w wÒ·Šît�ÈÏCæwÔ�­^  �Õs(”?–¥è¿‘Ûò>žA.´îSá<; O&Hô{}I”6ÛÃ(aNú¼·32¥&…'i O h§g|ØÆ3_>ø7ÄÔΑ²Ö)SÚ€Iê$ͼoPÁÀ3+ ž“ö�m8¬Ì7 ú̹ ž ©t¿±úY¨«wÿÖÖÖôCGývþ­höæ#µ{&’‰šQúg¿Ÿ .òù¨wc¬L„Dü•ïò¶Ë჆ÀmE°67fGm÷G¬�1ˆíìîE¼x-XÐë‘ ?0l˜©=ïÀïÝoaÔHªº¦„?CAÜ?ÁçE ¤â“A€ûtSP†ÑüýFÞ·¨nTª‡Ô õ �°´¾?ò?ÿtLøŸŸþãG‚¹MAg,¼ÉÎÊ33{6ך_„d!§Ru€˜u ÇÄUT…3‚ÁSýº& Õ™eì¶+Ë‹ºVÜîPz¯òó¶´‡¯']r’•Òhÿ†Rå;ñî>”Ã)IŽÌÒ„¿–JN-ÄÜ+A|Ê1½,”ö——SëŽ7³Z(…2oÀ=@$Ú—6nú¨PjÞC[Û#]¾`òÛ¥¨X ¡¿Ì0ô[ÁÂèE£ï*:÷xÈ\¹%'§ åÀ•N@`ìrHv¸SèT5úÖˆlצÁéÎKà‹ŠRòcºI·á¡\b���¦%qoX†MFÚB—a’�ïé‹KKƒmk–n4æU¸E+P~©®¨¨(„¤a²'ÎÅw1ÂK:<D¸ÒûvîßÕT³Ï"SËÚsc,,­áz×ópÛ¶98î9PËZðwa�P¯ôÉæ<‹{_űÈÕ“’]y0Ù†Z.‡a¨ÓÁ à´[AÏa2œÙcÛ$A÷ý’ŠÄ ;ùø�¿4Tóu¼ÖY¨2rÆÐY³â%Ç` â¡3ƒOIJŠ÷áT`³uÙ³„VÔçh|ºÝ ~A`pû6, ²Šöz{Ô|×Ý ÊÀÛƒû=™‰~l'°Ë—Áû‰¤gãðý¶KŠú¤Ò9&oÙrdÁÒÄy'ê q/¢÷ySÈ”�uß>ßÌþ) NZÒ/,´&ù›­?P +i5J 0ùCϸúÆõR[hZqúþ¶ˆ±E¡ƒ@ßÕwª†S“ô6,ú‡ÄΉ:8?ÒnRÉ…ëhú¡÷‘â¾Z-àeÔé1Sö@gtdâ´y *‘€KŸE„�Œ©îIüös÷]/èϽÀ��GØ©`ú,A�µ AýÎ Ó_DÂÅ+€Cx)]=,e×ËN®A„…¤‡„bÔJè�B²�Ù"K™]("HÃ,u%f@ Yk„˜™µ£3xÉ.Ol"ñ¡?Þp3PQéš5ëÐC 0[ãÚð© TéÃ(.¾Öæ0�é?&í Õ”YÎ,G»­üwH"×Ké€{`44µ'†�˜±�œš¤N›Ôjóþ•z¤G7\2¼°Kk'5Ò�én_ª÷ý X† V"„,"]GÝ:ˆep*¢­ÁXìÐ}‰Æ�Œ¸4M�lbP)©Ž 7JœµŠiÞÊú\o‡y°)Ñ7\{Û™¨NñØ:wàž-àiXXÝ–¹²Á[F\“0.âEVþ³Á×privW;â×DÏÆÞC ó»¨köIÍþm«Çå¤ožŠò+?–Û®åZŒÍM Ǽa�…‰RlI‚*}AP˺ؿØM룀?Õ§X-+°#ƒX�í¯‚k@§Tø®Ù¦º:2·ñ=lEÏì°Xºæ'y¾Äz: A»+°˜�* ΑS®VYaPÏ ¤¾³†g‡pvkñ“~� Jw–fê¯yݱ7€ùƒÀ+­®ú°´á¿mŽ`Ûô/4E*CgiQµ*1ÿ�Ûçû[ú¼¸«!…<ǯ<ÓDY’6 Êo5x&‹†ö¿Ui*ßy&t:¤¢$�ÒŸúÔ¶ê9ݬÏÄõNä==¯"žÜCú@(v}½t£¦PwÀ5¶BÞ’SP~ç…ý¾è}u邳&õxnÂK°vµaÍÏs~Äü¹ãp\n?vsbŠ:S\ƒÔd:™ZÐ&P/¾ÝÜ#%NÙý<#3æ2°iI W¥ƒ›Î9„þ#ebÀí@Ûß!~U9¼x©~�¹T SµpÔõÛ“í»¦0ÆqjBŒ9wk»o�É‹`®fð"?S­Úóí±„™œ ‘¼ Qöù€”d"»ë½ˆº…T� ×!e|ŒãS–c†A0µ@’ÿ�ÖSÞ!°%rîGûs“’xÿ´ì,<·#5‚!„cx„ÚŸôìé¶m¸ä¦{‚!gú(«ìB±�§§¥Å]#`±$ýÁ~Ø'Ë-VÁ2\åb‹ˆûñHÛºo¤-«Ü�ðÜnך2V áD›s¹j’õï-)€huÝŸ˜”D`± V ,Q¤ƒœ>0É×ýú?õ™Œ}Nrö³š®íU­ÌÛËž|ÒÆU·–˜¦‡.)Ç ‹öo>8£ ÐÔ‰Ã"·êØ ÙTæ�ÍN¢Ímñ[\à‰6ÛšŸ+=û¼¶¥OPìâ¶•;H ‘à¢Ó’„w}²Z WM§¢Sý]»ÏÎQM9ÑFð'?¦iò>-#ù]±3üLŸ,eŒÉ=eº«_?^ÄØ‰ûýÿ±'Ï{cGø"¸Ô$E’jyV ½Õv°>A×™hYÄ™¡“þ·“SäÔÇfõ̯Æo&±"� @2Te°Ê³š5j¬3,RLjÜ\‰hʉó ûÊ`0USÙ3À?2ë„ô]¨æÀ"È8nnó—Øyt¦ÔÕ2"øx)¬ÄX Dç¾Èè˹¤¼øo`#´˜1Iö xh^Œfu¨�¡£/é&Y´\VnrªÏdiñéÅUž‰K"ûí!÷‡Ãú-­h>ÝN³îÇÜ˦s!-ñy‹f2/q…½ùIT¤�Ã3“ª J~ïêãxûd¸N3ˆCgóVÌ›CQÿ"âª`u~8-Ð×p¥vBäÿDøígKkà“u¾ãâÍ鲞VÇ‚9‡Õä³òO™÷W&&ÒÊÐÉv�¡‚Û-×ÊQ­ŠF7“4q Y±a"G%)6&ÿpä½ÄêÆ¿@4÷®YDæÒðáT×ñ‰óûȸƒiû|‘àçÑÏí•�íJü–ØxÇr÷#H8Quäo,½©ÒqLè6˜Ä¸šZ ^à(´y¸Áøy˜A*q�Û¸e…H£¡Õ6ÛRÅÙšHílíg³¹»i¥ñ—¦iõŠJ«‹Î„À�’T¶MÍ�O·y'-AQÑ£ïKHÚq?0Í!�27ÉÁà'hìsø{]X•š9…%vA| ø�ð‰¢î¨YwŽE`ž«ô†û%Lw2ö«ÛÚŠ×6ú¬m j\0}¿î�ÌN­šnsã»øâA~#B™üŸ8ŽÐSʶlô‘¯Gç&o=\¢†¶ ¨ì††„¤X(Þu¾ˆjËÈ@‘äÊ»ìsôϼiŽÁb¸²‡À­G™§÷g—fl=Z éh—6ÁwŸn"j0ÜP(õAÆ&3s'œvI¼I´éDÝÏž=ÛdjÎ Á_¼ÊEÓÇ ‘g‰1ë…E8©%ªNF�óoƒ ^ójü‘góÑÓ�52¸N&Ra"ˆÍ€PÜ•6~†ƒ¼£'êØ$Ú]…²X…~÷L°m˜XëÀ´ˆKœõî÷I,wt–¸+¯JJƒªO!Þ1ò%„ùq=Ÿ Hsì%DZdÁRU3ãÛàOrÌ$î¬ýÿDø¦9³QŠš: €ä„Üˉᑊ©ÀÅžr¥44¤ëa~ZíàdµkTU?>Ñ6>wÁ_J4–°Ì—L˜ö�í�á7ìVqüÅ i*¶œá¹‹@~ lï3•¾@Æç“=Ñ%:¼õÝÀœmN ÅÌ{ÖÀÿI0½„hCKçYà¯ùLÒò¸V«›Ÿh‚úòجx–µîpåÞ*ÌÞ⨌`lCèPõBqĵ'2±×¹}­o �#‹O÷û“Ï’j4ãaÁÕH¶ËòO¾ðÀµ¹öúšý‘!•@9àtšh-°ÄAF¤ncôZšæ†ðœ®O;Äåö�ÜYΜwÈu¿5ñ‘“[ß1†‰Þ©—fûP|yLSnJåÔ]4/ü“p*ú²ÄEODcòKðAý @óÑAÕ8÷¤‹/æI¡ÌÂò÷Xië;,ϽÀSækëÝ�{׸ݤMäÁ'-‰ßµë"4»g×·G} ‚’h]äù�ŸÀÇ€°7&Rô#榯9²W|þ¶k >ý­Î¨.(æ Õ4öÑ`Â;ý¨‚ ‰[ÅSÐuó5{Þ94ÛÑñmí¾ Wr2ªo0ƒ ޾½ofÁc;|¡»I²KâœmOÆoˆëA˜ß⢫Ëá?V”!“f„UíRÐ} À~�TÙž½2É‘wß¿��H¬r� ÍÍ«Ä솵*0�àI«à~ÂõºÞÙc]û×þ�a¡/…;Á߇5U»þÛÀýwtJÇËÈ (mŽ.µ [UÅ| ˜;1_ƒUÚ5¦£K‡Ay£GÂiÌÄ¢ Q2~ÖvÄÿYW8ê¤ø*i'›*ó1‚*·BòE€™= ¾Y�]ÑÓæôᘮÓÐc``Q/äÁ!¸/æ­!ævÜÜ$)½p+€s€´i…ÿòB0{ >Ã.ö™Á°ÏKî5ååßÌQÍéú“ "b±ècð·äØ9¼…};›q¾ÐCï¡håckN„‰Ž¬Cú “–v�´ƒh€i.Ç•hQ9øjeC{Éq(Ò }V'Œ{l¤ͰýXŽ~$Íšqå˜Ì]7ÇïªD0m\ÁÃRÛ9„£ûï‰Ï`5Ýî�>ƒçú&zŸ6l©¯íõ‡Î&~ߤ̤¦ÝÛ7펲Z_h0^ á[s#3=d1gŒikƒe~h çöI±N ˜Å‰/§¶}!/Ö(h¥ mÉ‚{g&ž�B¨Ð¾™œÍ6 ¢ê§ÀžIÏ´—²¼“�~4FPVÏâõ[Í0ܵ e›�„ ÎÖÈL3¿÷³kž™úðÚÆðRr{u‹(YØHêj¢-ÿÍ^çèRÖ4 ÄQhtb#Ü"/½‹zÈÜ \1f†Ì˜KX•ºÓnn"ÊÙuXÇwß“òBJkkl lczöùkÌyu6ÁÂ=ȋɀB$[øàqº·áÿêó±’zȽ—4xû[Yú^dûÀhÛædÙGÅ á͘¢½æSµKlªå¼q¥€PvR£CSt7^Ú4¸â¦ìr‡ú$8¬ÿ¯úrïc…–{$k[K,ôÏ~®ƒ´Nû!<'â�0†üºÊžç‚ÀcË ™Êû£|ܽ-Zë gðvȳíß{¶ Üý˜ÿäóqŸpñ²,4]ˆ¥&)ÛµþÙ÷��Ò–‰ü´0¬Ò¥é�´ZdãôD‰—QŠH�J{fwã\Õ,Ôp+Ê¢ü Zk˜�>Sk“‹¯†v{›·ŒëÓ'®%oðz];:ÃçîóFîØŒ]RÕ 勎À&N(̪ÕxöqUÑæC ¯‡;>Ç2Ø Þ3>Puæ�Œ¦ý­mÝ kùdϺá–ɨq%ˆ¾á60žbÂÊh;ÜŒ@ë^WVV\ø’šPð} ×F (,ïͱ¤>¶©1821a{¥Ñ!ô‡³b/4ÎÛS³¬«ì �µÏ.?àÿÿÈûÀ¨ªìý×çMϤ'$BèÒ¤!(»`±¡bA×¾º Ø{A±#v]ÑEDš€R¤×„H/“L2}æõ÷ÿn`bÀ îŠ»ëÿ÷vq&3oÞ»÷¾{Ï=ç;ç|Gš¸7N�¹ß)�>G5µ h×+ØöAP>`µºÏ J:¡‚,�ï0ž_²�Èw¨8»KD .ÈSä4f´8ÌÒÜö$&diPâZ úÓ‚Ÿê&Ó Ž4œ®‚6i¢=xZ{\ €Þ—hk#ŒÐCö…f�Žg[Ãñ†%Mqù#\¦Ð�AG¸ëÃ÷�á ¼tÛ/ nwes,"÷�ÞüÄŠÃ%’~Ë瀪aü`³`=g·ƒ‹<ïT°š6ÚñMñ¨2�:°a5+á¥??µãyk‘6K*'Ã9ʺ0ÒÚ_ˆåY8-/C¿±ïP_`3üÊÁvÕŒ¿{hË–{ýæþ—NÜ‚*ÄP(D”ÐɬCBÒÃ[·|·ãL—« E?‡BÒ€ªÝ7Û¼þ. ¸éÀ²eá€dìBÈU)­±%pFŒœÑã7ÁNÿ¥n÷¶ Ѩ�a>„j™˜QœhwóL¬³Óaí½ÊÆŒ· MÂÛkA%•‡Þ6³ ʹ ! #yOKS¡tY®¤é,§G‰µ@¾ûwŽ–€ôçÇmýIf~áYNhSꪋ:÷-2—AÕ�Áò’5æ”WùËD¿žêR ¥ƒ®P·ÓxJ¯�#ê'sBÂ-¨ &ç‘ì¡wT©Qá­žS �ZgÝW S…&毧×À©pÀ]¯þ—` NŽ1Ü£=›ÂK·$;ÿÑéĉ¶N¶V­ÝýŒ± «9+—,é’>³8¼­ïÄ›6ŸNR—Qdä.ìü�P„ðE–u+( KêBN0Ñ O7ÅŠ!Ô Ï˜1>5s0µÓ@Í8h¦§ëÀ2úäæÞ‡‹y‰iˆ·%/#çÐ^‰�ót”B:ß!Aߪãå k»Ra0&ÅÇà£�ˆú*(Þµ>Å*Œâ(öºp’oª‚×Nâæ¦Ù©5Áb3I2ãõC{81QcM¾î¥ `H†p.òj‹Ó�ü›Xº/<¸»”¨<Ãíx™a²€ñu€ÀÎ×5­4'+Cm¬ n† Dl¹qnª˜º±H |ÃÉÌ=YI\ÝPÄ ­,’¶;ÓÄe؇†±,´&ƒy 0F"QÆÃ)8Ñ.êQhÀ�&Ì`’(B±®Aà(Î…Æê«—£ÛPº»MÂc‡CxBòöz“’�hG�ïG3u"rxS]Ç6¿Uj “Oæsªš¢6{ðXm9p>kðð�Lºs±«èØHîöIR‰�°{ô>>75µj`�–kþô†ÀaüÉ…·Á�Qt-I`CÌôwÓÇ�oö;t²i±M€.cæ‚%nĸéÓ÷¡ûÆ‘Ôô0"!vQÛi >�AY›ŸÕŸiˆÂ + ˆ•c]7F; 3ôrTޙ߆‘í’I\‚³ƒdP‹Ÿ²X¦ÛG¢XvpgwG0¤QCÐÛÔ£�»ÇþàWþ>áš/Ù àáy_2ô�fD’Ê7.­€xZeÕU™CTÍÕ¯»QÊ\¾Öfå³ ]Û, —eÖ÷Œ-ø©2É$£m„ˆ´˜©HE­ðµÃš2 ~¶9,#Ž÷°“- ÌãK��å‹ÒòWº¤|²L¬Öõ3ów8ÈÌaR”ôMµm’ž¼v`»¤íCàÙ'ä1‰ñ"f¾na¦ÁQ3Zw‘!Io€'¶nWSÜ…‡è@Š2ê-RþúúUÍ;ß7N;ãDÌLÈãİނÚ`sã±X‹óƒhheß-Ù–äga¾O^ù.døJ\~—ÖÙT€ûà³0k�2û/¡¯Ú+ªñÐ=ܦ`ß‚ôdM”@Ó'm%t‘Þï—¶Ò{5—îŒ�˜ùäq„™¸ñ~Á¤Dßxž¯CØhx©ž�êŸéÞð{¿¡×Üp0ø´JKb?Qm¢{¹ª+„éɈìÝÄäÇ€¯F{ Š(­ÙŒ§"�ïÑezcµä[Ê ®÷5U}NI8×̉.9÷¥‹�¾ çzI�cc:™·$‰öw6Õ…¯†™Ñ¬¥+�Äö•—·àk‰6ÿÚ+ ’TO޲&ÒEÎ+³J«aŒöÓ®#ø;}›y�•Bë†g&uÊpÔG(i3ÌÏ×2ŽpàþÚ=þŒßsýG_Ö$kSP¸4 sS•u¦l[mm3ß H­AsœaãØNHRýŠ™V_Rr”L ˜<‰à!I9ÄËÿg²N¶Õ ½´K�˜° kËþ“‡aõÞ|qu\Õf�jƤOd,P âZøGcOm˜÷ê«-²€|O²!º fr2d@U^Þ‚zøÛý¿G ô¿þó¶q°QZIE¥wOëÖÎOŒ°#¸Ë� ¬ñtš‡ V�$e™©0V�åGÄ¢òV—²‹ ßˆéõE å´ÜÙeé 3ýEhS•À7ÏçÀ[À~B‹ù”*Kkéx� !‘.‡0ë$R–œ’p½PÁ»ˆåSYUóDk«¥D%Gâ;brmª ŒÜŒû V÷4¤^ï8¡2zÉ[_V„ót”yuBƒÇ†`FMŠÁidl@ 2vÂi_  )þ#J™ï”6Ô.·y®S¬Vv{}xøŽÆÀE[ëCg¦ Ûop»äšXÉîÕ‘¸ùf$&=å ÕþÀYød@,Hé¢×Õûüeä^¤]»›¢£°‰äáaW…e5f•ùv Ï>7-³KÃá®8…9¢¥�Ó©xÕΘÄQ¯CY™‚¦_‹ô݇±QÔ;Ý„/þð×¥íˆÔ(&ÓÓ"$ݱ£"Ü3=);ÙTÃ'¹=Ž®úÞLÿ¿M¨iÖçàr˜ŠÈ’ƒØÐ&ºíìŒ%U¡f<›˜aÀŒ»«ï¶%ååEú{¨`}�´¶ àÆ2±‘y€ìÏD?Æ‚ýî³h\}PÓÃh `ró!½>:’@DÛw*î×t(˜½¶&rúzàÇ›«üc7FÀ¹¿8Wu“­ƒ;r!c•7é}µ�¯gYä]5Í‹Èfèï�lé+,À–q {eØÉsý·MÇÃ#ú¿ùßµáS�Pr6âvðWl‚·×'ëÚc};ô¸cî�Æ1 &ß|¼ÿÓ¡§``ÌiÅÖ*X“mÇò.·qÊÿÌGDÛL~bú_Û�²ÔÔÔt�¹p¸AîR� e �zƒÕW]IœÅDÑÛûÝ—a‚Ay2›�¬òQ"n©µë†åæJE¾àÓÀŸ„ÖýH—¬ÜGB>}�aDò›µ¨qãÆ±:ÍŒFµŠó©­•syæm3_znÿþq2ÒÁ¸ár È–£©��B€<{×7І"t'¥§·(­Oú³¼ÿê`S.�ÀzDå‚»aÊAuiæ€Ië÷aû‚•ÂÜ…R]·»XÙØŒUÚ¥"–È?è ¾¦Öä‚}¾ìz]ï–.ÒY¢Nñ8]…p€=gž¯ÿÓCA„éž×ΞˆE0ºœGxb!š¨Uò©M¤OÏÇ%ˆ8‚þÄ^Ãeååí~=¯Ê].© ƒ�(ño´¿z;•—Iª8Cꪲ†%¤â6¡>µXYpmÓXŽê¯vêCà?²îþ­ãÍp#`c•¿�«]ÒÁn4ÝâhkÝR’3=}<¼«GŽ- ¡G06¥‚¢.U¹i@VV|“?Ò“’”®™Yq,½#Ì‹öNJ–Ë9‡#\·OãR½ 7ÞY¨Y’œ\`qRá‹9šë|¯ÌÔc º§¦’ˆŠ6w-’¦igðõ�Б:"Ù÷rOšã+@Ë0£‘-çõŸK±ü¨|¥¾¸|a /ŽQËó$)8.ËWÛëR�=G©˜ÏKËyxèÀ–+þWpô”CØŒiD'88^¬ÖÎF|e3Kvëž§�fMÍ€¹.¡rÜ,Þw†9ø*Ê÷<Ñ ¦òî¦Ð=ÐZï‡Öº ‚s4È+q.‹²;÷Ôú“¾tÓÜNÒÖ_:Hûr“³³ñÛ�‘]Á`†šl†OÖ,K_ùËÄC·>ÿÖÝ6��v³o8JyÒd‘ÚnR ê!YÚð¡Y)ûP䳋…À†a‰ÇÕ¡§€¶÷4¨ñyn8,"fPl:ù§Þ4 †7Xó\"Þ±àãÂö.¢-V¼‘Kƒû¾—:Œ<ë8†n¤óæ¼ÿù?Éâú¾îÖB„”¹+e òX¸IUßÎ[âñxŒhÏ´t; Ðô0Ýò�pQkeÙEœ®¾‚&Ç8Ü·D@†á•™Œ˜kP‹šYQ£ªYšíâ^ ­ó~ �–õú¿4(DÞû"W�Ö`Œ*’ÐD¢œÞ+ÙA¬,ª.I ¨ÌtLŽ å ø‚©í4Åã�Ã#`âáÝyöï/E*ÇÝ4‡µl^ÞÁíöˆzäQX¬`±7vcÆôQ)ë#€ Ò×!œ ‰÷ä•„¤ùŒè»¸ïd˜'Ë»t�â÷£tšF†K�i|ŒòÚ-8¨Ì»ñ�ÓtU{Á§7ÁKNë`o×-‡7•rÃì�ô¿Fª´§uŘïÆZ¤:ß‘æÎœ´šÉù:°…| ‘5GŠ~z¡Q�ðª†L¿ƒé'.U¿7èAs <ôõ(óLÌ”_FYpBM)0{¦Å—B®õ[‚?Íò”ïÙë_¨†ÔYq¿ô|ƒ�»rΓáXÀ”‹ÍA Ë]˜ÏÈ�ãÞ„8zZ0a©*‹—ãÞ°tųB3í-Âtž8?í?¼ PÑ÷{L¡½ÌÇ鬯èG™µÍ /€,ŒgrF³“×ÊÁØ34Ï®=¸ü›½šª–±ºù,Ʋ„>1ºÍV8*†‚Ÿy.kÒ—CÀÈcŒE Æyú9«VýÄlFÆ‚˜•‰ð¡6ÆÆÀFÝH<ÇtÐS§ü9>"sˆ3Ø×˾aP™È!f]<ûž¯_=eÊ”æ2è“{¤Õ5µwÁ¦s%ðͳ#u©%¢Í¶ ª-ïNžƒv»È³'Y®h|‘õgêþ&à0ÐÙ²ÏqƒÒ¦/Ej÷ÿâˆ4Cˆ �MzÖÏ‹XGЉ˜É{|±fS'Óáh�hå!ðC_…9¶}¨„àõa~�ök«ʳDð..k¼ ")§ksDÍúOé�ø±ÎÔH–=6–ª§÷Ù�Sš#­þݱøC`‡Dcˆ²ÜïÿÁ%Sy‚ižÛ±ðÌýóÖW­$1›¤,<’¾F¨¾D}oÖ6�¥SÄ3ü �ýP™Çi]ݧNøN3�Í&dâúä•Óè=(TÕ  ªð�öÝçÅUÕ†„ˆHé…uä#Uí\”ñÅL­3kò!P>^ääl)¨´à�³ëFÝ•™¾³)R/Xèwê6mÚCÌö#;ß=§àf“á»jk›L�Ï ;©az0i'¢ º®ß�¼×ž€'ÀãôÙ�‚äH ²² f-îöPÐS?¦sݰÐ�ÕÁÏhž ¬ô.‡fYƒËîÄ?#6츅´‚be<ÔºˆÑÑfDêtwR™,Üß±«·ÃHÝ#ëá=ýS3¢ðF¯M2E/BÄzuš˜Xµ wtάU´Ú\7ºLnm~JsÙ“£4º#x7�eZ x»ñ'Eö3l ï±1ú ·Uì³Ìa*ªWµêåäù-ãÇÚp_ôÄbT%„íýi9'Kžöú{ËÂ`ckC$"õtC˜â�ðn(„ÐÆA6âÜSÎ+aÞìÆ²P±¾¾Ê.fWÒòº/�0!™¹ÓôC[ý0%Àllþ=‚/@¼j>¸j�D¢o²6.uæ—™º™å,<¼QŠEÇO2RrN…kÖºÙ>¤Ëú;í’¾GíZL¤µà¯¼ Yj“®#™Æ[œNE_ƒc�gPÝ·X7uÈCf£ág^pðÝ| wŽÊ’9ÝSÐ ŽïÃ!ÚŒˆ†`ÍŠÂæJ¼‡?ŒàBPÁÜèt¼Ö 4ͳñ;ëðyË• ©Õj€/�2—‚g~8ê!i =qOÀÚt’i‰ÙÒò;r_Â66Ðn¯¯l�›ðŒqC˜â>ÂïNCѹŨÈ{H‹2a²ír¡ýr³fa²ÆZ_ßé ¿å°¥¼“éV¤‘¶D:�ßÁWä­"$?¿p�Må�¼‘½[·î®Ú÷ȹ/ï!Þoò2÷€•õ„ÄFD=‡äŒYH:é�¶ž“Ô4¦"ò#áÀy[€“iHöØóHìuî)§Ÿx(`“Ðs¬€&®Âߘˣ/P°Ë(�oÁ^%�΂ƒÿQ•N-­yÁQc²ew—1iêV//‚�y‡Óf[¾pæL}sE0˜ü{pÂÞ ,¾í;¦F9ž�“]å?¿l±¢Àñ›†öšLcŒô­åYàýŸî Ú/)ÕÄ4…Á�Lß­•)Œ“æÍû©¿‰N ´Ä±·¢ú »´UR�Í0oØiƾ¹óï^DÝ6¸ tQ÷´NÕ|0(k�>U¿³âPcبüH´ó×^w65åÄ ýc(ë÷�îØQ>™Œg’²X&ý6kèFÔ $½8qBÖÔ?ËéËw¹JÞ}î¹}½çÉ`³ÑL÷˜¦£Ø/-ð³‹Dôä{òmˆóO‘UÕ ©^ªD—$®õ{^Oøà�KxŠAø¢ev<™RÙKŸ[¡ÚwFȵ– i0Ê7ìà|‰êË%«ûz¯{mcu O�£Xè{°2Â"\ ¡ò®•ŸC­°ú¶vb>ÿóÍç÷ ÛK°§®eiõRÙBÝñÔÍ߈šÖb0�5�h»#£sN{‰Ž3w~ÈÆ ëŽl±³ìD`™�¡ÑžL‰1§š7ÒFr¿¾Hþ&4s ¾=ÚMXbá {&ì--"óá3ÿ÷Õ5k�ÉǬ€ǹÄ#z—ydÁϘaZ9vLè�†®>Õj¨Æ/Ž»àIP¶äÌ¡O±˜B@Z�‰��}$P¡¸Ûñ{‡…åÞ�hÉŸ®6"�^‡ýlÅq¸rÈzˆS Eëí}ÚØ‹c¦ðeª/|'4˜f3þp«Ûþ/éñv›ð'Î@@:‚ÐÍî„òin G_s!îw6˜u.ŒÇš> ™œ¯*úl˜k;pÞ]aotÆ_ø/¥³A¾ÎèªùhMÀ[ŽÓL”›ŠnÙÁí[Lå¸Z¢T0VÝ1XY3vÕGk¬º ½a¿™‰ç$ó®÷:Ž:kHù~ù뇗.(ƒöj 8îV0ô ÛÙ¼€¬>Qe?V@»‰g�r¢IH-o�›RDNlF¯­ÝÝ’¤’èçŸõõJL©.þ9*„\�M0Áý¤é·îÓ×%M=°>&Ã1g‡ òÚúîêå;§F‰sÉ<\ý᫇ µ¬á hÚ…ƒJþû|È$äE;^|0 ¬™™¤�‰6W5Ö¬iê[l*ùºH_‘ø¼Õ+¦ôOsš|ÎQ‘!®ŸÂo0¹h)ùl-(6Q'ñ†1žGìüªó»V@Èw¿ç8áÂ×f³ášl¯hr”à¬.˜†6„†¹›P2¢ÛON·xÑÖR_HyÑm#‰‹À求È}’ÜgÐ ÁŠŠxL~JëÔã9Zä#;¤ÖA[c«*R‹c;Lpp2Ù)»²KRRé §³ñ³ÏÆërÑú2ñ´ò©¼ ý¼OZ8‘$*Ò MDÝè�ª†ú&œ+àXøe×I<`ËÃ;ö>äo”Y��¥2‚'¡á+qæëÑ={ŽÞR<ƒ(Äi �\4]ÿ5ðYdqˤÎ�€“Lò›¼äÊÈÞàwÇ ÷Ä=ÉõïÁSo£O�ìãF”ë ’�}K‚Òuœ“{ vN�Ó"Ëé�. —óBÄæ†´¢ÑÖÇ6nWó5Qj"›R$UTý\#JÜ·­WÒ®M ™&ëºÖCždhõ�HüÀ˜üü`ÿ\÷ÁÒ–l–•TžÐ$È5ê6¬ØƱIp/‚R¥ˆÌ‹€eï5djr½¨~J*X Ðê#Ðà†v/bgÑQw3ƒ¢H6ð�Ó�*�" ÌNk¥\¹rÙ²J+£O�ïu4i6¸�ÛuáÇß—?ž!õ¹ HÆ";ÐjåÙ!)¢õ¡Ž¼ó „Ê¥Ka‚ݺY²šçø$:½/h“Óél�"Ƚÿì�‡vôÆž+Á)„„— �¡Â&²­Ó¯Õ6–oš2¥››Ygn e‹„aú•Væ”þo�Ó®`УQ–Y˜Ôç๠ìœ=þØ+%iñå�µ¥RÆ“€ „ÙÝH¸º­­dÞ�¿¢Qþçè7ßlެ!ý&Ñ4£sRª£Ñ†Õ­ü¯]ï—¾‡bö‹²æ—~ÛæwÛ ,'(\,–E –S±[Áôíò,OÕc'ýz@IDATôVÚWâÇ$Z�VØlT·0M;ÂúL´è{¨‘û!<‚f@›C8 VTW§°´£ÑøCݳ<7µ®­ë%®›x%^K`¨Þeºƒ†akÚÐt0{�p„ø&0×T”ü§àçv�=PÐî0ö<&Jr h¬Aœò±÷(ÃPmñœ�ü%šl|˜à&ÞR=Ž£YŠiK1e* M!Ss†y‹¤�)“£Ço+ß·Žh‘DˆýÒCÙËM2l)YîïóŽ`²°u,ÛàÔê›Óá<Iú;–@{`½Õ §^¶ËíÞNp̽¾p•f>F˜W>ØF§*2áÄ &@ëœn£˜pçÇ>@.@ #%E*úâµKÉäJŒ×¯½¬¼Êïq´1øI*¢@aÞ*Äô¯Õ‹Ûº‰¡sÙ-vI0lq“·dY¥Øºh4|Y(WÆí€Ç‡â™tÆÐ$¡òÄ}”jÄ€©ß !�j¼¦&»3ÊÛ¹W·ó†Ç ´1¥›�v�R‹‡Îå8êYuÄ{[ Y#–­2ÜbT1¿CÝÐö4cdër‚E ³»´Â{ÊøþùÁ­ ó�ñhHë}©Îo ë·Õþ?Ûg‰øT"€HòJ;W^Á~k¼ìÎV]É\pÈ7ü«ç#7U¥µS/ï’µ}ÅüùAÎ_Têû Å]ÆÆd}#jžsGß¼ÀÏÏüÏ|Rˆö‰™ô@Wqøa�¡I;A‚Ž9`„ñ÷f0.@i�Åà‡™‰ >I5�[Oò8^ÿÏ´î×ïrÂ…ïí‹K,SežƒUƒxzo£:²7ðcaaÛä7¥õÑŒÍEJ«Øµ S ÅýÎ�þà†Ò´kBYžõ…X �ÈàÑÄÑ <9/ÕP…š0\ nTw„�œÎŸšp›{\úÖp Õ�/(µ %ˆ”‡ éÀ[‰ !‚`ÜÍwtä~ Ç‚T%ƒ U}ªw†» ¿2Ás;hŸ_ÍÊB°Á«šô�ï‘Ó¢ÍËÚ»ÐöÜM¢$2`SYC&ã?��Óx.J㺭 `EE}¾ˆÁYž�zàØÍ‡lfj‡NÃyŠ-€˜êWÚIQKãM©mÛ�=íþMaP£­< U[ÏçÅ¢ÑJ‹Mìâ àmùà�ÈÃ}¶‰RÓ%Š˜ò,•©Š©ÝÆT$}TKÕò)Ùö5ˆ½ìÍL_ Î3V`¾D³Ý¦&�oæåÙÎd;[—Ý�íy“8Œf6À¯Q•²qp–{Û2ο©áÿƒ'‘òïHaÕ™Eq¾¹ˆAGÀe}Ý4óá²\Oqâ¡ÌÛÛ-‰(ÆÈ|·ø•Wj˜|í®”¶ºõÍ!_Pw.kjb»ˆ6õŠ#éÞͧ“5ÖÖ|jëZ¿ç3rŸápªRQŒ¤æöM£¡à©ó3Ý%�(ì×fZqðþìÈáHØÚ…j§v»ãݟȆÑ×ßQÕ?ÃÊå;>Þ¹¿÷óŸ™ðÿî‰FäŠÇõIÒOÕÀÅLºÛD‰ephnoKðîE9ÅÊ܇]éŒNNVaû z‡V ûés«˜4AÑ›#rpW!ˇ¼zµOþAÖw“¬Þk¨\¯¡î¢rŽîïýæ„”Ž_UHbDáBª;6Xv¦�·¿ÒWÔ%ÃÝyñÞóÏKùÛ(Ħ#zeÅÛÏXü……”)…;oã®ÁDh\JSâô‡o'Yš‰<cBÌEÓî>Én2g f'ÅäJârpÑüW^©¹þú‹Ú4ƒadÚláÒ¦ú’Åo¼$ý©‹)“1¤#!Ößó¼ß¢¢tK�2—¬2Ì�ù) e<<"/e?îoM›Œm¢-[£ƒƒŠ †ΆÊñ5z)œèpÚ“»‚Í^; Òãòó#g;Õl+%wJN޶s‰Mo<ûlˆC®E°Ú�Ñè~Ö>©Ðã‹üÑn;}¡=½R‡ ‚(qUÓYüR�Êp(ÒÙãL_ª,f·e�ìÏ�ÍÌŒå9�±—5´æÛ¯ëÍWÙe'�¤”øj«ß»LcY„¸1÷ *5³Ð4•'˜“UÃõ©Y(Ë0+Ãô’Y¦CUX÷ ‚| ‹³Ÿ�‡xîÝÝ37WÛïtrÒSABg«ºPIæBbþ¬¯°î¨} Õ^ж&¡"W>ùÎ@åêÝñÈÁ‹ Úý,Tó@Pn‡TÙS1Ê¢fÝâþÙÙG=ÇÄ8ôïÐs|@1ïw Œ o%k¨¿¸›³~9²¾lNö(w çp8R‡j”v¾NÙº6Fb›:¸m‘‰‹´z%¿ó*ò•Þ�{ëùÃk¥Õ×Ç}» 1h뛀zÂB^qÚ7 ö›+WšyyyF^’(�¥ Ü8Ìå°f¿€fùWd¢B‘¢ÀƒY¤ZßR¹cÃrî±7 ë`—˜vW£˜jA±Åbôçï©W‚™ƒÇž"þþݰký“‘ š©Ên±î‹‡èÓh!¾¼bUÚ¡¶¼¬;½ÁŽ2Í>*«ÒLk�ZÕ˜Ã82û4 gÆó*Yn,+<ÆÛúK�%m˜±r%;('‡…¦£aƪØ,¼Åæ¶Zâ�tN’•°¿qMRU[íi}mbŠuqẅ²ä—˜xZ²Ð †L•¤ŽðXÄŽŠ®V‰:ûöyÁiý»õØý…Îî›�ÁÞ¯h‚¨Ù.»P‰ª— �àZìÀH¦—šTlÒàôtoâ·Ä[kpÈ’3¨LhrNÃT×Ñ)®==iåˆ0…þ…cgCì:†3Ÿá(zƒij3c¥¶M•\LNOÀùm—~=ØP1“PqǙȊO𿎑ýó‘vëû©ÍÐFÐ0+tMÚÆ³ât°µmAŒ±,0F_ñô"äf!OëRÜ¢–“p´¶š¸¸¤.�,× ‡hQ™*e£xè{Ö¼3¹KÆ;wT\”bµÞRÞïÓD `im„©RÏØÜú0B¦!0ª›=¨ÊÓXÇì3:9ê�½yæ_×ÔXóu»XÎÅØô˜EÐ)9”³M Àì#QÝeå˜829PŽÔ´"[pé�†²'öî IG4hE^¿aPËfìýþlÉêêRQÞ³ÙGÂÏHûɘ%Æ'Ñкæ4™Úb°Py€�Ÿ»­ mÇôVÚ19oe¹ÿœÊ¸úb’ÈvB6œD•èHÅî×'J ËoÇ>SÒ)‡dó,ûÂû@sœ€÷U™nÇ-#ALÞú¾¤’ù}£Ï< Åtûé”¶h@ZÒÜæg1ÑÆÄkIS¤GÌ ?`ª/ž'ÃAÄ—‡ x‘Áêk»8�>Bˆå²y6ã~‚Õ)¨wÂÊ´ÇÊ ©l fW¢&`âšäõýu…™K#Óóý’z•Sˆ]/Òîóâ†>,×.ΚéÚØúüõžhã¿û'ï2$O×#Hý3®¥ÁËÿé·¯¾zðx‚‰Õ0 º9ykÇCV?Ij¨·Y/*ªÑÀqºØ°)ïg»/q �hbþÛ`ò`‰ILÌ"´ÏéÕÞQAfæ7Ÿª<ˆ<ívÙÉß¿>mʯ ^r]bŠ­þèåšqH2 ÁèÞC{ø²¢ÝnNü’dˆ „g\‡ Ô[·c(<ËÕ�ò[púüލl࿳YŸìÄóº±À½H žÝ"´|XèÇþÆÐ@š³]sùd0ëçƒMlí­Ú,²ÍTìÖ÷;ö=YXMQå \ë ¬48­¸ŽÜnÝ t0 �†ú|-0¯ƒ(4‚·9VW�€´ÞÝ µ'KÒÄ�Mñœ�7- ;iE 2ò­X¶· •VÄ‚ú1]É@:j"¡±t¬UÔ«¬ºÐ·9|îØÆàï�½ÑÒ¨78¤\´³YF¢,‡…ö˜0-¿ ›ê»²NO©–B§­j�L ‚7÷Ô�ómQ¥§1ÄŒ©0¥�VIì¹>ƒÖ ¼lÕ²]µñøõˆ˜¹‰�"µûeš£þ ¿÷c‡z÷nÖp—�³¶¬AZ"ÊÆ’ú­[[4ï6ºó§ûh@2:¿W~}Bð’¼W\}Úœ"ï�$©"Ñ!��׸þ Ä:8dššÕ¿¼i0Æøp$6§¯6Þ^#+³œ6ŸGl&¤YMþ¦‰à%Úb@ÒÎðˆÃ/këá^¹·¤D{? ÷ƒ"öql £½ÁèŸ<èJÜ�¼Þ1rl a½u][CµÃ�euíñ1M"…6B1C’áÏÖ8qtvzÙ’} ’ ¶"�X¦#´Ü+ázñ1ß5FŸÌ°§öǵ–�ïì.a4)§Õñú;W.Úv¬à%Š±Ú‘!ÙPÃÌHH[ÊÍ"Óp>[WV#ëmz RO6†?äøÝš/iY£ÆOµ9Ò„K- Ó‰;— Í¡Žk‘ò=9œãCàRÝ�ä0›Õc‹á”? Âé|ž‹ÿ­_êOÕg½^V˜(©Úx”Yàuð¶L^(—ê"_ M{d¦c÷\ÃÆÄÓš`F'[³ÖH„°Ù£˜¿×üW^I‡]wg§€×W?¡~oS+%¸mëìŽ è"@cÃáëƒÉÔÀ÷~Œ•W“$…P‡ÆBà¥ça·HF|™ªóɲÚ3¨];¢}µ\›´SuC“·¦Ù‰ø3 �h× >u´[`K\Òy±�›Wô€aL@ºòPLÔHuÎÔtõ†õ%_’ †ô”_]9 µæ¢pÉn¨FÙµµºªÎÄ„-'í@F¢u°\iîΚ®‡0¨�‹Úë�²WÆLuI(X»šh�äZm¤íŸî¯Iä™+€/}«Ê–i"§fDc=¯M©Hÿ ÐŒ¶¤"0±,›å`­#+}UU}2ÛeEhýŠªˆ|ZOß| G»�Ó�ôû‹’ºž¨}m†EœÇJºÊ2qCeHì¨pÀÇUUa³(ÖÁÊÕuR]µJiÌÉÐGÚ"ËúÒ9säc£;H;ó¥­~üÿòÙ'»ë3$ðH#Ôî”\‘y­!/­¹â7yƶ,q¢bÐE²Ùxf ¢‚›bºÞe†zq¦™,°lýjTb^º¦gúa~jÌ»`ÏSæclÇ*x¤êýN/ìѼ±�"‚k� gÅúæP¸ìÓļ#B»Ã°³’bŒ’æâ„‹J`¤ÛÇ Œ|›²y£ñƒY³Â­ŸY§Ý _„™‚¬ ÌMs¸N1§NèƒÅâÀ?È`*þ2À¹àY"—î'ÖÏCçHôGÙφ?,*›_as9 VaT°ÅpŽëF òmnF G’1þ¨çB„o¢qP­bg±1¡°Gƒ�ÿ}�Iž„Û`B€žëQ_�Ó𘃣áªË Ueï×�k½S-EGhK@�s£]`lÈB "4i>ã þF‚gãQæ5¿®u…æ¶qL#*=€@Ëx$íWÕ›aEÇ€Õ@ÖÂ#îeYÕgrbC0 ºt¼ÁÊITѪxkMáèÖÎ û¥ïç'¢!⡌jVvºåeˆ$„�sˆ6jìENÜ!É�òÚÝ»ëÒÓ™ ­6�ĵÈ+© gø ¨˜<¶Ô{ÓÓËÇ“Éväø¡6x22“ºEúÇr_ÍžâyóšqâñS§õQþvôÔ˜tµ@ÅÇ€±¿ŠüŒ´1½÷ ×’vC±�PZø3•'³› yg8oé‘F5/$r>ðçv)f¡Z%"n/‡© »{$ï™ÞÆf@ÎOoí¨îv–‹ðLî’uõ~hÏÿ ýB”´Ù—éÐî6$›æÕ”70á»xTzì…½3êçlx|—ƒÜ}©§î<ï¤Ã0M³¯  Ï_ì;àÉŽ‰p,ß„r7É*&†�`bQ`hÿi,ÅÕ;4éƒAyp¶1wz#'Ñ¢£ºwø¿ xÉ󘜕£µgc’±<ÓeééàùÙC�ÝEúÿ,øz»ù>qÜŠ½Ñàºu&C ¦rrÜ�Àï52Ñï¯@5‘ij%¯ Ë|Ó‘;þ€O’£™VîŒõíS·‘9±¼Â?"¤ióñHÜØ÷&ûýåó<‡¤ÜVB“ç™Ü½_.$åhp…ÕÂ[=B0ÌoCº^ ~íòu �>»ç,JÄË×XQðz5­cGd=Ž<§X¥íDªŽ�µ‘ܘKDö f­1ß꯻¼µI´}IyJã ±ªm7ÍÊ™ÓM�Ž ¥u†W®JËu ¼‘�ù>ú£‡'Tø&:F ‚”¤¤lyji‚h†(Üâ�V Õ¬@k+âòÇv‹UO§õ“‘�ïúN­d羡C‡Æ·ûÃÃÁ£’C«¡… gò â9½3ÎXne¹<àw1íúügÏö%Ý­W¿@œúÚ0f¾aåèE0=ž•$êdZ0�5³<1*vvÃ%ð •ÖÁqc�WbšÖ$éf}KÝ'¼êloÍÔ7ªj��a}†,ûY^@¦œ/Nˆþ=Úù­•€‹éXt5H›[ïð‰1lýº­!ò2~¢"µq®®ê×À[ÿI ŵ­ðHLêbÄ%ÛäX� ª¤6€¢qËø‚L""8%£³3ÉÁŽC ù_!ƒ>Ù <=ÚßÞ;ï»ÜÐ�L™Û5ƒ™xrš½v3ðgÁà¿BÐF߸*'UˆÑ2‘[ÒöÏŠêíÅõE�zZ¾8ÎËã§ä$Y¸]¨÷¤� >ï¦Ü=œ u+¬Éó´&¨Õàôô «$c»¡n¾u‘U îƒø`JûÔ¹°%U²Ü9òìr<–y±£&k6'i4„¶šJ¬Ö¯³6�¦†:­<9«–%6¼c›·Ã}�|M\]SÈû6àªÿZ¼ê±mû#þ^u°¡¬ü×a£­GΜ\‘î°×{hoB’{’5&§¸ä$G#"er¢UQ¥ˆi¶Õ¥Ìá˜ì%H`au6v„“‚� /cªßB{#ý’� =@ ¿…¾QY{Û%0ßEjâ!á£iq¹ÿVÃÐûZDöá1Y>i�QG¶S€Þ 2§ àòßÓÈÏç­ ¥†ÕiIé9}’{pþϬ>Òfþ¤“¬‘(å(H¶wÄ3Pð,6^Õ®/@)xÒ7r¬ª­ME5°sê%%�¬'dMv)î1$%Ýn ë3”4ÃbŒƒ…�ˆ ífù¨lÏÔæü‡_d � ,æI,âEŠ"Ïyxõö†ÈTƒ6oÅ„±œó �K¦ú¬�wÈ>-¾°fK€,fb’äž9ƒ§‘bªG$„/¼5X”AŠ_އŒJÏê˜1Ùi-@=yR~ŸÇàw³¬QÿDÙȇ#Н’¶¤>”$ð7`7|IΈ@ën^xµq�Ãó<ÛM&?‘†GqÊiC¼Œh—�w@U"dÐZš kB&‘ µÅ¡Q—h’<·2X8µ +¢uþ%.q´`#}<ö؆¢”ÇÍGµ�碪´UĹ`÷™c‘�•7È&Õ†*Õi”=�µó¢¼–Þ›¹�˜õÍN«ë$ÎäL�ÕIEéqHÂ;±Ÿ6ˆ®ËŠSÍ�øa•¶­LÑÝd”ÁógÍ*>÷–{Îád©a_»äM­µícÛyìß?TOóÊÊX'•!U?Í#pô=i]^ÚÎóæÑ\¯aI ÏeAð²pÄÕ~¨ò¿‰:xd I†ÞtS˜Ù‚²§k ÿ $ƒÜƒ%®˜[\vöÞȺ›–$Y÷Ù£GÛJ<•PºBqÊWun%Ϩ¢Cªv-]´=qnë¶¶~OæFzÏž"RÐv�å¿yëåêDÛÉy‹+où’ZS í<÷œÛÂŒAd–î�Ä–^Ó3gÇòCÞtQ°ŒBI*¬ $Lm;Ym­ïq"ߟpáK‚ðé]Ä ÷AÚð×í•usô«M]]¤[mËXEެ¯;Q€1i£U`¿ú+Ÿ™´–h7»|±þ À9X0ä �˜<½2—磄OÍJ˜×ªf,Dn9h5õѤTvb0ˆF¶¼¢é5à¬kbÚƒZL‹8Ì–Vù¯„ÆÕÁÕÞY]_äËHï|UG§ý9LÄ-19|³…âC¬ ´õºÙº�ÁcGyÞŒ!q8DÑ–—ú§[JqþQ¿ ¶ù´ƒhˆÁ¤A×Hmãyï²={šz44˜iii´§Ï ³Pïqàí=�SøE]À;e RƒÉ½‹›Â×BƒysEµ_ž í&¼«)úpéQPRç{½�'Q�ä¨0¹¶ÚL>[[+µ/i ÚD&ƒ|{åA8/[kZdÁ‘s‹êÅíu…¹.zˆ@sÕHá.DàôYѯÞÑ9­ó%çÿ«ë��áKà,<Æ‚Ìá�äì¥Ðæ¬ÎŸ×ëðX¥áÿ«÷ø_?Ÿàº©™b{ÉÍÔJ!s44Ì3àhÚ§RiÜo¾àW4²®½©ù�Iºv3�ïP]õ¬óºf”aÐÌ¥åMúþeD5RYò¤ÓL{˜ðJ!N„Á£ » q†ÍpȆY•k·>¸æÇ%ÿLÔ“#cH¬f·='ÓBÇÉ&×´˜<ê,®—ëØ #z?9éxã�„©v°çKª¾æê5 ËªÈ‡\<}’&«‹dNRw”•EFô�ÂA¨.–)Ð˽XýÞ{Jká}¼{ü»ŸC&žØƒ€Û²˜Õè)˜ì×?Ô ò \Ý,<»¥2I¬L‹5}fš«!dÃ(¶¸G’Ô½Dðn�I´?뀿Ì6y K<ÀCFm²$pÊ2 Aò›9hp»¥kr‰Ç²²²RŒ›t 0 :8¯îŠ7c¢Þ&åKUÒ_;‘—hÖÙ61ä·Ø@§§1�±ˆŠ° æG;Ôp6øKÎ"Š ùü�¹Ev!Öj-”¤Â˜®¡fdfC Œ’N-èÓìPg�uQ×™ÐW£ÁäG¬�DûÛ¸ü ùè„k¾à’uFMñjšÕkƒ!sEªË‚J¿Ê$x£o J �Ö5-öõ_KJÔ{õ¿ØëâXqXÁ(íGT‰`(î|}lnTµÅŒðRŠÅnY`|‡ÊŒn¾|Öv%”œ¡u@ ÜQx-ÂÉv·ð,ã|gmÊêÞÜz¤VV‡žCI†áÙ}Sµé3Ç e&8 ™"Ân"£®žšÞ`pññùB¬M»nÚ{N–¹¦ÈCµMÒùñ ¹*¹ý½pV¤k33´~IÐ9tTOÆ!æ0Ü´×cÚw4+w²q¶'�ZtTMý> ½cG€]“…™ Mú¿¬zl<íµ1œL H•²¡½ÞÐ?";É ¹2'C¬N²ðôTYUF€…rœFÅ®){?(½™3Ìy(ôž¬à GËb ý%“/ÌG}Ðì.˜½ô=\þš|G4’‹®¹Ã±p9n �¯ªú½Çe�ñ3ºôƒ‘¸ÿeBÒC&;ÊÑ|ŒŸ1 .�·kÓê { 8ŸâÍ|K„ý¨k®­ßa ž˜ƒF»Þ7EÌu[8_LÕ'õÉŒýV(‡ÄŽ£v]ÇØ.÷÷É'{£Tνk¡í\žžçwš *Ñ®&KY±¥D¨ˆ~/ïä§ 5ÞóÍŒ¡?Ý+Ý]z"ûsbFåÄ^…X¯î,OJE¬;ƒ¾É¡ž P.Cë{#Í&Ôƒh*#+fÝV)ÄÎȲS+†gd4Ï+ò»o€ÙF5õiC§öÙ¬Ì9ãZ™äïïñ¾ˆßßÑ`šæ¨â‚ô ­7Mb•íÉKtþëq Ïx` ?©^ÖŸwñ¨8¥Ë A¯õ÷+:§5þD!x.oXNB ý8T¯¤­Œô8ÉðØ[7 Ñ.õvÎÚ³iÐv»¥kmDꈟŒV|�«Ýɉ*z;TŸá‹…£åB,6*-M�ãÌ�ÿwÁ,>â¥à tçúc¯ýGý}B…/‰Å 8SŸ„Zy)BO°ÉЗmûrÿ¦ž£:ˆ¬�ë�råw!쪼�‹âUãÚ'bCé~¢-��cl©¤£¬Å:�ÕÕ¾+üh_ª³$�5~~Èw‡�ã¦#ˆ{•HéïC¸ñ˜,i(<™ŠµZ€øÚó°XUð?œ~‚Åq©£ÌrüM/¯ ¼‹E9!¢š3«òJ‚Ÿ–Ü÷؃hC¬%eœU£Ñ–)a:òRÞ|Ñ® Iâû C1 `õk„8%ƒ¸ý슆’ïɵ4kÖ��ÖG€—‚^`è OÉè«ÀŒ]Xð35S]–bPæ—¾2õzÚ"L‹Üs2Ùl4�L¤.7j°�›x'LÿÓ�\ÆêÔ½HÜ@.„YÁqÔpÆ{±tٌӔuò¾]%DË%×ø¬¨ˆ?žÓ‰èçžÛÎQ§as¼××S,ÎÂwž›Y¿1@þر=CëÏ4~X}DýÆãnƒ)}©�–Ï4hñrÄž)ë('éÈôúm®�ÉÚÙ(�‡ð½¡¢ûBÉa—Õ?ÂÁ±õ;¼UÛ�7N'¢­ÿ‹×XSÕ88b2— ˜›¥­± ü DŽ M×ÉÚ=EçC#ó~J�X_\•Œ€üO²62®šŸ¥¸˜™µO/…iŸÝ(ÿ1Ôö ÐË»dnn-|[÷Ÿ8Te�ùĤ¨Áô#æ_ôˆüNY6’:¥¬N¬÷ÄoH�ÖÑó¡uÃÍ6CоÕ©¾ddAµ¡Ý€môTTikœ#HÊͬÙ÷Õ�«¬~¼„ŒÄù¿å•dn­¯mì¶» …")!FÆ3©6~ ÖüˆŒòJ7‰ˆCe:ÚXýû¨¢LÇÆd« þqÌxžYÃxxO]LK*à 7.«ÙµuërÑý–~þ'ÎYòn3eõuðX$ŒÝF°¨CèÞ>sê½—ìñµ¶<†öÈir1æ_!d¦XÙ‹@A9ÿë2ß§~ÕüHHÖ°ôuˆ<(=žà%Jˆ®y󷢞b í¸ÝeaïiŠ©]B†>WÝ[w3¸C­ûOÖÒ’—^ª0´À¢„¼ ß#;ϵ¶*p2…p ô¿¢™ÉwÊ1ãó¸®ïöÇ,7ÛYz“È À«"¯�WÅ—‚aÚ¬âYàŸáªÍ÷®_uˆÀ ÿé9p„/ =œÔ‡²üœ©9°bpr Y •Ôµ+êøñS–ߎ»†1ÍWØôôç�ªh.uVšŸ˜åâgÀ£ßÇ!šóf¦ø#Zz6”WÎÆÐõЀ%êO<£w”±‹S¬UD�z`Ë�A�ª ?¾r–fÁ52HL„•? •«·~æTMØ›‚ n –ÆW\¿ªÍóè�Š Éo†?NoLdöxlý]M OÖ† %Ž"±`ªFXÕrÊÃÝÏÌ\Éņ÷€Óa’�6Ÿl¨«;*Ój  ƒ¢¢"z(*S3œKÍ}­Bç¸WxP9r&? 0ÎVhx'Q¦úMY¥V„œ'CÀÔ³œµUïPl�òÙ�އiLJ«xÃ5K5SºÜ›'Ìœ‰l_ú„oX‘êû�æ-m.7³íp5¢�Ë1Y}ìÀÎÃÕ;DBRuI(Xë¬înœ¬’—æ´XÓV­Âð·yï󖓉Ãg{cäڈ̴±ÂãÐnoò6F– ¿kMÆ8+;Ã(¹„aÙKP»w-CëÅ&#LspÜ HÝ^N·Yê ÅfFbÚÌÒ€·%­9+VBË�þ½™Ž¸ó�½VªZdP¸ÒÛAšS£sü=€þv¦³†Ôí{¢jÎvK }h÷+Å^Ki%võ®°0ÎܨЂ"©»R­Üß%™&üÙA,­:Ƹ ܺc!¥ ?Ïóq:¼°²^yBû]£ `³�PbÚu„޵õÈ:!PùŒÀfkPx.ù‚ºy1lÌ¿3!£8G`?AÀ0�1û9há#„QÃZkl Ež¸¨{NSùê÷”–Gz Bƒv�ÝnÓê÷^ª8ÊïÐú¦ðû&|³Ò¹ ”¬IC°óv¤õEúËÉ,¥¾Î‰NÔUŠÿõÐ÷UÛ 1j仿„ žë‰Æ¤Áüy~1�y_ìÜY‹’bÍo+(䀓v¼ÀƒÀ»VÏÈ8jÁ躜 ͆ÁŽê³PlsX¹vëC¤’’ð£$Äõªq…òWk™­Ïiý^fÄ”8Ê×CŒG(; âÔúûÄ{ÍàÒÀïKH‚XÞd¯¥ß²ÊÀ_à”›,ƒ7å/>Nv nX �8ÎýgÚ4¹?82!è�‰|;†»ç¿„§"V±E6﵊òœÑiiQïú%‡Üœ9 šÂ hƒíT�y˜âé·‘Ó>Ìé,¤�Ý�—ÔÕÙÒE±‚QONb“.X�â~hV3'1Y0¤Ö1ɪ•�ãO¿Ø`´ïìj ‚;![_²¦Bpô¤¨l"$ä[}ÁvÕ‡‡þ’ &}§õ}ÀGëPptŠÇžò’ÞÅ» Zï^N¡g‹(ŒYÜù;‚ †Å’ƲìGø~#T?mHö5‹/nÀÀ '$HÈ 6\VÖ,X‰«1¶;@Ä4ÅN3ñ¸¾É9²E�G ]'“ûo©ßBèÑàé­ú+ÐðÛcL08Aƒ MÛYOÚIur`9à’ïွ‡ãX¿h2W³Rüý‡—.-ÛZ¹ã—o<·'ØèKû¶Êß›8pÈ¿^yéþFù¬Ÿ5ÏSY—á£Uûd�#A*ÿ7âämˆUíSØØc&ÇÈð‹ì@L{ÀS]²:]µ²Æ×5Á¹@0©‚\UZÛæá ÒxOe…þòèöÉEÇ3Ý¿.ñ«Þtðä¥!ëm+ªfœ×1�@wÍGXU3Qœ<Š”•Úý;ñáQk8(Gä�_DÈ  ­†DÊ|fCùŽb<°ÇÀ÷|Á‘çRÈw9ŠS8æÅÚÊȆ Ý2ËÈú NïÌ‚ a¥EuÍ¿»êÐ>òykHåH3þ£/'LøV•jaò—_»ÅÂ:ú®jE†BzÄìéÞ�DâÊÞÉ……-šé@hU˜�€+Úi°!eg{¦tJÉ9™8Š#áâ? Š=0)B ®†ƒm”Eä'h~¿}á~oÇLkú§Ø9¯·0´&þp‡`¹ õíg Ý" n;€¤ë'ó gAбb9B°ü3aš¸'yB€Š¶ÐÄL&3A¾Õ”AÎÃÂÏç}W«’t«ªPWƒ‚äBÝÂ_bªÞ™™IhíL"\‹½e[AD~êš<¡šF”¥�sL“{·&9pÙ<8Éõ‡M¦34Z/)mrv‰NÙ�\L|ÿÓ+hA ”Âxléï¢bÑ5Ɔ¨ˆ9š¿•ƒ×[v6žÇ“SPGgBǺÆþ[|¾,â¬HŒÓO×£¨�QºÈ‚ !d=ej„Ò/åyúlEŽÎö{<J’ü¨ù!øãpn= z9Ìå¾ÈFH;XóbbºôíåeM˜(ôãðzºÛ˜*¨ÔßÞß×0œ„™ûˆ€ GÜüí] pUå�?¯ïœsßÉÍ ’4@yƒ ²j�dɬӮ–n­­ãØ.Ú�µÛu;íìlµììt‡meU´jíÔuºSug¡Êê Bl$D!R!¼“�\ò¸Inrßç}ö÷O¼r ¤`'Ø6gnrsî9ç~ç;ÿïÿøý?§�…)B³âØýèw g;±+¢{¦Ó–µ3‡^Þz¼…´µÅÞî;Aýï]°Ìÿ…LÑÊÒÛŒzÁ½øjÛŠ++“½rÜ„` ïGQÈ6^€ÀÅ‹^ ¡òÏx¯Tpù¹È®lA#ÎíæÀ@xne0vK8ÜE$ZàòCIòÒÎgù�¬¦U)k2–÷Þ´®¯ŽCYÞÓ’"›Zá󇼒¬$ÅS#Þ:]ߟòF†KvãõŽaDs Ë~XýW .ùþeæßر¢g>zB«Þ^ÓýÚË—¢Ðì*¼ôoÉd¤1Õæ>ûåê²h:Ó»÷«y¥tœ0žÍ€<Æ�ä ¤/`ÖéKÜš‹éy ©rÈÛõDzåC¹Ýw"C ^ïèE'­Å7êÍØ;)òl}ùE²?×Å6¡P3*ê””­…Íû.¼YÈf:"—ÏûáA½nèҦ啞n@ÒnútnÏ#�áüh£„|åòu¡-šÎ‘aPˆªÕÌ‹«Ò…ƒ?Ù2R ƒÂ�Úüâ‹# hºAíííÊ›ŸTÄ,§¥EËSÈN6 ?²~ýˆ‡ý.d~lIY-ðN®+€´E9XÇ*tHR<Ö/ƶ–Öw%Ö)ÌÍ©¢ í±ça ¾°=ﮞMêjŽ0ÀY UÓ¹Ë}¬_‹>s9)–úÈq$ Ìèü³=±ÎýžQÊ¥4Pv£Î‰ÿ ¯¹”�§RöÖWð;¨äbu\óAÞŠí]‰jn¬ò_iÜf­__â*)Oæp�dì—¨¥À=§KAúÇœ�ÉØ—À 2»Ë´Øc&o‘lñlů‡ý=`X‘¬‘øa®¸‘;ñ›¼z‡nX!�h¼a Þ=ˆdJcÉÔŸ+²�uÜ߀މ¼kºß�%í»WNÅ7àAEW¦ns†»b ¯GíÂT„^üËvÊŒµ7ÇÜŸZm§~ÁªfÞ æo;ýÒ)ŠÂ™¿jN'öNãCÞR_ÀÖ“+€Ù݆ûùk×ä¿=ž4 Í�7Ú£Äîö!�ªn ço› .m¦ø ‹ƒëÓ e½¿»ÄF¯�dçÚåsc}½¼R|ÑO+0ÝÔgAŠhBòË2%¢™]ƒ³ÑHñC„‚@ÁZ �Ò (Ȱ¿”4´ïÜ]’Í=Éb[âæ0ǹM’Åž±",öòxsç§}Ñ‚¹ÙËÄ¿ÔÍÍ€Â<·ú;˜š lV¸—p#òa�)jáÖ@g³>T<½VTXu:k,Ãúõ+£vÊ©þñ, F¿7›”²­$tÄÈõÓ¸å �mCC¡„«ÎNôY…èb?:±`º-:‹P:kEö.�À7aúÛ’¾Ç¼Â�¦¯ÂWýÅ^Û1Å cY+‹x0(îBƒÌÝ oy7ë&‡7–næ™njÚ€^1K= ²š§‚ê+ÔµHß‘T+\™»‡9Â)Kƒ¡öö¢€`ãáféU3·Ûß"d´çEŸç[HCÜ�Å»�uÿŠ6Ô€Wté{NZS½Ø[æ)Ýð‰hì̽TÌüGÃåò1¤ƒœ•´C ¸ïžêLßO>oφwð‹$,y¦Ì„Å:,ÍZ!ˆì¼"¸¾¡”µÍÃ2úÎ�¯ÜçÈé+”ÜŽ¦£³7C9›Þoèþ,"¾ô@ꇿknäŽq-^'ÜøÒ— FÞèYMâ÷¤!9Ï}ÁÆÞDú›4Ž�ð¦�·JØ/¹}<“dàV=€kG” ÁI¤ SÊr� qý6°²;YBw† àŸõ4]½ÒÉH¬ã׿‹[6wî«y…ATõŒäuÐü>Œêœ_p½®&J|b˜D©¤ Ï¿Ýû ˆ6þ%*ùeèì!Ç»Þ` jÌ__W¦˜ðø€/ t[û©Æßü¶®®Î ç‘ßÜDÆ&œó¾G¾ï´†?g Y;gM×ArÉÊ•ž@g§®•Ï\æäv¤åŠh•¾Ë0­%š®ÿSQ::8—ñHØscHûíOÌDwgµèd�•½µ�b†6f^‘š8ÓX†JÃI`Z†ÒF;ER„FYû3K§µîOOq,«©`t¨ K�òÚßöÞ[ÿM) Š*K¢Qçrs#wþkýú1¾WúRû{S_¡[2åêAÉUÒ ¢© Ê\¼ B JÍ=À¼[ ~E%n^A®Ñ§hr„žîŒYXå—�fuP�™hL<ÅÒ¯�å½Òu\íßi’�q#cA[îsô€§ÁÑN1iˆb0Èùϼ¶>xflhFé ?Ǿ¯ðÈ¥è¶߇kcbBTˆÃy>•Î<•äØžHt¡k³ùŒ hðˆÅö+ä)L‹�ÙÔÓpàƒ)ˆe¡KÎëÚ2`Ævš/€Äž ò†Æ|IDAT®:çˆEé¬ýãÛ�ŠÈ]sî•&g¹Xª¢·rZ¤¿·ºn3˜�¾ íÏæ¼ö&4i¤  r TƒjA„Ô›ä샢ÆÃ˜ÇôÊ©wšôòüµ(ðÁÉ·ïéøyþƒHçÁKEâ‹jÃÃoE²ƒƒ”«k�$Â[û™÷²cn—ã€Á˜tæDJ'÷ÜX€r5Ïy–»¦Û ^ÏVÛ§[~Î�GÇ–\�¡pPïâÚ ÐSF9/ ¨Svȹ‹B’ôó^ËT<¼øwœeG$EšaÚú/ÈÌ¢ò%/ºå¶C÷òò?"¡Ü}žÈWâÑ4µ€%†Ri¿2“ ò¤˜N8†å器ŽúB‡šåâˆÆFˆšèùp¤ÀÂHÆþQ@‚Ù¾¿ñgeÇ >J˜ðMkêþÄZåh†Ý-°È ³]Ó®Ó™¡”V½RcÖζ›ÇŽeɈ¶t ”›Šò%ò3…ɾ]É‚)ßEš ž\/"¦ùHÑ5ö'£ÛÇ>{9y¬kb|w Ôt¿�0\(ôhf—Ç#+§œÒ™µ|ÛÑV»¿´„û³r&gÌ…>4BŒÔÍ9®FJ-Æ{NZš1Í7âí$Ï«ReÀvÖ-, þøáÌDÚxÇÊéü‚On_J},½ï?˜€Œ0ƒÉv>¼dŒ>”ôÄSò±//§¬»¯'¾“ªÚµ¹!S0γ³ÇÛ¥êê€ßWô0Œ4šù„~Ì8Ò;€7CÿYrO¸_)É®ŽÙ>€Ö:´‹2 ªPVãþÞúeAï¹k£W*´9‚4OM¤ÞŸ? röÄ—dg­#ÉM›å÷HVBwýLpö÷ïâæµŠEE!Å7MƒÕŠ)ì�U¦vàp/PtlÚöõ ÿŠ’s2Y÷Ù0ÀdÔ€CžxÕŠÅŠ\ªðÁ»Z¾W4ú©ÑÿDã5‡µÈVR˜r R@Âð*ք̘x¨)¡bô³‚k¡ö!UGèG÷[XpõzZ[×C ye‘ZÝŽÂKšf¼ øw PœuÈ·¿ÙÕðöá\!'ÿü“?�ŽÍoC .1ÒÏ+´X�Gñ[¦Qå@à@°yÜ^¶,ÛRž}o<¹¹�Ð&ç+k¦2ßCRÖ|ùÐl_íèX-]ñZ�z~ç¼n61¿¼AÓ�¸7 üòÂqCKuîIçß�–��rÔs¿¢+ðoÛ¨ç@øB½ˆ £ÏDñýhÅ(0�“r̈́훋£¿å¶ÃÝñµÐhY€¦ˆˆ"ØÃGßh¯ÏåúsûŒ÷Ú B%““�þ5bˆ†æjGxTÌK®ðáà gr}’£Ãèòšë(¼ÎËj�;�2*K¶©*S“fÒ FÅŠ€”é„oC„ehØÍF{uëå"„ñ®iò}˜>l„ö)4ʤ’ZEd?›“ØMÐÌôù¹#7tbœFRt»ÏÅ‚mï¼–Ù¸qãǨ!Z|kV¬ÿ:ºH«€ÏÞ $’èafvZÁS  ¸´ðIûO¿cÝ"8Ó1Þ¡ðΓ–#*íÃ]í­Po¹œÓs=ß§Iã{=ß�¼k£‰N^Š^.yýàç%$šçí:a?Ò9é`túùùƳ%Å•þ5†ï·‹jý#Øå«=Ù�Hâ¤,T¯nôżNŠšD®ö³Äˆ5Õ+ÏþiÀ6pÎÙ1Uúò�íHô¦L{&ªÓƒŠPŽtÚ¦vw›;“I—5ºvJÑÃI?ç�-:sQÅ›*=ÊäÿŸf(ÍÀtF>ÍŠ45ù^ëxÇ9Ü›XŠ®ÄY3v\Mñ°Õy¼$-ÌhúáúŸ=wµ‰Kš“Æ;×õöþ¤ñ½ÞîÈu|=d¨ÂP�øæe `Wºljt¡}V­Z…úÊżù•>—û;å¨Q\³iÁɽG¯¿Ï±ò??ùó§Z¼¸ ¯ î«£rôŸîÓ—îM÷5TTÄÆÂ=/ÝsôвHµé…²ÿß<ݱßiÒøŽ‘Éß'G`r&dÆFrÐ?¢ƒü†¤1æ=¬ÃIEND®B`‚docs/_templates/nav_links.html000064400000000314151742731020012503 0ustar00
  • GitHub
  • Forum
  • IRC
  • docs/clients.rst000064400000121444151742731020007677 0ustar00======= Clients ======= Clients are used to create requests, create transactions, send requests through an HTTP handler, and return a response. You can add default request options to a client that are applied to every request (e.g., default headers, default query string parameters, etc.), and you can add event listeners and subscribers to every request created by a client. Creating a client ================= The constructor of a client accepts an associative array of configuration options. base_url Configures a base URL for the client so that requests created using a relative URL are combined with the ``base_url`` of the client according to section `5.2 of RFC 3986 `_. .. code-block:: php // Create a client with a base URL $client = new GuzzleHttp\Client(['base_url' => 'https://github.com']); // Send a request to https://github.com/notifications $response = $client->get('/notifications'); `Absolute URLs `_ sent through a client will not use the base URL of the client. handler Configures the `RingPHP handler `_ used to transfer the HTTP requests of a client. Guzzle will, by default, utilize a stacked handlers that chooses the best handler to use based on the provided request options and based on the extensions available in the environment. message_factory Specifies the factory used to create HTTP requests and responses (``GuzzleHttp\Message\MessageFactoryInterface``). defaults Associative array of :ref:`request-options` that are applied to every request created by the client. This allows you to specify things like default headers (e.g., User-Agent), default query string parameters, SSL configurations, and any other supported request options. emitter Specifies an event emitter (``GuzzleHttp\Event\EmitterInterface``) instance to be used by the client to emit request events. This option is useful if you need to inject an emitter with listeners/subscribers already attached. Here's an example of creating a client with various options. .. code-block:: php use GuzzleHttp\Client; $client = new Client([ 'base_url' => ['https://api.twitter.com/{version}/', ['version' => 'v1.1']], 'defaults' => [ 'headers' => ['Foo' => 'Bar'], 'query' => ['testing' => '123'], 'auth' => ['username', 'password'], 'proxy' => 'tcp://localhost:80' ] ]); Sending Requests ================ Requests can be created using various methods of a client. You can create **and** send requests using one of the following methods: - ``GuzzleHttp\Client::get``: Sends a GET request. - ``GuzzleHttp\Client::head``: Sends a HEAD request - ``GuzzleHttp\Client::post``: Sends a POST request - ``GuzzleHttp\Client::put``: Sends a PUT request - ``GuzzleHttp\Client::delete``: Sends a DELETE request - ``GuzzleHttp\Client::options``: Sends an OPTIONS request Each of the above methods accepts a URL as the first argument and an optional associative array of :ref:`request-options` as the second argument. Synchronous Requests -------------------- Guzzle sends synchronous (blocking) requests when the ``future`` request option is not specified. This means that the request will complete immediately, and if an error is encountered, a ``GuzzleHttp\Exception\RequestException`` will be thrown. .. code-block:: php $client = new GuzzleHttp\Client(); $client->put('http://httpbin.org', [ 'headers' => ['X-Foo' => 'Bar'], 'body' => 'this is the body!', 'save_to' => '/path/to/local/file', 'allow_redirects' => false, 'timeout' => 5 ]); Synchronous Error Handling ~~~~~~~~~~~~~~~~~~~~~~~~~~ When a recoverable error is encountered while calling the ``send()`` method of a client, a ``GuzzleHttp\Exception\RequestException`` is thrown. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; $client = new Client(); try { $client->get('http://httpbin.org'); } catch (RequestException $e) { echo $e->getRequest() . "\n"; if ($e->hasResponse()) { echo $e->getResponse() . "\n"; } } ``GuzzleHttp\Exception\RequestException`` always contains a ``GuzzleHttp\Message\RequestInterface`` object that can be accessed using the exception's ``getRequest()`` method. A response might be present in the exception. In the event of a networking error, no response will be received. You can check if a ``RequestException`` has a response using the ``hasResponse()`` method. If the exception has a response, then you can access the associated ``GuzzleHttp\Message\ResponseInterface`` using the ``getResponse()`` method of the exception. Asynchronous Requests --------------------- You can send asynchronous requests by setting the ``future`` request option to ``true`` (or a string that your handler understands). This creates a ``GuzzleHttp\Message\FutureResponse`` object that has not yet completed. Once you have a future response, you can use a promise object obtained by calling the ``then`` method of the response to take an action when the response has completed or encounters an error. .. code-block:: php $response = $client->put('http://httpbin.org/get', ['future' => true]); // Call the function when the response completes $response->then(function ($response) { echo $response->getStatusCode(); }); You can call the ``wait()`` method of a future response to block until it has completed. You also use a future response object just like a normal response object by accessing the methods of the response. Using a future response like a normal response object, also known as *dereferencing*, will block until the response has completed. .. code-block:: php $response = $client->put('http://httpbin.org/get', ['future' => true]); // Block until the response has completed echo $response->getStatusCode(); .. important:: If an exception occurred while transferring the future response, then the exception encountered will be thrown when dereferencing. Asynchronous Error Handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Handling errors with future response object promises is a bit different. When using a promise, exceptions are forwarded to the ``$onError`` function provided to the second argument of the ``then()`` function. .. code-block:: php $response = $client->put('http://httpbin.org/get', ['future' => true]); $response ->then( function ($response) { // This is called when the request succeeded echo 'Success: ' . $response->getStatusCode(); // Returning a value will forward the value to the next promise // in the chain. return $response; }, function ($error) { // This is called when the exception failed. echo 'Exception: ' . $error->getMessage(); // Throwing will "forward" the exception to the next promise // in the chain. throw $error; } ) ->then( function($response) { // This is called after the first promise in the chain. It // receives the value returned from the first promise. echo $response->getReasonPhrase(); }, function ($error) { // This is called if the first promise error handler in the // chain rethrows the exception. echo 'Error: ' . $error->getMessage(); } ); Please see the `React/Promises project documentation `_ for more information on how promise resolution and rejection forwarding works. HTTP Errors ----------- If the ``exceptions`` request option is not set to ``false``, then exceptions are thrown for HTTP protocol errors as well: ``GuzzleHttp\Exception\ClientErrorResponseException`` for 4xx level HTTP responses and ``GuzzleHttp\Exception\ServerException`` for 5xx level responses, both of which extend from ``GuzzleHttp\Exception\BadResponseException``. Creating Requests ----------------- You can create a request without sending it. This is useful for building up requests over time or sending requests in concurrently. .. code-block:: php $request = $client->createRequest('GET', 'http://httpbin.org', [ 'headers' => ['X-Foo' => 'Bar'] ]); // Modify the request as needed $request->setHeader('Baz', 'bar'); After creating a request, you can send it with the client's ``send()`` method. .. code-block:: php $response = $client->send($request); Sending Requests With a Pool ============================ You can send requests concurrently using a fixed size pool via the ``GuzzleHttp\Pool`` class. The Pool class is an implementation of ``GuzzleHttp\Ring\Future\FutureInterface``, meaning it can be dereferenced at a later time or cancelled before sending. The Pool constructor accepts a client object, iterator or array that yields ``GuzzleHttp\Message\RequestInterface`` objects, and an optional associative array of options that can be used to affect the transfer. .. code-block:: php use GuzzleHttp\Pool; $requests = [ $client->createRequest('GET', 'http://httpbin.org'), $client->createRequest('DELETE', 'http://httpbin.org/delete'), $client->createRequest('PUT', 'http://httpbin.org/put', ['body' => 'test']) ]; $options = []; // Create a pool. Note: the options array is optional. $pool = new Pool($client, $requests, $options); // Send the requests $pool->wait(); The Pool constructor accepts the following associative array of options: - **pool_size**: Integer representing the maximum number of requests that are allowed to be sent concurrently. - **before**: Callable or array representing the event listeners to add to each request's :ref:`before_event` event. - **complete**: Callable or array representing the event listeners to add to each request's :ref:`complete_event` event. - **error**: Callable or array representing the event listeners to add to each request's :ref:`error_event` event. - **end**: Callable or array representing the event listeners to add to each request's :ref:`end_event` event. The "before", "complete", "error", and "end" event options accept a callable or an array of associative arrays where each associative array contains a "fn" key with a callable value, an optional "priority" key representing the event priority (with a default value of 0), and an optional "once" key that can be set to true so that the event listener will be removed from the request after it is first triggered. .. code-block:: php use GuzzleHttp\Pool; use GuzzleHttp\Event\CompleteEvent; // Add a single event listener using a callable. Pool::send($client, $requests, [ 'complete' => function (CompleteEvent $event) { echo 'Completed request to ' . $event->getRequest()->getUrl() . "\n"; echo 'Response: ' . $event->getResponse()->getBody() . "\n\n"; } ]); // The above is equivalent to the following, but the following structure // allows you to add multiple event listeners to the same event name. Pool::send($client, $requests, [ 'complete' => [ [ 'fn' => function (CompleteEvent $event) { /* ... */ }, 'priority' => 0, // Optional 'once' => false // Optional ] ] ]); Asynchronous Response Handling ------------------------------ When sending requests concurrently using a pool, the request/response/error lifecycle must be handled asynchronously. This means that you give the Pool multiple requests and handle the response or errors that is associated with the request using event callbacks. .. code-block:: php use GuzzleHttp\Pool; use GuzzleHttp\Event\ErrorEvent; Pool::send($client, $requests, [ 'complete' => function (CompleteEvent $event) { echo 'Completed request to ' . $event->getRequest()->getUrl() . "\n"; echo 'Response: ' . $event->getResponse()->getBody() . "\n\n"; // Do something with the completion of the request... }, 'error' => function (ErrorEvent $event) { echo 'Request failed: ' . $event->getRequest()->getUrl() . "\n"; echo $event->getException(); // Do something to handle the error... } ]); The ``GuzzleHttp\Event\ErrorEvent`` event object is emitted when an error occurs during a transfer. With this event, you have access to the request that was sent, the response that was received (if one was received), access to transfer statistics, and the ability to intercept the exception with a different ``GuzzleHttp\Message\ResponseInterface`` object. See :doc:`events` for more information. Handling Errors After Transferring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It sometimes might be easier to handle all of the errors that occurred during a transfer after all of the requests have been sent. Here we are adding each failed request to an array that we can use to process errors later. .. code-block:: php use GuzzleHttp\Pool; use GuzzleHttp\Event\ErrorEvent; $errors = []; Pool::send($client, $requests, [ 'error' => function (ErrorEvent $event) use (&$errors) { $errors[] = $event; } ]); foreach ($errors as $error) { // Handle the error... } .. _batch-requests: Batching Requests ----------------- Sometimes you just want to send a few requests concurrently and then process the results all at once after they've been sent. Guzzle provides a convenience function ``GuzzleHttp\Pool::batch()`` that makes this very simple: .. code-block:: php use GuzzleHttp\Pool; use GuzzleHttp\Client; $client = new Client(); $requests = [ $client->createRequest('GET', 'http://httpbin.org/get'), $client->createRequest('HEAD', 'http://httpbin.org/get'), $client->createRequest('PUT', 'http://httpbin.org/put'), ]; // Results is a GuzzleHttp\BatchResults object. $results = Pool::batch($client, $requests); // Can be accessed by index. echo $results[0]->getStatusCode(); // Can be accessed by request. echo $results->getResult($requests[0])->getStatusCode(); // Retrieve all successful responses foreach ($results->getSuccessful() as $response) { echo $response->getStatusCode() . "\n"; } // Retrieve all failures. foreach ($results->getFailures() as $requestException) { echo $requestException->getMessage() . "\n"; } ``GuzzleHttp\Pool::batch()`` accepts an optional associative array of options in the third argument that allows you to specify the 'before', 'complete', 'error', and 'end' events as well as specify the maximum number of requests to send concurrently using the 'pool_size' option key. .. _request-options: Request Options =============== You can customize requests created by a client using **request options**. Request options control various aspects of a request including, headers, query string parameters, timeout settings, the body of a request, and much more. All of the following examples use the following client: .. code-block:: php $client = new GuzzleHttp\Client(['base_url' => 'http://httpbin.org']); headers ------- :Summary: Associative array of headers to add to the request. Each key is the name of a header, and each value is a string or array of strings representing the header field values. :Types: array :Defaults: None .. code-block:: php // Set various headers on a request $client->get('/get', [ 'headers' => [ 'User-Agent' => 'testing/1.0', 'Accept' => 'application/json', 'X-Foo' => ['Bar', 'Baz'] ] ]); body ---- :Summary: The ``body`` option is used to control the body of an entity enclosing request (e.g., PUT, POST, PATCH). :Types: - string - ``fopen()`` resource - ``GuzzleHttp\Stream\StreamInterface`` - ``GuzzleHttp\Post\PostBodyInterface`` :Default: None This setting can be set to any of the following types: - string .. code-block:: php // You can send requests that use a string as the message body. $client->put('/put', ['body' => 'foo']); - resource returned from ``fopen()`` .. code-block:: php // You can send requests that use a stream resource as the body. $resource = fopen('http://httpbin.org', 'r'); $client->put('/put', ['body' => $resource]); - Array Use an array to send POST style requests that use a ``GuzzleHttp\Post\PostBodyInterface`` object as the body. .. code-block:: php // You can send requests that use a POST body containing fields & files. $client->post('/post', [ 'body' => [ 'field' => 'abc', 'other_field' => '123', 'file_name' => fopen('/path/to/file', 'r') ] ]); - ``GuzzleHttp\Stream\StreamInterface`` .. code-block:: php // You can send requests that use a Guzzle stream object as the body $stream = GuzzleHttp\Stream\Stream::factory('contents...'); $client->post('/post', ['body' => $stream]); json ---- :Summary: The ``json`` option is used to easily upload JSON encoded data as the body of a request. A Content-Type header of ``application/json`` will be added if no Content-Type header is already present on the message. :Types: Any PHP type that can be operated on by PHP's ``json_encode()`` function. :Default: None .. code-block:: php $request = $client->createRequest('/put', ['json' => ['foo' => 'bar']]); echo $request->getHeader('Content-Type'); // application/json echo $request->getBody(); // {"foo":"bar"} .. note:: This request option does not support customizing the Content-Type header or any of the options from PHP's `json_encode() `_ function. If you need to customize these settings, then you must pass the JSON encoded data into the request yourself using the ``body`` request option and you must specify the correct Content-Type header using the ``headers`` request option. query ----- :Summary: Associative array of query string values to add to the request. :Types: - array - ``GuzzleHttp\Query`` :Default: None .. code-block:: php // Send a GET request to /get?foo=bar $client->get('/get', ['query' => ['foo' => 'bar']]); Query strings specified in the ``query`` option are combined with any query string values that are parsed from the URL. .. code-block:: php // Send a GET request to /get?abc=123&foo=bar $client->get('/get?abc=123', ['query' => ['foo' => 'bar']]); auth ---- :Summary: Pass an array of HTTP authentication parameters to use with the request. The array must contain the username in index [0], the password in index [1], and you can optionally provide a built-in authentication type in index [2]. Pass ``null`` to disable authentication for a request. :Types: - array - string - null :Default: None The built-in authentication types are as follows: basic Use `basic HTTP authentication `_ in the ``Authorization`` header (the default setting used if none is specified). .. code-block:: php $client->get('/get', ['auth' => ['username', 'password']]); digest Use `digest authentication `_ (must be supported by the HTTP handler). .. code-block:: php $client->get('/get', ['auth' => ['username', 'password', 'digest']]); *This is currently only supported when using the cURL handler, but creating a replacement that can be used with any HTTP handler is planned.* .. important:: The authentication type (whether it's provided as a string or as the third option in an array) is always converted to a lowercase string. Take this into account when implementing custom authentication types and when implementing custom message factories. Custom Authentication Schemes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also provide a string representing a custom authentication type name. When using a custom authentication type string, you will need to implement the authentication method in an event listener that checks the ``auth`` request option of a request before it is sent. Authentication listeners that require a request is not modified after they are signed should have a very low priority to ensure that they are fired last or near last in the event chain. .. code-block:: php use GuzzleHttp\Event\BeforeEvent; use GuzzleHttp\Event\RequestEvents; /** * Custom authentication listener that handles the "foo" auth type. * * Listens to the "before" event of a request and only modifies the request * when the "auth" config setting of the request is "foo". */ class FooAuth implements GuzzleHttp\Event\SubscriberInterface { private $password; public function __construct($password) { $this->password = $password; } public function getEvents() { return ['before' => ['sign', RequestEvents::SIGN_REQUEST]]; } public function sign(BeforeEvent $e) { if ($e->getRequest()->getConfig()['auth'] == 'foo') { $e->getRequest()->setHeader('X-Foo', 'Foo ' . $this->password); } } } $client->getEmitter()->attach(new FooAuth('password')); $client->get('/', ['auth' => 'foo']); Adapter Specific Authentication Schemes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you need to use authentication methods provided by cURL (e.g., NTLM, GSS, etc.), then you need to specify a curl handler option in the ``options`` request option array. See :ref:`config-option` for more information. .. _cookies-option: cookies ------- :Summary: Specifies whether or not cookies are used in a request or what cookie jar to use or what cookies to send. :Types: - bool - array - ``GuzzleHttp\Cookie\CookieJarInterface`` :Default: None Set to ``true`` to use a shared cookie session associated with the client. .. code-block:: php // Enable cookies using the shared cookie jar of the client. $client->get('/get', ['cookies' => true]); Pass an associative array containing cookies to send in the request and start a new cookie session. .. code-block:: php // Enable cookies and send specific cookies $client->get('/get', ['cookies' => ['foo' => 'bar']]); Set to a ``GuzzleHttp\Cookie\CookieJarInterface`` object to use an existing cookie jar. .. code-block:: php $jar = new GuzzleHttp\Cookie\CookieJar(); $client->get('/get', ['cookies' => $jar]); .. _allow_redirects-option: allow_redirects --------------- :Summary: Describes the redirect behavior of a request :Types: - bool - array :Default: ``['max' => 5, 'strict' => false, 'referer' => true]`` Set to ``false`` to disable redirects. .. code-block:: php $res = $client->get('/redirect/3', ['allow_redirects' => false]); echo $res->getStatusCode(); // 302 Set to ``true`` (the default setting) to enable normal redirects with a maximum number of 5 redirects. .. code-block:: php $res = $client->get('/redirect/3'); echo $res->getStatusCode(); // 200 Pass an associative array containing the 'max' key to specify the maximum number of redirects, optionally provide a 'strict' key value to specify whether or not to use strict RFC compliant redirects (meaning redirect POST requests with POST requests vs. doing what most browsers do which is redirect POST requests with GET requests), and optionally provide a 'referer' key to specify whether or not the "Referer" header should be added when redirecting. .. code-block:: php $res = $client->get('/redirect/3', [ 'allow_redirects' => [ 'max' => 10, 'strict' => true, 'referer' => true ] ]); echo $res->getStatusCode(); // 200 decode_content -------------- :Summary: Specify whether or not ``Content-Encoding`` responses (gzip, deflate, etc.) are automatically decoded. :Types: - string - bool :Default: ``true`` This option can be used to control how content-encoded response bodies are handled. By default, ``decode_content`` is set to true, meaning any gzipped or deflated response will be decoded by Guzzle. When set to ``false``, the body of a response is never decoded, meaning the bytes pass through the handler unchanged. .. code-block:: php // Request gzipped data, but do not decode it while downloading $client->get('/foo.js', [ 'headers' => ['Accept-Encoding' => 'gzip'], 'decode_content' => false ]); When set to a string, the bytes of a response are decoded and the string value provided to the ``decode_content`` option is passed as the ``Accept-Encoding`` header of the request. .. code-block:: php // Pass "gzip" as the Accept-Encoding header. $client->get('/foo.js', ['decode_content' => 'gzip']); .. _save_to-option: save_to ------- :Summary: Specify where the body of a response will be saved. :Types: - string - ``fopen()`` resource - ``GuzzleHttp\Stream\StreamInterface`` :Default: PHP temp stream Pass a string to specify the path to a file that will store the contents of the response body: .. code-block:: php $client->get('/stream/20', ['save_to' => '/path/to/file']); Pass a resource returned from ``fopen()`` to write the response to a PHP stream: .. code-block:: php $resource = fopen('/path/to/file', 'w'); $client->get('/stream/20', ['save_to' => $resource]); Pass a ``GuzzleHttp\Stream\StreamInterface`` object to stream the response body to an open Guzzle stream: .. code-block:: php $resource = fopen('/path/to/file', 'w'); $stream = GuzzleHttp\Stream\Stream::factory($resource); $client->get('/stream/20', ['save_to' => $stream]); .. _events-option: events ------ :Summary: An associative array mapping event names to a callable. Or an associative array containing the 'fn' key that maps to a callable, an optional 'priority' key used to specify the event priority, and an optional 'once' key used to specify if the event should remove itself the first time it is triggered. :Types: array :Default: None .. code-block:: php use GuzzleHttp\Event\BeforeEvent; use GuzzleHttp\Event\HeadersEvent; use GuzzleHttp\Event\CompleteEvent; use GuzzleHttp\Event\ErrorEvent; $client->get('/', [ 'events' => [ 'before' => function (BeforeEvent $e) { echo 'Before'; }, 'complete' => function (CompleteEvent $e) { echo 'Complete'; }, 'error' => function (ErrorEvent $e) { echo 'Error'; }, ] ]); Here's an example of using the associative array format for control over the priority and whether or not an event should be triggered more than once. .. code-block:: php $client->get('/', [ 'events' => [ 'before' => [ 'fn' => function (BeforeEvent $e) { echo 'Before'; }, 'priority' => 100, 'once' => true ] ] ]); .. _subscribers-option: subscribers ----------- :Summary: Array of event subscribers to add to the request. Each value in the array must be an instance of ``GuzzleHttp\Event\SubscriberInterface``. :Types: array :Default: None .. code-block:: php use GuzzleHttp\Subscriber\History; use GuzzleHttp\Subscriber\Mock; use GuzzleHttp\Message\Response; $history = new History(); $mock = new Mock([new Response(200)]); $client->get('/', ['subscribers' => [$history, $mock]]); echo $history; // Outputs the request and response history .. _exceptions-option: exceptions ---------- :Summary: Set to ``false`` to disable throwing exceptions on an HTTP protocol errors (i.e., 4xx and 5xx responses). Exceptions are thrown by default when HTTP protocol errors are encountered. :Types: bool :Default: ``true`` .. code-block:: php $client->get('/status/500'); // Throws a GuzzleHttp\Exception\ServerException $res = $client->get('/status/500', ['exceptions' => false]); echo $res->getStatusCode(); // 500 .. _timeout-option: timeout ------- :Summary: Float describing the timeout of the request in seconds. Use ``0`` to wait indefinitely (the default behavior). :Types: float :Default: ``0`` .. code-block:: php // Timeout if a server does not return a response in 3.14 seconds. $client->get('/delay/5', ['timeout' => 3.14]); // PHP Fatal error: Uncaught exception 'GuzzleHttp\Exception\RequestException' .. _connect_timeout-option: connect_timeout --------------- :Summary: Float describing the number of seconds to wait while trying to connect to a server. Use ``0`` to wait indefinitely (the default behavior). :Types: float :Default: ``0`` .. code-block:: php // Timeout if the client fails to connect to the server in 3.14 seconds. $client->get('/delay/5', ['connect_timeout' => 3.14]); .. note:: This setting must be supported by the HTTP handler used to send a request. ``connect_timeout`` is currently only supported by the built-in cURL handler. .. _verify-option: verify ------ :Summary: Describes the SSL certificate verification behavior of a request. - Set to ``true`` to enable SSL certificate verification and use the default CA bundle provided by operating system. - Set to ``false`` to disable certificate verification (this is insecure!). - Set to a string to provide the path to a CA bundle to enable verification using a custom certificate. :Types: - bool - string :Default: ``true`` .. code-block:: php // Use the system's CA bundle (this is the default setting) $client->get('/', ['verify' => true]); // Use a custom SSL certificate on disk. $client->get('/', ['verify' => '/path/to/cert.pem']); // Disable validation entirely (don't do this!). $client->get('/', ['verify' => false]); Not all system's have a known CA bundle on disk. For example, Windows and OS X do not have a single common location for CA bundles. When setting "verify" to ``true``, Guzzle will do its best to find the most appropriate CA bundle on your system. When using cURL or the PHP stream wrapper on PHP versions >= 5.6, this happens by default. When using the PHP stream wrapper on versions < 5.6, Guzzle tries to find your CA bundle in the following order: 1. Check if ``openssl.cafile`` is set in your php.ini file. 2. Check if ``curl.cainfo`` is set in your php.ini file. 3. Check if ``/etc/pki/tls/certs/ca-bundle.crt`` exists (Red Hat, CentOS, Fedora; provided by the ca-certificates package) 4. Check if ``/etc/ssl/certs/ca-certificates.crt`` exists (Ubuntu, Debian; provided by the ca-certificates package) 5. Check if ``/usr/local/share/certs/ca-root-nss.crt`` exists (FreeBSD; provided by the ca_root_nss package) 6. Check if ``/usr/local/etc/openssl/cert.pem`` (OS X; provided by homebrew) 7. Check if ``C:\windows\system32\curl-ca-bundle.crt`` exists (Windows) 8. Check if ``C:\windows\curl-ca-bundle.crt`` exists (Windows) The result of this lookup is cached in memory so that subsequent calls in the same process will return very quickly. However, when sending only a single request per-process in something like Apache, you should consider setting the ``openssl.cafile`` environment variable to the path on disk to the file so that this entire process is skipped. If you do not need a specific certificate bundle, then Mozilla provides a commonly used CA bundle which can be downloaded `here `_ (provided by the maintainer of cURL). Once you have a CA bundle available on disk, you can set the "openssl.cafile" PHP ini setting to point to the path to the file, allowing you to omit the "verify" request option. Much more detail on SSL certificates can be found on the `cURL website `_. .. _cert-option: cert ---- :Summary: Set to a string to specify the path to a file containing a PEM formatted client side certificate. If a password is required, then set to an array containing the path to the PEM file in the first array element followed by the password required for the certificate in the second array element. :Types: - string - array :Default: None .. code-block:: php $client->get('/', ['cert' => ['/path/server.pem', 'password']]); .. _ssl_key-option: ssl_key ------- :Summary: Specify the path to a file containing a private SSL key in PEM format. If a password is required, then set to an array containing the path to the SSL key in the first array element followed by the password required for the certificate in the second element. :Types: - string - array :Default: None .. note:: ``ssl_key`` is implemented by HTTP handlers. This is currently only supported by the cURL handler, but might be supported by other third-part handlers. .. _proxy-option: proxy ----- :Summary: Pass a string to specify an HTTP proxy, or an array to specify different proxies for different protocols. :Types: - string - array :Default: None Pass a string to specify a proxy for all protocols. .. code-block:: php $client->get('/', ['proxy' => 'tcp://localhost:8125']); Pass an associative array to specify HTTP proxies for specific URI schemes (i.e., "http", "https"). .. code-block:: php $client->get('/', [ 'proxy' => [ 'http' => 'tcp://localhost:8125', // Use this proxy with "http" 'https' => 'tcp://localhost:9124' // Use this proxy with "https" ] ]); .. note:: You can provide proxy URLs that contain a scheme, username, and password. For example, ``"http://username:password@192.168.16.1:10"``. .. _debug-option: debug ----- :Summary: Set to ``true`` or set to a PHP stream returned by ``fopen()`` to enable debug output with the handler used to send a request. For example, when using cURL to transfer requests, cURL's verbose of ``CURLOPT_VERBOSE`` will be emitted. When using the PHP stream wrapper, stream wrapper notifications will be emitted. If set to true, the output is written to PHP's STDOUT. If a PHP stream is provided, output is written to the stream. :Types: - bool - ``fopen()`` resource :Default: None .. code-block:: php $client->get('/get', ['debug' => true]); Running the above example would output something like the following: :: * About to connect() to httpbin.org port 80 (#0) * Trying 107.21.213.98... * Connected to httpbin.org (107.21.213.98) port 80 (#0) > GET /get HTTP/1.1 Host: httpbin.org User-Agent: Guzzle/4.0 curl/7.21.4 PHP/5.5.7 < HTTP/1.1 200 OK < Access-Control-Allow-Origin: * < Content-Type: application/json < Date: Sun, 16 Feb 2014 06:50:09 GMT < Server: gunicorn/0.17.4 < Content-Length: 335 < Connection: keep-alive < * Connection #0 to host httpbin.org left intact .. _stream-option: stream ------ :Summary: Set to ``true`` to stream a response rather than download it all up-front. :Types: bool :Default: ``false`` .. code-block:: php $response = $client->get('/stream/20', ['stream' => true]); // Read bytes off of the stream until the end of the stream is reached $body = $response->getBody(); while (!$body->eof()) { echo $body->read(1024); } .. note:: Streaming response support must be implemented by the HTTP handler used by a client. This option might not be supported by every HTTP handler, but the interface of the response object remains the same regardless of whether or not it is supported by the handler. .. _expect-option: expect ------ :Summary: Controls the behavior of the "Expect: 100-Continue" header. :Types: - bool - integer :Default: ``1048576`` Set to ``true`` to enable the "Expect: 100-Continue" header for all requests that sends a body. Set to ``false`` to disable the "Expect: 100-Continue" header for all requests. Set to a number so that the size of the payload must be greater than the number in order to send the Expect header. Setting to a number will send the Expect header for all requests in which the size of the payload cannot be determined or where the body is not rewindable. By default, Guzzle will add the "Expect: 100-Continue" header when the size of the body of a request is greater than 1 MB and a request is using HTTP/1.1. .. note:: This option only takes effect when using HTTP/1.1. The HTTP/1.0 and HTTP/2.0 protocols do not support the "Expect: 100-Continue" header. Support for handling the "Expect: 100-Continue" workflow must be implemented by Guzzle HTTP handlers used by a client. .. _version-option: version ------- :Summary: Protocol version to use with the request. :Types: string, float :Default: ``1.1`` .. code-block:: php // Force HTTP/1.0 $request = $client->createRequest('GET', '/get', ['version' => 1.0]); echo $request->getProtocolVersion(); // 1.0 .. _config-option: config ------ :Summary: Associative array of config options that are forwarded to a request's configuration collection. These values are used as configuration options that can be consumed by plugins and handlers. :Types: array :Default: None .. code-block:: php $request = $client->createRequest('GET', '/get', ['config' => ['foo' => 'bar']]); echo $request->getConfig('foo'); // 'bar' Some HTTP handlers allow you to specify custom handler-specific settings. For example, you can pass custom cURL options to requests by passing an associative array in the ``config`` request option under the ``curl`` key. .. code-block:: php // Use custom cURL options with the request. This example uses NTLM auth // to authenticate with a server. $client->get('/', [ 'config' => [ 'curl' => [ CURLOPT_HTTPAUTH => CURLAUTH_NTLM, CURLOPT_USERPWD => 'username:password' ] ] ]); future ------ :Summary: Specifies whether or not a response SHOULD be an instance of a ``GuzzleHttp\Message\FutureResponse`` object. :Types: - bool - string :Default: ``false`` By default, Guzzle requests should be synchronous. You can create asynchronous future responses by passing the ``future`` request option as ``true``. The response will only be executed when it is used like a normal response, the ``wait()`` method of the response is called, or the corresponding handler that created the response is destructing and there are futures that have not been resolved. .. important:: This option only has an effect if your handler can create and return future responses. However, even if a response is completed synchronously, Guzzle will ensure that a FutureResponse object is returned for API consistency. .. code-block:: php $response = $client->get('/foo', ['future' => true]) ->then(function ($response) { echo 'I got a response! ' . $response; }); Event Subscribers ================= Requests emit lifecycle events when they are transferred. A client object has a ``GuzzleHttp\Common\EventEmitter`` object that can be used to add event *listeners* and event *subscribers* to all requests created by the client. .. important:: **Every** event listener or subscriber added to a client will be added to every request created by the client. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Event\BeforeEvent; $client = new Client(); // Add a listener that will echo out requests before they are sent $client->getEmitter()->on('before', function (BeforeEvent $e) { echo 'About to send request: ' . $e->getRequest(); }); $client->get('http://httpbin.org/get'); // Outputs the request as a string because of the event See :doc:`events` for more information on the event system used in Guzzle. Environment Variables ===================== Guzzle exposes a few environment variables that can be used to customize the behavior of the library. ``GUZZLE_CURL_SELECT_TIMEOUT`` Controls the duration in seconds that a curl_multi_* handler will use when selecting on curl handles using ``curl_multi_select()``. Some systems have issues with PHP's implementation of ``curl_multi_select()`` where calling this function always results in waiting for the maximum duration of the timeout. ``HTTP_PROXY`` Defines the proxy to use when sending requests using the "http" protocol. ``HTTPS_PROXY`` Defines the proxy to use when sending requests using the "https" protocol. Relevant ini Settings --------------------- Guzzle can utilize PHP ini settings when configuring clients. ``openssl.cafile`` Specifies the path on disk to a CA file in PEM format to use when sending requests over "https". See: https://wiki.php.net/rfc/tls-peer-verification#phpini_defaults docs/conf.py000064400000001435151742731020007000 0ustar00import sys, os from sphinx.highlighting import lexers from pygments.lexers.web import PhpLexer lexers['php'] = PhpLexer(startinline=True, linenos=1) lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1) primary_domain = 'php' extensions = [] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'Guzzle' copyright = u'2014, Michael Dowling' version = '5.0.0' html_title = "Guzzle Documentation" html_short_title = "Guzzle" exclude_patterns = ['_build'] html_static_path = ['_static'] on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] docs/events.rst000064400000044506151742731020007545 0ustar00============ Event System ============ Guzzle uses an event emitter to allow you to easily extend the behavior of a request, change the response associated with a request, and implement custom error handling. All events in Guzzle are managed and emitted by an **event emitter**. Event Emitters ============== Clients, requests, and any other class that implements the ``GuzzleHttp\Common\HasEmitterInterface`` interface have a ``GuzzleHttp\Common\EventEmitter`` object. You can add event *listeners* and event *subscribers* to an event *emitter*. emitter An object that implements ``GuzzleHttp\Common\EventEmitterInterface``. This object emits named events to event listeners. You may register event listeners on subscribers on an emitter. event listeners Callable functions that are registered on an event emitter for specific events. Event listeners are registered on an emitter with a *priority* setting. If no priority is provided, ``0`` is used by default. event subscribers Classes that tell an event emitter what methods to listen to and what functions on the class to invoke when the event is triggered. Event subscribers subscribe event listeners to an event emitter. They should be used when creating more complex event based logic in applications (i.e., cookie handling is implemented using an event subscriber because it's easier to share a subscriber than an anonymous function and because handling cookies is a complex process). priority Describes the order in which event listeners are invoked when an event is emitted. The higher a priority value, the earlier the event listener will be invoked (a higher priority means the listener is more important). If no priority is provided, the priority is assumed to be ``0``. When specifying an event priority, you can pass ``"first"`` or ``"last"`` to dynamically specify the priority based on the current event priorities associated with the given event name in the emitter. Use ``"first"`` to set the priority to the current highest priority plus one. Use ``"last"`` to set the priority to the current lowest event priority minus one. It is important to remember that these dynamic priorities are calculated only at the point of insertion into the emitter and they are not rearranged after subsequent listeners are added to an emitter. propagation Describes whether or not other event listeners are triggered. Event emitters will trigger every event listener registered to a specific event in priority order until all of the listeners have been triggered **or** until the propagation of an event is stopped. Getting an EventEmitter ----------------------- You can get the event emitter of ``GuzzleHttp\Common\HasEmitterInterface`` object using the the ``getEmitter()`` method. Here's an example of getting a client object's event emitter. .. code-block:: php $client = new GuzzleHttp\Client(); $emitter = $client->getEmitter(); .. note:: You'll notice that the event emitter used in Guzzle is very similar to the `Symfony2 EventDispatcher component `_. This is because the Guzzle event system is based on the Symfony2 event system with several changes. Guzzle uses its own event emitter to improve performance, isolate Guzzle from changes to the Symfony, and provide a few improvements that make it easier to use for an HTTP client (e.g., the addition of the ``once()`` method). Adding Event Listeners ---------------------- After you have the emitter, you can register event listeners that listen to specific events using the ``on()`` method. When registering an event listener, you must tell the emitter what event to listen to (e.g., "before", "after", "progress", "complete", "error", etc.), what callable to invoke when the event is triggered, and optionally provide a priority. .. code-block:: php use GuzzleHttp\Event\BeforeEvent; $emitter->on('before', function (BeforeEvent $event) { echo $event->getRequest(); }); When a listener is triggered, it is passed an event that implements the ``GuzzleHttp\Common\EventInterface`` interface, the name of the event, and the event emitter itself. The above example could more verbosely be written as follows: .. code-block:: php use GuzzleHttp\Event\BeforeEvent; $emitter->on('before', function ( BeforeEvent $event, $name, EmitterInterface $emitter ) { echo $event->getRequest(); }); You can add an event listener that automatically removes itself after it is triggered using the ``once()`` method of an event emitter. .. code-block:: php $client = new GuzzleHttp\Client(); $client->getEmitter()->once('before', function () { echo 'This will only happen once... per request!'; }); Event Propagation ----------------- Event listeners can prevent other event listeners from being triggered by stopping an event's propagation. Stopping event propagation can be useful, for example, if an event listener has changed the state of the subject to such an extent that allowing subsequent event listeners to be triggered could place the subject in an inconsistent state. This technique is used in Guzzle extensively when intercepting error events with responses. You can stop the propagation of an event using the ``stopPropagation()`` method of a ``GuzzleHttp\Common\EventInterface`` object: .. code-block:: php use GuzzleHttp\Event\ErrorEvent; $emitter->on('error', function (ErrorEvent $event) { $event->stopPropagation(); }); After stopping the propagation of an event, any subsequent event listeners that have not yet been triggered will not be triggered. You can check to see if the propagation of an event was stopped using the ``isPropagationStopped()`` method of the event. .. code-block:: php $client = new GuzzleHttp\Client(); $emitter = $client->getEmitter(); // Note: assume that the $errorEvent was created if ($emitter->emit('error', $errorEvent)->isPropagationStopped()) { echo 'It was stopped!'; } .. hint:: When emitting events, the event that was emitted is returned from the emitter. This allows you to easily chain calls as shown in the above example. Event Subscribers ----------------- Event subscribers are classes that implement the ``GuzzleHttp\Common\EventSubscriberInterface`` object. They are used to register one or more event listeners to methods of the class. Event subscribers tell event emitters exactly which events to listen to and what method to invoke on the class when the event is triggered by called the ``getEvents()`` method of a subscriber. The following example registers event listeners to the ``before`` and ``complete`` event of a request. When the ``before`` event is emitted, the ``onBefore`` instance method of the subscriber is invoked. When the ``complete`` event is emitted, the ``onComplete`` event of the subscriber is invoked. Each array value in the ``getEvents()`` return value MUST contain the name of the method to invoke and can optionally contain the priority of the listener (as shown in the ``before`` listener in the example). .. code-block:: php use GuzzleHttp\Event\EmitterInterface; use GuzzleHttp\Event\SubscriberInterface; use GuzzleHttp\Event\BeforeEvent; use GuzzleHttp\Event\CompleteEvent; class SimpleSubscriber implements SubscriberInterface { public function getEvents() { return [ // Provide name and optional priority 'before' => ['onBefore', 100], 'complete' => ['onComplete'], // You can pass a list of listeners with different priorities 'error' => [['beforeError', 'first'], ['afterError', 'last']] ]; } public function onBefore(BeforeEvent $event, $name) { echo 'Before!'; } public function onComplete(CompleteEvent $event, $name) { echo 'Complete!'; } } .. note:: You can specify event priorities using integers or ``"first"`` and ``"last"`` to dynamically determine the priority. Event Priorities ================ When adding event listeners or subscribers, you can provide an optional event priority. This priority is used to determine how early or late a listener is triggered. Specifying the correct priority is an important aspect of ensuring a listener behaves as expected. For example, if you wanted to ensure that cookies associated with a redirect were added to a cookie jar, you'd need to make sure that the listener that collects the cookies is triggered before the listener that performs the redirect. In order to help make the process of determining the correct event priority of a listener easier, Guzzle provides several pre-determined named event priorities. These priorities are exposed as constants on the ``GuzzleHttp\Event\RequestEvents`` object. last Use ``"last"`` as an event priority to set the priority to the current lowest event priority minus one. first Use ``"first"`` as an event priority to set the priority to the current highest priority plus one. ``GuzzleHttp\Event\RequestEvents::EARLY`` Used when you want a listener to be triggered as early as possible in the event chain. ``GuzzleHttp\Event\RequestEvents::LATE`` Used when you want a listener to be to be triggered as late as possible in the event chain. ``GuzzleHttp\Event\RequestEvents::PREPARE_REQUEST`` Used when you want a listener to be trigger while a request is being prepared during the ``before`` event. This event priority is used by the ``GuzzleHttp\Subscriber\Prepare`` event subscriber which is responsible for guessing a Content-Type, Content-Length, and Expect header of a request. You should subscribe after this event is triggered if you want to ensure that this subscriber has already been triggered. ``GuzzleHttp\Event\RequestEvents::SIGN_REQUEST`` Used when you want a listener to be triggered when a request is about to be signed. Any listener triggered at this point should expect that the request object will no longer be mutated. If you are implementing a custom signature subscriber, then you should use this event priority to sign requests. ``GuzzleHttp\Event\RequestEvents::VERIFY_RESPONSE`` Used when you want a listener to be triggered when a response is being validated during the ``complete`` event. The ``GuzzleHttp\Subscriber\HttpError`` event subscriber uses this event priority to check if an exception should be thrown due to a 4xx or 5xx level response status code. If you are doing any kind of verification of a response during the complete event, it should happen at this priority. ``GuzzleHttp\Event\RequestEvents::REDIRECT_RESPONSE`` Used when you want a listener to be triggered when a response is being redirected during the ``complete`` event. The ``GuzzleHttp\Subscriber\Redirect`` event subscriber uses this event priority when performing redirects. You can use the above event priorities as a guideline for determining the priority of you event listeners. You can use these constants and add to or subtract from them to ensure that a listener happens before or after the named priority. .. note:: "first" and "last" priorities are not adjusted after they added to an emitter. For example, if you add a listener with a priority of "first", you can still add subsequent listeners with a higher priority which would be triggered before the listener added with a priority of "first". Working With Request Events =========================== Requests emit lifecycle events when they are transferred. .. important:: Excluding the ``end`` event, request lifecycle events may be triggered multiple times due to redirects, retries, or reusing a request multiple times. Use the ``once()`` method want the event to be triggered once. You can also remove an event listener from an emitter by using the emitter which is provided to the listener. .. _before_event: before ------ The ``before`` event is emitted before a request is sent. The event emitted is a ``GuzzleHttp\Event\BeforeEvent``. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Common\EmitterInterface; use GuzzleHttp\Event\BeforeEvent; $client = new Client(['base_url' => 'http://httpbin.org']); $request = $client->createRequest('GET', '/'); $request->getEmitter()->on( 'before', function (BeforeEvent $e, $name, EmitterInterface $emitter) { echo $name . "\n"; // "before" echo $e->getRequest()->getMethod() . "\n"; // "GET" / "POST" / "PUT" / etc. echo get_class($e->getClient()); // "GuzzleHttp\Client" } ); You can intercept a request with a response before the request is sent over the wire. The ``intercept()`` method of the ``BeforeEvent`` accepts a ``GuzzleHttp\Message\ResponseInterface``. Intercepting the event will prevent the request from being sent over the wire and stops the propagation of the ``before`` event, preventing subsequent event listeners from being invoked. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Event\BeforeEvent; use GuzzleHttp\Message\Response; $client = new Client(['base_url' => 'http://httpbin.org']); $request = $client->createRequest('GET', '/status/500'); $request->getEmitter()->on('before', function (BeforeEvent $e) { $response = new Response(200); $e->intercept($response); }); $response = $client->send($request); echo $response->getStatusCode(); // 200 .. attention:: Any exception encountered while executing the ``before`` event will trigger the ``error`` event of a request. .. _complete_event: complete -------- The ``complete`` event is emitted after a transaction completes and an entire response has been received. The event is a ``GuzzleHttp\Event\CompleteEvent``. You can intercept the ``complete`` event with a different response if needed using the ``intercept()`` method of the event. This can be useful, for example, for changing the response for caching. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Event\CompleteEvent; use GuzzleHttp\Message\Response; $client = new Client(['base_url' => 'http://httpbin.org']); $request = $client->createRequest('GET', '/status/302'); $cachedResponse = new Response(200); $request->getEmitter()->on( 'complete', function (CompleteEvent $e) use ($cachedResponse) { if ($e->getResponse()->getStatusCode() == 302) { // Intercept the original transaction with the new response $e->intercept($cachedResponse); } } ); $response = $client->send($request); echo $response->getStatusCode(); // 200 .. attention:: Any ``GuzzleHttp\Exception\RequestException`` encountered while executing the ``complete`` event will trigger the ``error`` event of a request. .. _error_event: error ----- The ``error`` event is emitted when a request fails (whether it's from a networking error or an HTTP protocol error). The event emitted is a ``GuzzleHttp\Event\ErrorEvent``. This event is useful for retrying failed requests. Here's an example of retrying failed basic auth requests by re-sending the original request with a username and password. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Event\ErrorEvent; $client = new Client(['base_url' => 'http://httpbin.org']); $request = $client->createRequest('GET', '/basic-auth/foo/bar'); $request->getEmitter()->on('error', function (ErrorEvent $e) { if ($e->getResponse()->getStatusCode() == 401) { // Add authentication stuff as needed and retry the request $e->getRequest()->setHeader('Authorization', 'Basic ' . base64_encode('foo:bar')); // Get the client of the event and retry the request $newResponse = $e->getClient()->send($e->getRequest()); // Intercept the original transaction with the new response $e->intercept($newResponse); } }); .. attention:: If an ``error`` event is intercepted with a response, then the ``complete`` event of a request is triggered. If the ``complete`` event fails, then the ``error`` event is triggered once again. .. _progress_event: progress -------- The ``progress`` event is emitted when data is uploaded or downloaded. The event emitted is a ``GuzzleHttp\Event\ProgressEvent``. You can access the emitted progress values using the corresponding public properties of the event object: - ``$downloadSize``: The number of bytes that will be downloaded (if known) - ``$downloaded``: The number of bytes that have been downloaded - ``$uploadSize``: The number of bytes that will be uploaded (if known) - ``$uploaded``: The number of bytes that have been uploaded This event cannot be intercepted. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Event\ProgressEvent; $client = new Client(['base_url' => 'http://httpbin.org']); $request = $client->createRequest('PUT', '/put', [ 'body' => str_repeat('.', 100000) ]); $request->getEmitter()->on('progress', function (ProgressEvent $e) { echo 'Downloaded ' . $e->downloaded . ' of ' . $e->downloadSize . ' ' . 'Uploaded ' . $e->uploaded . ' of ' . $e->uploadSize . "\r"; }); $client->send($request); echo "\n"; .. _end_event: end --- The ``end`` event is a terminal event, emitted once per request, that provides access to the repsonse that was received or the exception that was encountered. The event emitted is a ``GuzzleHttp\Event\EndEvent``. This event can be intercepted, but keep in mind that the ``complete`` event will not fire after intercepting this event. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Event\EndEvent; $client = new Client(['base_url' => 'http://httpbin.org']); $request = $client->createRequest('PUT', '/put', [ 'body' => str_repeat('.', 100000) ]); $request->getEmitter()->on('end', function (EndEvent $e) { if ($e->getException()) { echo 'Error: ' . $e->getException()->getMessage(); } else { echo 'Response: ' . $e->getResponse(); } }); $client->send($request); echo "\n"; docs/faq.rst000064400000017501151742731020007003 0ustar00=== FAQ === Why should I use Guzzle? ======================== Guzzle makes it easy to send HTTP requests and super simple to integrate with web services. Guzzle manages things like persistent connections, represents query strings as collections, makes it simple to send streaming POST requests with fields and files, and abstracts away the underlying HTTP transport layer. By providing an object oriented interface for HTTP clients, requests, responses, headers, and message bodies, Guzzle makes it so that you no longer need to fool around with cURL options, stream contexts, or sockets. **Asynchronous and Synchronous Requests** Guzzle allows you to send both asynchronous and synchronous requests using the same interface and no direct dependency on an event loop. This flexibility allows Guzzle to send an HTTP request using the most appropriate HTTP handler based on the request being sent. For example, when sending synchronous requests, Guzzle will by default send requests using cURL easy handles to ensure you're using the fastest possible method for serially transferring HTTP requests. When sending asynchronous requests, Guzzle might use cURL's multi interface or any other asynchronous handler you configure. When you request streaming data, Guzzle will by default use PHP's stream wrapper. **Streams** Request and response message bodies use :doc:`Guzzle Streams `, allowing you to stream data without needing to load it all into memory. Guzzle's stream layer provides a large suite of functionality: - You can modify streams at runtime using custom or a number of pre-made decorators. - You can emit progress events as data is read from a stream. - You can validate the integrity of a stream using a rolling hash as data is read from a stream. **Event System and Plugins** Guzzle's event system allows you to completely modify the behavior of a client or request at runtime to cater them for any API. You can send a request with a client, and the client can do things like automatically retry your request if it fails, automatically redirect, log HTTP messages that are sent over the wire, emit progress events as data is uploaded and downloaded, sign requests using OAuth 1.0, verify the integrity of messages before and after they are sent over the wire, and anything else you might need. **Testable** Another important aspect of Guzzle is that it's really :doc:`easy to test clients `. You can mock HTTP responses and when testing an handler implementation, Guzzle provides a mock node.js web server. **Ecosystem** Guzzle has a large `ecosystem of plugins `_, including `service descriptions `_ which allows you to abstract web services using service descriptions. These service descriptions define how to serialize an HTTP request and how to parse an HTTP response into a more meaningful model object. - `Guzzle Command `_: Provides the building blocks for service description abstraction. - `Guzzle Services `_: Provides an implementation of "Guzzle Command" that utlizes Guzzle's service description format. Does Guzzle require cURL? ========================= No. Guzzle can use any HTTP handler to send requests. This means that Guzzle can be used with cURL, PHP's stream wrapper, sockets, and non-blocking libraries like `React `_. You just need to configure a `RingPHP `_ handler to use a different method of sending requests. .. note:: Guzzle has historically only utilized cURL to send HTTP requests. cURL is an amazing HTTP client (arguably the best), and Guzzle will continue to use it by default when it is available. It is rare, but some developers don't have cURL installed on their systems or run into version specific issues. By allowing swappable HTTP handlers, Guzzle is now much more customizable and able to adapt to fit the needs of more developers. Can Guzzle send asynchronous requests? ====================================== Yes. Pass the ``future`` true request option to a request to send it asynchronously. Guzzle will then return a ``GuzzleHttp\Message\FutureResponse`` object that can be used synchronously by accessing the response object like a normal response, and it can be used asynchronoulsy using a promise that is notified when the response is resolved with a real response or rejected with an exception. .. code-block:: php $request = $client->createRequest('GET', ['future' => true]); $client->send($request)->then(function ($response) { echo 'Got a response! ' . $response; }); You can force an asynchronous response to complete using the ``wait()`` method of a response. .. code-block:: php $request = $client->createRequest('GET', ['future' => true]); $futureResponse = $client->send($request); $futureResponse->wait(); How can I add custom cURL options? ================================== cURL offer a huge number of `customizable options `_. While Guzzle normalizes many of these options across different handlers, there are times when you need to set custom cURL options. This can be accomplished by passing an associative array of cURL settings in the **curl** key of the **config** request option. For example, let's say you need to customize the outgoing network interface used with a client. .. code-block:: php $client->get('/', [ 'config' => [ 'curl' => [ CURLOPT_INTERFACE => 'xxx.xxx.xxx.xxx' ] ] ]); How can I add custom stream context options? ============================================ You can pass custom `stream context options `_ using the **stream_context** key of the **config** request option. The **stream_context** array is an associative array where each key is a PHP transport, and each value is an associative array of transport options. For example, let's say you need to customize the outgoing network interface used with a client and allow self-signed certificates. .. code-block:: php $client->get('/', [ 'stream' => true, 'config' => [ 'stream_context' => [ 'ssl' => [ 'allow_self_signed' => true ], 'socket' => [ 'bindto' => 'xxx.xxx.xxx.xxx' ] ] ] ]); Why am I getting an SSL verification error? =========================================== You need to specify the path on disk to the CA bundle used by Guzzle for verifying the peer certificate. See :ref:`verify-option`. What is this Maximum function nesting error? ============================================ Maximum function nesting level of '100' reached, aborting You could run into this error if you have the XDebug extension installed and you execute a lot of requests in callbacks. This error message comes specifically from the XDebug extension. PHP itself does not have a function nesting limit. Change this setting in your php.ini to increase the limit:: xdebug.max_nesting_level = 1000 Why am I getting a 417 error response? ====================================== This can occur for a number of reasons, but if you are sending PUT, POST, or PATCH requests with an ``Expect: 100-Continue`` header, a server that does not support this header will return a 417 response. You can work around this by setting the ``expect`` request option to ``false``: .. code-block:: php $client = new GuzzleHttp\Client(); // Disable the expect header on a single request $response = $client->put('/', [], 'the body', [ 'expect' => false ]); // Disable the expect header on all client requests $client->setDefaultOption('expect', false) docs/handlers.rst000064400000003027151742731020010032 0ustar00================ RingPHP Handlers ================ Guzzle uses RingPHP handlers to send HTTP requests over the wire. RingPHP provides a low-level library that can be used to "glue" Guzzle with any transport method you choose. By default, Guzzle utilizes cURL and PHP's stream wrappers to send HTTP requests. RingPHP handlers makes it extremely simple to integrate Guzzle with any HTTP transport. For example, you could quite easily bridge Guzzle and React to use Guzzle in React's event loop. Using a handler --------------- You can change the handler used by a client using the ``handler`` option in the ``GuzzleHttp\Client`` constructor. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Ring\Client\MockHandler; // Create a mock handler that always returns a 200 response. $handler = new MockHandler(['status' => 200]); // Configure to client to use the mock handler. $client = new Client(['handler' => $handler]); At its core, handlers are simply PHP callables that accept a request array and return a ``GuzzleHttp\Ring\Future\FutureArrayInterface``. This future array can be used just like a normal PHP array, causing it to block, or you can use the promise interface using the ``then()`` method of the future. Guzzle hooks up to the RingPHP project using a very simple bridge class (``GuzzleHttp\RingBridge``). Creating a handler ------------------ See the `RingPHP `_ project documentation for more information on creating custom handlers that can be used with Guzzle clients. docs/http-messages.rst000064400000034747151742731020011033 0ustar00============================= Request and Response Messages ============================= Guzzle is an HTTP client that sends HTTP requests to a server and receives HTTP responses. Both requests and responses are referred to as messages. Headers ======= Both request and response messages contain HTTP headers. Complex Headers --------------- Some headers contain additional key value pair information. For example, Link headers contain a link and several key value pairs: :: ; rel="thing"; type="image/jpeg" Guzzle provides a convenience feature that can be used to parse these types of headers: .. code-block:: php use GuzzleHttp\Message\Request; $request = new Request('GET', '/', [ 'Link' => '; rel="front"; type="image/jpeg"' ]); $parsed = Request::parseHeader($request, 'Link'); var_export($parsed); Will output: .. code-block:: php array ( 0 => array ( 0 => '', 'rel' => 'front', 'type' => 'image/jpeg', ), ) The result contains a hash of key value pairs. Header values that have no key (i.e., the link) are indexed numerically while headers parts that form a key value pair are added as a key value pair. See :ref:`headers` for information on how the headers of a request and response can be accessed and modified. Body ==== Both request and response messages can contain a body. You can check to see if a request or response has a body using the ``getBody()`` method: .. code-block:: php $response = GuzzleHttp\get('http://httpbin.org/get'); if ($response->getBody()) { echo $response->getBody(); // JSON string: { ... } } The body used in request and response objects is a ``GuzzleHttp\Stream\StreamInterface``. This stream is used for both uploading data and downloading data. Guzzle will, by default, store the body of a message in a stream that uses PHP temp streams. When the size of the body exceeds 2 MB, the stream will automatically switch to storing data on disk rather than in memory (protecting your application from memory exhaustion). You can change the body used in a request or response using the ``setBody()`` method: .. code-block:: php use GuzzleHttp\Stream\Stream; $request = $client->createRequest('PUT', 'http://httpbin.org/put'); $request->setBody(Stream::factory('foo')); The easiest way to create a body for a request is using the static ``GuzzleHttp\Stream\Stream::factory()`` method. This method accepts various inputs like strings, resources returned from ``fopen()``, and other ``GuzzleHttp\Stream\StreamInterface`` objects. The body of a request or response can be cast to a string or you can read and write bytes off of the stream as needed. .. code-block:: php use GuzzleHttp\Stream\Stream; $request = $client->createRequest('PUT', 'http://httpbin.org/put', ['body' => 'testing...']); echo $request->getBody()->read(4); // test echo $request->getBody()->read(4); // ing. echo $request->getBody()->read(1024); // .. var_export($request->eof()); // true You can find out more about Guzzle stream objects in :doc:`streams`. Requests ======== Requests are sent from a client to a server. Requests include the method to be applied to a resource, the identifier of the resource, and the protocol version to use. Clients are used to create request messages. More precisely, clients use a ``GuzzleHttp\Message\MessageFactoryInterface`` to create request messages. You create requests with a client using the ``createRequest()`` method. .. code-block:: php // Create a request but don't send it immediately $request = $client->createRequest('GET', 'http://httpbin.org/get'); Request Methods --------------- When creating a request, you are expected to provide the HTTP method you wish to perform. You can specify any method you'd like, including a custom method that might not be part of RFC 7231 (like "MOVE"). .. code-block:: php // Create a request using a completely custom HTTP method $request = $client->createRequest('MOVE', 'http://httpbin.org/move', ['exceptions' => false]); echo $request->getMethod(); // MOVE $response = $client->send($request); echo $response->getStatusCode(); // 405 You can create and send a request using methods on a client that map to the HTTP method you wish to use. :GET: ``$client->get('http://httpbin.org/get', [/** options **/])`` :POST: ``$client->post('http://httpbin.org/post', [/** options **/])`` :HEAD: ``$client->head('http://httpbin.org/get', [/** options **/])`` :PUT: ``$client->put('http://httpbin.org/put', [/** options **/])`` :DELETE: ``$client->delete('http://httpbin.org/delete', [/** options **/])`` :OPTIONS: ``$client->options('http://httpbin.org/get', [/** options **/])`` :PATCH: ``$client->patch('http://httpbin.org/put', [/** options **/])`` .. code-block:: php $response = $client->patch('http://httpbin.org/patch', ['body' => 'content']); Request URI ----------- The resource you are requesting with an HTTP request is identified by the path of the request, the query string, and the "Host" header of the request. When creating a request, you can provide the entire resource URI as a URL. .. code-block:: php $response = $client->get('http://httbin.org/get?q=foo'); Using the above code, you will send a request that uses ``httpbin.org`` as the Host header, sends the request over port 80, uses ``/get`` as the path, and sends ``?q=foo`` as the query string. All of this is parsed automatically from the provided URI. Sometimes you don't know what the entire request will be when it is created. In these cases, you can modify the request as needed before sending it using the ``createRequest()`` method of the client and methods on the request that allow you to change it. .. code-block:: php $request = $client->createRequest('GET', 'http://httbin.org'); You can change the path of the request using ``setPath()``: .. code-block:: php $request->setPath('/get'); echo $request->getPath(); // /get echo $request->getUrl(); // http://httpbin.com/get Scheme ~~~~~~ The `scheme `_ of a request specifies the protocol to use when sending the request. When using Guzzle, the scheme can be set to "http" or "https". You can change the scheme of the request using the ``setScheme()`` method: .. code-block:: php $request = $client->createRequest('GET', 'http://httbin.org'); $request->setScheme('https'); echo $request->getScheme(); // https echo $request->getUrl(); // https://httpbin.com/get Port ~~~~ No port is necessary when using the "http" or "https" schemes, but you can override the port using ``setPort()``. If you need to modify the port used with the specified scheme from the default setting, then you must use the ``setPort()`` method. .. code-block:: php $request = $client->createRequest('GET', 'http://httbin.org'); $request->setPort(8080); echo $request->getPort(); // 8080 echo $request->getUrl(); // https://httpbin.com:8080/get // Set the port back to the default value for the scheme $request->setPort(443); echo $request->getUrl(); // https://httpbin.com/get Query string ~~~~~~~~~~~~ You can get the query string of the request using the ``getQuery()`` method. This method returns a ``GuzzleHttp\Query`` object. A Query object can be accessed like a PHP array, iterated in a foreach statement like a PHP array, and cast to a string. .. code-block:: php $request = $client->createRequest('GET', 'http://httbin.org'); $query = $request->getQuery(); $query['foo'] = 'bar'; $query['baz'] = 'bam'; $query['bam'] = ['test' => 'abc']; echo $request->getQuery(); // foo=bar&baz=bam&bam%5Btest%5D=abc echo $request->getQuery()['foo']; // bar echo $request->getQuery()->get('foo'); // bar echo $request->getQuery()->get('foo'); // bar var_export($request->getQuery()['bam']); // array('test' => 'abc') foreach ($query as $key => $value) { var_export($value); } echo $request->getUrl(); // https://httpbin.com/get?foo=bar&baz=bam&bam%5Btest%5D=abc Query Aggregators ^^^^^^^^^^^^^^^^^ Query objects can store scalar values or arrays of values. When an array of values is added to a query object, the query object uses a query aggregator to convert the complex structure into a string. Query objects will use `PHP style query strings `_ when complex query string parameters are converted to a string. You can customize how complex query string parameters are aggregated using the ``setAggregator()`` method of a query string object. .. code-block:: php $query->setAggregator($query::duplicateAggregator()); In the above example, we've changed the query object to use the "duplicateAggregator". This aggregator will allow duplicate entries to appear in a query string rather than appending "[n]" to each value. So if you had a query string with ``['a' => ['b', 'c']]``, the duplicate aggregator would convert this to "a=b&a=c" while the default aggregator would convert this to "a[0]=b&a[1]=c" (with urlencoded brackets). The ``setAggregator()`` method accepts a ``callable`` which is used to convert a deeply nested array of query string variables into a flattened array of key value pairs. The callable accepts an array of query data and returns a flattened array of key value pairs where each value is an array of strings. You can use the ``GuzzleHttp\Query::walkQuery()`` static function to easily create custom query aggregators. Host ~~~~ You can change the host header of the request in a predictable way using the ``setHost()`` method of a request: .. code-block:: php $request->setHost('www.google.com'); echo $request->getHost(); // www.google.com echo $request->getUrl(); // https://www.google.com/get?foo=bar&baz=bam .. note:: The Host header can also be changed by modifying the Host header of a request directly, but modifying the Host header directly could result in sending a request to a different Host than what is specified in the Host header (sometimes this is actually the desired behavior). Resource ~~~~~~~~ You can use the ``getResource()`` method of a request to return the path and query string of a request in a single string. .. code-block:: php $request = $client->createRequest('GET', 'http://httpbin.org/get?baz=bar'); echo $request->getResource(); // /get?baz=bar Request Config -------------- Request messages contain a configuration collection that can be used by event listeners and HTTP handlers to modify how a request behaves or is transferred over the wire. For example, many of the request options that are specified when creating a request are actually set as config options that are only acted upon by handlers and listeners when the request is sent. You can get access to the request's config object using the ``getConfig()`` method of a request. .. code-block:: php $request = $client->createRequest('GET', '/'); $config = $request->getConfig(); The config object is a ``GuzzleHttp\Common\Collection`` object that acts like an associative array. You can grab values from the collection using array like access. You can also modify and remove values using array like access. .. code-block:: php $config['foo'] = 'bar'; echo $config['foo']; // bar var_export(isset($config['foo'])); // true unset($config['foo']); var_export(isset($config['foo'])); // false var_export($config['foo']); // NULL HTTP handlers and event listeners can expose additional customization options through request config settings. For example, in order to specify custom cURL options to the cURL handler, you need to specify an associative array in the ``curl`` ``config`` request option. .. code-block:: php $client->get('/', [ 'config' => [ 'curl' => [ CURLOPT_HTTPAUTH => CURLAUTH_NTLM, CURLOPT_USERPWD => 'username:password' ] ] ]); Consult the HTTP handlers and event listeners you are using to see if they allow customization through request configuration options. Event Emitter ------------- Request objects implement ``GuzzleHttp\Common\HasEmitterInterface``, so they have a method called ``getEmitter()`` that can be used to get an event emitter used by the request. Any listener or subscriber attached to a request will only be triggered for the lifecycle events of a specific request. Conversely, adding an event listener or subscriber to a client will listen to all lifecycle events of all requests created by the client. See :doc:`events` for more information. Responses ========= Responses are the HTTP messages a client receives from a server after sending an HTTP request message. Start-Line ---------- The start-line of a response contains the protocol and protocol version, status code, and reason phrase. .. code-block:: php $response = GuzzleHttp\get('http://httpbin.org/get'); echo $response->getStatusCode(); // 200 echo $response->getReasonPhrase(); // OK echo $response->getProtocolVersion(); // 1.1 Body ---- As described earlier, you can get the body of a response using the ``getBody()`` method. .. code-block:: php if ($body = $response->getBody()) { echo $body; // Cast to a string: { ... } $body->seek(0); // Rewind the body $body->read(1024); // Read bytes of the body } When working with JSON responses, you can use the ``json()`` method of a response: .. code-block:: php $json = $response->json(); .. note:: Guzzle uses the ``json_decode()`` method of PHP and uses arrays rather than ``stdClass`` objects for objects. You can use the ``xml()`` method when working with XML data. .. code-block:: php $xml = $response->xml(); .. note:: Guzzle uses the ``SimpleXMLElement`` objects when converting response bodies to XML. Effective URL ------------- The URL that was ultimately accessed that returned a response can be accessed using the ``getEffectiveUrl()`` method of a response. This method will return the URL of a request or the URL of the last redirected URL if any redirects occurred while transferring a request. .. code-block:: php $response = GuzzleHttp\get('http://httpbin.org/get'); echo $response->getEffectiveUrl(); // http://httpbin.org/get $response = GuzzleHttp\get('http://httpbin.org/redirect-to?url=http://www.google.com'); echo $response->getEffectiveUrl(); // http://www.google.com docs/index.rst000064400000007052151742731020007343 0ustar00.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services ====== Guzzle ====== Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services. - Manages things like persistent connections, represents query strings as collections, simplifies sending streaming POST requests with fields and files, and abstracts away the underlying HTTP transport layer. - Can send both synchronous and asynchronous requests using the same interface without requiring a dependency on a specific event loop. - Pluggable HTTP handlers allows Guzzle to integrate with any method you choose for sending HTTP requests over the wire (e.g., cURL, sockets, PHP's stream wrapper, non-blocking event loops like `React `_, etc.). - Guzzle makes it so that you no longer need to fool around with cURL options, stream contexts, or sockets. .. code-block:: php $client = new GuzzleHttp\Client(); $response = $client->get('http://guzzlephp.org'); $res = $client->get('https://api.github.com/user', ['auth' => ['user', 'pass']]); echo $res->getStatusCode(); // "200" echo $res->getHeader('content-type'); // 'application/json; charset=utf8' echo $res->getBody(); // {"type":"User"...' var_export($res->json()); // Outputs the JSON decoded data // Send an asynchronous request. $req = $client->createRequest('GET', 'http://httpbin.org', ['future' => true]); $client->send($req)->then(function ($response) { echo 'I completed! ' . $response; }); User guide ---------- .. toctree:: :maxdepth: 2 overview quickstart clients http-messages events streams handlers testing faq HTTP Components --------------- There are a number of optional libraries you can use along with Guzzle's HTTP layer to add capabilities to the client. `Log Subscriber `_ Logs HTTP requests and responses sent over the wire using customizable log message templates. `OAuth Subscriber `_ Signs requests using OAuth 1.0. `Cache Subscriber `_ Implements a private transparent proxy cache that caches HTTP responses. `Retry Subscriber `_ Retries failed requests using customizable retry strategies (e.g., retry based on response status code, cURL error codes, etc.) `Message Integrity Subscriber `_ Verifies the message integrity of HTTP responses using customizable validators. This plugin can be used, for example, to verify the Content-MD5 headers of responses. Service Description Commands ---------------------------- You can use the **Guzzle Command** library to encapsulate interaction with a web service using command objects. Building on top of Guzzle's command abstraction allows you to easily implement things like service description that can be used to serialize requests and parse responses using a meta-description of a web service. `Guzzle Command `_ Provides the foundational elements used to build high-level, command based, web service clients with Guzzle. `Guzzle Services `_ Provides an implementation of the *Guzzle Command* library that uses Guzzle service descriptions to describe web services, serialize requests, and parse responses into easy to use model structures. docs/overview.rst000064400000011505151742731020010100 0ustar00======== Overview ======== Requirements ============ #. PHP 5.4.0 #. To use the PHP stream handler, ``allow_url_fopen`` must be enabled in your system's php.ini. #. To use the cURL handler, you must have a recent version of cURL >= 7.16.2 compiled with OpenSSL and zlib. .. note:: Guzzle no longer requires cURL in order to send HTTP requests. Guzzle will use the PHP stream wrapper to send HTTP requests if cURL is not installed. Alternatively, you can provide your own HTTP handler used to send requests. .. _installation: Installation ============ The recommended way to install Guzzle is with `Composer `_. Composer is a dependency management tool for PHP that allows you to declare the dependencies your project needs and installs them into your project. .. code-block:: bash # Install Composer curl -sS https://getcomposer.org/installer | php You can add Guzzle as a dependency using the composer.phar CLI: .. code-block:: bash php composer.phar require guzzlehttp/guzzle:~5.0 Alternatively, you can specify Guzzle as a dependency in your project's existing composer.json file: .. code-block:: js { "require": { "guzzlehttp/guzzle": "~5.0" } } After installing, you need to require Composer's autoloader: .. code-block:: php require 'vendor/autoload.php'; You can find out more on how to install Composer, configure autoloading, and other best-practices for defining dependencies at `getcomposer.org `_. Bleeding edge ------------- During your development, you can keep up with the latest changes on the master branch by setting the version requirement for Guzzle to ``~5.0@dev``. .. code-block:: js { "require": { "guzzlehttp/guzzle": "~5.0@dev" } } License ======= Licensed using the `MIT license `_. Copyright (c) 2014 Michael Dowling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Contributing ============ Guidelines ---------- 1. Guzzle follows PSR-0, PSR-1, and PSR-2. 2. Guzzle is meant to be lean and fast with very few dependencies. 3. Guzzle has a minimum PHP version requirement of PHP 5.4. Pull requests must not require a PHP version greater than PHP 5.4. 4. All pull requests must include unit tests to ensure the change works as expected and to prevent regressions. Running the tests ----------------- In order to contribute, you'll need to checkout the source from GitHub and install Guzzle's dependencies using Composer: .. code-block:: bash git clone https://github.com/guzzle/guzzle.git cd guzzle && curl -s http://getcomposer.org/installer | php && ./composer.phar install --dev Guzzle is unit tested with PHPUnit. Run the tests using the vendored PHPUnit binary: .. code-block:: bash vendor/bin/phpunit .. note:: You'll need to install node.js v0.5.0 or newer in order to perform integration tests on Guzzle's HTTP handlers. Reporting a security vulnerability ================================== We want to ensure that Guzzle is a secure HTTP client library for everyone. If you've discovered a security vulnerability in Guzzle, we appreciate your help in disclosing it to us in a `responsible manner `_. Publicly disclosing a vulnerability can put the entire community at risk. If you've discovered a security concern, please email us at security@guzzlephp.org. We'll work with you to make sure that we understand the scope of the issue, and that we fully address your concern. We consider correspondence sent to security@guzzlephp.org our highest priority, and work to address any issues that arise as quickly as possible. After a security vulnerability has been corrected, a security hotfix release will be deployed as soon as possible. docs/quickstart.rst000064400000033072151742731020010427 0ustar00========== Quickstart ========== This page provides a quick introduction to Guzzle and introductory examples. If you have not already installed, Guzzle, head over to the :ref:`installation` page. Make a Request ============== You can send requests with Guzzle using a ``GuzzleHttp\ClientInterface`` object. Creating a Client ----------------- The procedural API is simple but not very testable; it's best left for quick prototyping. If you want to use Guzzle in a more flexible and testable way, then you'll need to use a ``GuzzleHttp\ClientInterface`` object. .. code-block:: php use GuzzleHttp\Client; $client = new Client(); $response = $client->get('http://httpbin.org/get'); // You can use the same methods you saw in the procedural API $response = $client->delete('http://httpbin.org/delete'); $response = $client->head('http://httpbin.org/get'); $response = $client->options('http://httpbin.org/get'); $response = $client->patch('http://httpbin.org/patch'); $response = $client->post('http://httpbin.org/post'); $response = $client->put('http://httpbin.org/put'); You can create a request with a client and then send the request with the client when you're ready. .. code-block:: php $request = $client->createRequest('GET', 'http://www.foo.com'); $response = $client->send($request); Client objects provide a great deal of flexibility in how request are transferred including default request options, subscribers that are attached to each request, and a base URL that allows you to send requests with relative URLs. You can find out all about clients in the :doc:`clients` page of the documentation. Using Responses =============== In the previous examples, we retrieved a ``$response`` variable. This value is actually a ``GuzzleHttp\Message\ResponseInterface`` object and contains lots of helpful information. You can get the status code and reason phrase of the response. .. code-block:: php $code = $response->getStatusCode(); // 200 $reason = $response->getReasonPhrase(); // OK By providing the ``future`` request option to a request, you can send requests asynchronously using the promise interface of a future response. .. code-block:: php $client->get('http://httpbin.org', ['future' => true]) ->then(function ($response) { echo $response->getStatusCode(); }); Response Body ------------- The body of a response can be retrieved and cast to a string. .. code-block:: php $body = $response->getBody(); echo $body; // { "some_json_data" ...} You can also read read bytes from body of a response like a stream. .. code-block:: php $body = $response->getBody(); while (!$body->eof()) { echo $body->read(1024); } JSON Responses ~~~~~~~~~~~~~~ You can more easily work with JSON responses using the ``json()`` method of a response. .. code-block:: php $response = $client->get('http://httpbin.org/get'); $json = $response->json(); var_dump($json[0]['origin']); Guzzle internally uses PHP's ``json_decode()`` function to parse responses. If Guzzle is unable to parse the JSON response body, then a ``GuzzleHttp\Exception\ParseException`` is thrown. XML Responses ~~~~~~~~~~~~~ You can use a response's ``xml()`` method to more easily work with responses that contain XML data. .. code-block:: php $response = $client->get('https://github.com/mtdowling.atom'); $xml = $response->xml(); echo $xml->id; // tag:github.com,2008:/mtdowling Guzzle internally uses a ``SimpleXMLElement`` object to parse responses. If Guzzle is unable to parse the XML response body, then a ``GuzzleHttp\Exception\ParseException`` is thrown. Query String Parameters ======================= Sending query string parameters with a request is easy. You can set query string parameters in the request's URL. .. code-block:: php $response = $client->get('http://httpbin.org?foo=bar'); You can also specify the query string parameters using the ``query`` request option. .. code-block:: php $client->get('http://httpbin.org', [ 'query' => ['foo' => 'bar'] ]); And finally, you can build up the query string of a request as needed by calling the ``getQuery()`` method of a request and modifying the request's ``GuzzleHttp\Query`` object as needed. .. code-block:: php $request = $client->createRequest('GET', 'http://httpbin.org'); $query = $request->getQuery(); $query->set('foo', 'bar'); // You can use the query string object like an array $query['baz'] = 'bam'; // The query object can be cast to a string echo $query; // foo=bar&baz=bam // Setting a value to false or null will cause the "=" sign to be omitted $query['empty'] = null; echo $query; // foo=bar&baz=bam&empty // Use an empty string to include the "=" sign with an empty value $query['empty'] = ''; echo $query; // foo=bar&baz=bam&empty= .. _headers: Request and Response Headers ---------------------------- You can specify request headers when sending or creating requests with a client. In the following example, we send the ``X-Foo-Header`` with a value of ``value`` by setting the ``headers`` request option. .. code-block:: php $response = $client->get('http://httpbin.org/get', [ 'headers' => ['X-Foo-Header' => 'value'] ]); You can view the headers of a response using header specific methods of a response class. Headers work exactly the same way for request and response object. You can retrieve a header from a request or response using the ``getHeader()`` method of the object. This method is case-insensitive and by default will return a string containing the header field value. .. code-block:: php $response = $client->get('http://www.yahoo.com'); $length = $response->getHeader('Content-Length'); Header fields that contain multiple values can be retrieved as a string or as an array. Retrieving the field values as a string will naively concatenate all of the header values together with a comma. Because not all header fields should be represented this way (e.g., ``Set-Cookie``), you can pass an optional flag to the ``getHeader()`` method to retrieve the header values as an array. .. code-block:: php $values = $response->getHeader('Set-Cookie', true); foreach ($values as $value) { echo $value; } You can test if a request or response has a specific header using the ``hasHeader()`` method. This method accepts a case-insensitive string and returns true if the header is present or false if it is not. You can retrieve all of the headers of a message using the ``getHeaders()`` method of a request or response. The return value is an associative array where the keys represent the header name as it will be sent over the wire, and each value is an array of strings associated with the header. .. code-block:: php $headers = $response->getHeaders(); foreach ($message->getHeaders() as $name => $values) { echo $name . ": " . implode(", ", $values); } Modifying headers ----------------- The headers of a message can be modified using the ``setHeader()``, ``addHeader()``, ``setHeaders()``, and ``removeHeader()`` methods of a request or response object. .. code-block:: php $request = $client->createRequest('GET', 'http://httpbin.org/get'); // Set a single value for a header $request->setHeader('User-Agent', 'Testing!'); // Set multiple values for a header in one call $request->setHeader('X-Foo', ['Baz', 'Bar']); // Add a header to the message $request->addHeader('X-Foo', 'Bam'); echo $request->getHeader('X-Foo'); // Baz, Bar, Bam // Remove a specific header using a case-insensitive name $request->removeHeader('x-foo'); echo $request->getHeader('X-Foo'); // Echoes an empty string: '' Uploading Data ============== Guzzle provides several methods of uploading data. You can send requests that contain a stream of data by passing a string, resource returned from ``fopen``, or a ``GuzzleHttp\Stream\StreamInterface`` object to the ``body`` request option. .. code-block:: php $r = $client->post('http://httpbin.org/post', ['body' => 'raw data']); You can easily upload JSON data using the ``json`` request option. .. code-block:: php $r = $client->put('http://httpbin.org/put', ['json' => ['foo' => 'bar']]); POST Requests ------------- In addition to specifying the raw data of a request using the ``body`` request option, Guzzle provides helpful abstractions over sending POST data. Sending POST Fields ~~~~~~~~~~~~~~~~~~~ Sending ``application/x-www-form-urlencoded`` POST requests requires that you specify the body of a POST request as an array. .. code-block:: php $response = $client->post('http://httpbin.org/post', [ 'body' => [ 'field_name' => 'abc', 'other_field' => '123' ] ]); You can also build up POST requests before sending them. .. code-block:: php $request = $client->createRequest('POST', 'http://httpbin.org/post'); $postBody = $request->getBody(); // $postBody is an instance of GuzzleHttp\Post\PostBodyInterface $postBody->setField('foo', 'bar'); echo $postBody->getField('foo'); // 'bar' echo json_encode($postBody->getFields()); // {"foo": "bar"} // Send the POST request $response = $client->send($request); Sending POST Files ~~~~~~~~~~~~~~~~~~ Sending ``multipart/form-data`` POST requests (POST requests that contain files) is the same as sending ``application/x-www-form-urlencoded``, except some of the array values of the POST fields map to PHP ``fopen`` resources, or ``GuzzleHttp\Stream\StreamInterface``, or ``GuzzleHttp\Post\PostFileInterface`` objects. .. code-block:: php use GuzzleHttp\Post\PostFile; $response = $client->post('http://httpbin.org/post', [ 'body' => [ 'field_name' => 'abc', 'file_filed' => fopen('/path/to/file', 'r'), 'other_file' => new PostFile('other_file', 'this is the content') ] ]); Just like when sending POST fields, you can also build up POST requests with files before sending them. .. code-block:: php use GuzzleHttp\Post\PostFile; $request = $client->createRequest('POST', 'http://httpbin.org/post'); $postBody = $request->getBody(); $postBody->setField('foo', 'bar'); $postBody->addFile(new PostFile('test', fopen('/path/to/file', 'r'))); $response = $client->send($request); Cookies ======= Guzzle can maintain a cookie session for you if instructed using the ``cookies`` request option. - Set to ``true`` to use a shared cookie session associated with the client. - Pass an associative array containing cookies to send in the request and start a new cookie session. - Set to a ``GuzzleHttp\Subscriber\CookieJar\CookieJarInterface`` object to use an existing cookie jar. Redirects ========= Guzzle will automatically follow redirects unless you tell it not to. You can customize the redirect behavior using the ``allow_redirects`` request option. - Set to true to enable normal redirects with a maximum number of 5 redirects. This is the default setting. - Set to false to disable redirects. - Pass an associative array containing the 'max' key to specify the maximum number of redirects and optionally provide a 'strict' key value to specify whether or not to use strict RFC compliant redirects (meaning redirect POST requests with POST requests vs. doing what most browsers do which is redirect POST requests with GET requests). .. code-block:: php $response = $client->get('http://github.com'); echo $response->getStatusCode(); // 200 echo $response->getEffectiveUrl(); // 'https://github.com/' The following example shows that redirects can be disabled. .. code-block:: php $response = $client->get('http://github.com', ['allow_redirects' => false]); echo $response->getStatusCode(); // 301 echo $response->getEffectiveUrl(); // 'http://github.com/' Exceptions ========== Guzzle throws exceptions for errors that occur during a transfer. - In the event of a networking error (connection timeout, DNS errors, etc.), a ``GuzzleHttp\Exception\RequestException`` is thrown. This exception extends from ``GuzzleHttp\Exception\TransferException``. Catching this exception will catch any exception that can be thrown while transferring (non-parallel) requests. .. code-block:: php use GuzzleHttp\Exception\RequestException; try { $client->get('https://github.com/_abc_123_404'); } catch (RequestException $e) { echo $e->getRequest(); if ($e->hasResponse()) { echo $e->getResponse(); } } - A ``GuzzleHttp\Exception\ClientException`` is thrown for 400 level errors if the ``exceptions`` request option is set to true. This exception extends from ``GuzzleHttp\Exception\BadResponseException`` and ``GuzzleHttp\Exception\BadResponseException`` extends from ``GuzzleHttp\Exception\RequestException``. .. code-block:: php use GuzzleHttp\Exception\ClientException; try { $client->get('https://github.com/_abc_123_404'); } catch (ClientException $e) { echo $e->getRequest(); echo $e->getResponse(); } - A ``GuzzleHttp\Exception\ServerException`` is thrown for 500 level errors if the ``exceptions`` request option is set to true. This exception extends from ``GuzzleHttp\Exception\BadResponseException``. - A ``GuzzleHttp\Exception\TooManyRedirectsException`` is thrown when too many redirects are followed. This exception extends from ``GuzzleHttp\Exception\RequestException``. All of the above exceptions extend from ``GuzzleHttp\Exception\TransferException``. docs/requirements.txt000064400000000051151742731020010756 0ustar00Sphinx>=1.2b1 guzzle_sphinx_theme>=0.6.0 docs/streams.rst000064400000014317151742731020007714 0ustar00======= Streams ======= Guzzle uses stream objects to represent request and response message bodies. These stream objects allow you to work with various types of data all using a common interface. HTTP messages consist of a start-line, headers, and a body. The body of an HTTP message can be very small or extremely large. Attempting to represent the body of a message as a string can easily consume more memory than intended because the body must be stored completely in memory. Attempting to store the body of a request or response in memory would preclude the use of that implementation from being able to work with large message bodies. The StreamInterface is used in order to hide the implementation details of where a stream of data is read from or written to. Guzzle's StreamInterface exposes several methods that enable streams to be read from, written to, and traversed effectively. Streams expose their capabilities using three methods: ``isReadable()``, ``isWritable()``, and ``isSeekable()``. These methods can be used by stream collaborators to determine if a stream is capable of their requirements. Each stream instance has various capabilities: they can be read-only, write-only, read-write, allow arbitrary random access (seeking forwards or backwards to any location), or only allow sequential access (for example in the case of a socket or pipe). Creating Streams ================ The best way to create a stream is using the static factory method, ``GuzzleHttp\Stream\Stream::factory()``. This factory accepts strings, resources returned from ``fopen()``, an object that implements ``__toString()``, and an object that implements ``GuzzleHttp\Stream\StreamInterface``. .. code-block:: php use GuzzleHttp\Stream\Stream; $stream = Stream::factory('string data'); echo $stream; // string data echo $stream->read(3); // str echo $stream->getContents(); // ing data var_export($stream->eof()); // true var_export($stream->tell()); // 11 Metadata ======== Guzzle streams expose stream metadata through the ``getMetadata()`` method. This method provides the data you would retrieve when calling PHP's `stream_get_meta_data() function `_, and can optionally expose other custom data. .. code-block:: php use GuzzleHttp\Stream\Stream; $resource = fopen('/path/to/file', 'r'); $stream = Stream::factory($resource); echo $stream->getMetadata('uri'); // /path/to/file var_export($stream->isReadable()); // true var_export($stream->isWritable()); // false var_export($stream->isSeekable()); // true Stream Decorators ================= With the small and focused interface, add custom functionality to streams is very simple with stream decorators. Guzzle provides several built-in decorators that provide additional stream functionality. CachingStream ------------- The CachingStream is used to allow seeking over previously read bytes on non-seekable streams. This can be useful when transferring a non-seekable entity body fails due to needing to rewind the stream (for example, resulting from a redirect). Data that is read from the remote stream will be buffered in a PHP temp stream so that previously read bytes are cached first in memory, then on disk. .. code-block:: php use GuzzleHttp\Stream\Stream; use GuzzleHttp\Stream\CachingStream; $original = Stream::factory(fopen('http://www.google.com', 'r')); $stream = new CachingStream($original); $stream->read(1024); echo $stream->tell(); // 1024 $stream->seek(0); echo $stream->tell(); // 0 LimitStream ----------- LimitStream can be used to read a subset or slice of an existing stream object. This can be useful for breaking a large file into smaller pieces to be sent in chunks (e.g. Amazon S3's multipart upload API). .. code-block:: php use GuzzleHttp\Stream\Stream; use GuzzleHttp\Stream\LimitStream; $original = Stream::factory(fopen('/tmp/test.txt', 'r+')); echo $original->getSize(); // >>> 1048576 // Limit the size of the body to 1024 bytes and start reading from byte 2048 $stream = new LimitStream($original, 1024, 2048); echo $stream->getSize(); // >>> 1024 echo $stream->tell(); // >>> 0 NoSeekStream ------------ NoSeekStream wraps a stream and does not allow seeking. .. code-block:: php use GuzzleHttp\Stream\Stream; use GuzzleHttp\Stream\LimitStream; $original = Stream::factory('foo'); $noSeek = new NoSeekStream($original); echo $noSeek->read(3); // foo var_export($noSeek->isSeekable()); // false $noSeek->seek(0); var_export($noSeek->read(3)); // NULL Creating Custom Decorators -------------------------- Creating a stream decorator is very easy thanks to the ``GuzzleHttp\Stream\StreamDecoratorTrait``. This trait provides methods that implement ``GuzzleHttp\Stream\StreamInterface`` by proxying to an underlying stream. Just ``use`` the ``StreamDecoratorTrait`` and implement your custom methods. For example, let's say we wanted to call a specific function each time the last byte is read from a stream. This could be implemented by overriding the ``read()`` method. .. code-block:: php use GuzzleHttp\Stream\StreamDecoratorTrait; class EofCallbackStream implements StreamInterface { use StreamDecoratorTrait; private $callback; public function __construct(StreamInterface $stream, callable $callback) { $this->stream = $stream; $this->callback = $callback; } public function read($length) { $result = $this->stream->read($length); // Invoke the callback when EOF is hit. if ($this->eof()) { call_user_func($this->callback); } return $result; } } This decorator could be added to any existing stream and used like so: .. code-block:: php use GuzzleHttp\Stream\Stream; $original = Stream::factory('foo'); $eofStream = new EofCallbackStream($original, function () { echo 'EOF!'; }); $eofStream->read(2); $eofStream->read(1); // echoes "EOF!" $eofStream->seek(0); $eofStream->read(3); // echoes "EOF!" docs/testing.rst000064400000016604151742731020007714 0ustar00====================== Testing Guzzle Clients ====================== Guzzle provides several tools that will enable you to easily mock the HTTP layer without needing to send requests over the internet. * Mock subscriber * Mock handler * Node.js web server for integration testing Mock Subscriber =============== When testing HTTP clients, you often need to simulate specific scenarios like returning a successful response, returning an error, or returning specific responses in a certain order. Because unit tests need to be predictable, easy to bootstrap, and fast, hitting an actual remote API is a test smell. Guzzle provides a mock subscriber that can be attached to clients or requests that allows you to queue up a list of responses to use rather than hitting a remote API. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Subscriber\Mock; use GuzzleHttp\Message\Response; $client = new Client(); // Create a mock subscriber and queue two responses. $mock = new Mock([ new Response(200, ['X-Foo' => 'Bar']), // Use response object "HTTP/1.1 202 OK\r\nContent-Length: 0\r\n\r\n" // Use a response string ]); // Add the mock subscriber to the client. $client->getEmitter()->attach($mock); // The first request is intercepted with the first response. echo $client->get('/')->getStatusCode(); //> 200 // The second request is intercepted with the second response. echo $client->get('/')->getStatusCode(); //> 202 When no more responses are in the queue and a request is sent, an ``OutOfBoundsException`` is thrown. History Subscriber ================== When using things like the ``Mock`` subscriber, you often need to know if the requests you expected to send were sent exactly as you intended. While the mock subscriber responds with mocked responses, the ``GuzzleHttp\Subscriber\History`` subscriber maintains a history of the requests that were sent by a client. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Subscriber\History; $client = new Client(); $history = new History(); // Add the history subscriber to the client. $client->getEmitter()->attach($history); $client->get('http://httpbin.org/get'); $client->head('http://httpbin.org/get'); // Count the number of transactions echo count($history); //> 2 // Get the last request $lastRequest = $history->getLastRequest(); // Get the last response $lastRequest = $history->getLastResponse(); // Iterate over the transactions that were sent foreach ($history as $transaction) { echo $transaction['request']->getMethod(); //> GET, HEAD echo $transaction['response']->getStatusCode(); //> 200, 200 } The history subscriber can also be printed, revealing the requests and responses that were sent as a string, in order. .. code-block:: php echo $history; :: > GET /get HTTP/1.1 Host: httpbin.org User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8 < HTTP/1.1 200 OK Access-Control-Allow-Origin: * Content-Type: application/json Date: Tue, 25 Mar 2014 03:53:27 GMT Server: gunicorn/0.17.4 Content-Length: 270 Connection: keep-alive { "headers": { "Connection": "close", "X-Request-Id": "3d0f7d5c-c937-4394-8248-2b8e03fcccdb", "User-Agent": "Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8", "Host": "httpbin.org" }, "origin": "76.104.247.1", "args": {}, "url": "http://httpbin.org/get" } > HEAD /get HTTP/1.1 Host: httpbin.org User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8 < HTTP/1.1 200 OK Access-Control-Allow-Origin: * Content-length: 270 Content-Type: application/json Date: Tue, 25 Mar 2014 03:53:27 GMT Server: gunicorn/0.17.4 Connection: keep-alive Mock Adapter ============ In addition to using the Mock subscriber, you can use the ``GuzzleHttp\Ring\Client\MockAdapter`` as the handler of a client to return the same response over and over or return the result of a callable function. Test Web Server =============== Using mock responses is almost always enough when testing a web service client. When implementing custom :doc:`HTTP handlers `, you'll need to send actual HTTP requests in order to sufficiently test the handler. However, a best practice is to contact a local web server rather than a server over the internet. - Tests are more reliable - Tests do not require a network connection - Tests have no external dependencies Using the test server --------------------- .. warning:: The following functionality is provided to help developers of Guzzle develop HTTP handlers. There is no promise of backwards compatibility when it comes to the node.js test server or the ``GuzzleHttp\Tests\Server`` class. If you are using the test server or ``Server`` class outside of guzzlehttp/guzzle, then you will need to configure autoloading and ensure the web server is started manually. .. hint:: You almost never need to use this test web server. You should only ever consider using it when developing HTTP handlers. The test web server is not necessary for mocking requests. For that, please use the Mock subcribers and History subscriber. Guzzle ships with a node.js test server that receives requests and returns responses from a queue. The test server exposes a simple API that is used to enqueue responses and inspect the requests that it has received. Any operation on the ``Server`` object will ensure that the server is running and wait until it is able to receive requests before returning. .. code-block:: php use GuzzleHttp\Client; use GuzzleHttp\Tests\Server; // Start the server and queue a response Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n"); $client = new Client(['base_url' => Server::$url]); echo $client->get('/foo')->getStatusCode(); // 200 ``GuzzleHttp\Tests\Server`` provides a static interface to the test server. You can queue an HTTP response or an array of responses by calling ``Server::enqueue()``. This method accepts a string representing an HTTP response message, a ``GuzzleHttp\Message\ResponseInterface``, or an array of HTTP message strings / ``GuzzleHttp\Message\ResponseInterface`` objects. .. code-block:: php // Queue single response Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n"); // Clear the queue and queue an array of responses Server::enqueue([ "HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n", "HTTP/1.1 404 Not Found\r\n\Content-Length: 0r\n\r\n" ]); When a response is queued on the test server, the test server will remove any previously queued responses. As the server receives requests, queued responses are dequeued and returned to the request. When the queue is empty, the server will return a 500 response. You can inspect the requests that the server has retrieved by calling ``Server::received()``. This method accepts an optional ``$hydrate`` parameter that specifies if you are retrieving an array of HTTP requests as strings or an array of ``GuzzleHttp\Message\RequestInterface`` objects. .. code-block:: php foreach (Server::received() as $response) { echo $response; } You can clear the list of received requests from the web server using the ``Server::flush()`` method. .. code-block:: php Server::flush(); echo count(Server::received()); // 0 phpunit.xml.dist000064400000000633151742731020007723 0ustar00 tests src src/ src/BatchResults.php000064400000006311151742731020010452 0ustar00hash = $hash; } /** * Get the keys that are available on the batch result. * * @return array */ public function getKeys() { return iterator_to_array($this->hash); } /** * Gets a result from the container for the given object. When getting * results for a batch of requests, provide the request object. * * @param object $forObject Object to retrieve the result for. * * @return mixed|null */ public function getResult($forObject) { return isset($this->hash[$forObject]) ? $this->hash[$forObject] : null; } /** * Get an array of successful results. * * @return array */ public function getSuccessful() { $results = []; foreach ($this->hash as $key) { if (!($this->hash[$key] instanceof \Exception)) { $results[] = $this->hash[$key]; } } return $results; } /** * Get an array of failed results. * * @return array */ public function getFailures() { $results = []; foreach ($this->hash as $key) { if ($this->hash[$key] instanceof \Exception) { $results[] = $this->hash[$key]; } } return $results; } /** * Allows iteration over all batch result values. * * @return \ArrayIterator */ public function getIterator() { $results = []; foreach ($this->hash as $key) { $results[] = $this->hash[$key]; } return new \ArrayIterator($results); } /** * Counts the number of elements in the batch result. * * @return int */ public function count() { return count($this->hash); } /** * Checks if the batch contains a specific numerical array index. * * @param int $key Index to access * * @return bool */ public function offsetExists($key) { return $key < count($this->hash); } /** * Allows access of the batch using a numerical array index. * * @param int $key Index to access. * * @return mixed|null */ public function offsetGet($key) { $i = -1; foreach ($this->hash as $obj) { if ($key === ++$i) { return $this->hash[$obj]; } } return null; } public function offsetUnset($key) { throw new \RuntimeException('Not implemented'); } public function offsetSet($key, $value) { throw new \RuntimeException('Not implemented'); } } src/Client.php000064400000027752151742731020007301 0ustar00 [ * 'http://www.foo.com/{version}/', * ['version' => '123'] * ], * 'defaults' => [ * 'timeout' => 10, * 'allow_redirects' => false, * 'proxy' => '192.168.16.1:10' * ] * ]); * * @param array $config Client configuration settings * - base_url: Base URL of the client that is merged into relative URLs. * Can be a string or an array that contains a URI template followed * by an associative array of expansion variables to inject into the * URI template. * - handler: callable RingPHP handler used to transfer requests * - message_factory: Factory used to create request and response object * - defaults: Default request options to apply to each request * - emitter: Event emitter used for request events * - fsm: (internal use only) The request finite state machine. A * function that accepts a transaction and optional final state. The * function is responsible for transitioning a request through its * lifecycle events. */ public function __construct(array $config = []) { $this->configureBaseUrl($config); $this->configureDefaults($config); if (isset($config['emitter'])) { $this->emitter = $config['emitter']; } $this->messageFactory = isset($config['message_factory']) ? $config['message_factory'] : new MessageFactory(); if (isset($config['fsm'])) { $this->fsm = $config['fsm']; } else { if (isset($config['handler'])) { $handler = $config['handler']; } elseif (isset($config['adapter'])) { $handler = $config['adapter']; } else { $handler = static::getDefaultHandler(); } $this->fsm = new RequestFsm($handler, $this->messageFactory); } } /** * Create a default handler to use based on the environment * * @throws \RuntimeException if no viable Handler is available. */ public static function getDefaultHandler() { $default = $future = $streaming = null; if (extension_loaded('curl')) { $config = [ 'select_timeout' => getenv('GUZZLE_CURL_SELECT_TIMEOUT') ?: 1 ]; if ($maxHandles = getenv('GUZZLE_CURL_MAX_HANDLES')) { $config['max_handles'] = $maxHandles; } $future = new CurlMultiHandler($config); if (function_exists('curl_reset')) { $default = new CurlHandler(); } } if (ini_get('allow_url_fopen')) { $streaming = new StreamHandler(); } if (!($default = ($default ?: $future) ?: $streaming)) { throw new \RuntimeException('Guzzle requires cURL, the ' . 'allow_url_fopen ini setting, or a custom HTTP handler.'); } $handler = $default; if ($streaming && $streaming !== $default) { $handler = Middleware::wrapStreaming($default, $streaming); } if ($future) { $handler = Middleware::wrapFuture($handler, $future); } return $handler; } /** * Get the default User-Agent string to use with Guzzle * * @return string */ public static function getDefaultUserAgent() { static $defaultAgent = ''; if (!$defaultAgent) { $defaultAgent = 'Guzzle/' . self::VERSION; if (extension_loaded('curl')) { $defaultAgent .= ' curl/' . curl_version()['version']; } $defaultAgent .= ' PHP/' . PHP_VERSION; } return $defaultAgent; } public function getDefaultOption($keyOrPath = null) { return $keyOrPath === null ? $this->defaults : Utils::getPath($this->defaults, $keyOrPath); } public function setDefaultOption($keyOrPath, $value) { Utils::setPath($this->defaults, $keyOrPath, $value); } public function getBaseUrl() { return (string) $this->baseUrl; } public function createRequest($method, $url = null, array $options = []) { $headers = $this->mergeDefaults($options); // Use a clone of the client's emitter $options['config']['emitter'] = clone $this->getEmitter(); $request = $this->messageFactory->createRequest( $method, $url ? (string) $this->buildUrl($url) : (string) $this->baseUrl, $options ); // Merge in default headers if ($headers) { foreach ($headers as $key => $value) { if (!$request->hasHeader($key)) { $request->setHeader($key, $value); } } } return $request; } public function get($url = null, $options = []) { return $this->send($this->createRequest('GET', $url, $options)); } public function head($url = null, array $options = []) { return $this->send($this->createRequest('HEAD', $url, $options)); } public function delete($url = null, array $options = []) { return $this->send($this->createRequest('DELETE', $url, $options)); } public function put($url = null, array $options = []) { return $this->send($this->createRequest('PUT', $url, $options)); } public function patch($url = null, array $options = []) { return $this->send($this->createRequest('PATCH', $url, $options)); } public function post($url = null, array $options = []) { return $this->send($this->createRequest('POST', $url, $options)); } public function options($url = null, array $options = []) { return $this->send($this->createRequest('OPTIONS', $url, $options)); } public function send(RequestInterface $request) { $trans = new Transaction($this, $request); $fn = $this->fsm; // Ensure a future response is returned if one was requested. if ($request->getConfig()->get('future')) { try { $fn($trans); // Turn the normal response into a future if needed. return $trans->response instanceof FutureInterface ? $trans->response : new FutureResponse(new FulfilledPromise($trans->response)); } catch (RequestException $e) { // Wrap the exception in a promise if the user asked for a future. return new FutureResponse(new RejectedPromise($e)); } } else { try { $fn($trans); return $trans->response instanceof FutureInterface ? $trans->response->wait() : $trans->response; } catch (\Exception $e) { throw RequestException::wrapException($trans->request, $e); } } } /** * Get an array of default options to apply to the client * * @return array */ protected function getDefaultOptions() { $settings = [ 'allow_redirects' => true, 'exceptions' => true, 'decode_content' => true, 'verify' => true ]; // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set if ($proxy = getenv('HTTP_PROXY')) { $settings['proxy']['http'] = $proxy; } if ($proxy = getenv('HTTPS_PROXY')) { $settings['proxy']['https'] = $proxy; } return $settings; } /** * Expand a URI template and inherit from the base URL if it's relative * * @param string|array $url URL or URI template to expand * * @return string */ private function buildUrl($url) { // URI template (absolute or relative) if (!is_array($url)) { return strpos($url, '://') ? (string) $url : (string) $this->baseUrl->combine($url); } // Absolute URL if (strpos($url[0], '://')) { return Utils::uriTemplate($url[0], $url[1]); } // Combine the relative URL with the base URL return (string) $this->baseUrl->combine( Utils::uriTemplate($url[0], $url[1]) ); } private function configureBaseUrl(&$config) { if (!isset($config['base_url'])) { $this->baseUrl = new Url('', ''); } elseif (is_array($config['base_url'])) { $this->baseUrl = Url::fromString( Utils::uriTemplate( $config['base_url'][0], $config['base_url'][1] ) ); $config['base_url'] = (string) $this->baseUrl; } else { $this->baseUrl = Url::fromString($config['base_url']); } } private function configureDefaults($config) { if (!isset($config['defaults'])) { $this->defaults = $this->getDefaultOptions(); } else { $this->defaults = array_replace( $this->getDefaultOptions(), $config['defaults'] ); } // Add the default user-agent header if (!isset($this->defaults['headers'])) { $this->defaults['headers'] = [ 'User-Agent' => static::getDefaultUserAgent() ]; } elseif (!Core::hasHeader($this->defaults, 'User-Agent')) { // Add the User-Agent header if one was not already set $this->defaults['headers']['User-Agent'] = static::getDefaultUserAgent(); } } /** * Merges default options into the array passed by reference and returns * an array of headers that need to be merged in after the request is * created. * * @param array $options Options to modify by reference * * @return array|null */ private function mergeDefaults(&$options) { // Merging optimization for when no headers are present if (!isset($options['headers']) || !isset($this->defaults['headers'])) { $options = array_replace_recursive($this->defaults, $options); return null; } $defaults = $this->defaults; unset($defaults['headers']); $options = array_replace_recursive($defaults, $options); return $this->defaults['headers']; } /** * @deprecated Use {@see GuzzleHttp\Pool} instead. * @see GuzzleHttp\Pool */ public function sendAll($requests, array $options = []) { (new Pool($this, $requests, $options))->wait(); } } src/ClientInterface.php000064400000011604151742731020011107 0ustar00data = $data; } /** * Create a new collection from an array, validate the keys, and add default * values where missing * * @param array $config Configuration values to apply. * @param array $defaults Default parameters * @param array $required Required parameter names * * @return self * @throws \InvalidArgumentException if a parameter is missing */ public static function fromConfig( array $config = [], array $defaults = [], array $required = [] ) { $data = $config + $defaults; if ($missing = array_diff($required, array_keys($data))) { throw new \InvalidArgumentException( 'Config is missing the following keys: ' . implode(', ', $missing)); } return new self($data); } /** * Removes all key value pairs */ public function clear() { $this->data = []; } /** * Get a specific key value. * * @param string $key Key to retrieve. * * @return mixed|null Value of the key or NULL */ public function get($key) { return isset($this->data[$key]) ? $this->data[$key] : null; } /** * Set a key value pair * * @param string $key Key to set * @param mixed $value Value to set */ public function set($key, $value) { $this->data[$key] = $value; } /** * Add a value to a key. If a key of the same name has already been added, * the key value will be converted into an array and the new value will be * pushed to the end of the array. * * @param string $key Key to add * @param mixed $value Value to add to the key */ public function add($key, $value) { if (!array_key_exists($key, $this->data)) { $this->data[$key] = $value; } elseif (is_array($this->data[$key])) { $this->data[$key][] = $value; } else { $this->data[$key] = array($this->data[$key], $value); } } /** * Remove a specific key value pair * * @param string $key A key to remove */ public function remove($key) { unset($this->data[$key]); } /** * Get all keys in the collection * * @return array */ public function getKeys() { return array_keys($this->data); } /** * Returns whether or not the specified key is present. * * @param string $key The key for which to check the existence. * * @return bool */ public function hasKey($key) { return array_key_exists($key, $this->data); } /** * Checks if any keys contains a certain value * * @param string $value Value to search for * * @return mixed Returns the key if the value was found FALSE if the value * was not found. */ public function hasValue($value) { return array_search($value, $this->data, true); } /** * Replace the data of the object with the value of an array * * @param array $data Associative array of data */ public function replace(array $data) { $this->data = $data; } /** * Add and merge in a Collection or array of key value pair data. * * @param Collection|array $data Associative array of key value pair data */ public function merge($data) { foreach ($data as $key => $value) { $this->add($key, $value); } } /** * Overwrite key value pairs in this collection with all of the data from * an array or collection. * * @param array|\Traversable $data Values to override over this config */ public function overwriteWith($data) { if (is_array($data)) { $this->data = $data + $this->data; } elseif ($data instanceof Collection) { $this->data = $data->toArray() + $this->data; } else { foreach ($data as $key => $value) { $this->data[$key] = $value; } } } /** * Returns a Collection containing all the elements of the collection after * applying the callback function to each one. * * The callable should accept three arguments: * - (string) $key * - (string) $value * - (array) $context * * The callable must return a the altered or unaltered value. * * @param callable $closure Map function to apply * @param array $context Context to pass to the callable * * @return Collection */ public function map(callable $closure, array $context = []) { $collection = new static(); foreach ($this as $key => $value) { $collection[$key] = $closure($key, $value, $context); } return $collection; } /** * Iterates over each key value pair in the collection passing them to the * callable. If the callable returns true, the current value from input is * returned into the result Collection. * * The callable must accept two arguments: * - (string) $key * - (string) $value * * @param callable $closure Evaluation function * * @return Collection */ public function filter(callable $closure) { $collection = new static(); foreach ($this->data as $key => $value) { if ($closure($key, $value)) { $collection[$key] = $value; } } return $collection; } } src/Cookie/CookieJar.php000064400000016430151742731020011131 0ustar00strictMode = $strictMode; foreach ($cookieArray as $cookie) { if (!($cookie instanceof SetCookie)) { $cookie = new SetCookie($cookie); } $this->setCookie($cookie); } } /** * Create a new Cookie jar from an associative array and domain. * * @param array $cookies Cookies to create the jar from * @param string $domain Domain to set the cookies to * * @return self */ public static function fromArray(array $cookies, $domain) { $cookieJar = new self(); foreach ($cookies as $name => $value) { $cookieJar->setCookie(new SetCookie([ 'Domain' => $domain, 'Name' => $name, 'Value' => $value, 'Discard' => true ])); } return $cookieJar; } /** * Quote the cookie value if it is not already quoted and it contains * problematic characters. * * @param string $value Value that may or may not need to be quoted * * @return string */ public static function getCookieValue($value) { if (substr($value, 0, 1) !== '"' && substr($value, -1, 1) !== '"' && strpbrk($value, ';,') ) { $value = '"' . $value . '"'; } return $value; } public function toArray() { return array_map(function (SetCookie $cookie) { return $cookie->toArray(); }, $this->getIterator()->getArrayCopy()); } public function clear($domain = null, $path = null, $name = null) { if (!$domain) { $this->cookies = []; return; } elseif (!$path) { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain) { return !$cookie->matchesDomain($domain); } ); } elseif (!$name) { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain) { return !($cookie->matchesPath($path) && $cookie->matchesDomain($domain)); } ); } else { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) use ($path, $domain, $name) { return !($cookie->getName() == $name && $cookie->matchesPath($path) && $cookie->matchesDomain($domain)); } ); } } public function clearSessionCookies() { $this->cookies = array_filter( $this->cookies, function (SetCookie $cookie) { return !$cookie->getDiscard() && $cookie->getExpires(); } ); } public function setCookie(SetCookie $cookie) { // Only allow cookies with set and valid domain, name, value $result = $cookie->validate(); if ($result !== true) { if ($this->strictMode) { throw new \RuntimeException('Invalid cookie: ' . $result); } else { $this->removeCookieIfEmpty($cookie); return false; } } // Resolve conflicts with previously set cookies foreach ($this->cookies as $i => $c) { // Two cookies are identical, when their path, and domain are // identical. if ($c->getPath() != $cookie->getPath() || $c->getDomain() != $cookie->getDomain() || $c->getName() != $cookie->getName() ) { continue; } // The previously set cookie is a discard cookie and this one is // not so allow the new cookie to be set if (!$cookie->getDiscard() && $c->getDiscard()) { unset($this->cookies[$i]); continue; } // If the new cookie's expiration is further into the future, then // replace the old cookie if ($cookie->getExpires() > $c->getExpires()) { unset($this->cookies[$i]); continue; } // If the value has changed, we better change it if ($cookie->getValue() !== $c->getValue()) { unset($this->cookies[$i]); continue; } // The cookie exists, so no need to continue return false; } $this->cookies[] = $cookie; return true; } public function count() { return count($this->cookies); } public function getIterator() { return new \ArrayIterator(array_values($this->cookies)); } public function extractCookies( RequestInterface $request, ResponseInterface $response ) { if ($cookieHeader = $response->getHeaderAsArray('Set-Cookie')) { foreach ($cookieHeader as $cookie) { $sc = SetCookie::fromString($cookie); if (!$sc->getDomain()) { $sc->setDomain($request->getHost()); } $this->setCookie($sc); } } } public function addCookieHeader(RequestInterface $request) { $values = []; $scheme = $request->getScheme(); $host = $request->getHost(); $path = $request->getPath(); foreach ($this->cookies as $cookie) { if ($cookie->matchesPath($path) && $cookie->matchesDomain($host) && !$cookie->isExpired() && (!$cookie->getSecure() || $scheme == 'https') ) { $values[] = $cookie->getName() . '=' . self::getCookieValue($cookie->getValue()); } } if ($values) { $request->setHeader('Cookie', implode('; ', $values)); } } /** * If a cookie already exists and the server asks to set it again with a * null value, the cookie must be deleted. * * @param SetCookie $cookie */ private function removeCookieIfEmpty(SetCookie $cookie) { $cookieValue = $cookie->getValue(); if ($cookieValue === null || $cookieValue === '') { $this->clear( $cookie->getDomain(), $cookie->getPath(), $cookie->getName() ); } } } src/Cookie/CookieJarInterface.php000064400000004774151742731020012762 0ustar00filename = $cookieFile; if (file_exists($cookieFile)) { $this->load($cookieFile); } } /** * Saves the file when shutting down */ public function __destruct() { $this->save($this->filename); } /** * Saves the cookies to a file. * * @param string $filename File to save * @throws \RuntimeException if the file cannot be found or created */ public function save($filename) { $json = []; foreach ($this as $cookie) { if ($cookie->getExpires() && !$cookie->getDiscard()) { $json[] = $cookie->toArray(); } } if (false === file_put_contents($filename, json_encode($json))) { // @codeCoverageIgnoreStart throw new \RuntimeException("Unable to save file {$filename}"); // @codeCoverageIgnoreEnd } } /** * Load cookies from a JSON formatted file. * * Old cookies are kept unless overwritten by newly loaded ones. * * @param string $filename Cookie file to load. * @throws \RuntimeException if the file cannot be loaded. */ public function load($filename) { $json = file_get_contents($filename); if (false === $json) { // @codeCoverageIgnoreStart throw new \RuntimeException("Unable to load file {$filename}"); // @codeCoverageIgnoreEnd } $data = Utils::jsonDecode($json, true); if (is_array($data)) { foreach (Utils::jsonDecode($json, true) as $cookie) { $this->setCookie(new SetCookie($cookie)); } } elseif (strlen($data)) { throw new \RuntimeException("Invalid cookie file: {$filename}"); } } } src/Cookie/SessionCookieJar.php000064400000002772151742731020012501 0ustar00sessionKey = $sessionKey; $this->load(); } /** * Saves cookies to session when shutting down */ public function __destruct() { $this->save(); } /** * Save cookies to the client session */ public function save() { $json = []; foreach ($this as $cookie) { if ($cookie->getExpires() && !$cookie->getDiscard()) { $json[] = $cookie->toArray(); } } $_SESSION[$this->sessionKey] = json_encode($json); } /** * Load the contents of the client session into the data array */ protected function load() { $cookieJar = isset($_SESSION[$this->sessionKey]) ? $_SESSION[$this->sessionKey] : null; $data = Utils::jsonDecode($cookieJar, true); if (is_array($data)) { foreach ($data as $cookie) { $this->setCookie(new SetCookie($cookie)); } } elseif (strlen($data)) { throw new \RuntimeException("Invalid cookie data"); } } } src/Cookie/SetCookie.php000064400000021726151742731020011154 0ustar00 null, 'Value' => null, 'Domain' => null, 'Path' => '/', 'Max-Age' => null, 'Expires' => null, 'Secure' => false, 'Discard' => false, 'HttpOnly' => false ]; /** @var array Cookie data */ private $data; /** * Create a new SetCookie object from a string * * @param string $cookie Set-Cookie header string * * @return self */ public static function fromString($cookie) { // Create the default return array $data = self::$defaults; // Explode the cookie string using a series of semicolons $pieces = array_filter(array_map('trim', explode(';', $cookie))); // The name of the cookie (first kvp) must include an equal sign. if (empty($pieces) || !strpos($pieces[0], '=')) { return new self($data); } // Add the cookie pieces into the parsed data array foreach ($pieces as $part) { $cookieParts = explode('=', $part, 2); $key = trim($cookieParts[0]); $value = isset($cookieParts[1]) ? trim($cookieParts[1], " \n\r\t\0\x0B\"") : true; // Only check for non-cookies when cookies have been found if (empty($data['Name'])) { $data['Name'] = $key; $data['Value'] = $value; } else { foreach (array_keys(self::$defaults) as $search) { if (!strcasecmp($search, $key)) { $data[$search] = $value; continue 2; } } $data[$key] = $value; } } return new self($data); } /** * @param array $data Array of cookie data provided by a Cookie parser */ public function __construct(array $data = []) { $this->data = array_replace(self::$defaults, $data); // Extract the Expires value and turn it into a UNIX timestamp if needed if (!$this->getExpires() && $this->getMaxAge()) { // Calculate the Expires date $this->setExpires(time() + $this->getMaxAge()); } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { $this->setExpires($this->getExpires()); } } public function __toString() { $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; foreach ($this->data as $k => $v) { if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) { if ($k == 'Expires') { $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; } else { $str .= ($v === true ? $k : "{$k}={$v}") . '; '; } } } return rtrim($str, '; '); } public function toArray() { return $this->data; } /** * Get the cookie name * * @return string */ public function getName() { return $this->data['Name']; } /** * Set the cookie name * * @param string $name Cookie name */ public function setName($name) { $this->data['Name'] = $name; } /** * Get the cookie value * * @return string */ public function getValue() { return $this->data['Value']; } /** * Set the cookie value * * @param string $value Cookie value */ public function setValue($value) { $this->data['Value'] = $value; } /** * Get the domain * * @return string|null */ public function getDomain() { return $this->data['Domain']; } /** * Set the domain of the cookie * * @param string $domain */ public function setDomain($domain) { $this->data['Domain'] = $domain; } /** * Get the path * * @return string */ public function getPath() { return $this->data['Path']; } /** * Set the path of the cookie * * @param string $path Path of the cookie */ public function setPath($path) { $this->data['Path'] = $path; } /** * Maximum lifetime of the cookie in seconds * * @return int|null */ public function getMaxAge() { return $this->data['Max-Age']; } /** * Set the max-age of the cookie * * @param int $maxAge Max age of the cookie in seconds */ public function setMaxAge($maxAge) { $this->data['Max-Age'] = $maxAge; } /** * The UNIX timestamp when the cookie Expires * * @return mixed */ public function getExpires() { return $this->data['Expires']; } /** * Set the unix timestamp for which the cookie will expire * * @param int $timestamp Unix timestamp */ public function setExpires($timestamp) { $this->data['Expires'] = is_numeric($timestamp) ? (int) $timestamp : strtotime($timestamp); } /** * Get whether or not this is a secure cookie * * @return null|bool */ public function getSecure() { return $this->data['Secure']; } /** * Set whether or not the cookie is secure * * @param bool $secure Set to true or false if secure */ public function setSecure($secure) { $this->data['Secure'] = $secure; } /** * Get whether or not this is a session cookie * * @return null|bool */ public function getDiscard() { return $this->data['Discard']; } /** * Set whether or not this is a session cookie * * @param bool $discard Set to true or false if this is a session cookie */ public function setDiscard($discard) { $this->data['Discard'] = $discard; } /** * Get whether or not this is an HTTP only cookie * * @return bool */ public function getHttpOnly() { return $this->data['HttpOnly']; } /** * Set whether or not this is an HTTP only cookie * * @param bool $httpOnly Set to true or false if this is HTTP only */ public function setHttpOnly($httpOnly) { $this->data['HttpOnly'] = $httpOnly; } /** * Check if the cookie matches a path value * * @param string $path Path to check against * * @return bool */ public function matchesPath($path) { return !$this->getPath() || 0 === stripos($path, $this->getPath()); } /** * Check if the cookie matches a domain value * * @param string $domain Domain to check against * * @return bool */ public function matchesDomain($domain) { // Remove the leading '.' as per spec in RFC 6265. // http://tools.ietf.org/html/rfc6265#section-5.2.3 $cookieDomain = ltrim($this->getDomain(), '.'); // Domain not set or exact match. if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { return true; } // Matching the subdomain according to RFC 6265. // http://tools.ietf.org/html/rfc6265#section-5.1.3 if (filter_var($domain, FILTER_VALIDATE_IP)) { return false; } return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/i', $domain); } /** * Check if the cookie is expired * * @return bool */ public function isExpired() { return $this->getExpires() && time() > $this->getExpires(); } /** * Check if the cookie is valid according to RFC 6265 * * @return bool|string Returns true if valid or an error message if invalid */ public function validate() { // Names must not be empty, but can be 0 $name = $this->getName(); if (empty($name) && !is_numeric($name)) { return 'The cookie name must not be empty'; } // Check if any of the invalid characters are present in the cookie name if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { return "Cookie name must not cannot invalid characters: =,; \\t\\r\\n\\013\\014"; } // Value must not be empty, but can be 0 $value = $this->getValue(); if (empty($value) && !is_numeric($value)) { return 'The cookie value must not be empty'; } // Domains must not be empty, but can be 0 // A "0" is not a valid internet domain, but may be used as server name // in a private network. $domain = $this->getDomain(); if (empty($domain) && !is_numeric($domain)) { return 'The cookie domain must not be empty'; } return true; } } src/Event/AbstractEvent.php000064400000000571151742731020011677 0ustar00propagationStopped; } public function stopPropagation() { $this->propagationStopped = true; } } src/Event/AbstractRequestEvent.php000064400000001757151742731020013257 0ustar00transaction = $transaction; } /** * Get the HTTP client associated with the event. * * @return ClientInterface */ public function getClient() { return $this->transaction->client; } /** * Get the request object * * @return RequestInterface */ public function getRequest() { return $this->transaction->request; } /** * @return Transaction */ protected function getTransaction() { return $this->transaction; } } src/Event/AbstractRetryableEvent.php000064400000002621151742731020013547 0ustar00transaction->response = null; $this->transaction->exception = null; $this->transaction->state = 'before'; if ($afterDelay) { $this->transaction->request->getConfig()->set('delay', $afterDelay); } $this->stopPropagation(); } } src/Event/AbstractTransferEvent.php000064400000003144151742731020013403 0ustar00transaction->transferInfo : (isset($this->transaction->transferInfo[$name]) ? $this->transaction->transferInfo[$name] : null); } /** * Returns true/false if a response is available. * * @return bool */ public function hasResponse() { return !($this->transaction->response instanceof FutureInterface); } /** * Get the response. * * @return ResponseInterface|null */ public function getResponse() { return $this->hasResponse() ? $this->transaction->response : null; } /** * Intercept the request and associate a response * * @param ResponseInterface $response Response to set */ public function intercept(ResponseInterface $response) { $this->transaction->response = $response; $this->transaction->exception = null; $this->stopPropagation(); } } src/Event/BeforeEvent.php000064400000001304151742731020011331 0ustar00transaction->response = $response; $this->transaction->exception = null; $this->stopPropagation(); } } src/Event/CompleteEvent.php000064400000000664151742731020011707 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. * * @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher */ class Emitter implements EmitterInterface { /** @var array */ private $listeners = []; /** @var array */ private $sorted = []; public function on($eventName, callable $listener, $priority = 0) { if ($priority === 'first') { $priority = isset($this->listeners[$eventName]) ? max(array_keys($this->listeners[$eventName])) + 1 : 1; } elseif ($priority === 'last') { $priority = isset($this->listeners[$eventName]) ? min(array_keys($this->listeners[$eventName])) - 1 : -1; } $this->listeners[$eventName][$priority][] = $listener; unset($this->sorted[$eventName]); } public function once($eventName, callable $listener, $priority = 0) { $onceListener = function ( EventInterface $event, $eventName ) use (&$onceListener, $eventName, $listener, $priority) { $this->removeListener($eventName, $onceListener); $listener($event, $eventName, $this); }; $this->on($eventName, $onceListener, $priority); } public function removeListener($eventName, callable $listener) { if (empty($this->listeners[$eventName])) { return; } foreach ($this->listeners[$eventName] as $priority => $listeners) { if (false !== ($key = array_search($listener, $listeners, true))) { unset( $this->listeners[$eventName][$priority][$key], $this->sorted[$eventName] ); } } } public function listeners($eventName = null) { // Return all events in a sorted priority order if ($eventName === null) { foreach (array_keys($this->listeners) as $eventName) { if (empty($this->sorted[$eventName])) { $this->listeners($eventName); } } return $this->sorted; } // Return the listeners for a specific event, sorted in priority order if (empty($this->sorted[$eventName])) { $this->sorted[$eventName] = []; if (isset($this->listeners[$eventName])) { krsort($this->listeners[$eventName], SORT_NUMERIC); foreach ($this->listeners[$eventName] as $listeners) { foreach ($listeners as $listener) { $this->sorted[$eventName][] = $listener; } } } } return $this->sorted[$eventName]; } public function hasListeners($eventName) { return !empty($this->listeners[$eventName]); } public function emit($eventName, EventInterface $event) { if (isset($this->listeners[$eventName])) { foreach ($this->listeners($eventName) as $listener) { $listener($event, $eventName); if ($event->isPropagationStopped()) { break; } } } return $event; } public function attach(SubscriberInterface $subscriber) { foreach ($subscriber->getEvents() as $eventName => $listeners) { if (is_array($listeners[0])) { foreach ($listeners as $listener) { $this->on( $eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0 ); } } else { $this->on( $eventName, [$subscriber, $listeners[0]], isset($listeners[1]) ? $listeners[1] : 0 ); } } } public function detach(SubscriberInterface $subscriber) { foreach ($subscriber->getEvents() as $eventName => $listener) { $this->removeListener($eventName, [$subscriber, $listener[0]]); } } } src/Event/EmitterInterface.php000064400000006726151742731020012374 0ustar00transaction->exception; } } src/Event/ErrorEvent.php000064400000001244151742731020011223 0ustar00transaction->exception; } } src/Event/EventInterface.php000064400000001041151742731020012025 0ustar00emitter) { $this->emitter = new Emitter(); } return $this->emitter; } } src/Event/ListenerAttacherTrait.php000064400000006424151742731020013402 0ustar00getEmitter(); foreach ($listeners as $el) { if ($el['once']) { $emitter->once($el['name'], $el['fn'], $el['priority']); } else { $emitter->on($el['name'], $el['fn'], $el['priority']); } } } /** * Extracts the allowed events from the provided array, and ignores anything * else in the array. The event listener must be specified as a callable or * as an array of event listener data ("name", "fn", "priority", "once"). * * @param array $source Array containing callables or hashes of data to be * prepared as event listeners. * @param array $events Names of events to look for in the provided $source * array. Other keys are ignored. * @return array */ private function prepareListeners(array $source, array $events) { $listeners = []; foreach ($events as $name) { if (isset($source[$name])) { $this->buildListener($name, $source[$name], $listeners); } } return $listeners; } /** * Creates a complete event listener definition from the provided array of * listener data. Also works recursively if more than one listeners are * contained in the provided array. * * @param string $name Name of the event the listener is for. * @param array|callable $data Event listener data to prepare. * @param array $listeners Array of listeners, passed by reference. * * @throws \InvalidArgumentException if the event data is malformed. */ private function buildListener($name, $data, &$listeners) { static $defaults = ['priority' => 0, 'once' => false]; // If a callable is provided, normalize it to the array format. if (is_callable($data)) { $data = ['fn' => $data]; } // Prepare the listener and add it to the array, recursively. if (isset($data['fn'])) { $data['name'] = $name; $listeners[] = $data + $defaults; } elseif (is_array($data)) { foreach ($data as $listenerData) { $this->buildListener($name, $listenerData, $listeners); } } else { throw new \InvalidArgumentException('Each event listener must be a ' . 'callable or an associative array containing a "fn" key.'); } } } src/Event/ProgressEvent.php000064400000003137151742731020011741 0ustar00downloadSize = $downloadSize; $this->downloaded = $downloaded; $this->uploadSize = $uploadSize; $this->uploaded = $uploaded; } } src/Event/RequestEvents.php000064400000003142151742731020011744 0ustar00 ['methodName']] * - ['eventName' => ['methodName', $priority]] * - ['eventName' => [['methodName'], ['otherMethod']] * - ['eventName' => [['methodName'], ['otherMethod', $priority]] * - ['eventName' => [['methodName', $priority], ['otherMethod', $priority]] * * @return array */ public function getEvents(); } src/Exception/BadResponseException.php000064400000000240151742731020014064 0ustar00response = $response; } /** * Get the associated response * * @return ResponseInterface|null */ public function getResponse() { return $this->response; } } src/Exception/RequestException.php000064400000006534151742731020013323 0ustar00getStatusCode() : 0; parent::__construct($message, $code, $previous); $this->request = $request; $this->response = $response; } /** * Wrap non-RequesExceptions with a RequestException * * @param RequestInterface $request * @param \Exception $e * * @return RequestException */ public static function wrapException(RequestInterface $request, \Exception $e) { if ($e instanceof RequestException) { return $e; } elseif ($e instanceof ConnectException) { return new HttpConnectException($e->getMessage(), $request, null, $e); } else { return new RequestException($e->getMessage(), $request, null, $e); } } /** * Factory method to create a new exception with a normalized error message * * @param RequestInterface $request Request * @param ResponseInterface $response Response received * @param \Exception $previous Previous exception * * @return self */ public static function create( RequestInterface $request, ResponseInterface $response = null, \Exception $previous = null ) { if (!$response) { return new self('Error completing request', $request, null, $previous); } $level = floor($response->getStatusCode() / 100); if ($level == '4') { $label = 'Client error response'; $className = __NAMESPACE__ . '\\ClientException'; } elseif ($level == '5') { $label = 'Server error response'; $className = __NAMESPACE__ . '\\ServerException'; } else { $label = 'Unsuccessful response'; $className = __CLASS__; } $message = $label . ' [url] ' . $request->getUrl() . ' [status code] ' . $response->getStatusCode() . ' [reason phrase] ' . $response->getReasonPhrase(); return new $className($message, $request, $response, $previous); } /** * Get the request that caused the exception * * @return RequestInterface */ public function getRequest() { return $this->request; } /** * Get the associated response * * @return ResponseInterface|null */ public function getResponse() { return $this->response; } /** * Check if a response was received * * @return bool */ public function hasResponse() { return $this->response !== null; } } src/Exception/ServerException.php000064400000000241151742731020013126 0ustar00error = $error; } /** * Get the associated error * * @return \LibXMLError|null */ public function getError() { return $this->error; } } src/HasDataTrait.php000064400000003164151742731020010363 0ustar00data); } public function offsetGet($offset) { return isset($this->data[$offset]) ? $this->data[$offset] : null; } public function offsetSet($offset, $value) { $this->data[$offset] = $value; } public function offsetExists($offset) { return isset($this->data[$offset]); } public function offsetUnset($offset) { unset($this->data[$offset]); } public function toArray() { return $this->data; } public function count() { return count($this->data); } /** * Get a value from the collection using a path syntax to retrieve nested * data. * * @param string $path Path to traverse and retrieve a value from * * @return mixed|null */ public function getPath($path) { return Utils::getPath($this->data, $path); } /** * Set a value into a nested array key. Keys will be created as needed to * set the value. * * @param string $path Path to set * @param mixed $value Value to set at the key * * @throws \RuntimeException when trying to setPath using a nested path * that travels through a scalar value */ public function setPath($path, $value) { Utils::setPath($this->data, $path, $value); } } src/Message/AbstractMessage.php000064400000016171151742731020012510 0ustar00getBody(); } public function getProtocolVersion() { return $this->protocolVersion; } public function getBody() { return $this->body; } public function setBody(StreamInterface $body = null) { if ($body === null) { // Setting a null body will remove the body of the request $this->removeHeader('Content-Length'); $this->removeHeader('Transfer-Encoding'); } $this->body = $body; } public function addHeader($header, $value) { if (is_array($value)) { $current = array_merge($this->getHeaderAsArray($header), $value); } else { $current = $this->getHeaderAsArray($header); $current[] = (string) $value; } $this->setHeader($header, $current); } public function addHeaders(array $headers) { foreach ($headers as $name => $header) { $this->addHeader($name, $header); } } public function getHeader($header) { $name = strtolower($header); return isset($this->headers[$name]) ? implode(', ', $this->headers[$name]) : ''; } public function getHeaderAsArray($header) { $name = strtolower($header); return isset($this->headers[$name]) ? $this->headers[$name] : []; } public function getHeaders() { $headers = []; foreach ($this->headers as $name => $values) { $headers[$this->headerNames[$name]] = $values; } return $headers; } public function setHeader($header, $value) { $header = trim($header); $name = strtolower($header); $this->headerNames[$name] = $header; if (is_array($value)) { foreach ($value as &$v) { $v = trim($v); } $this->headers[$name] = $value; } else { $this->headers[$name] = [trim($value)]; } } public function setHeaders(array $headers) { $this->headers = $this->headerNames = []; foreach ($headers as $key => $value) { $this->setHeader($key, $value); } } public function hasHeader($header) { return isset($this->headers[strtolower($header)]); } public function removeHeader($header) { $name = strtolower($header); unset($this->headers[$name], $this->headerNames[$name]); } /** * Parse an array of header values containing ";" separated data into an * array of associative arrays representing the header key value pair * data of the header. When a parameter does not contain a value, but just * contains a key, this function will inject a key with a '' string value. * * @param MessageInterface $message That contains the header * @param string $header Header to retrieve from the message * * @return array Returns the parsed header values. */ public static function parseHeader(MessageInterface $message, $header) { static $trimmed = "\"' \n\t\r"; $params = $matches = []; foreach (self::normalizeHeader($message, $header) as $val) { $part = []; foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { $m = $matches[0]; if (isset($m[1])) { $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); } else { $part[] = trim($m[0], $trimmed); } } } if ($part) { $params[] = $part; } } return $params; } /** * Converts an array of header values that may contain comma separated * headers into an array of headers with no comma separated values. * * @param MessageInterface $message That contains the header * @param string $header Header to retrieve from the message * * @return array Returns the normalized header field values. */ public static function normalizeHeader(MessageInterface $message, $header) { $h = $message->getHeaderAsArray($header); for ($i = 0, $total = count($h); $i < $total; $i++) { if (strpos($h[$i], ',') === false) { continue; } foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $h[$i]) as $v) { $h[] = trim($v); } unset($h[$i]); } return $h; } /** * Gets the start-line and headers of a message as a string * * @param MessageInterface $message * * @return string */ public static function getStartLineAndHeaders(MessageInterface $message) { return static::getStartLine($message) . self::getHeadersAsString($message); } /** * Gets the headers of a message as a string * * @param MessageInterface $message * * @return string */ public static function getHeadersAsString(MessageInterface $message) { $result = ''; foreach ($message->getHeaders() as $name => $values) { $result .= "\r\n{$name}: " . implode(', ', $values); } return $result; } /** * Gets the start line of a message * * @param MessageInterface $message * * @return string * @throws \InvalidArgumentException */ public static function getStartLine(MessageInterface $message) { if ($message instanceof RequestInterface) { return trim($message->getMethod() . ' ' . $message->getResource()) . ' HTTP/' . $message->getProtocolVersion(); } elseif ($message instanceof ResponseInterface) { return 'HTTP/' . $message->getProtocolVersion() . ' ' . $message->getStatusCode() . ' ' . $message->getReasonPhrase(); } else { throw new \InvalidArgumentException('Unknown message type'); } } /** * Accepts and modifies the options provided to the message in the * constructor. * * Can be overridden in subclasses as necessary. * * @param array $options Options array passed by reference. */ protected function handleOptions(array &$options) { if (isset($options['protocol_version'])) { $this->protocolVersion = $options['protocol_version']; } } } src/Message/FutureResponse.php000064400000007551151742731020012433 0ustar00then($onFulfilled, $onRejected, $onProgress), [$future, 'wait'], [$future, 'cancel'] ); } public function getStatusCode() { return $this->_value->getStatusCode(); } public function setStatusCode($code) { $this->_value->setStatusCode($code); } public function getReasonPhrase() { return $this->_value->getReasonPhrase(); } public function setReasonPhrase($phrase) { $this->_value->setReasonPhrase($phrase); } public function getEffectiveUrl() { return $this->_value->getEffectiveUrl(); } public function setEffectiveUrl($url) { $this->_value->setEffectiveUrl($url); } public function json(array $config = []) { return $this->_value->json($config); } public function xml(array $config = []) { return $this->_value->xml($config); } public function __toString() { try { return $this->_value->__toString(); } catch (\Exception $e) { trigger_error($e->getMessage(), E_USER_WARNING); return ''; } } public function getProtocolVersion() { return $this->_value->getProtocolVersion(); } public function setBody(StreamInterface $body = null) { $this->_value->setBody($body); } public function getBody() { return $this->_value->getBody(); } public function getHeaders() { return $this->_value->getHeaders(); } public function getHeader($header) { return $this->_value->getHeader($header); } public function getHeaderAsArray($header) { return $this->_value->getHeaderAsArray($header); } public function hasHeader($header) { return $this->_value->hasHeader($header); } public function removeHeader($header) { $this->_value->removeHeader($header); } public function addHeader($header, $value) { $this->_value->addHeader($header, $value); } public function addHeaders(array $headers) { $this->_value->addHeaders($headers); } public function setHeader($header, $value) { $this->_value->setHeader($header, $value); } public function setHeaders(array $headers) { $this->_value->setHeaders($headers); } } src/Message/MessageFactory.php000064400000026610151742731020012353 0ustar00 1, 'timeout' => 1, 'verify' => 1, 'ssl_key' => 1, 'cert' => 1, 'proxy' => 1, 'debug' => 1, 'save_to' => 1, 'stream' => 1, 'expect' => 1, 'future' => 1 ]; /** @var array Default allow_redirects request option settings */ private static $defaultRedirect = [ 'max' => 5, 'strict' => false, 'referer' => false ]; /** * @param array $customOptions Associative array of custom request option * names mapping to functions used to apply * the option. The function accepts the request * and the option value to apply. */ public function __construct(array $customOptions = []) { $this->errorPlugin = new HttpError(); $this->redirectPlugin = new Redirect(); $this->customOptions = $customOptions; } public function createResponse( $statusCode, array $headers = [], $body = null, array $options = [] ) { if (null !== $body) { $body = Stream::factory($body); } return new Response($statusCode, $headers, $body, $options); } public function createRequest($method, $url, array $options = []) { // Handle the request protocol version option that needs to be // specified in the request constructor. if (isset($options['version'])) { $options['config']['protocol_version'] = $options['version']; unset($options['version']); } $request = new Request($method, $url, [], null, isset($options['config']) ? $options['config'] : []); unset($options['config']); // Use a POST body by default if ($method == 'POST' && !isset($options['body']) && !isset($options['json']) ) { $options['body'] = []; } if ($options) { $this->applyOptions($request, $options); } return $request; } /** * Create a request or response object from an HTTP message string * * @param string $message Message to parse * * @return RequestInterface|ResponseInterface * @throws \InvalidArgumentException if unable to parse a message */ public function fromMessage($message) { static $parser; if (!$parser) { $parser = new MessageParser(); } // Parse a response if (strtoupper(substr($message, 0, 4)) == 'HTTP') { $data = $parser->parseResponse($message); return $this->createResponse( $data['code'], $data['headers'], $data['body'] === '' ? null : $data['body'], $data ); } // Parse a request if (!($data = ($parser->parseRequest($message)))) { throw new \InvalidArgumentException('Unable to parse request'); } return $this->createRequest( $data['method'], Url::buildUrl($data['request_url']), [ 'headers' => $data['headers'], 'body' => $data['body'] === '' ? null : $data['body'], 'config' => [ 'protocol_version' => $data['protocol_version'] ] ] ); } /** * Apply POST fields and files to a request to attempt to give an accurate * representation. * * @param RequestInterface $request Request to update * @param array $body Body to apply */ protected function addPostData(RequestInterface $request, array $body) { static $fields = ['string' => true, 'array' => true, 'NULL' => true, 'boolean' => true, 'double' => true, 'integer' => true]; $post = new PostBody(); foreach ($body as $key => $value) { if (isset($fields[gettype($value)])) { $post->setField($key, $value); } elseif ($value instanceof PostFileInterface) { $post->addFile($value); } else { $post->addFile(new PostFile($key, $value)); } } if ($request->getHeader('Content-Type') == 'multipart/form-data') { $post->forceMultipartUpload(true); } $request->setBody($post); } protected function applyOptions( RequestInterface $request, array $options = [] ) { $config = $request->getConfig(); $emitter = $request->getEmitter(); foreach ($options as $key => $value) { if (isset(self::$configMap[$key])) { $config[$key] = $value; continue; } switch ($key) { case 'allow_redirects': if ($value === false) { continue; } if ($value === true) { $value = self::$defaultRedirect; } elseif (!isset($value['max'])) { throw new Iae('allow_redirects must be true, false, or an ' . 'array that contains the \'max\' key'); } else { // Merge the default settings with the provided settings $value += self::$defaultRedirect; } $config['redirect'] = $value; $emitter->attach($this->redirectPlugin); break; case 'decode_content': if ($value === false) { continue; } $config['decode_content'] = true; if ($value !== true) { $request->setHeader('Accept-Encoding', $value); } break; case 'headers': if (!is_array($value)) { throw new Iae('header value must be an array'); } // Do not overwrite existing headers foreach ($value as $k => $v) { if (!$request->hasHeader($k)) { $request->setHeader($k, $v); } } break; case 'exceptions': if ($value === true) { $emitter->attach($this->errorPlugin); } break; case 'body': if (is_array($value)) { $this->addPostData($request, $value); } elseif ($value !== null) { $request->setBody(Stream::factory($value)); } break; case 'auth': if (!$value) { continue; } if (is_array($value)) { $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; } else { $type = strtolower($value); } $config['auth'] = $value; if ($type == 'basic') { $request->setHeader( 'Authorization', 'Basic ' . base64_encode("$value[0]:$value[1]") ); } elseif ($type == 'digest') { // @todo: Do not rely on curl $config->setPath('curl/' . CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); $config->setPath('curl/' . CURLOPT_USERPWD, "$value[0]:$value[1]"); } break; case 'query': if ($value instanceof Query) { $original = $request->getQuery(); // Do not overwrite existing query string variables by // overwriting the object with the query string data passed // in the URL $value->overwriteWith($original->toArray()); $request->setQuery($value); } elseif (is_array($value)) { // Do not overwrite existing query string variables $query = $request->getQuery(); foreach ($value as $k => $v) { if (!isset($query[$k])) { $query[$k] = $v; } } } else { throw new Iae('query must be an array or Query object'); } break; case 'cookies': if ($value === true) { static $cookie = null; if (!$cookie) { $cookie = new Cookie(); } $emitter->attach($cookie); } elseif (is_array($value)) { $emitter->attach( new Cookie(CookieJar::fromArray($value, $request->getHost())) ); } elseif ($value instanceof CookieJarInterface) { $emitter->attach(new Cookie($value)); } elseif ($value !== false) { throw new Iae('cookies must be an array, true, or CookieJarInterface'); } break; case 'events': if (!is_array($value)) { throw new Iae('events must be an array'); } $this->attachListeners($request, $this->prepareListeners( $value, ['before', 'complete', 'error', 'progress', 'end'] ) ); break; case 'subscribers': if (!is_array($value)) { throw new Iae('subscribers must be an array'); } foreach ($value as $subscribers) { $emitter->attach($subscribers); } break; case 'json': $request->setBody(Stream::factory(json_encode($value))); if (!$request->hasHeader('Content-Type')) { $request->setHeader('Content-Type', 'application/json'); } break; default: // Check for custom handler functions. if (isset($this->customOptions[$key])) { $fn = $this->customOptions[$key]; $fn($request, $value); continue; } throw new Iae("No method can handle the {$key} config key"); } } } } src/Message/MessageFactoryInterface.php000064400000006265151742731020014200 0ustar00getHeaders() as $name => $values) { * echo $name . ": " . implode(", ", $values); * } * * @return array Returns an associative array of the message's headers. */ public function getHeaders(); /** * Retrieve a header by the given case-insensitive name. * * @param string $header Case-insensitive header name. * * @return string */ public function getHeader($header); /** * Retrieves a header by the given case-insensitive name as an array of strings. * * @param string $header Case-insensitive header name. * * @return string[] */ public function getHeaderAsArray($header); /** * Checks if a header exists by the given case-insensitive name. * * @param string $header Case-insensitive header name. * * @return bool Returns true if any header names match the given header * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ public function hasHeader($header); /** * Remove a specific header by case-insensitive name. * * @param string $header Case-insensitive header name. */ public function removeHeader($header); /** * Appends a header value to any existing values associated with the * given header name. * * @param string $header Header name to add * @param string $value Value of the header */ public function addHeader($header, $value); /** * Merges in an associative array of headers. * * Each array key MUST be a string representing the case-insensitive name * of a header. Each value MUST be either a string or an array of strings. * For each value, the value is appended to any existing header of the same * name, or, if a header does not already exist by the given name, then the * header is added. * * @param array $headers Associative array of headers to add to the message */ public function addHeaders(array $headers); /** * Sets a header, replacing any existing values of any headers with the * same case-insensitive name. * * The header values MUST be a string or an array of strings. * * @param string $header Header name * @param string|array $value Header value(s) */ public function setHeader($header, $value); /** * Sets headers, replacing any headers that have already been set on the * message. * * The array keys MUST be a string. The array values must be either a * string or an array of strings. * * @param array $headers Headers to set. */ public function setHeaders(array $headers); } src/Message/MessageParser.php000064400000012211151742731020012170 0ustar00parseMessage($message))) { return false; } // Parse the protocol and protocol version if (isset($parts['start_line'][2])) { $startParts = explode('/', $parts['start_line'][2]); $protocol = strtoupper($startParts[0]); $version = isset($startParts[1]) ? $startParts[1] : '1.1'; } else { $protocol = 'HTTP'; $version = '1.1'; } $parsed = [ 'method' => strtoupper($parts['start_line'][0]), 'protocol' => $protocol, 'protocol_version' => $version, 'headers' => $parts['headers'], 'body' => $parts['body'] ]; $parsed['request_url'] = $this->getUrlPartsFromMessage( (isset($parts['start_line'][1]) ? $parts['start_line'][1] : ''), $parsed); return $parsed; } /** * Parse an HTTP response message into an associative array of parts. * * @param string $message HTTP response to parse * * @return array|bool Returns false if the message is invalid */ public function parseResponse($message) { if (!($parts = $this->parseMessage($message))) { return false; } list($protocol, $version) = explode('/', trim($parts['start_line'][0])); return [ 'protocol' => $protocol, 'protocol_version' => $version, 'code' => $parts['start_line'][1], 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '', 'headers' => $parts['headers'], 'body' => $parts['body'] ]; } /** * Parse a message into parts * * @param string $message Message to parse * * @return array|bool */ private function parseMessage($message) { if (!$message) { return false; } $startLine = null; $headers = []; $body = ''; // Iterate over each line in the message, accounting for line endings $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { $line = $lines[$i]; // If two line breaks were encountered, then this is the end of body if (empty($line)) { if ($i < $totalLines - 1) { $body = implode('', array_slice($lines, $i + 2)); } break; } // Parse message headers if (!$startLine) { $startLine = explode(' ', $line, 3); } elseif (strpos($line, ':')) { $parts = explode(':', $line, 2); $key = trim($parts[0]); $value = isset($parts[1]) ? trim($parts[1]) : ''; if (!isset($headers[$key])) { $headers[$key] = $value; } elseif (!is_array($headers[$key])) { $headers[$key] = [$headers[$key], $value]; } else { $headers[$key][] = $value; } } } return [ 'start_line' => $startLine, 'headers' => $headers, 'body' => $body ]; } /** * Create URL parts from HTTP message parts * * @param string $requestUrl Associated URL * @param array $parts HTTP message parts * * @return array */ private function getUrlPartsFromMessage($requestUrl, array $parts) { // Parse the URL information from the message $urlParts = ['path' => $requestUrl, 'scheme' => 'http']; // Check for the Host header if (isset($parts['headers']['Host'])) { $urlParts['host'] = $parts['headers']['Host']; } elseif (isset($parts['headers']['host'])) { $urlParts['host'] = $parts['headers']['host']; } else { $urlParts['host'] = null; } if (false === strpos($urlParts['host'], ':')) { $urlParts['port'] = ''; } else { $hostParts = explode(':', $urlParts['host']); $urlParts['host'] = trim($hostParts[0]); $urlParts['port'] = (int) trim($hostParts[1]); if ($urlParts['port'] == 443) { $urlParts['scheme'] = 'https'; } } // Check if a query is present $path = $urlParts['path']; $qpos = strpos($path, '?'); if ($qpos) { $urlParts['query'] = substr($path, $qpos + 1); $urlParts['path'] = substr($path, 0, $qpos); } else { $urlParts['query'] = ''; } return $urlParts; } } src/Message/Request.php000064400000011070151742731020011061 0ustar00setUrl($url); $this->method = strtoupper($method); $this->handleOptions($options); $this->transferOptions = new Collection($options); $this->addPrepareEvent(); if ($body !== null) { $this->setBody($body); } if ($headers) { foreach ($headers as $key => $value) { $this->setHeader($key, $value); } } } public function __clone() { if ($this->emitter) { $this->emitter = clone $this->emitter; } $this->transferOptions = clone $this->transferOptions; $this->url = clone $this->url; } public function setUrl($url) { $this->url = $url instanceof Url ? $url : Url::fromString($url); $this->updateHostHeaderFromUrl(); } public function getUrl() { return (string) $this->url; } public function setQuery($query) { $this->url->setQuery($query); } public function getQuery() { return $this->url->getQuery(); } public function setMethod($method) { $this->method = strtoupper($method); } public function getMethod() { return $this->method; } public function getScheme() { return $this->url->getScheme(); } public function setScheme($scheme) { $this->url->setScheme($scheme); } public function getPort() { return $this->url->getPort(); } public function setPort($port) { $this->url->setPort($port); $this->updateHostHeaderFromUrl(); } public function getHost() { return $this->url->getHost(); } public function setHost($host) { $this->url->setHost($host); $this->updateHostHeaderFromUrl(); } public function getPath() { return '/' . ltrim($this->url->getPath(), '/'); } public function setPath($path) { $this->url->setPath($path); } public function getResource() { $resource = $this->getPath(); if ($query = (string) $this->url->getQuery()) { $resource .= '?' . $query; } return $resource; } public function getConfig() { return $this->transferOptions; } protected function handleOptions(array &$options) { parent::handleOptions($options); // Use a custom emitter if one is specified, and remove it from // options that are exposed through getConfig() if (isset($options['emitter'])) { $this->emitter = $options['emitter']; unset($options['emitter']); } } /** * Adds a subscriber that ensures a request's body is prepared before * sending. */ private function addPrepareEvent() { static $subscriber; if (!$subscriber) { $subscriber = new Prepare(); } $this->getEmitter()->attach($subscriber); } private function updateHostHeaderFromUrl() { $port = $this->url->getPort(); $scheme = $this->url->getScheme(); if ($host = $this->url->getHost()) { if (($port == 80 && $scheme == 'http') || ($port == 443 && $scheme == 'https') ) { $this->setHeader('Host', $host); } else { $this->setHeader('Host', "{$host}:{$port}"); } } } } src/Message/RequestInterface.php000064400000006055151742731020012711 0ustar00 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 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', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Reserved for WebDAV advanced collections expired proposal', 426 => 'Upgrade required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates (Experimental)', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required', ]; /** @var string The reason phrase of the response (human readable code) */ private $reasonPhrase; /** @var string The status code of the response */ private $statusCode; /** @var string The effective URL that returned this response */ private $effectiveUrl; /** * @param int|string $statusCode The response status code (e.g. 200) * @param array $headers The response headers * @param StreamInterface $body The body of the response * @param array $options Response message options * - reason_phrase: Set a custom reason phrase * - protocol_version: Set a custom protocol version */ public function __construct( $statusCode, array $headers = [], StreamInterface $body = null, array $options = [] ) { $this->statusCode = (int) $statusCode; $this->handleOptions($options); // Assume a reason phrase if one was not applied as an option if (!$this->reasonPhrase && isset(self::$statusTexts[$this->statusCode]) ) { $this->reasonPhrase = self::$statusTexts[$this->statusCode]; } if ($headers) { $this->setHeaders($headers); } if ($body) { $this->setBody($body); } } public function getStatusCode() { return $this->statusCode; } public function setStatusCode($code) { return $this->statusCode = (int) $code; } public function getReasonPhrase() { return $this->reasonPhrase; } public function setReasonPhrase($phrase) { return $this->reasonPhrase = $phrase; } public function json(array $config = []) { try { return Utils::jsonDecode( (string) $this->getBody(), isset($config['object']) ? !$config['object'] : true, 512, isset($config['big_int_strings']) ? JSON_BIGINT_AS_STRING : 0 ); } catch (\InvalidArgumentException $e) { throw new ParseException( $e->getMessage(), $this ); } } public function xml(array $config = []) { $disableEntities = libxml_disable_entity_loader(true); $internalErrors = libxml_use_internal_errors(true); try { // Allow XML to be retrieved even if there is no response body $xml = new \SimpleXMLElement( (string) $this->getBody() ?: '', isset($config['libxml_options']) ? $config['libxml_options'] : LIBXML_NONET, false, isset($config['ns']) ? $config['ns'] : '', isset($config['ns_is_prefix']) ? $config['ns_is_prefix'] : false ); libxml_disable_entity_loader($disableEntities); libxml_use_internal_errors($internalErrors); } catch (\Exception $e) { libxml_disable_entity_loader($disableEntities); libxml_use_internal_errors($internalErrors); throw new XmlParseException( 'Unable to parse response body into XML: ' . $e->getMessage(), $this, $e, (libxml_get_last_error()) ?: null ); } return $xml; } public function getEffectiveUrl() { return $this->effectiveUrl; } public function setEffectiveUrl($url) { $this->effectiveUrl = $url; } /** * Accepts and modifies the options provided to the response in the * constructor. * * @param array $options Options array passed by reference. */ protected function handleOptions(array &$options = []) { parent::handleOptions($options); if (isset($options['reason_phrase'])) { $this->reasonPhrase = $options['reason_phrase']; } } } src/Message/ResponseInterface.php000064400000007646151742731020013066 0ustar00 'text/vnd.in3d.3dml', '3g2' => 'video/3gpp2', '3gp' => 'video/3gpp', '7z' => 'application/x-7z-compressed', 'aab' => 'application/x-authorware-bin', 'aac' => 'audio/x-aac', 'aam' => 'application/x-authorware-map', 'aas' => 'application/x-authorware-seg', 'abw' => 'application/x-abiword', 'ac' => 'application/pkix-attr-cert', 'acc' => 'application/vnd.americandynamics.acc', 'ace' => 'application/x-ace-compressed', 'acu' => 'application/vnd.acucobol', 'acutc' => 'application/vnd.acucorp', 'adp' => 'audio/adpcm', 'aep' => 'application/vnd.audiograph', 'afm' => 'application/x-font-type1', 'afp' => 'application/vnd.ibm.modcap', 'ahead' => 'application/vnd.ahead.space', 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'air' => 'application/vnd.adobe.air-application-installer-package+zip', 'ait' => 'application/vnd.dvb.ait', 'ami' => 'application/vnd.amiga.ami', 'apk' => 'application/vnd.android.package-archive', 'application' => 'application/x-ms-application', 'apr' => 'application/vnd.lotus-approach', 'asa' => 'text/plain', 'asax' => 'application/octet-stream', 'asc' => 'application/pgp-signature', 'ascx' => 'text/plain', 'asf' => 'video/x-ms-asf', 'ashx' => 'text/plain', 'asm' => 'text/x-asm', 'asmx' => 'text/plain', 'aso' => 'application/vnd.accpac.simply.aso', 'asp' => 'text/plain', 'aspx' => 'text/plain', 'asx' => 'video/x-ms-asf', 'atc' => 'application/vnd.acucorp', 'atom' => 'application/atom+xml', 'atomcat' => 'application/atomcat+xml', 'atomsvc' => 'application/atomsvc+xml', 'atx' => 'application/vnd.antix.game-component', 'au' => 'audio/basic', 'avi' => 'video/x-msvideo', 'aw' => 'application/applixware', 'axd' => 'text/plain', 'azf' => 'application/vnd.airzip.filesecure.azf', 'azs' => 'application/vnd.airzip.filesecure.azs', 'azw' => 'application/vnd.amazon.ebook', 'bat' => 'application/x-msdownload', 'bcpio' => 'application/x-bcpio', 'bdf' => 'application/x-font-bdf', 'bdm' => 'application/vnd.syncml.dm+wbxml', 'bed' => 'application/vnd.realvnc.bed', 'bh2' => 'application/vnd.fujitsu.oasysprs', 'bin' => 'application/octet-stream', 'bmi' => 'application/vnd.bmi', 'bmp' => 'image/bmp', 'book' => 'application/vnd.framemaker', 'box' => 'application/vnd.previewsystems.box', 'boz' => 'application/x-bzip2', 'bpk' => 'application/octet-stream', 'btif' => 'image/prs.btif', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip2', 'c' => 'text/x-c', 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', 'c4d' => 'application/vnd.clonk.c4group', 'c4f' => 'application/vnd.clonk.c4group', 'c4g' => 'application/vnd.clonk.c4group', 'c4p' => 'application/vnd.clonk.c4group', 'c4u' => 'application/vnd.clonk.c4group', 'cab' => 'application/vnd.ms-cab-compressed', 'car' => 'application/vnd.curl.car', 'cat' => 'application/vnd.ms-pki.seccat', 'cc' => 'text/x-c', 'cct' => 'application/x-director', 'ccxml' => 'application/ccxml+xml', 'cdbcmsg' => 'application/vnd.contact.cmsg', 'cdf' => 'application/x-netcdf', 'cdkey' => 'application/vnd.mediastation.cdkey', 'cdmia' => 'application/cdmi-capability', 'cdmic' => 'application/cdmi-container', 'cdmid' => 'application/cdmi-domain', 'cdmio' => 'application/cdmi-object', 'cdmiq' => 'application/cdmi-queue', 'cdx' => 'chemical/x-cdx', 'cdxml' => 'application/vnd.chemdraw+xml', 'cdy' => 'application/vnd.cinderella', 'cer' => 'application/pkix-cert', 'cfc' => 'application/x-coldfusion', 'cfm' => 'application/x-coldfusion', 'cgm' => 'image/cgm', 'chat' => 'application/x-chat', 'chm' => 'application/vnd.ms-htmlhelp', 'chrt' => 'application/vnd.kde.kchart', 'cif' => 'chemical/x-cif', 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', 'cil' => 'application/vnd.ms-artgalry', 'cla' => 'application/vnd.claymore', 'class' => 'application/java-vm', 'clkk' => 'application/vnd.crick.clicker.keyboard', 'clkp' => 'application/vnd.crick.clicker.palette', 'clkt' => 'application/vnd.crick.clicker.template', 'clkw' => 'application/vnd.crick.clicker.wordbank', 'clkx' => 'application/vnd.crick.clicker', 'clp' => 'application/x-msclip', 'cmc' => 'application/vnd.cosmocaller', 'cmdf' => 'chemical/x-cmdf', 'cml' => 'chemical/x-cml', 'cmp' => 'application/vnd.yellowriver-custom-menu', 'cmx' => 'image/x-cmx', 'cod' => 'application/vnd.rim.cod', 'com' => 'application/x-msdownload', 'conf' => 'text/plain', 'cpio' => 'application/x-cpio', 'cpp' => 'text/x-c', 'cpt' => 'application/mac-compactpro', 'crd' => 'application/x-mscardfile', 'crl' => 'application/pkix-crl', 'crt' => 'application/x-x509-ca-cert', 'cryptonote' => 'application/vnd.rig.cryptonote', 'cs' => 'text/plain', 'csh' => 'application/x-csh', 'csml' => 'chemical/x-csml', 'csp' => 'application/vnd.commonspace', 'css' => 'text/css', 'cst' => 'application/x-director', 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'curl' => 'text/vnd.curl', 'cww' => 'application/prs.cww', 'cxt' => 'application/x-director', 'cxx' => 'text/x-c', 'dae' => 'model/vnd.collada+xml', 'daf' => 'application/vnd.mobius.daf', 'dataless' => 'application/vnd.fdsn.seed', 'davmount' => 'application/davmount+xml', 'dcr' => 'application/x-director', 'dcurl' => 'text/vnd.curl.dcurl', 'dd2' => 'application/vnd.oma.dd2+xml', 'ddd' => 'application/vnd.fujixerox.ddd', 'deb' => 'application/x-debian-package', 'def' => 'text/plain', 'deploy' => 'application/octet-stream', 'der' => 'application/x-x509-ca-cert', 'dfac' => 'application/vnd.dreamfactory', 'dic' => 'text/x-c', 'dir' => 'application/x-director', 'dis' => 'application/vnd.mobius.dis', 'dist' => 'application/octet-stream', 'distz' => 'application/octet-stream', 'djv' => 'image/vnd.djvu', 'djvu' => 'image/vnd.djvu', 'dll' => 'application/x-msdownload', 'dmg' => 'application/octet-stream', 'dms' => 'application/octet-stream', 'dna' => 'application/vnd.dna', 'doc' => 'application/msword', 'docm' => 'application/vnd.ms-word.document.macroenabled.12', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dot' => 'application/msword', 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'dp' => 'application/vnd.osgi.dp', 'dpg' => 'application/vnd.dpgraph', 'dra' => 'audio/vnd.dra', 'dsc' => 'text/prs.lines.tag', 'dssc' => 'application/dssc+der', 'dtb' => 'application/x-dtbook+xml', 'dtd' => 'application/xml-dtd', 'dts' => 'audio/vnd.dts', 'dtshd' => 'audio/vnd.dts.hd', 'dump' => 'application/octet-stream', 'dvi' => 'application/x-dvi', 'dwf' => 'model/vnd.dwf', 'dwg' => 'image/vnd.dwg', 'dxf' => 'image/vnd.dxf', 'dxp' => 'application/vnd.spotfire.dxp', 'dxr' => 'application/x-director', 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', 'ecma' => 'application/ecmascript', 'edm' => 'application/vnd.novadigm.edm', 'edx' => 'application/vnd.novadigm.edx', 'efif' => 'application/vnd.picsel', 'ei6' => 'application/vnd.pg.osasli', 'elc' => 'application/octet-stream', 'eml' => 'message/rfc822', 'emma' => 'application/emma+xml', 'eol' => 'audio/vnd.digital-winds', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'es3' => 'application/vnd.eszigno3+xml', 'esf' => 'application/vnd.epson.esf', 'et3' => 'application/vnd.eszigno3+xml', 'etx' => 'text/x-setext', 'exe' => 'application/x-msdownload', 'exi' => 'application/exi', 'ext' => 'application/vnd.novadigm.ext', 'ez' => 'application/andrew-inset', 'ez2' => 'application/vnd.ezpix-album', 'ez3' => 'application/vnd.ezpix-package', 'f' => 'text/x-fortran', 'f4v' => 'video/x-f4v', 'f77' => 'text/x-fortran', 'f90' => 'text/x-fortran', 'fbs' => 'image/vnd.fastbidsheet', 'fcs' => 'application/vnd.isac.fcs', 'fdf' => 'application/vnd.fdf', 'fe_launch' => 'application/vnd.denovo.fcselayout-link', 'fg5' => 'application/vnd.fujitsu.oasysgp', 'fgd' => 'application/x-director', 'fh' => 'image/x-freehand', 'fh4' => 'image/x-freehand', 'fh5' => 'image/x-freehand', 'fh7' => 'image/x-freehand', 'fhc' => 'image/x-freehand', 'fig' => 'application/x-xfig', 'fli' => 'video/x-fli', 'flo' => 'application/vnd.micrografx.flo', 'flv' => 'video/x-flv', 'flw' => 'application/vnd.kde.kivio', 'flx' => 'text/vnd.fmi.flexstor', 'fly' => 'text/vnd.fly', 'fm' => 'application/vnd.framemaker', 'fnc' => 'application/vnd.frogans.fnc', 'for' => 'text/x-fortran', 'fpx' => 'image/vnd.fpx', 'frame' => 'application/vnd.framemaker', 'fsc' => 'application/vnd.fsc.weblaunch', 'fst' => 'image/vnd.fst', 'ftc' => 'application/vnd.fluxtime.clip', 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', 'fvt' => 'video/vnd.fvt', 'fxp' => 'application/vnd.adobe.fxp', 'fxpl' => 'application/vnd.adobe.fxp', 'fzs' => 'application/vnd.fuzzysheet', 'g2w' => 'application/vnd.geoplan', 'g3' => 'image/g3fax', 'g3w' => 'application/vnd.geospace', 'gac' => 'application/vnd.groove-account', 'gdl' => 'model/vnd.gdl', 'geo' => 'application/vnd.dynageo', 'gex' => 'application/vnd.geometry-explorer', 'ggb' => 'application/vnd.geogebra.file', 'ggt' => 'application/vnd.geogebra.tool', 'ghf' => 'application/vnd.groove-help', 'gif' => 'image/gif', 'gim' => 'application/vnd.groove-identity-message', 'gmx' => 'application/vnd.gmx', 'gnumeric' => 'application/x-gnumeric', 'gph' => 'application/vnd.flographit', 'gqf' => 'application/vnd.grafeq', 'gqs' => 'application/vnd.grafeq', 'gram' => 'application/srgs', 'gre' => 'application/vnd.geometry-explorer', 'grv' => 'application/vnd.groove-injector', 'grxml' => 'application/srgs+xml', 'gsf' => 'application/x-font-ghostscript', 'gtar' => 'application/x-gtar', 'gtm' => 'application/vnd.groove-tool-message', 'gtw' => 'model/vnd.gtw', 'gv' => 'text/vnd.graphviz', 'gxt' => 'application/vnd.geonext', 'h' => 'text/x-c', 'h261' => 'video/h261', 'h263' => 'video/h263', 'h264' => 'video/h264', 'hal' => 'application/vnd.hal+xml', 'hbci' => 'application/vnd.hbci', 'hdf' => 'application/x-hdf', 'hh' => 'text/x-c', 'hlp' => 'application/winhlp', 'hpgl' => 'application/vnd.hp-hpgl', 'hpid' => 'application/vnd.hp-hpid', 'hps' => 'application/vnd.hp-hps', 'hqx' => 'application/mac-binhex40', 'hta' => 'application/octet-stream', 'htc' => 'text/html', 'htke' => 'application/vnd.kenameaapp', 'htm' => 'text/html', 'html' => 'text/html', 'hvd' => 'application/vnd.yamaha.hv-dic', 'hvp' => 'application/vnd.yamaha.hv-voice', 'hvs' => 'application/vnd.yamaha.hv-script', 'i2g' => 'application/vnd.intergeo', 'icc' => 'application/vnd.iccprofile', 'ice' => 'x-conference/x-cooltalk', 'icm' => 'application/vnd.iccprofile', 'ico' => 'image/x-icon', 'ics' => 'text/calendar', 'ief' => 'image/ief', 'ifb' => 'text/calendar', 'ifm' => 'application/vnd.shana.informed.formdata', 'iges' => 'model/iges', 'igl' => 'application/vnd.igloader', 'igm' => 'application/vnd.insors.igm', 'igs' => 'model/iges', 'igx' => 'application/vnd.micrografx.igx', 'iif' => 'application/vnd.shana.informed.interchange', 'imp' => 'application/vnd.accpac.simply.imp', 'ims' => 'application/vnd.ms-ims', 'in' => 'text/plain', 'ini' => 'text/plain', 'ipfix' => 'application/ipfix', 'ipk' => 'application/vnd.shana.informed.package', 'irm' => 'application/vnd.ibm.rights-management', 'irp' => 'application/vnd.irepository.package+xml', 'iso' => 'application/octet-stream', 'itp' => 'application/vnd.shana.informed.formtemplate', 'ivp' => 'application/vnd.immervision-ivp', 'ivu' => 'application/vnd.immervision-ivu', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'jam' => 'application/vnd.jam', 'jar' => 'application/java-archive', 'java' => 'text/x-java-source', 'jisp' => 'application/vnd.jisp', 'jlt' => 'application/vnd.hp-jlyt', 'jnlp' => 'application/x-java-jnlp-file', 'joda' => 'application/vnd.joost.joda-archive', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpgm' => 'video/jpm', 'jpgv' => 'video/jpeg', 'jpm' => 'video/jpm', 'js' => 'text/javascript', 'json' => 'application/json', 'kar' => 'audio/midi', 'karbon' => 'application/vnd.kde.karbon', 'kfo' => 'application/vnd.kde.kformula', 'kia' => 'application/vnd.kidspiration', 'kml' => 'application/vnd.google-earth.kml+xml', 'kmz' => 'application/vnd.google-earth.kmz', 'kne' => 'application/vnd.kinar', 'knp' => 'application/vnd.kinar', 'kon' => 'application/vnd.kde.kontour', 'kpr' => 'application/vnd.kde.kpresenter', 'kpt' => 'application/vnd.kde.kpresenter', 'ksp' => 'application/vnd.kde.kspread', 'ktr' => 'application/vnd.kahootz', 'ktx' => 'image/ktx', 'ktz' => 'application/vnd.kahootz', 'kwd' => 'application/vnd.kde.kword', 'kwt' => 'application/vnd.kde.kword', 'lasxml' => 'application/vnd.las.las+xml', 'latex' => 'application/x-latex', 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', 'les' => 'application/vnd.hhe.lesson-player', 'lha' => 'application/octet-stream', 'link66' => 'application/vnd.route66.link66+xml', 'list' => 'text/plain', 'list3820' => 'application/vnd.ibm.modcap', 'listafp' => 'application/vnd.ibm.modcap', 'log' => 'text/plain', 'lostxml' => 'application/lost+xml', 'lrf' => 'application/octet-stream', 'lrm' => 'application/vnd.ms-lrm', 'ltf' => 'application/vnd.frogans.ltf', 'lvp' => 'audio/vnd.lucent.voice', 'lwp' => 'application/vnd.lotus-wordpro', 'lzh' => 'application/octet-stream', 'm13' => 'application/x-msmediaview', 'm14' => 'application/x-msmediaview', 'm1v' => 'video/mpeg', 'm21' => 'application/mp21', 'm2a' => 'audio/mpeg', 'm2v' => 'video/mpeg', 'm3a' => 'audio/mpeg', 'm3u' => 'audio/x-mpegurl', 'm3u8' => 'application/vnd.apple.mpegurl', 'm4a' => 'audio/mp4', 'm4u' => 'video/vnd.mpegurl', 'm4v' => 'video/mp4', 'ma' => 'application/mathematica', 'mads' => 'application/mads+xml', 'mag' => 'application/vnd.ecowin.chart', 'maker' => 'application/vnd.framemaker', 'man' => 'text/troff', 'mathml' => 'application/mathml+xml', 'mb' => 'application/mathematica', 'mbk' => 'application/vnd.mobius.mbk', 'mbox' => 'application/mbox', 'mc1' => 'application/vnd.medcalcdata', 'mcd' => 'application/vnd.mcd', 'mcurl' => 'text/vnd.curl.mcurl', 'mdb' => 'application/x-msaccess', 'mdi' => 'image/vnd.ms-modi', 'me' => 'text/troff', 'mesh' => 'model/mesh', 'meta4' => 'application/metalink4+xml', 'mets' => 'application/mets+xml', 'mfm' => 'application/vnd.mfmp', 'mgp' => 'application/vnd.osgeo.mapguide.package', 'mgz' => 'application/vnd.proteus.magazine', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mif' => 'application/vnd.mif', 'mime' => 'message/rfc822', 'mj2' => 'video/mj2', 'mjp2' => 'video/mj2', 'mlp' => 'application/vnd.dolby.mlp', 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', 'mmf' => 'application/vnd.smaf', 'mmr' => 'image/vnd.fujixerox.edmics-mmr', 'mny' => 'application/x-msmoney', 'mobi' => 'application/x-mobipocket-ebook', 'mods' => 'application/mods+xml', 'mov' => 'video/quicktime', 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', 'mp21' => 'application/mp21', 'mp2a' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4s' => 'application/mp4', 'mp4v' => 'video/mp4', 'mpc' => 'application/vnd.mophun.certificate', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpg4' => 'video/mp4', 'mpga' => 'audio/mpeg', 'mpkg' => 'application/vnd.apple.installer+xml', 'mpm' => 'application/vnd.blueice.multipass', 'mpn' => 'application/vnd.mophun.application', 'mpp' => 'application/vnd.ms-project', 'mpt' => 'application/vnd.ms-project', 'mpy' => 'application/vnd.ibm.minipay', 'mqy' => 'application/vnd.mobius.mqy', 'mrc' => 'application/marc', 'mrcx' => 'application/marcxml+xml', 'ms' => 'text/troff', 'mscml' => 'application/mediaservercontrol+xml', 'mseed' => 'application/vnd.fdsn.mseed', 'mseq' => 'application/vnd.mseq', 'msf' => 'application/vnd.epson.msf', 'msh' => 'model/mesh', 'msi' => 'application/x-msdownload', 'msl' => 'application/vnd.mobius.msl', 'msty' => 'application/vnd.muvee.style', 'mts' => 'model/vnd.mts', 'mus' => 'application/vnd.musician', 'musicxml' => 'application/vnd.recordare.musicxml+xml', 'mvb' => 'application/x-msmediaview', 'mwf' => 'application/vnd.mfer', 'mxf' => 'application/mxf', 'mxl' => 'application/vnd.recordare.musicxml', 'mxml' => 'application/xv+xml', 'mxs' => 'application/vnd.triscape.mxs', 'mxu' => 'video/vnd.mpegurl', 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', 'n3' => 'text/n3', 'nb' => 'application/mathematica', 'nbp' => 'application/vnd.wolfram.player', 'nc' => 'application/x-netcdf', 'ncx' => 'application/x-dtbncx+xml', 'ngdat' => 'application/vnd.nokia.n-gage.data', 'nlu' => 'application/vnd.neurolanguage.nlu', 'nml' => 'application/vnd.enliven', 'nnd' => 'application/vnd.noblenet-directory', 'nns' => 'application/vnd.noblenet-sealer', 'nnw' => 'application/vnd.noblenet-web', 'npx' => 'image/vnd.net-fpx', 'nsf' => 'application/vnd.lotus-notes', 'oa2' => 'application/vnd.fujitsu.oasys2', 'oa3' => 'application/vnd.fujitsu.oasys3', 'oas' => 'application/vnd.fujitsu.oasys', 'obd' => 'application/x-msbinder', 'oda' => 'application/oda', 'odb' => 'application/vnd.oasis.opendocument.database', 'odc' => 'application/vnd.oasis.opendocument.chart', 'odf' => 'application/vnd.oasis.opendocument.formula', 'odft' => 'application/vnd.oasis.opendocument.formula-template', 'odg' => 'application/vnd.oasis.opendocument.graphics', 'odi' => 'application/vnd.oasis.opendocument.image', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'odt' => 'application/vnd.oasis.opendocument.text', 'oga' => 'audio/ogg', 'ogg' => 'audio/ogg', 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'onepkg' => 'application/onenote', 'onetmp' => 'application/onenote', 'onetoc' => 'application/onenote', 'onetoc2' => 'application/onenote', 'opf' => 'application/oebps-package+xml', 'oprc' => 'application/vnd.palm', 'org' => 'application/vnd.lotus-organizer', 'osf' => 'application/vnd.yamaha.openscoreformat', 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'otc' => 'application/vnd.oasis.opendocument.chart-template', 'otf' => 'application/x-font-otf', 'otg' => 'application/vnd.oasis.opendocument.graphics-template', 'oth' => 'application/vnd.oasis.opendocument.text-web', 'oti' => 'application/vnd.oasis.opendocument.image-template', 'otp' => 'application/vnd.oasis.opendocument.presentation-template', 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', 'ott' => 'application/vnd.oasis.opendocument.text-template', 'oxt' => 'application/vnd.openofficeorg.extension', 'p' => 'text/x-pascal', 'p10' => 'application/pkcs10', 'p12' => 'application/x-pkcs12', 'p7b' => 'application/x-pkcs7-certificates', 'p7c' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7r' => 'application/x-pkcs7-certreqresp', 'p7s' => 'application/pkcs7-signature', 'p8' => 'application/pkcs8', 'pas' => 'text/x-pascal', 'paw' => 'application/vnd.pawaafile', 'pbd' => 'application/vnd.powerbuilder6', 'pbm' => 'image/x-portable-bitmap', 'pcf' => 'application/x-font-pcf', 'pcl' => 'application/vnd.hp-pcl', 'pclxl' => 'application/vnd.hp-pclxl', 'pct' => 'image/x-pict', 'pcurl' => 'application/vnd.curl.pcurl', 'pcx' => 'image/x-pcx', 'pdb' => 'application/vnd.palm', 'pdf' => 'application/pdf', 'pfa' => 'application/x-font-type1', 'pfb' => 'application/x-font-type1', 'pfm' => 'application/x-font-type1', 'pfr' => 'application/font-tdpfr', 'pfx' => 'application/x-pkcs12', 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', 'pgp' => 'application/pgp-encrypted', 'php' => 'text/x-php', 'phps' => 'application/x-httpd-phps', 'pic' => 'image/x-pict', 'pkg' => 'application/octet-stream', 'pki' => 'application/pkixcmp', 'pkipath' => 'application/pkix-pkipath', 'plb' => 'application/vnd.3gpp.pic-bw-large', 'plc' => 'application/vnd.mobius.plc', 'plf' => 'application/vnd.pocketlearn', 'pls' => 'application/pls+xml', 'pml' => 'application/vnd.ctc-posml', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'portpkg' => 'application/vnd.macports.portpkg', 'pot' => 'application/vnd.ms-powerpoint', 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', 'ppd' => 'application/vnd.cups-ppd', 'ppm' => 'image/x-portable-pixmap', 'pps' => 'application/vnd.ms-powerpoint', 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'ppt' => 'application/vnd.ms-powerpoint', 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pqa' => 'application/vnd.palm', 'prc' => 'application/x-mobipocket-ebook', 'pre' => 'application/vnd.lotus-freelance', 'prf' => 'application/pics-rules', 'ps' => 'application/postscript', 'psb' => 'application/vnd.3gpp.pic-bw-small', 'psd' => 'image/vnd.adobe.photoshop', 'psf' => 'application/x-font-linux-psf', 'pskcxml' => 'application/pskc+xml', 'ptid' => 'application/vnd.pvi.ptid1', 'pub' => 'application/x-mspublisher', 'pvb' => 'application/vnd.3gpp.pic-bw-var', 'pwn' => 'application/vnd.3m.post-it-notes', 'pya' => 'audio/vnd.ms-playready.media.pya', 'pyv' => 'video/vnd.ms-playready.media.pyv', 'qam' => 'application/vnd.epson.quickanime', 'qbo' => 'application/vnd.intu.qbo', 'qfx' => 'application/vnd.intu.qfx', 'qps' => 'application/vnd.publishare-delta-tree', 'qt' => 'video/quicktime', 'qwd' => 'application/vnd.quark.quarkxpress', 'qwt' => 'application/vnd.quark.quarkxpress', 'qxb' => 'application/vnd.quark.quarkxpress', 'qxd' => 'application/vnd.quark.quarkxpress', 'qxl' => 'application/vnd.quark.quarkxpress', 'qxt' => 'application/vnd.quark.quarkxpress', 'ra' => 'audio/x-pn-realaudio', 'ram' => 'audio/x-pn-realaudio', 'rar' => 'application/x-rar-compressed', 'ras' => 'image/x-cmu-raster', 'rb' => 'text/plain', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', 'rdf' => 'application/rdf+xml', 'rdz' => 'application/vnd.data-vision.rdz', 'rep' => 'application/vnd.businessobjects', 'res' => 'application/x-dtbresource+xml', 'resx' => 'text/xml', 'rgb' => 'image/x-rgb', 'rif' => 'application/reginfo+xml', 'rip' => 'audio/vnd.rip', 'rl' => 'application/resource-lists+xml', 'rlc' => 'image/vnd.fujixerox.edmics-rlc', 'rld' => 'application/resource-lists-diff+xml', 'rm' => 'application/vnd.rn-realmedia', 'rmi' => 'audio/midi', 'rmp' => 'audio/x-pn-realaudio-plugin', 'rms' => 'application/vnd.jcp.javame.midlet-rms', 'rnc' => 'application/relax-ng-compact-syntax', 'roff' => 'text/troff', 'rp9' => 'application/vnd.cloanto.rp9', 'rpss' => 'application/vnd.nokia.radio-presets', 'rpst' => 'application/vnd.nokia.radio-preset', 'rq' => 'application/sparql-query', 'rs' => 'application/rls-services+xml', 'rsd' => 'application/rsd+xml', 'rss' => 'application/rss+xml', 'rtf' => 'application/rtf', 'rtx' => 'text/richtext', 's' => 'text/x-asm', 'saf' => 'application/vnd.yamaha.smaf-audio', 'sbml' => 'application/sbml+xml', 'sc' => 'application/vnd.ibm.secure-container', 'scd' => 'application/x-msschedule', 'scm' => 'application/vnd.lotus-screencam', 'scq' => 'application/scvp-cv-request', 'scs' => 'application/scvp-cv-response', 'scurl' => 'text/vnd.curl.scurl', 'sda' => 'application/vnd.stardivision.draw', 'sdc' => 'application/vnd.stardivision.calc', 'sdd' => 'application/vnd.stardivision.impress', 'sdkd' => 'application/vnd.solent.sdkm+xml', 'sdkm' => 'application/vnd.solent.sdkm+xml', 'sdp' => 'application/sdp', 'sdw' => 'application/vnd.stardivision.writer', 'see' => 'application/vnd.seemail', 'seed' => 'application/vnd.fdsn.seed', 'sema' => 'application/vnd.sema', 'semd' => 'application/vnd.semd', 'semf' => 'application/vnd.semf', 'ser' => 'application/java-serialized-object', 'setpay' => 'application/set-payment-initiation', 'setreg' => 'application/set-registration-initiation', 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', 'sfs' => 'application/vnd.spotfire.sfs', 'sgl' => 'application/vnd.stardivision.writer-global', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'sh' => 'application/x-sh', 'shar' => 'application/x-shar', 'shf' => 'application/shf+xml', 'sig' => 'application/pgp-signature', 'silo' => 'model/mesh', 'sis' => 'application/vnd.symbian.install', 'sisx' => 'application/vnd.symbian.install', 'sit' => 'application/x-stuffit', 'sitx' => 'application/x-stuffitx', 'skd' => 'application/vnd.koan', 'skm' => 'application/vnd.koan', 'skp' => 'application/vnd.koan', 'skt' => 'application/vnd.koan', 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 'slt' => 'application/vnd.epson.salt', 'sm' => 'application/vnd.stepmania.stepchart', 'smf' => 'application/vnd.stardivision.math', 'smi' => 'application/smil+xml', 'smil' => 'application/smil+xml', 'snd' => 'audio/basic', 'snf' => 'application/x-font-snf', 'so' => 'application/octet-stream', 'spc' => 'application/x-pkcs7-certificates', 'spf' => 'application/vnd.yamaha.smaf-phrase', 'spl' => 'application/x-futuresplash', 'spot' => 'text/vnd.in3d.spot', 'spp' => 'application/scvp-vp-response', 'spq' => 'application/scvp-vp-request', 'spx' => 'audio/ogg', 'src' => 'application/x-wais-source', 'sru' => 'application/sru+xml', 'srx' => 'application/sparql-results+xml', 'sse' => 'application/vnd.kodak-descriptor', 'ssf' => 'application/vnd.epson.ssf', 'ssml' => 'application/ssml+xml', 'st' => 'application/vnd.sailingtracker.track', 'stc' => 'application/vnd.sun.xml.calc.template', 'std' => 'application/vnd.sun.xml.draw.template', 'stf' => 'application/vnd.wt.stf', 'sti' => 'application/vnd.sun.xml.impress.template', 'stk' => 'application/hyperstudio', 'stl' => 'application/vnd.ms-pki.stl', 'str' => 'application/vnd.pg.format', 'stw' => 'application/vnd.sun.xml.writer.template', 'sub' => 'image/vnd.dvb.subtitle', 'sus' => 'application/vnd.sus-calendar', 'susp' => 'application/vnd.sus-calendar', 'sv4cpio' => 'application/x-sv4cpio', 'sv4crc' => 'application/x-sv4crc', 'svc' => 'application/vnd.dvb.service', 'svd' => 'application/vnd.svd', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'swa' => 'application/x-director', 'swf' => 'application/x-shockwave-flash', 'swi' => 'application/vnd.aristanetworks.swi', 'sxc' => 'application/vnd.sun.xml.calc', 'sxd' => 'application/vnd.sun.xml.draw', 'sxg' => 'application/vnd.sun.xml.writer.global', 'sxi' => 'application/vnd.sun.xml.impress', 'sxm' => 'application/vnd.sun.xml.math', 'sxw' => 'application/vnd.sun.xml.writer', 't' => 'text/troff', 'tao' => 'application/vnd.tao.intent-module-archive', 'tar' => 'application/x-tar', 'tcap' => 'application/vnd.3gpp2.tcap', 'tcl' => 'application/x-tcl', 'teacher' => 'application/vnd.smart.teacher', 'tei' => 'application/tei+xml', 'teicorpus' => 'application/tei+xml', 'tex' => 'application/x-tex', 'texi' => 'application/x-texinfo', 'texinfo' => 'application/x-texinfo', 'text' => 'text/plain', 'tfi' => 'application/thraud+xml', 'tfm' => 'application/x-tex-tfm', 'thmx' => 'application/vnd.ms-officetheme', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'tmo' => 'application/vnd.tmobile-livetv', 'torrent' => 'application/x-bittorrent', 'tpl' => 'application/vnd.groove-tool-template', 'tpt' => 'application/vnd.trid.tpt', 'tr' => 'text/troff', 'tra' => 'application/vnd.trueapp', 'trm' => 'application/x-msterminal', 'tsd' => 'application/timestamped-data', 'tsv' => 'text/tab-separated-values', 'ttc' => 'application/x-font-ttf', 'ttf' => 'application/x-font-ttf', 'ttl' => 'text/turtle', 'twd' => 'application/vnd.simtech-mindmapper', 'twds' => 'application/vnd.simtech-mindmapper', 'txd' => 'application/vnd.genomatix.tuxedo', 'txf' => 'application/vnd.mobius.txf', 'txt' => 'text/plain', 'u32' => 'application/x-authorware-bin', 'udeb' => 'application/x-debian-package', 'ufd' => 'application/vnd.ufdl', 'ufdl' => 'application/vnd.ufdl', 'umj' => 'application/vnd.umajin', 'unityweb' => 'application/vnd.unity', 'uoml' => 'application/vnd.uoml+xml', 'uri' => 'text/uri-list', 'uris' => 'text/uri-list', 'urls' => 'text/uri-list', 'ustar' => 'application/x-ustar', 'utz' => 'application/vnd.uiq.theme', 'uu' => 'text/x-uuencode', 'uva' => 'audio/vnd.dece.audio', 'uvd' => 'application/vnd.dece.data', 'uvf' => 'application/vnd.dece.data', 'uvg' => 'image/vnd.dece.graphic', 'uvh' => 'video/vnd.dece.hd', 'uvi' => 'image/vnd.dece.graphic', 'uvm' => 'video/vnd.dece.mobile', 'uvp' => 'video/vnd.dece.pd', 'uvs' => 'video/vnd.dece.sd', 'uvt' => 'application/vnd.dece.ttml+xml', 'uvu' => 'video/vnd.uvvu.mp4', 'uvv' => 'video/vnd.dece.video', 'uvva' => 'audio/vnd.dece.audio', 'uvvd' => 'application/vnd.dece.data', 'uvvf' => 'application/vnd.dece.data', 'uvvg' => 'image/vnd.dece.graphic', 'uvvh' => 'video/vnd.dece.hd', 'uvvi' => 'image/vnd.dece.graphic', 'uvvm' => 'video/vnd.dece.mobile', 'uvvp' => 'video/vnd.dece.pd', 'uvvs' => 'video/vnd.dece.sd', 'uvvt' => 'application/vnd.dece.ttml+xml', 'uvvu' => 'video/vnd.uvvu.mp4', 'uvvv' => 'video/vnd.dece.video', 'uvvx' => 'application/vnd.dece.unspecified', 'uvx' => 'application/vnd.dece.unspecified', 'vcd' => 'application/x-cdlink', 'vcf' => 'text/x-vcard', 'vcg' => 'application/vnd.groove-vcard', 'vcs' => 'text/x-vcalendar', 'vcx' => 'application/vnd.vcx', 'vis' => 'application/vnd.visionary', 'viv' => 'video/vnd.vivo', 'vor' => 'application/vnd.stardivision.writer', 'vox' => 'application/x-authorware-bin', 'vrml' => 'model/vrml', 'vsd' => 'application/vnd.visio', 'vsf' => 'application/vnd.vsf', 'vss' => 'application/vnd.visio', 'vst' => 'application/vnd.visio', 'vsw' => 'application/vnd.visio', 'vtu' => 'model/vnd.vtu', 'vxml' => 'application/voicexml+xml', 'w3d' => 'application/x-director', 'wad' => 'application/x-doom', 'wav' => 'audio/x-wav', 'wax' => 'audio/x-ms-wax', 'wbmp' => 'image/vnd.wap.wbmp', 'wbs' => 'application/vnd.criticaltools.wbs+xml', 'wbxml' => 'application/vnd.wap.wbxml', 'wcm' => 'application/vnd.ms-works', 'wdb' => 'application/vnd.ms-works', 'weba' => 'audio/webm', 'webm' => 'video/webm', 'webp' => 'image/webp', 'wg' => 'application/vnd.pmi.widget', 'wgt' => 'application/widget', 'wks' => 'application/vnd.ms-works', 'wm' => 'video/x-ms-wm', 'wma' => 'audio/x-ms-wma', 'wmd' => 'application/x-ms-wmd', 'wmf' => 'application/x-msmetafile', 'wml' => 'text/vnd.wap.wml', 'wmlc' => 'application/vnd.wap.wmlc', 'wmls' => 'text/vnd.wap.wmlscript', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wmv' => 'video/x-ms-wmv', 'wmx' => 'video/x-ms-wmx', 'wmz' => 'application/x-ms-wmz', 'woff' => 'application/x-font-woff', 'wpd' => 'application/vnd.wordperfect', 'wpl' => 'application/vnd.ms-wpl', 'wps' => 'application/vnd.ms-works', 'wqd' => 'application/vnd.wqd', 'wri' => 'application/x-mswrite', 'wrl' => 'model/vrml', 'wsdl' => 'application/wsdl+xml', 'wspolicy' => 'application/wspolicy+xml', 'wtb' => 'application/vnd.webturbo', 'wvx' => 'video/x-ms-wvx', 'x32' => 'application/x-authorware-bin', 'x3d' => 'application/vnd.hzn-3d-crossword', 'xap' => 'application/x-silverlight-app', 'xar' => 'application/vnd.xara', 'xbap' => 'application/x-ms-xbap', 'xbd' => 'application/vnd.fujixerox.docuworks.binder', 'xbm' => 'image/x-xbitmap', 'xdf' => 'application/xcap-diff+xml', 'xdm' => 'application/vnd.syncml.dm+xml', 'xdp' => 'application/vnd.adobe.xdp+xml', 'xdssc' => 'application/dssc+xml', 'xdw' => 'application/vnd.fujixerox.docuworks', 'xenc' => 'application/xenc+xml', 'xer' => 'application/patch-ops-error+xml', 'xfdf' => 'application/vnd.adobe.xfdf', 'xfdl' => 'application/vnd.xfdl', 'xht' => 'application/xhtml+xml', 'xhtml' => 'application/xhtml+xml', 'xhvml' => 'application/xv+xml', 'xif' => 'image/vnd.xiff', 'xla' => 'application/vnd.ms-excel', 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', 'xlc' => 'application/vnd.ms-excel', 'xlm' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlt' => 'application/vnd.ms-excel', 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlw' => 'application/vnd.ms-excel', 'xml' => 'application/xml', 'xo' => 'application/vnd.olpc-sugar', 'xop' => 'application/xop+xml', 'xpi' => 'application/x-xpinstall', 'xpm' => 'image/x-xpixmap', 'xpr' => 'application/vnd.is-xpr', 'xps' => 'application/vnd.ms-xpsdocument', 'xpw' => 'application/vnd.intercon.formnet', 'xpx' => 'application/vnd.intercon.formnet', 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml', 'xsm' => 'application/vnd.syncml+xml', 'xspf' => 'application/xspf+xml', 'xul' => 'application/vnd.mozilla.xul+xml', 'xvm' => 'application/xv+xml', 'xvml' => 'application/xv+xml', 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz', 'yaml' => 'text/yaml', 'yang' => 'application/yang', 'yin' => 'application/yin+xml', 'yml' => 'text/yaml', 'zaz' => 'application/vnd.zzazz.deck+xml', 'zip' => 'application/zip', 'zir' => 'application/vnd.zul', 'zirz' => 'application/vnd.zul', 'zmm' => 'application/vnd.handheld-entertainment+xml' ); /** * Get a singleton instance of the class * * @return self * @codeCoverageIgnore */ public static function getInstance() { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Get a mimetype value from a file extension * * @param string $extension File extension * * @return string|null * */ public function fromExtension($extension) { $extension = strtolower($extension); return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null; } /** * Get a mimetype from a filename * * @param string $filename Filename to generate a mimetype from * * @return string|null */ public function fromFilename($filename) { return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); } } src/Pool.php000064400000022137151742731020006764 0ustar00client = $client; $this->iter = $this->coerceIterable($requests); $this->deferred = new Deferred(); $this->promise = $this->deferred->promise(); $this->poolSize = isset($options['pool_size']) ? $options['pool_size'] : 25; $this->eventListeners = $this->prepareListeners( $options, ['before', 'complete', 'error', 'end'] ); } /** * Sends multiple requests in parallel and returns an array of responses * and exceptions that uses the same ordering as the provided requests. * * IMPORTANT: This method keeps every request and response in memory, and * as such, is NOT recommended when sending a large number or an * indeterminate number of requests concurrently. * * @param ClientInterface $client Client used to send the requests * @param array|\Iterator $requests Requests to send in parallel * @param array $options Passes through the options available in * {@see GuzzleHttp\Pool::__construct} * * @return BatchResults Returns a container for the results. * @throws \InvalidArgumentException if the event format is incorrect. */ public static function batch( ClientInterface $client, $requests, array $options = [] ) { $hash = new \SplObjectStorage(); foreach ($requests as $request) { $hash->attach($request); } // In addition to the normally run events when requests complete, add // and event to continuously track the results of transfers in the hash. (new self($client, $requests, RequestEvents::convertEventArray( $options, ['end'], [ 'priority' => RequestEvents::LATE, 'fn' => function (EndEvent $e) use ($hash) { $hash[$e->getRequest()] = $e->getException() ? $e->getException() : $e->getResponse(); } ] )))->wait(); return new BatchResults($hash); } /** * Creates a Pool and immediately sends the requests. * * @param ClientInterface $client Client used to send the requests * @param array|\Iterator $requests Requests to send in parallel * @param array $options Passes through the options available in * {@see GuzzleHttp\Pool::__construct} */ public static function send( ClientInterface $client, $requests, array $options = [] ) { (new self($client, $requests, $options))->wait(); } public function wait() { if ($this->isRealized) { return false; } // Seed the pool with N number of requests. for ($i = 0; $i < $this->poolSize; $i++) { if (!$this->addNextRequest()) { break; } } // Stop if the pool was cancelled while transferring requests. if ($this->isRealized) { return false; } // Wait on any outstanding FutureResponse objects. while ($response = array_pop($this->waitQueue)) { try { $response->wait(); } catch (\Exception $e) { // Eat exceptions because they should be handled asynchronously } } // Clean up no longer needed state. $this->isRealized = true; $this->waitQueue = $this->eventListeners = []; $this->client = $this->iter = null; $this->deferred->resolve(true); return true; } /** * {@inheritdoc} * * Attempt to cancel all outstanding requests (requests that are queued for * dereferencing). Returns true if all outstanding requests can be * cancelled. * * @return bool */ public function cancel() { if ($this->isRealized) { return false; } $success = $this->isRealized = true; foreach ($this->waitQueue as $response) { if (!$response->cancel()) { $success = false; } } return $success; } /** * Returns a promise that is invoked when the pool completed. There will be * no passed value. * * {@inheritdoc} */ public function then( callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null ) { return $this->promise->then($onFulfilled, $onRejected, $onProgress); } public function promise() { return $this->promise; } private function coerceIterable($requests) { if ($requests instanceof \Iterator) { return $requests; } elseif (is_array($requests)) { return new \ArrayIterator($requests); } throw new \InvalidArgumentException('Expected Iterator or array. ' . 'Found ' . Core::describeType($requests)); } /** * Adds the next request to pool and tracks what requests need to be * dereferenced when completing the pool. */ private function addNextRequest() { if ($this->isRealized || !$this->iter || !$this->iter->valid()) { return false; } $request = $this->iter->current(); $this->iter->next(); if (!($request instanceof RequestInterface)) { throw new \InvalidArgumentException(sprintf( 'All requests in the provided iterator must implement ' . 'RequestInterface. Found %s', Core::describeType($request) )); } // Be sure to use "lazy" futures, meaning they do not send right away. $request->getConfig()->set('future', 'lazy'); $this->attachListeners($request, $this->eventListeners); $response = $this->client->send($request); $hash = spl_object_hash($request); $this->waitQueue[$hash] = $response; // Use this function for both resolution and rejection. $fn = function ($value) use ($request, $hash) { unset($this->waitQueue[$hash]); $result = $value instanceof ResponseInterface ? ['request' => $request, 'response' => $value, 'error' => null] : ['request' => $request, 'response' => null, 'error' => $value]; $this->deferred->progress($result); $this->addNextRequest(); }; $response->then($fn, $fn); return true; } } src/Post/MultipartBody.php000064400000005646151742731020011605 0ustar00boundary = $boundary ?: uniqid(); $this->stream = $this->createStream($fields, $files); } /** * Get the boundary * * @return string */ public function getBoundary() { return $this->boundary; } public function isWritable() { return false; } /** * Get the string needed to transfer a POST field */ private function getFieldString($name, $value) { return sprintf( "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", $this->boundary, $name, $value ); } /** * Get the headers needed before transferring the content of a POST file */ private function getFileHeaders(PostFileInterface $file) { $headers = ''; foreach ($file->getHeaders() as $key => $value) { $headers .= "{$key}: {$value}\r\n"; } return "--{$this->boundary}\r\n" . trim($headers) . "\r\n\r\n"; } /** * Create the aggregate stream that will be used to upload the POST data */ protected function createStream(array $fields, array $files) { $stream = new AppendStream(); foreach ($fields as $name => $fieldValues) { foreach ((array) $fieldValues as $value) { $stream->addStream( Stream::factory($this->getFieldString($name, $value)) ); } } foreach ($files as $file) { if (!$file instanceof PostFileInterface) { throw new \InvalidArgumentException('All POST fields must ' . 'implement PostFieldInterface'); } $stream->addStream( Stream::factory($this->getFileHeaders($file)) ); $stream->addStream($file->getContent()); $stream->addStream(Stream::factory("\r\n")); } // Add the trailing boundary with CRLF $stream->addStream(Stream::factory("--{$this->boundary}--\r\n")); return $stream; } } src/Post/PostBody.php000064400000014135151742731020010542 0ustar00files || $this->forceMultipart) { $request->setHeader( 'Content-Type', 'multipart/form-data; boundary=' . $this->getBody()->getBoundary() ); } elseif ($this->fields && !$request->hasHeader('Content-Type')) { $request->setHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); } if ($size = $this->getSize()) { $request->setHeader('Content-Length', $size); } } public function forceMultipartUpload($force) { $this->forceMultipart = $force; } public function setAggregator(callable $aggregator) { $this->aggregator = $aggregator; } public function setField($name, $value) { $this->fields[$name] = $value; $this->mutate(); } public function replaceFields(array $fields) { $this->fields = $fields; $this->mutate(); } public function getField($name) { return isset($this->fields[$name]) ? $this->fields[$name] : null; } public function removeField($name) { unset($this->fields[$name]); $this->mutate(); } public function getFields($asString = false) { if (!$asString) { return $this->fields; } $query = new Query($this->fields); $query->setEncodingType(Query::RFC1738); $query->setAggregator($this->getAggregator()); return (string) $query; } public function hasField($name) { return isset($this->fields[$name]); } public function getFile($name) { foreach ($this->files as $file) { if ($file->getName() == $name) { return $file; } } return null; } public function getFiles() { return $this->files; } public function addFile(PostFileInterface $file) { $this->files[] = $file; $this->mutate(); } public function clearFiles() { $this->files = []; $this->mutate(); } /** * Returns the numbers of fields + files * * @return int */ public function count() { return count($this->files) + count($this->fields); } public function __toString() { return (string) $this->getBody(); } public function getContents($maxLength = -1) { return $this->getBody()->getContents(); } public function close() { $this->detach(); } public function detach() { $this->detached = true; $this->fields = $this->files = []; if ($this->body) { $this->body->close(); $this->body = null; } } public function attach($stream) { throw new CannotAttachException(); } public function eof() { return $this->getBody()->eof(); } public function tell() { return $this->body ? $this->body->tell() : 0; } public function isSeekable() { return true; } public function isReadable() { return true; } public function isWritable() { return false; } public function getSize() { return $this->getBody()->getSize(); } public function seek($offset, $whence = SEEK_SET) { return $this->getBody()->seek($offset, $whence); } public function read($length) { return $this->getBody()->read($length); } public function write($string) { return false; } public function getMetadata($key = null) { return $key ? null : []; } /** * Return a stream object that is built from the POST fields and files. * * If one has already been created, the previously created stream will be * returned. */ private function getBody() { if ($this->body) { return $this->body; } elseif ($this->files || $this->forceMultipart) { return $this->body = $this->createMultipart(); } elseif ($this->fields) { return $this->body = $this->createUrlEncoded(); } else { return $this->body = Stream::factory(); } } /** * Get the aggregator used to join multi-valued field parameters * * @return callable */ final protected function getAggregator() { if (!$this->aggregator) { $this->aggregator = Query::phpAggregator(); } return $this->aggregator; } /** * Creates a multipart/form-data body stream * * @return MultipartBody */ private function createMultipart() { // Flatten the nested query string values using the correct aggregator return new MultipartBody( call_user_func($this->getAggregator(), $this->fields), $this->files ); } /** * Creates an application/x-www-form-urlencoded stream body * * @return StreamInterface */ private function createUrlEncoded() { return Stream::factory($this->getFields(true)); } /** * Get rid of any cached data */ private function mutate() { $this->body = null; } } src/Post/PostBodyInterface.php000064400000005636151742731020012371 0ustar00headers = $headers; $this->name = $name; $this->prepareContent($content); $this->prepareFilename($filename); $this->prepareDefaultHeaders(); } public function getName() { return $this->name; } public function getFilename() { return $this->filename; } public function getContent() { return $this->content; } public function getHeaders() { return $this->headers; } /** * Prepares the contents of a POST file. * * @param mixed $content Content of the POST file */ private function prepareContent($content) { $this->content = $content; if (!($this->content instanceof StreamInterface)) { $this->content = Stream::factory($this->content); } elseif ($this->content instanceof MultipartBody) { if (!$this->hasHeader('Content-Disposition')) { $disposition = 'form-data; name="' . $this->name .'"'; $this->headers['Content-Disposition'] = $disposition; } if (!$this->hasHeader('Content-Type')) { $this->headers['Content-Type'] = sprintf( "multipart/form-data; boundary=%s", $this->content->getBoundary() ); } } } /** * Applies a file name to the POST file based on various checks. * * @param string|null $filename Filename to apply (or null to guess) */ private function prepareFilename($filename) { $this->filename = $filename; if (!$this->filename) { $this->filename = $this->content->getMetadata('uri'); } if (!$this->filename || substr($this->filename, 0, 6) === 'php://') { $this->filename = $this->name; } } /** * Applies default Content-Disposition and Content-Type headers if needed. */ private function prepareDefaultHeaders() { // Set a default content-disposition header if one was no provided if (!$this->hasHeader('Content-Disposition')) { $this->headers['Content-Disposition'] = sprintf( 'form-data; name="%s"; filename="%s"', $this->name, basename($this->filename) ); } // Set a default Content-Type if one was not supplied if (!$this->hasHeader('Content-Type')) { $this->headers['Content-Type'] = Mimetypes::getInstance() ->fromFilename($this->filename) ?: 'text/plain'; } } /** * Check if a specific header exists on the POST file by name. * * @param string $name Case-insensitive header to check * * @return bool */ private function hasHeader($name) { return isset(array_change_key_case($this->headers)[strtolower($name)]); } } src/Post/PostFileInterface.php000064400000001404151742731020012340 0ustar00setEncodingType($urlEncoding); } $qp->parseInto($q, $query, $urlEncoding); return $q; } /** * Convert the query string parameters to a query string string * * @return string */ public function __toString() { if (!$this->data) { return ''; } // The default aggregator is statically cached static $defaultAggregator; if (!$this->aggregator) { if (!$defaultAggregator) { $defaultAggregator = self::phpAggregator(); } $this->aggregator = $defaultAggregator; } $result = ''; $aggregator = $this->aggregator; $encoder = $this->encoding; foreach ($aggregator($this->data) as $key => $values) { foreach ($values as $value) { if ($result) { $result .= '&'; } $result .= $encoder($key); if ($value !== null) { $result .= '=' . $encoder($value); } } } return $result; } /** * Controls how multi-valued query string parameters are aggregated into a * string. * * $query->setAggregator($query::duplicateAggregator()); * * @param callable $aggregator Callable used to convert a deeply nested * array of query string variables into a flattened array of key value * pairs. The callable accepts an array of query data and returns a * flattened array of key value pairs where each value is an array of * strings. */ public function setAggregator(callable $aggregator) { $this->aggregator = $aggregator; } /** * Specify how values are URL encoded * * @param string|bool $type One of 'RFC1738', 'RFC3986', or false to disable encoding * * @throws \InvalidArgumentException */ public function setEncodingType($type) { switch ($type) { case self::RFC3986: $this->encoding = 'rawurlencode'; break; case self::RFC1738: $this->encoding = 'urlencode'; break; case false: $this->encoding = function ($v) { return $v; }; break; default: throw new \InvalidArgumentException('Invalid URL encoding type'); } } /** * Query string aggregator that does not aggregate nested query string * values and allows duplicates in the resulting array. * * Example: http://test.com?q=1&q=2 * * @return callable */ public static function duplicateAggregator() { return function (array $data) { return self::walkQuery($data, '', function ($key, $prefix) { return is_int($key) ? $prefix : "{$prefix}[{$key}]"; }); }; } /** * Aggregates nested query string variables using the same technique as * ``http_build_query()``. * * @param bool $numericIndices Pass false to not include numeric indices * when multi-values query string parameters are present. * * @return callable */ public static function phpAggregator($numericIndices = true) { return function (array $data) use ($numericIndices) { return self::walkQuery( $data, '', function ($key, $prefix) use ($numericIndices) { return !$numericIndices && is_int($key) ? "{$prefix}[]" : "{$prefix}[{$key}]"; } ); }; } /** * Easily create query aggregation functions by providing a key prefix * function to this query string array walker. * * @param array $query Query string to walk * @param string $keyPrefix Key prefix (start with '') * @param callable $prefixer Function used to create a key prefix * * @return array */ public static function walkQuery(array $query, $keyPrefix, callable $prefixer) { $result = []; foreach ($query as $key => $value) { if ($keyPrefix) { $key = $prefixer($key, $keyPrefix); } if (is_array($value)) { $result += self::walkQuery($value, $key, $prefixer); } elseif (isset($result[$key])) { $result[$key][] = $value; } else { $result[$key] = array($value); } } return $result; } } src/QueryParser.php000064400000011406151742731020010332 0ustar00duplicates = false; $this->numericIndices = true; $decoder = self::getDecoder($urlEncoding); foreach (explode('&', $str) as $kvp) { $parts = explode('=', $kvp, 2); $key = $decoder($parts[0]); $value = isset($parts[1]) ? $decoder($parts[1]) : null; // Special handling needs to be taken for PHP nested array syntax if (strpos($key, '[') !== false) { $this->parsePhpValue($key, $value, $result); continue; } if (!isset($result[$key])) { $result[$key] = $value; } else { $this->duplicates = true; if (!is_array($result[$key])) { $result[$key] = [$result[$key]]; } $result[$key][] = $value; } } $query->replace($result); if (!$this->numericIndices) { $query->setAggregator(Query::phpAggregator(false)); } elseif ($this->duplicates) { $query->setAggregator(Query::duplicateAggregator()); } } /** * Returns a callable that is used to URL decode query keys and values. * * @param string|bool $type One of true, false, RFC3986, and RFC1738 * * @return callable|string */ private static function getDecoder($type) { if ($type === true) { return function ($value) { return rawurldecode(str_replace('+', ' ', $value)); }; } elseif ($type == Query::RFC3986) { return 'rawurldecode'; } elseif ($type == Query::RFC1738) { return 'urldecode'; } else { return function ($str) { return $str; }; } } /** * Parses a PHP style key value pair. * * @param string $key Key to parse (e.g., "foo[a][b]") * @param string|null $value Value to set * @param array $result Result to modify by reference */ private function parsePhpValue($key, $value, array &$result) { $node =& $result; $keyBuffer = ''; for ($i = 0, $t = strlen($key); $i < $t; $i++) { switch ($key[$i]) { case '[': if ($keyBuffer) { $this->prepareNode($node, $keyBuffer); $node =& $node[$keyBuffer]; $keyBuffer = ''; } break; case ']': $k = $this->cleanKey($node, $keyBuffer); $this->prepareNode($node, $k); $node =& $node[$k]; $keyBuffer = ''; break; default: $keyBuffer .= $key[$i]; break; } } if (isset($node)) { $this->duplicates = true; $node[] = $value; } else { $node = $value; } } /** * Prepares a value in the array at the given key. * * If the key already exists, the key value is converted into an array. * * @param array $node Result node to modify * @param string $key Key to add or modify in the node */ private function prepareNode(&$node, $key) { if (!isset($node[$key])) { $node[$key] = null; } elseif (!is_array($node[$key])) { $node[$key] = [$node[$key]]; } } /** * Returns the appropriate key based on the node and key. */ private function cleanKey($node, $key) { if ($key === '') { $key = $node ? (string) count($node) : 0; // Found a [] key, so track this to ensure that we disable numeric // indexing of keys in the resolved query aggregator. $this->numericIndices = false; } return $key; } } src/RequestFsm.php000064400000016512151742731020010151 0ustar00 [ 'success' => 'send', 'intercept' => 'complete', 'error' => 'error' ], // The complete and error events are handled using the "then" of // the RingPHP request, so we exit the FSM. 'send' => ['error' => 'error'], 'complete' => [ 'success' => 'end', 'intercept' => 'before', 'error' => 'error' ], 'error' => [ 'success' => 'complete', 'intercept' => 'before', 'error' => 'end' ], 'end' => [] ]; public function __construct( callable $handler, MessageFactoryInterface $messageFactory, $maxTransitions = 200 ) { $this->mf = $messageFactory; $this->maxTransitions = $maxTransitions; $this->handler = $handler; } /** * Runs the state machine until a terminal state is entered or the * optionally supplied $finalState is entered. * * @param Transaction $trans Transaction being transitioned. * @param string $finalState The state to stop on. If unspecified, * runs until a terminal state is found. * * @throws \Exception if a terminal state throws an exception. */ public function __invoke(Transaction $trans, $finalState = null) { $trans->_transitionCount = 1; if (!$trans->state) { $trans->state = 'before'; } while ($trans->state !== $finalState) { if (!isset($this->states[$trans->state])) { throw new StateException("Invalid state: {$trans->state}"); } elseif (++$trans->_transitionCount > $this->maxTransitions) { throw new StateException('Too many state transitions were ' . 'encountered ({$trans->_transitionCount}). This likely ' . 'means that a combination of event listeners are in an ' . 'infinite loop.'); } $state = $this->states[$trans->state]; try { /** @var callable $fn */ $fn = [$this, $trans->state]; if ($fn($trans)) { // Handles transitioning to the "intercept" state. if (isset($state['intercept'])) { $trans->state = $state['intercept']; continue; } throw new StateException('Invalid intercept state ' . 'transition from ' . $trans->state); } if (isset($state['success'])) { // Transition to the success state $trans->state = $state['success']; } else { // Break: this is a terminal state with no transition. break; } } catch (StateException $e) { // State exceptions are thrown no matter what. throw $e; } catch (\Exception $e) { $trans->exception = $e; // Terminal error states throw the exception. if (!isset($state['error'])) { throw $e; } // Transition to the error state. $trans->state = $state['error']; } } } private function before(Transaction $trans) { $trans->request->getEmitter()->emit('before', new BeforeEvent($trans)); // When a response is set during the before event (i.e., a mock), then // we don't need to send anything. Skip ahead to the complete event // by returning to to go to the intercept state. return (bool) $trans->response; } private function send(Transaction $trans) { $fn = $this->handler; $trans->response = FutureResponse::proxy( $fn(RingBridge::prepareRingRequest($trans)), function ($value) use ($trans) { RingBridge::completeRingResponse($trans, $value, $this->mf, $this); return $trans->response; } ); } /** * Emits the error event and ensures that the exception is set and is an * instance of RequestException. If the error event is not intercepted, * then the exception is thrown and we transition to the "end" event. This * event also allows requests to be retried, and when retried, transitions * to the "before" event. Otherwise, when no retries, and the exception is * intercepted, transition to the "complete" event. */ private function error(Transaction $trans) { // Convert non-request exception to a wrapped exception if (!($trans->exception instanceof RequestException)) { $trans->exception = RequestException::wrapException( $trans->request, $trans->exception ); } // Dispatch an event and allow interception $event = new ErrorEvent($trans); $trans->request->getEmitter()->emit('error', $event); if ($trans->exception) { throw $trans->exception; } $trans->exception = null; // Return true to transition to the 'before' state. False otherwise. return $trans->state === 'before'; } /** * Emits a complete event, and if a request is marked for a retry during * the complete event, then the "before" state is transitioned to. */ private function complete(Transaction $trans) { // Futures will have their own end events emitted when dereferenced. if ($trans->response instanceof FutureInterface) { return false; } $trans->response->setEffectiveUrl($trans->request->getUrl()); $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans)); // Return true to transition to the 'before' state. False otherwise. return $trans->state === 'before'; } /** * Emits the "end" event and throws an exception if one is present. */ private function end(Transaction $trans) { // Futures will have their own end events emitted when dereferenced, // but still emit, even for futures, when an exception is present. if (!$trans->exception && $trans->response instanceof FutureInterface) { return; } $trans->request->getEmitter()->emit('end', new EndEvent($trans)); // Throw exceptions in the terminal event if the exception was not // handled by an "end" event listener. if ($trans->exception) { throw $trans->exception; } } } src/RingBridge.php000064400000013730151742731020010066 0ustar00getConfig()->toArray(); $url = $request->getUrl(); // No need to calculate the query string twice (in URL and query). $qs = ($pos = strpos($url, '?')) ? substr($url, $pos + 1) : null; return [ 'scheme' => $request->getScheme(), 'http_method' => $request->getMethod(), 'url' => $url, 'uri' => $request->getPath(), 'headers' => $request->getHeaders(), 'body' => $request->getBody(), 'version' => $request->getProtocolVersion(), 'client' => $options, 'query_string' => $qs, 'future' => isset($options['future']) ? $options['future'] : false ]; } /** * Creates a Ring request from a request object AND prepares the callbacks. * * @param Transaction $trans Transaction to update. * * @return array Converted Guzzle Ring request. */ public static function prepareRingRequest(Transaction $trans) { // Clear out the transaction state when initiating. $trans->exception = null; $request = self::createRingRequest($trans->request); // Emit progress events if any progress listeners are registered. if ($trans->request->getEmitter()->hasListeners('progress')) { $emitter = $trans->request->getEmitter(); $request['client']['progress'] = function ($a, $b, $c, $d) use ($trans, $emitter) { $emitter->emit('progress', new ProgressEvent($trans, $a, $b, $c, $d)); }; } return $request; } /** * Handles the process of processing a response received from a ring * handler. The created response is added to the transaction, and any * necessary events are emitted based on the ring response. * * @param Transaction $trans Owns request and response. * @param array $response Ring response array * @param MessageFactoryInterface $messageFactory Creates response objects. * @param callable $fsm Request FSM function. */ public static function completeRingResponse( Transaction $trans, array $response, MessageFactoryInterface $messageFactory, callable $fsm ) { $trans->state = 'complete'; $trans->transferInfo = isset($response['transfer_stats']) ? $response['transfer_stats'] : []; if (!empty($response['status'])) { $options = []; if (isset($response['version'])) { $options['protocol_version'] = $response['version']; } if (isset($response['reason'])) { $options['reason_phrase'] = $response['reason']; } $trans->response = $messageFactory->createResponse( $response['status'], isset($response['headers']) ? $response['headers'] : [], isset($response['body']) ? $response['body'] : null, $options ); if (isset($response['effective_url'])) { $trans->response->setEffectiveUrl($response['effective_url']); } } elseif (empty($response['error'])) { // When nothing was returned, then we need to add an error. $response['error'] = self::getNoRingResponseException($trans->request); } if (isset($response['error'])) { $trans->state = 'error'; $trans->exception = $response['error']; } // Complete the lifecycle of the request. $fsm($trans); } /** * Creates a Guzzle request object using a ring request array. * * @param array $request Ring request * * @return Request * @throws \InvalidArgumentException for incomplete requests. */ public static function fromRingRequest(array $request) { $options = []; if (isset($request['version'])) { $options['protocol_version'] = $request['version']; } if (!isset($request['http_method'])) { throw new \InvalidArgumentException('No http_method'); } return new Request( $request['http_method'], Core::url($request), isset($request['headers']) ? $request['headers'] : [], isset($request['body']) ? Stream::factory($request['body']) : null, $options ); } /** * Get an exception that can be used when a RingPHP handler does not * populate a response. * * @param RequestInterface $request * * @return RequestException */ public static function getNoRingResponseException(RequestInterface $request) { $message = <<cookieJar = $cookieJar ?: new CookieJar(); } public function getEvents() { // Fire the cookie plugin complete event before redirecting return [ 'before' => ['onBefore'], 'complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE + 10] ]; } /** * Get the cookie cookieJar * * @return CookieJarInterface */ public function getCookieJar() { return $this->cookieJar; } public function onBefore(BeforeEvent $event) { $this->cookieJar->addCookieHeader($event->getRequest()); } public function onComplete(CompleteEvent $event) { $this->cookieJar->extractCookies( $event->getRequest(), $event->getResponse() ); } } src/Subscriber/History.php000064400000012050151742731020011610 0ustar00limit = $limit; } public function getEvents() { return [ 'complete' => ['onComplete', RequestEvents::EARLY], 'error' => ['onError', RequestEvents::EARLY], ]; } /** * Convert to a string that contains all request and response headers * * @return string */ public function __toString() { $lines = array(); foreach ($this->transactions as $entry) { $response = isset($entry['response']) ? $entry['response'] : ''; $lines[] = '> ' . trim($entry['sent_request']) . "\n\n< " . trim($response) . "\n"; } return implode("\n", $lines); } public function onComplete(CompleteEvent $event) { $this->add($event->getRequest(), $event->getResponse()); } public function onError(ErrorEvent $event) { // Only track when no response is present, meaning this didn't ever // emit a complete event if (!$event->getResponse()) { $this->add($event->getRequest()); } } /** * Returns an Iterator that yields associative array values where each * associative array contains the following key value pairs: * * - request: Representing the actual request that was received. * - sent_request: A clone of the request that will not be mutated. * - response: The response that was received (if available). * * @return \Iterator */ public function getIterator() { return new \ArrayIterator($this->transactions); } /** * Get all of the requests sent through the plugin. * * Requests can be modified after they are logged by the history * subscriber. By default this method will return the actual request * instances that were received. Pass true to this method if you wish to * get copies of the requests that represent the request state when it was * initially logged by the history subscriber. * * @param bool $asSent Set to true to get clones of the requests that have * not been mutated since the request was received by * the history subscriber. * * @return RequestInterface[] */ public function getRequests($asSent = false) { return array_map(function ($t) use ($asSent) { return $asSent ? $t['sent_request'] : $t['request']; }, $this->transactions); } /** * Get the number of requests in the history * * @return int */ public function count() { return count($this->transactions); } /** * Get the last request sent. * * Requests can be modified after they are logged by the history * subscriber. By default this method will return the actual request * instance that was received. Pass true to this method if you wish to get * a copy of the request that represents the request state when it was * initially logged by the history subscriber. * * @param bool $asSent Set to true to get a clone of the last request that * has not been mutated since the request was received * by the history subscriber. * * @return RequestInterface */ public function getLastRequest($asSent = false) { return $asSent ? end($this->transactions)['sent_request'] : end($this->transactions)['request']; } /** * Get the last response in the history * * @return ResponseInterface|null */ public function getLastResponse() { return end($this->transactions)['response']; } /** * Clears the history */ public function clear() { $this->transactions = array(); } /** * Add a request to the history * * @param RequestInterface $request Request to add * @param ResponseInterface $response Response of the request */ private function add( RequestInterface $request, ResponseInterface $response = null ) { $this->transactions[] = [ 'request' => $request, 'sent_request' => clone $request, 'response' => $response ]; if (count($this->transactions) > $this->limit) { array_shift($this->transactions); } } } src/Subscriber/HttpError.php000064400000001706151742731020012106 0ustar00 ['onComplete', RequestEvents::VERIFY_RESPONSE]]; } /** * Throw a RequestException on an HTTP protocol error * * @param CompleteEvent $event Emitted event * @throws RequestException */ public function onComplete(CompleteEvent $event) { $code = (string) $event->getResponse()->getStatusCode(); // Throw an exception for an unsuccessful response if ($code[0] >= 4) { throw RequestException::create( $event->getRequest(), $event->getResponse() ); } } } src/Subscriber/Mock.php000064400000007020151742731020011041 0ustar00factory = new MessageFactory(); $this->readBodies = $readBodies; $this->addMultiple($items); } public function getEvents() { // Fire the event last, after signing return ['before' => ['onBefore', RequestEvents::SIGN_REQUEST - 10]]; } /** * @throws \OutOfBoundsException|\Exception */ public function onBefore(BeforeEvent $event) { if (!$item = array_shift($this->queue)) { throw new \OutOfBoundsException('Mock queue is empty'); } elseif ($item instanceof RequestException) { throw $item; } // Emulate reading a response body $request = $event->getRequest(); if ($this->readBodies && $request->getBody()) { while (!$request->getBody()->eof()) { $request->getBody()->read(8096); } } $event->intercept($item); } public function count() { return count($this->queue); } /** * Add a response to the end of the queue * * @param string|ResponseInterface $response Response or path to response file * * @return self * @throws \InvalidArgumentException if a string or Response is not passed */ public function addResponse($response) { if (is_string($response)) { $response = file_exists($response) ? $this->factory->fromMessage(file_get_contents($response)) : $this->factory->fromMessage($response); } elseif (!($response instanceof ResponseInterface)) { throw new \InvalidArgumentException('Response must a message ' . 'string, response object, or path to a file'); } $this->queue[] = $response; return $this; } /** * Add an exception to the end of the queue * * @param RequestException $e Exception to throw when the request is executed * * @return self */ public function addException(RequestException $e) { $this->queue[] = $e; return $this; } /** * Add multiple items to the queue * * @param array $items Items to add */ public function addMultiple(array $items) { foreach ($items as $item) { if ($item instanceof RequestException) { $this->addException($item); } else { $this->addResponse($item); } } } /** * Clear the queue */ public function clearQueue() { $this->queue = []; } } src/Subscriber/Prepare.php000064400000010447151742731020011555 0ustar00 ['onBefore', RequestEvents::PREPARE_REQUEST]]; } public function onBefore(BeforeEvent $event) { $request = $event->getRequest(); // Set the appropriate Content-Type for a request if one is not set and // there are form fields if (!($body = $request->getBody())) { return; } $this->addContentLength($request, $body); if ($body instanceof PostBodyInterface) { // Synchronize the POST body with the request's headers $body->applyRequestHeaders($request); } elseif (!$request->hasHeader('Content-Type')) { $this->addContentType($request, $body); } $this->addExpectHeader($request, $body); } private function addContentType( RequestInterface $request, StreamInterface $body ) { if (!($uri = $body->getMetadata('uri'))) { return; } // Guess the content-type based on the stream's "uri" metadata value. // The file extension is used to determine the appropriate mime-type. if ($contentType = Mimetypes::getInstance()->fromFilename($uri)) { $request->setHeader('Content-Type', $contentType); } } private function addContentLength( RequestInterface $request, StreamInterface $body ) { // Set the Content-Length header if it can be determined, and never // send a Transfer-Encoding: chunked and Content-Length header in // the same request. if ($request->hasHeader('Content-Length')) { // Remove transfer-encoding if content-length is set. $request->removeHeader('Transfer-Encoding'); return; } if ($request->hasHeader('Transfer-Encoding')) { return; } if (null !== ($size = $body->getSize())) { $request->setHeader('Content-Length', $size); $request->removeHeader('Transfer-Encoding'); } elseif ('1.1' == $request->getProtocolVersion()) { // Use chunked Transfer-Encoding if there is no determinable // content-length header and we're using HTTP/1.1. $request->setHeader('Transfer-Encoding', 'chunked'); $request->removeHeader('Content-Length'); } } private function addExpectHeader( RequestInterface $request, StreamInterface $body ) { // Determine if the Expect header should be used if ($request->hasHeader('Expect')) { return; } $expect = $request->getConfig()['expect']; // Return if disabled or if you're not using HTTP/1.1 if ($expect === false || $request->getProtocolVersion() !== '1.1') { return; } // The expect header is unconditionally enabled if ($expect === true) { $request->setHeader('Expect', '100-Continue'); return; } // By default, send the expect header when the payload is > 1mb if ($expect === null) { $expect = 1048576; } // Always add if the body cannot be rewound, the size cannot be // determined, or the size is greater than the cutoff threshold $size = $body->getSize(); if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { $request->setHeader('Expect', '100-Continue'); } } } src/Subscriber/Redirect.php000064400000012325151742731020011715 0ustar00 ['onComplete', RequestEvents::REDIRECT_RESPONSE]]; } /** * Rewind the entity body of the request if needed * * @param RequestInterface $redirectRequest * @throws CouldNotRewindStreamException */ public static function rewindEntityBody(RequestInterface $redirectRequest) { // Rewind the entity body of the request if needed if ($body = $redirectRequest->getBody()) { // Only rewind the body if some of it has been read already, and // throw an exception if the rewind fails if ($body->tell() && !$body->seek(0)) { throw new CouldNotRewindStreamException( 'Unable to rewind the non-seekable request body after redirecting', $redirectRequest ); } } } /** * Called when a request receives a redirect response * * @param CompleteEvent $event Event emitted * @throws TooManyRedirectsException */ public function onComplete(CompleteEvent $event) { $response = $event->getResponse(); if (substr($response->getStatusCode(), 0, 1) != '3' || !$response->hasHeader('Location') ) { return; } $request = $event->getRequest(); $config = $request->getConfig(); // Increment the redirect and initialize the redirect state. if ($redirectCount = $config['redirect_count']) { $config['redirect_count'] = ++$redirectCount; } else { $config['redirect_scheme'] = $request->getScheme(); $config['redirect_count'] = $redirectCount = 1; } $max = $config->getPath('redirect/max') ?: 5; if ($redirectCount > $max) { throw new TooManyRedirectsException( "Will not follow more than {$redirectCount} redirects", $request ); } $this->modifyRedirectRequest($request, $response); $event->retry(); } private function modifyRedirectRequest( RequestInterface $request, ResponseInterface $response ) { $config = $request->getConfig(); // Use a GET request if this is an entity enclosing request and we are // not forcing RFC compliance, but rather emulating what all browsers // would do. $statusCode = $response->getStatusCode(); if ($statusCode == 303 || ($statusCode <= 302 && $request->getBody() && !$config->getPath('redirect/strict')) ) { $request->setMethod('GET'); $request->setBody(null); } $previousUrl = $request->getUrl(); $this->setRedirectUrl($request, $response); $this->rewindEntityBody($request); // Add the Referer header if it is told to do so and only // add the header if we are not redirecting from https to http. if ($config->getPath('redirect/referer') && ($request->getScheme() == 'https' || $request->getScheme() == $config['redirect_scheme']) ) { $url = Url::fromString($previousUrl); $url->setUsername(null); $url->setPassword(null); $request->setHeader('Referer', (string) $url); } else { $request->removeHeader('Referer'); } } /** * Set the appropriate URL on the request based on the location header * * @param RequestInterface $request * @param ResponseInterface $response */ private function setRedirectUrl( RequestInterface $request, ResponseInterface $response ) { $location = $response->getHeader('Location'); $location = Url::fromString($location); // Combine location with the original URL if it is not absolute. if (!$location->isAbsolute()) { $originalUrl = Url::fromString($request->getUrl()); // Remove query string parameters and just take what is present on // the redirect Location header $originalUrl->getQuery()->clear(); $location = $originalUrl->combine($location); } $request->setUrl($location); } } src/ToArrayInterface.php000064400000000361151742731020011250 0ustar00client = $client; $this->request = $request; } } src/UriTemplate.php000064400000020001151742731020010272 0ustar00 array('prefix' => '', 'joiner' => ',', 'query' => false), '+' => array('prefix' => '', 'joiner' => ',', 'query' => false), '#' => array('prefix' => '#', 'joiner' => ',', 'query' => false), '.' => array('prefix' => '.', 'joiner' => '.', 'query' => false), '/' => array('prefix' => '/', 'joiner' => '/', 'query' => false), ';' => array('prefix' => ';', 'joiner' => ';', 'query' => true), '?' => array('prefix' => '?', 'joiner' => '&', 'query' => true), '&' => array('prefix' => '&', 'joiner' => '&', 'query' => true) ); /** @var array Delimiters */ private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='); /** @var array Percent encoded delimiters */ private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D'); public function expand($template, array $variables) { if (false === strpos($template, '{')) { return $template; } $this->template = $template; $this->variables = $variables; return preg_replace_callback( '/\{([^\}]+)\}/', [$this, 'expandMatch'], $this->template ); } /** * Parse an expression into parts * * @param string $expression Expression to parse * * @return array Returns an associative array of parts */ private function parseExpression($expression) { $result = array(); if (isset(self::$operatorHash[$expression[0]])) { $result['operator'] = $expression[0]; $expression = substr($expression, 1); } else { $result['operator'] = ''; } foreach (explode(',', $expression) as $value) { $value = trim($value); $varspec = array(); if ($colonPos = strpos($value, ':')) { $varspec['value'] = substr($value, 0, $colonPos); $varspec['modifier'] = ':'; $varspec['position'] = (int) substr($value, $colonPos + 1); } elseif (substr($value, -1) == '*') { $varspec['modifier'] = '*'; $varspec['value'] = substr($value, 0, -1); } else { $varspec['value'] = (string) $value; $varspec['modifier'] = ''; } $result['values'][] = $varspec; } return $result; } /** * Process an expansion * * @param array $matches Matches met in the preg_replace_callback * * @return string Returns the replacement string */ private function expandMatch(array $matches) { static $rfc1738to3986 = array('+' => '%20', '%7e' => '~'); $replacements = array(); $parsed = self::parseExpression($matches[1]); $prefix = self::$operatorHash[$parsed['operator']]['prefix']; $joiner = self::$operatorHash[$parsed['operator']]['joiner']; $useQuery = self::$operatorHash[$parsed['operator']]['query']; foreach ($parsed['values'] as $value) { if (!isset($this->variables[$value['value']])) { continue; } $variable = $this->variables[$value['value']]; $actuallyUseQuery = $useQuery; $expanded = ''; if (is_array($variable)) { $isAssoc = $this->isAssoc($variable); $kvp = array(); foreach ($variable as $key => $var) { if ($isAssoc) { $key = rawurlencode($key); $isNestedArray = is_array($var); } else { $isNestedArray = false; } if (!$isNestedArray) { $var = rawurlencode($var); if ($parsed['operator'] == '+' || $parsed['operator'] == '#' ) { $var = $this->decodeReserved($var); } } if ($value['modifier'] == '*') { if ($isAssoc) { if ($isNestedArray) { // Nested arrays must allow for deeply nested // structures. $var = strtr( http_build_query([$key => $var]), $rfc1738to3986 ); } else { $var = $key . '=' . $var; } } elseif ($key > 0 && $actuallyUseQuery) { $var = $value['value'] . '=' . $var; } } $kvp[$key] = $var; } if (empty($variable)) { $actuallyUseQuery = false; } elseif ($value['modifier'] == '*') { $expanded = implode($joiner, $kvp); if ($isAssoc) { // Don't prepend the value name when using the explode // modifier with an associative array. $actuallyUseQuery = false; } } else { if ($isAssoc) { // When an associative array is encountered and the // explode modifier is not set, then the result must be // a comma separated list of keys followed by their // respective values. foreach ($kvp as $k => &$v) { $v = $k . ',' . $v; } } $expanded = implode(',', $kvp); } } else { if ($value['modifier'] == ':') { $variable = substr($variable, 0, $value['position']); } $expanded = rawurlencode($variable); if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { $expanded = $this->decodeReserved($expanded); } } if ($actuallyUseQuery) { if (!$expanded && $joiner != '&') { $expanded = $value['value']; } else { $expanded = $value['value'] . '=' . $expanded; } } $replacements[] = $expanded; } $ret = implode($joiner, $replacements); if ($ret && $prefix) { return $prefix . $ret; } return $ret; } /** * Determines if an array is associative. * * This makes the assumption that input arrays are sequences or hashes. * This assumption is a tradeoff for accuracy in favor of speed, but it * should work in almost every case where input is supplied for a URI * template. * * @param array $array Array to check * * @return bool */ private function isAssoc(array $array) { return $array && array_keys($array)[0] !== 0; } /** * Removes percent encoding on reserved characters (used with + and # * modifiers). * * @param string $string String to fix * * @return string */ private function decodeReserved($string) { return str_replace(self::$delimsPct, self::$delims, $string); } } src/Url.php000064400000036770151742731020006625 0ustar00 80, 'https' => 443, 'ftp' => 21]; private static $pathPattern = '/[^a-zA-Z0-9\-\._~!\$&\'\(\)\*\+,;=%:@\/]+|%(?![A-Fa-f0-9]{2})/'; private static $queryPattern = '/[^a-zA-Z0-9\-\._~!\$\'\(\)\*\+,;%:@\/\?=&]+|%(?![A-Fa-f0-9]{2})/'; /** @var Query|string Query part of the URL */ private $query; /** * Factory method to create a new URL from a URL string * * @param string $url Full URL used to create a Url object * * @return Url * @throws \InvalidArgumentException */ public static function fromString($url) { static $defaults = ['scheme' => null, 'host' => null, 'path' => null, 'port' => null, 'query' => null, 'user' => null, 'pass' => null, 'fragment' => null]; if (false === ($parts = parse_url($url))) { throw new \InvalidArgumentException('Unable to parse malformed ' . 'url: ' . $url); } $parts += $defaults; // Convert the query string into a Query object if ($parts['query'] || 0 !== strlen($parts['query'])) { $parts['query'] = Query::fromString($parts['query']); } return new static($parts['scheme'], $parts['host'], $parts['user'], $parts['pass'], $parts['port'], $parts['path'], $parts['query'], $parts['fragment']); } /** * Build a URL from parse_url parts. The generated URL will be a relative * URL if a scheme or host are not provided. * * @param array $parts Array of parse_url parts * * @return string */ public static function buildUrl(array $parts) { $url = $scheme = ''; if (!empty($parts['scheme'])) { $scheme = $parts['scheme']; $url .= $scheme . ':'; } if (!empty($parts['host'])) { $url .= '//'; if (isset($parts['user'])) { $url .= $parts['user']; if (isset($parts['pass'])) { $url .= ':' . $parts['pass']; } $url .= '@'; } $url .= $parts['host']; // Only include the port if it is not the default port of the scheme if (isset($parts['port']) && (!isset(self::$defaultPorts[$scheme]) || $parts['port'] != self::$defaultPorts[$scheme]) ) { $url .= ':' . $parts['port']; } } // Add the path component if present if (isset($parts['path']) && strlen($parts['path'])) { // Always ensure that the path begins with '/' if set and something // is before the path if (!empty($parts['host']) && $parts['path'][0] != '/') { $url .= '/'; } $url .= $parts['path']; } // Add the query string if present if (isset($parts['query'])) { $queryStr = (string) $parts['query']; if ($queryStr || $queryStr === '0') { $url .= '?' . $queryStr; } } // Ensure that # is only added to the url if fragment contains anything. if (isset($parts['fragment'])) { $url .= '#' . $parts['fragment']; } return $url; } /** * Create a new URL from URL parts * * @param string $scheme Scheme of the URL * @param string $host Host of the URL * @param string $username Username of the URL * @param string $password Password of the URL * @param int $port Port of the URL * @param string $path Path of the URL * @param Query|array|string $query Query string of the URL * @param string $fragment Fragment of the URL */ public function __construct( $scheme, $host, $username = null, $password = null, $port = null, $path = null, Query $query = null, $fragment = null ) { $this->scheme = $scheme; $this->host = $host; $this->port = $port; $this->username = $username; $this->password = $password; $this->fragment = $fragment; if ($query) { $this->setQuery($query); } $this->setPath($path); } /** * Clone the URL */ public function __clone() { if ($this->query instanceof Query) { $this->query = clone $this->query; } } /** * Returns the URL as a URL string * * @return string */ public function __toString() { return static::buildUrl($this->getParts()); } /** * Get the parts of the URL as an array * * @return array */ public function getParts() { return array( 'scheme' => $this->scheme, 'user' => $this->username, 'pass' => $this->password, 'host' => $this->host, 'port' => $this->port, 'path' => $this->path, 'query' => $this->query, 'fragment' => $this->fragment, ); } /** * Set the host of the request. * * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com) * * @return Url */ public function setHost($host) { if (strpos($host, ':') === false) { $this->host = $host; } else { list($host, $port) = explode(':', $host); $this->host = $host; $this->setPort($port); } } /** * Get the host part of the URL * * @return string */ public function getHost() { return $this->host; } /** * Set the scheme part of the URL (http, https, ftp, etc.) * * @param string $scheme Scheme to set */ public function setScheme($scheme) { // Remove the default port if one is specified if ($this->port && isset(self::$defaultPorts[$this->scheme]) && self::$defaultPorts[$this->scheme] == $this->port ) { $this->port = null; } $this->scheme = $scheme; } /** * Get the scheme part of the URL * * @return string */ public function getScheme() { return $this->scheme; } /** * Set the port part of the URL * * @param int $port Port to set */ public function setPort($port) { $this->port = $port; } /** * Get the port part of the URl. * * If no port was set, this method will return the default port for the * scheme of the URI. * * @return int|null */ public function getPort() { if ($this->port) { return $this->port; } elseif (isset(self::$defaultPorts[$this->scheme])) { return self::$defaultPorts[$this->scheme]; } return null; } /** * Set the path part of the URL. * * The provided URL is URL encoded as necessary. * * @param string $path Path string to set */ public function setPath($path) { $this->path = self::encodePath($path); } /** * Removes dot segments from a URL * @link http://tools.ietf.org/html/rfc3986#section-5.2.4 */ public function removeDotSegments() { static $noopPaths = ['' => true, '/' => true, '*' => true]; static $ignoreSegments = ['.' => true, '..' => true]; if (isset($noopPaths[$this->path])) { return; } $results = []; $segments = $this->getPathSegments(); foreach ($segments as $segment) { if ($segment == '..') { array_pop($results); } elseif (!isset($ignoreSegments[$segment])) { $results[] = $segment; } } $newPath = implode('/', $results); // Add the leading slash if necessary if (substr($this->path, 0, 1) === '/' && substr($newPath, 0, 1) !== '/' ) { $newPath = '/' . $newPath; } // Add the trailing slash if necessary if ($newPath != '/' && isset($ignoreSegments[end($segments)])) { $newPath .= '/'; } $this->path = $newPath; } /** * Add a relative path to the currently set path. * * @param string $relativePath Relative path to add */ public function addPath($relativePath) { if ($relativePath != '/' && is_string($relativePath) && strlen($relativePath) > 0 ) { // Add a leading slash if needed if ($relativePath[0] !== '/' && substr($this->path, -1, 1) !== '/' ) { $relativePath = '/' . $relativePath; } $this->setPath($this->path . $relativePath); } } /** * Get the path part of the URL * * @return string */ public function getPath() { return $this->path; } /** * Get the path segments of the URL as an array * * @return array */ public function getPathSegments() { return explode('/', $this->path); } /** * Set the password part of the URL * * @param string $password Password to set */ public function setPassword($password) { $this->password = $password; } /** * Get the password part of the URL * * @return null|string */ public function getPassword() { return $this->password; } /** * Set the username part of the URL * * @param string $username Username to set */ public function setUsername($username) { $this->username = $username; } /** * Get the username part of the URl * * @return null|string */ public function getUsername() { return $this->username; } /** * Get the query part of the URL as a Query object * * @return Query */ public function getQuery() { // Convert the query string to a query object if not already done. if (!$this->query instanceof Query) { $this->query = $this->query === null ? new Query() : Query::fromString($this->query); } return $this->query; } /** * Set the query part of the URL. * * You may provide a query string as a string and pass $rawString as true * to provide a query string that is not parsed until a call to getQuery() * is made. Setting a raw query string will still encode invalid characters * in a query string. * * @param Query|string|array $query Query string value to set. Can * be a string that will be parsed into a Query object, an array * of key value pairs, or a Query object. * @param bool $rawString Set to true when providing a raw query string. * * @throws \InvalidArgumentException */ public function setQuery($query, $rawString = false) { if ($query instanceof Query) { $this->query = $query; } elseif (is_string($query)) { if (!$rawString) { $this->query = Query::fromString($query); } else { // Ensure the query does not have illegal characters. $this->query = preg_replace_callback( self::$queryPattern, [__CLASS__, 'encodeMatch'], $query ); } } elseif (is_array($query)) { $this->query = new Query($query); } else { throw new \InvalidArgumentException('Query must be a Query, ' . 'array, or string. Got ' . Core::describeType($query)); } } /** * Get the fragment part of the URL * * @return null|string */ public function getFragment() { return $this->fragment; } /** * Set the fragment part of the URL * * @param string $fragment Fragment to set */ public function setFragment($fragment) { $this->fragment = $fragment; } /** * Check if this is an absolute URL * * @return bool */ public function isAbsolute() { return $this->scheme && $this->host; } /** * Combine the URL with another URL and return a new URL instance. * * Follows the rules specific in RFC 3986 section 5.4. * * @param string $url Relative URL to combine with * * @return Url * @throws \InvalidArgumentException * @link http://tools.ietf.org/html/rfc3986#section-5.4 */ public function combine($url) { $url = static::fromString($url); // Use the more absolute URL as the base URL if (!$this->isAbsolute() && $url->isAbsolute()) { $url = $url->combine($this); } $parts = $url->getParts(); // Passing a URL with a scheme overrides everything if ($parts['scheme']) { return clone $url; } // Setting a host overrides the entire rest of the URL if ($parts['host']) { return new static( $this->scheme, $parts['host'], $parts['user'], $parts['pass'], $parts['port'], $parts['path'], $parts['query'] instanceof Query ? clone $parts['query'] : $parts['query'], $parts['fragment'] ); } if (!$parts['path'] && $parts['path'] !== '0') { // The relative URL has no path, so check if it is just a query $path = $this->path ?: ''; $query = $parts['query'] ?: $this->query; } else { $query = $parts['query']; if ($parts['path'][0] == '/' || !$this->path) { // Overwrite the existing path if the rel path starts with "/" $path = $parts['path']; } else { // If the relative URL does not have a path or the base URL // path does not end in a "/" then overwrite the existing path // up to the last "/" $path = substr($this->path, 0, strrpos($this->path, '/') + 1) . $parts['path']; } } $result = new self( $this->scheme, $this->host, $this->username, $this->password, $this->port, $path, $query instanceof Query ? clone $query : $query, $parts['fragment'] ); if ($path) { $result->removeDotSegments(); } return $result; } /** * Encodes the path part of a URL without double-encoding percent-encoded * key value pairs. * * @param string $path Path to encode * * @return string */ public static function encodePath($path) { static $cb = [__CLASS__, 'encodeMatch']; return preg_replace_callback(self::$pathPattern, $cb, $path); } private static function encodeMatch(array $match) { return rawurlencode($match[0]); } } src/Utils.php000064400000011660151742731020007152 0ustar00expand($template, $variables); } /** * Wrapper for JSON decode that implements error detection with helpful * error messages. * * @param string $json JSON data to parse * @param bool $assoc When true, returned objects will be converted * into associative arrays. * @param int $depth User specified recursion depth. * @param int $options Bitmask of JSON decode options. * * @return mixed * @throws \InvalidArgumentException if the JSON cannot be parsed. * @link http://www.php.net/manual/en/function.json-decode.php */ public static function jsonDecode($json, $assoc = false, $depth = 512, $options = 0) { static $jsonErrors = [ JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' ]; $data = \json_decode($json, $assoc, $depth, $options); if (JSON_ERROR_NONE !== json_last_error()) { $last = json_last_error(); throw new \InvalidArgumentException( 'Unable to parse JSON data: ' . (isset($jsonErrors[$last]) ? $jsonErrors[$last] : 'Unknown error') ); } return $data; } } tests/BatchResultsTest.php000064400000003227151742731020011670 0ustar00assertCount(3, $batch); $this->assertEquals([$a, $b, $c], $batch->getKeys()); $this->assertEquals([$hash[$c]], $batch->getFailures()); $this->assertEquals(['1', '2'], $batch->getSuccessful()); $this->assertEquals('1', $batch->getResult($a)); $this->assertNull($batch->getResult(new \stdClass())); $this->assertTrue(isset($batch[0])); $this->assertFalse(isset($batch[10])); $this->assertEquals('1', $batch[0]); $this->assertEquals('2', $batch[1]); $this->assertNull($batch[100]); $this->assertInstanceOf('Exception', $batch[2]); $results = iterator_to_array($batch); $this->assertEquals(['1', '2', $hash[$c]], $results); } /** * @expectedException \RuntimeException */ public function testCannotSetByIndex() { $hash = new \SplObjectStorage(); $batch = new BatchResults($hash); $batch[10] = 'foo'; } /** * @expectedException \RuntimeException */ public function testCannotUnsetByIndex() { $hash = new \SplObjectStorage(); $batch = new BatchResults($hash); unset($batch[10]); } } tests/ClientTest.php000064400000046225151742731020010510 0ustar00ma = function () { throw new \RuntimeException('Should not have been called.'); }; } public function testProvidesDefaultUserAgent() { $ua = Client::getDefaultUserAgent(); $this->assertEquals(1, preg_match('#^Guzzle/.+ curl/.+ PHP/.+$#', $ua)); } public function testUsesDefaultDefaultOptions() { $client = new Client(); $this->assertTrue($client->getDefaultOption('allow_redirects')); $this->assertTrue($client->getDefaultOption('exceptions')); $this->assertTrue($client->getDefaultOption('verify')); } public function testUsesProvidedDefaultOptions() { $client = new Client([ 'defaults' => [ 'allow_redirects' => false, 'query' => ['foo' => 'bar'] ] ]); $this->assertFalse($client->getDefaultOption('allow_redirects')); $this->assertTrue($client->getDefaultOption('exceptions')); $this->assertTrue($client->getDefaultOption('verify')); $this->assertEquals(['foo' => 'bar'], $client->getDefaultOption('query')); } public function testCanSpecifyBaseUrl() { $this->assertSame('', (new Client())->getBaseUrl()); $this->assertEquals('http://foo', (new Client([ 'base_url' => 'http://foo' ]))->getBaseUrl()); } public function testCanSpecifyBaseUrlUriTemplate() { $client = new Client(['base_url' => ['http://foo.com/{var}/', ['var' => 'baz']]]); $this->assertEquals('http://foo.com/baz/', $client->getBaseUrl()); } /** * @expectedException \Exception * @expectedExceptionMessage Foo */ public function testCanSpecifyHandler() { $client = new Client(['handler' => function () { throw new \Exception('Foo'); }]); $client->get('http://httpbin.org'); } /** * @expectedException \Exception * @expectedExceptionMessage Foo */ public function testCanSpecifyHandlerAsAdapter() { $client = new Client(['adapter' => function () { throw new \Exception('Foo'); }]); $client->get('http://httpbin.org'); } /** * @expectedException \Exception * @expectedExceptionMessage Foo */ public function testCanSpecifyMessageFactory() { $factory = $this->getMockBuilder('GuzzleHttp\Message\MessageFactoryInterface') ->setMethods(['createRequest']) ->getMockForAbstractClass(); $factory->expects($this->once()) ->method('createRequest') ->will($this->throwException(new \Exception('Foo'))); $client = new Client(['message_factory' => $factory]); $client->get(); } public function testCanSpecifyEmitter() { $emitter = $this->getMockBuilder('GuzzleHttp\Event\EmitterInterface') ->setMethods(['listeners']) ->getMockForAbstractClass(); $emitter->expects($this->once()) ->method('listeners') ->will($this->returnValue('foo')); $client = new Client(['emitter' => $emitter]); $this->assertEquals('foo', $client->getEmitter()->listeners()); } public function testAddsDefaultUserAgentHeaderWithDefaultOptions() { $client = new Client(['defaults' => ['allow_redirects' => false]]); $this->assertFalse($client->getDefaultOption('allow_redirects')); $this->assertEquals( ['User-Agent' => Client::getDefaultUserAgent()], $client->getDefaultOption('headers') ); } public function testAddsDefaultUserAgentHeaderWithoutDefaultOptions() { $client = new Client(); $this->assertEquals( ['User-Agent' => Client::getDefaultUserAgent()], $client->getDefaultOption('headers') ); } private function getRequestClient() { $client = $this->getMockBuilder('GuzzleHttp\Client') ->setMethods(['send']) ->getMock(); $client->expects($this->once()) ->method('send') ->will($this->returnArgument(0)); return $client; } public function requestMethodProvider() { return [ ['GET', false], ['HEAD', false], ['DELETE', false], ['OPTIONS', false], ['POST', 'foo'], ['PUT', 'foo'], ['PATCH', 'foo'] ]; } /** * @dataProvider requestMethodProvider */ public function testClientProvidesMethodShortcut($method, $body) { $client = $this->getRequestClient(); if ($body) { $request = $client->{$method}('http://foo.com', [ 'headers' => ['X-Baz' => 'Bar'], 'body' => $body, 'query' => ['a' => 'b'] ]); } else { $request = $client->{$method}('http://foo.com', [ 'headers' => ['X-Baz' => 'Bar'], 'query' => ['a' => 'b'] ]); } $this->assertEquals($method, $request->getMethod()); $this->assertEquals('Bar', $request->getHeader('X-Baz')); $this->assertEquals('a=b', $request->getQuery()); if ($body) { $this->assertEquals($body, $request->getBody()); } } public function testClientMergesDefaultOptionsWithRequestOptions() { $f = $this->getMockBuilder('GuzzleHttp\Message\MessageFactoryInterface') ->setMethods(array('createRequest')) ->getMockForAbstractClass(); $o = null; // Intercept the creation $f->expects($this->once()) ->method('createRequest') ->will($this->returnCallback( function ($method, $url, array $options = []) use (&$o) { $o = $options; return (new MessageFactory())->createRequest($method, $url, $options); } )); $client = new Client([ 'message_factory' => $f, 'defaults' => [ 'headers' => ['Foo' => 'Bar'], 'query' => ['baz' => 'bam'], 'exceptions' => false ] ]); $request = $client->createRequest('GET', 'http://foo.com?a=b', [ 'headers' => ['Hi' => 'there', '1' => 'one'], 'allow_redirects' => false, 'query' => ['t' => 1] ]); $this->assertFalse($o['allow_redirects']); $this->assertFalse($o['exceptions']); $this->assertEquals('Bar', $request->getHeader('Foo')); $this->assertEquals('there', $request->getHeader('Hi')); $this->assertEquals('one', $request->getHeader('1')); $this->assertEquals('a=b&baz=bam&t=1', $request->getQuery()); } public function testClientMergesDefaultHeadersCaseInsensitively() { $client = new Client(['defaults' => ['headers' => ['Foo' => 'Bar']]]); $request = $client->createRequest('GET', 'http://foo.com?a=b', [ 'headers' => ['foo' => 'custom', 'user-agent' => 'test'] ]); $this->assertEquals('test', $request->getHeader('User-Agent')); $this->assertEquals('custom', $request->getHeader('Foo')); } public function testDoesNotOverwriteExistingUA() { $client = new Client(['defaults' => [ 'headers' => ['User-Agent' => 'test'] ]]); $this->assertEquals( ['User-Agent' => 'test'], $client->getDefaultOption('headers') ); } public function testUsesBaseUrlWhenNoUrlIsSet() { $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); $this->assertEquals( 'http://www.foo.com/baz?bam=bar', $client->createRequest('GET')->getUrl() ); } public function testUsesBaseUrlCombinedWithProvidedUrl() { $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); $this->assertEquals( 'http://www.foo.com/bar/bam', $client->createRequest('GET', 'bar/bam')->getUrl() ); } public function testUsesBaseUrlCombinedWithProvidedUrlViaUriTemplate() { $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); $this->assertEquals( 'http://www.foo.com/bar/123', $client->createRequest('GET', ['bar/{bam}', ['bam' => '123']])->getUrl() ); } public function testSettingAbsoluteUrlOverridesBaseUrl() { $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); $this->assertEquals( 'http://www.foo.com/foo', $client->createRequest('GET', '/foo')->getUrl() ); } public function testSettingAbsoluteUriTemplateOverridesBaseUrl() { $client = new Client(['base_url' => 'http://www.foo.com/baz?bam=bar']); $this->assertEquals( 'http://goo.com/1', $client->createRequest( 'GET', ['http://goo.com/{bar}', ['bar' => '1']] )->getUrl() ); } public function testCanSetRelativeUrlStartingWithHttp() { $client = new Client(['base_url' => 'http://www.foo.com']); $this->assertEquals( 'http://www.foo.com/httpfoo', $client->createRequest('GET', 'httpfoo')->getUrl() ); } public function testClientSendsRequests() { $mock = new MockHandler(['status' => 200, 'headers' => []]); $client = new Client(['handler' => $mock]); $response = $client->get('http://test.com'); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('http://test.com', $response->getEffectiveUrl()); } public function testSendingRequestCanBeIntercepted() { $response = new Response(200); $client = new Client(['handler' => $this->ma]); $client->getEmitter()->on( 'before', function (BeforeEvent $e) use ($response) { $e->intercept($response); } ); $this->assertSame($response, $client->get('http://test.com')); $this->assertEquals('http://test.com', $response->getEffectiveUrl()); } /** * @expectedException \GuzzleHttp\Exception\RequestException * @expectedExceptionMessage Argument 1 passed to GuzzleHttp\Message\FutureResponse::proxy() must implement interface GuzzleHttp\Ring\Future\FutureInterface */ public function testEnsuresResponseIsPresentAfterSending() { $handler = function () {}; $client = new Client(['handler' => $handler]); $client->get('http://httpbin.org'); } /** * @expectedException \GuzzleHttp\Exception\RequestException * @expectedExceptionMessage Waiting did not resolve future */ public function testEnsuresResponseIsPresentAfterDereferencing() { $deferred = new Deferred(); $handler = new MockHandler(function () use ($deferred) { return new FutureArray( $deferred->promise(), function () {} ); }); $client = new Client(['handler' => $handler]); $response = $client->get('http://httpbin.org'); $response->wait(); } public function testClientHandlesErrorsDuringBeforeSend() { $client = new Client(); $client->getEmitter()->on('before', function ($e) { throw new \Exception('foo'); }); $client->getEmitter()->on('error', function (ErrorEvent $e) { $e->intercept(new Response(200)); }); $this->assertEquals( 200, $client->get('http://test.com')->getStatusCode() ); } /** * @expectedException \GuzzleHttp\Exception\RequestException * @expectedExceptionMessage foo */ public function testClientHandlesErrorsDuringBeforeSendAndThrowsIfUnhandled() { $client = new Client(); $client->getEmitter()->on('before', function (BeforeEvent $e) { throw new RequestException('foo', $e->getRequest()); }); $client->get('http://httpbin.org'); } /** * @expectedException \GuzzleHttp\Exception\RequestException * @expectedExceptionMessage foo */ public function testClientWrapsExceptions() { $client = new Client(); $client->getEmitter()->on('before', function (BeforeEvent $e) { throw new \Exception('foo'); }); $client->get('http://httpbin.org'); } public function testCanInjectResponseForFutureError() { $calledFuture = false; $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function () use ($deferred, &$calledFuture) { $calledFuture = true; $deferred->resolve(['error' => new \Exception('Noo!')]); } ); $mock = new MockHandler($future); $client = new Client(['handler' => $mock]); $called = 0; $response = $client->get('http://localhost:123/foo', [ 'future' => true, 'events' => [ 'error' => function (ErrorEvent $e) use (&$called) { $called++; $e->intercept(new Response(200)); } ] ]); $this->assertEquals(0, $called); $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $response); $this->assertEquals(200, $response->getStatusCode()); $this->assertTrue($calledFuture); $this->assertEquals(1, $called); } public function testCanReturnFutureResults() { $called = false; $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function () use ($deferred, &$called) { $called = true; $deferred->resolve(['status' => 201, 'headers' => []]); } ); $mock = new MockHandler($future); $client = new Client(['handler' => $mock]); $response = $client->get('http://localhost:123/foo', ['future' => true]); $this->assertFalse($called); $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $response); $this->assertEquals(201, $response->getStatusCode()); $this->assertTrue($called); } public function testThrowsExceptionsWhenDereferenced() { $calledFuture = false; $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function () use ($deferred, &$calledFuture) { $calledFuture = true; $deferred->resolve(['error' => new \Exception('Noop!')]); } ); $client = new Client(['handler' => new MockHandler($future)]); try { $res = $client->get('http://localhost:123/foo', ['future' => true]); $res->wait(); $this->fail('Did not throw'); } catch (RequestException $e) { $this->assertEquals(1, $calledFuture); } } /** * @expectedExceptionMessage Noo! * @expectedException \GuzzleHttp\Exception\RequestException */ public function testThrowsExceptionsSynchronously() { $client = new Client([ 'handler' => new MockHandler(['error' => new \Exception('Noo!')]) ]); $client->get('http://localhost:123/foo'); } public function testCanSetDefaultValues() { $client = new Client(['foo' => 'bar']); $client->setDefaultOption('headers/foo', 'bar'); $this->assertNull($client->getDefaultOption('foo')); $this->assertEquals('bar', $client->getDefaultOption('headers/foo')); } public function testSendsAllInParallel() { $client = new Client(); $client->getEmitter()->attach(new Mock([ new Response(200), new Response(201), new Response(202), ])); $history = new History(); $client->getEmitter()->attach($history); $requests = [ $client->createRequest('GET', 'http://test.com'), $client->createRequest('POST', 'http://test.com'), $client->createRequest('PUT', 'http://test.com') ]; $client->sendAll($requests); $requests = array_map(function($r) { return $r->getMethod(); }, $history->getRequests()); $this->assertContains('GET', $requests); $this->assertContains('POST', $requests); $this->assertContains('PUT', $requests); } public function testCanDisableAuthPerRequest() { $client = new Client(['defaults' => ['auth' => 'foo']]); $request = $client->createRequest('GET', 'http://test.com'); $this->assertEquals('foo', $request->getConfig()['auth']); $request = $client->createRequest('GET', 'http://test.com', ['auth' => null]); $this->assertFalse($request->getConfig()->hasKey('auth')); } public function testUsesProxyEnvironmentVariables() { $http = getenv('HTTP_PROXY'); $https = getenv('HTTPS_PROXY'); $client = new Client(); $this->assertNull($client->getDefaultOption('proxy')); putenv('HTTP_PROXY=127.0.0.1'); $client = new Client(); $this->assertEquals( ['http' => '127.0.0.1'], $client->getDefaultOption('proxy') ); putenv('HTTPS_PROXY=127.0.0.2'); $client = new Client(); $this->assertEquals( ['http' => '127.0.0.1', 'https' => '127.0.0.2'], $client->getDefaultOption('proxy') ); putenv("HTTP_PROXY=$http"); putenv("HTTPS_PROXY=$https"); } public function testReturnsFutureForErrorWhenRequested() { $client = new Client(['handler' => new MockHandler(['status' => 404])]); $request = $client->createRequest('GET', 'http://localhost:123/foo', [ 'future' => true ]); $res = $client->send($request); $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res); try { $res->wait(); $this->fail('did not throw'); } catch (RequestException $e) { $this->assertContains('404', $e->getMessage()); } } public function testReturnsFutureForResponseWhenRequested() { $client = new Client(['handler' => new MockHandler(['status' => 200])]); $request = $client->createRequest('GET', 'http://localhost:123/foo', [ 'future' => true ]); $res = $client->send($request); $this->assertInstanceOf('GuzzleHttp\Message\FutureResponse', $res); $this->assertEquals(200, $res->getStatusCode()); } } tests/CollectionTest.php000064400000031206151742731020011356 0ustar00coll = new Collection(); } public function testConstructorCanBeCalledWithNoParams() { $this->coll = new Collection(); $p = $this->coll->toArray(); $this->assertEmpty($p, '-> Collection must be empty when no data is passed'); } public function testConstructorCanBeCalledWithParams() { $testData = array( 'test' => 'value', 'test_2' => 'value2' ); $this->coll = new Collection($testData); $this->assertEquals($this->coll->toArray(), $testData); $this->assertEquals($this->coll->toArray(), $this->coll->toArray()); } public function testImplementsIteratorAggregate() { $this->coll->set('key', 'value'); $this->assertInstanceOf('ArrayIterator', $this->coll->getIterator()); $this->assertEquals(1, count($this->coll)); $total = 0; foreach ($this->coll as $key => $value) { $this->assertEquals('key', $key); $this->assertEquals('value', $value); $total++; } $this->assertEquals(1, $total); } public function testCanAddValuesToExistingKeysByUsingArray() { $this->coll->add('test', 'value1'); $this->assertEquals($this->coll->toArray(), array('test' => 'value1')); $this->coll->add('test', 'value2'); $this->assertEquals($this->coll->toArray(), array('test' => array('value1', 'value2'))); $this->coll->add('test', 'value3'); $this->assertEquals($this->coll->toArray(), array('test' => array('value1', 'value2', 'value3'))); } public function testHandlesMergingInDisparateDataSources() { $params = array( 'test' => 'value1', 'test2' => 'value2', 'test3' => array('value3', 'value4') ); $this->coll->merge($params); $this->assertEquals($this->coll->toArray(), $params); $this->coll->merge(new Collection(['test4' => 'hi'])); $this->assertEquals( $this->coll->toArray(), $params + ['test4' => 'hi'] ); } public function testCanClearAllDataOrSpecificKeys() { $this->coll->merge(array( 'test' => 'value1', 'test2' => 'value2' )); // Clear a specific parameter by name $this->coll->remove('test'); $this->assertEquals($this->coll->toArray(), array( 'test2' => 'value2' )); // Clear all parameters $this->coll->clear(); $this->assertEquals($this->coll->toArray(), array()); } public function testProvidesKeys() { $this->assertEquals(array(), $this->coll->getKeys()); $this->coll->merge(array( 'test1' => 'value1', 'test2' => 'value2' )); $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); // Returns the cached array previously returned $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); $this->coll->remove('test1'); $this->assertEquals(array('test2'), $this->coll->getKeys()); $this->coll->add('test3', 'value3'); $this->assertEquals(array('test2', 'test3'), $this->coll->getKeys()); } public function testChecksIfHasKey() { $this->assertFalse($this->coll->hasKey('test')); $this->coll->add('test', 'value'); $this->assertEquals(true, $this->coll->hasKey('test')); $this->coll->add('test2', 'value2'); $this->assertEquals(true, $this->coll->hasKey('test')); $this->assertEquals(true, $this->coll->hasKey('test2')); $this->assertFalse($this->coll->hasKey('testing')); $this->assertEquals(false, $this->coll->hasKey('AB-C', 'junk')); } public function testChecksIfHasValue() { $this->assertFalse($this->coll->hasValue('value')); $this->coll->add('test', 'value'); $this->assertEquals('test', $this->coll->hasValue('value')); $this->coll->add('test2', 'value2'); $this->assertEquals('test', $this->coll->hasValue('value')); $this->assertEquals('test2', $this->coll->hasValue('value2')); $this->assertFalse($this->coll->hasValue('val')); } public function testImplementsCount() { $data = new Collection(); $this->assertEquals(0, $data->count()); $data->add('key', 'value'); $this->assertEquals(1, count($data)); $data->add('key', 'value2'); $this->assertEquals(1, count($data)); $data->add('key_2', 'value3'); $this->assertEquals(2, count($data)); } public function testAddParamsByMerging() { $params = array( 'test' => 'value1', 'test2' => 'value2', 'test3' => array('value3', 'value4') ); // Add some parameters $this->coll->merge($params); // Add more parameters by merging them in $this->coll->merge(array( 'test' => 'another', 'different_key' => 'new value' )); $this->assertEquals(array( 'test' => array('value1', 'another'), 'test2' => 'value2', 'test3' => array('value3', 'value4'), 'different_key' => 'new value' ), $this->coll->toArray()); } public function testAllowsFunctionalFilter() { $this->coll->merge(array( 'fruit' => 'apple', 'number' => 'ten', 'prepositions' => array('about', 'above', 'across', 'after'), 'same_number' => 'ten' )); $filtered = $this->coll->filter(function ($key, $value) { return $value == 'ten'; }); $this->assertNotSame($filtered, $this->coll); $this->assertEquals(array( 'number' => 'ten', 'same_number' => 'ten' ), $filtered->toArray()); } public function testAllowsFunctionalMapping() { $this->coll->merge(array( 'number_1' => 1, 'number_2' => 2, 'number_3' => 3 )); $mapped = $this->coll->map(function ($key, $value) { return $value * $value; }); $this->assertNotSame($mapped, $this->coll); $this->assertEquals(array( 'number_1' => 1, 'number_2' => 4, 'number_3' => 9 ), $mapped->toArray()); } public function testImplementsArrayAccess() { $this->coll->merge(array( 'k1' => 'v1', 'k2' => 'v2' )); $this->assertTrue($this->coll->offsetExists('k1')); $this->assertFalse($this->coll->offsetExists('Krull')); $this->coll->offsetSet('k3', 'v3'); $this->assertEquals('v3', $this->coll->offsetGet('k3')); $this->assertEquals('v3', $this->coll->get('k3')); $this->coll->offsetUnset('k1'); $this->assertFalse($this->coll->offsetExists('k1')); } public function testCanReplaceAllData() { $this->coll->replace(array('a' => '123')); $this->assertEquals(array('a' => '123'), $this->coll->toArray()); } public function testPreparesFromConfig() { $c = Collection::fromConfig(array( 'a' => '123', 'base_url' => 'http://www.test.com/' ), array( 'a' => 'xyz', 'b' => 'lol' ), array('a')); $this->assertInstanceOf('GuzzleHttp\Collection', $c); $this->assertEquals(array( 'a' => '123', 'b' => 'lol', 'base_url' => 'http://www.test.com/' ), $c->toArray()); try { $c = Collection::fromConfig(array(), array(), array('a')); $this->fail('Exception not throw when missing config'); } catch (\InvalidArgumentException $e) { } } function falseyDataProvider() { return array( array(false, false), array(null, null), array('', ''), array(array(), array()), array(0, 0), ); } /** * @dataProvider falseyDataProvider */ public function testReturnsCorrectData($a, $b) { $c = new Collection(array('value' => $a)); $this->assertSame($b, $c->get('value')); } public function testRetrievesNestedKeysUsingPath() { $data = array( 'foo' => 'bar', 'baz' => array( 'mesa' => array( 'jar' => 'jar' ) ) ); $collection = new Collection($data); $this->assertEquals('bar', $collection->getPath('foo')); $this->assertEquals('jar', $collection->getPath('baz/mesa/jar')); $this->assertNull($collection->getPath('wewewf')); $this->assertNull($collection->getPath('baz/mesa/jar/jar')); } public function testFalseyKeysStillDescend() { $collection = new Collection(array( '0' => array( 'a' => 'jar' ), 1 => 'other' )); $this->assertEquals('jar', $collection->getPath('0/a')); $this->assertEquals('other', $collection->getPath('1')); } public function getPathProvider() { $data = array( 'foo' => 'bar', 'baz' => array( 'mesa' => array( 'jar' => 'jar', 'array' => array('a', 'b', 'c') ), 'bar' => array( 'baz' => 'bam', 'array' => array('d', 'e', 'f') ) ), 'bam' => array( array('foo' => 1), array('foo' => 2), array('array' => array('h', 'i')) ) ); $c = new Collection($data); return array( // Simple path selectors array($c, 'foo', 'bar'), array($c, 'baz', $data['baz']), array($c, 'bam', $data['bam']), array($c, 'baz/mesa', $data['baz']['mesa']), array($c, 'baz/mesa/jar', 'jar'), // Does not barf on missing keys array($c, 'fefwfw', null), array($c, 'baz/mesa/array', $data['baz']['mesa']['array']) ); } /** * @dataProvider getPathProvider */ public function testGetPath(Collection $c, $path, $expected, $separator = '/') { $this->assertEquals($expected, $c->getPath($path, $separator)); } public function testOverridesSettings() { $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); $c->overwriteWith(array('foo' => 10, 'bar' => 300)); $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray()); } public function testOverwriteWithCollection() { $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); $b = new Collection(array('foo' => 10, 'bar' => 300)); $c->overwriteWith($b); $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray()); } public function testOverwriteWithTraversable() { $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); $b = new Collection(array('foo' => 10, 'bar' => 300)); $c->overwriteWith($b->getIterator()); $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->toArray()); } public function testCanSetNestedPathValueThatDoesNotExist() { $c = new Collection(array()); $c->setPath('foo/bar/baz/123', 'hi'); $this->assertEquals('hi', $c['foo']['bar']['baz']['123']); } public function testCanSetNestedPathValueThatExists() { $c = new Collection(array('foo' => array('bar' => 'test'))); $c->setPath('foo/bar', 'hi'); $this->assertEquals('hi', $c['foo']['bar']); } /** * @expectedException \RuntimeException */ public function testVerifiesNestedPathIsValidAtExactLevel() { $c = new Collection(array('foo' => 'bar')); $c->setPath('foo/bar', 'hi'); $this->assertEquals('hi', $c['foo']['bar']); } /** * @expectedException \RuntimeException */ public function testVerifiesThatNestedPathIsValidAtAnyLevel() { $c = new Collection(array('foo' => 'bar')); $c->setPath('foo/bar/baz', 'test'); } public function testCanAppendToNestedPathValues() { $c = new Collection(); $c->setPath('foo/bar/[]', 'a'); $c->setPath('foo/bar/[]', 'b'); $this->assertEquals(['a', 'b'], $c['foo']['bar']); } } tests/Cookie/CookieJarTest.php000064400000026577151742731020012361 0ustar00jar = new CookieJar(); } protected function getTestCookies() { return [ new SetCookie(['Name' => 'foo', 'Value' => 'bar', 'Domain' => 'foo.com', 'Path' => '/', 'Discard' => true]), new SetCookie(['Name' => 'test', 'Value' => '123', 'Domain' => 'baz.com', 'Path' => '/foo', 'Expires' => 2]), new SetCookie(['Name' => 'you', 'Value' => '123', 'Domain' => 'bar.com', 'Path' => '/boo', 'Expires' => time() + 1000]) ]; } public function testQuotesBadCookieValues() { $this->assertEquals('foo', CookieJar::getCookieValue('foo')); $this->assertEquals('"foo,bar"', CookieJar::getCookieValue('foo,bar')); } public function testCreatesFromArray() { $jar = CookieJar::fromArray([ 'foo' => 'bar', 'baz' => 'bam' ], 'example.com'); $this->assertCount(2, $jar); } /** * Provides test data for cookie cookieJar retrieval */ public function getCookiesDataProvider() { return [ [['foo', 'baz', 'test', 'muppet', 'googoo'], '', '', '', false], [['foo', 'baz', 'muppet', 'googoo'], '', '', '', true], [['googoo'], 'www.example.com', '', '', false], [['muppet', 'googoo'], 'test.y.example.com', '', '', false], [['foo', 'baz'], 'example.com', '', '', false], [['muppet'], 'x.y.example.com', '/acme/', '', false], [['muppet'], 'x.y.example.com', '/acme/test/', '', false], [['googoo'], 'x.y.example.com', '/test/acme/test/', '', false], [['foo', 'baz'], 'example.com', '', '', false], [['baz'], 'example.com', '', 'baz', false], ]; } public function testStoresAndRetrievesCookies() { $cookies = $this->getTestCookies(); foreach ($cookies as $cookie) { $this->assertTrue($this->jar->setCookie($cookie)); } $this->assertEquals(3, count($this->jar)); $this->assertEquals(3, count($this->jar->getIterator())); $this->assertEquals($cookies, $this->jar->getIterator()->getArrayCopy()); } public function testRemovesTemporaryCookies() { $cookies = $this->getTestCookies(); foreach ($this->getTestCookies() as $cookie) { $this->jar->setCookie($cookie); } $this->jar->clearSessionCookies(); $this->assertEquals( [$cookies[1], $cookies[2]], $this->jar->getIterator()->getArrayCopy() ); } public function testRemovesSelectively() { foreach ($this->getTestCookies() as $cookie) { $this->jar->setCookie($cookie); } // Remove foo.com cookies $this->jar->clear('foo.com'); $this->assertEquals(2, count($this->jar)); // Try again, removing no further cookies $this->jar->clear('foo.com'); $this->assertEquals(2, count($this->jar)); // Remove bar.com cookies with path of /boo $this->jar->clear('bar.com', '/boo'); $this->assertEquals(1, count($this->jar)); // Remove cookie by name $this->jar->clear(null, null, 'test'); $this->assertEquals(0, count($this->jar)); } public function testDoesNotAddIncompleteCookies() { $this->assertEquals(false, $this->jar->setCookie(new SetCookie())); $this->assertFalse($this->jar->setCookie(new SetCookie(array( 'Name' => 'foo' )))); $this->assertFalse($this->jar->setCookie(new SetCookie(array( 'Name' => false )))); $this->assertFalse($this->jar->setCookie(new SetCookie(array( 'Name' => true )))); $this->assertFalse($this->jar->setCookie(new SetCookie(array( 'Name' => 'foo', 'Domain' => 'foo.com' )))); } public function testDoesAddValidCookies() { $this->assertTrue($this->jar->setCookie(new SetCookie(array( 'Name' => 'foo', 'Domain' => 'foo.com', 'Value' => 0 )))); $this->assertTrue($this->jar->setCookie(new SetCookie(array( 'Name' => 'foo', 'Domain' => 'foo.com', 'Value' => 0.0 )))); $this->assertTrue($this->jar->setCookie(new SetCookie(array( 'Name' => 'foo', 'Domain' => 'foo.com', 'Value' => '0' )))); } public function testOverwritesCookiesThatAreOlderOrDiscardable() { $t = time() + 1000; $data = array( 'Name' => 'foo', 'Value' => 'bar', 'Domain' => '.example.com', 'Path' => '/', 'Max-Age' => '86400', 'Secure' => true, 'Discard' => true, 'Expires' => $t ); // Make sure that the discard cookie is overridden with the non-discard $this->assertTrue($this->jar->setCookie(new SetCookie($data))); $this->assertEquals(1, count($this->jar)); $data['Discard'] = false; $this->assertTrue($this->jar->setCookie(new SetCookie($data))); $this->assertEquals(1, count($this->jar)); $c = $this->jar->getIterator()->getArrayCopy(); $this->assertEquals(false, $c[0]->getDiscard()); // Make sure it doesn't duplicate the cookie $this->jar->setCookie(new SetCookie($data)); $this->assertEquals(1, count($this->jar)); // Make sure the more future-ful expiration date supersede the other $data['Expires'] = time() + 2000; $this->assertTrue($this->jar->setCookie(new SetCookie($data))); $this->assertEquals(1, count($this->jar)); $c = $this->jar->getIterator()->getArrayCopy(); $this->assertNotEquals($t, $c[0]->getExpires()); } public function testOverwritesCookiesThatHaveChanged() { $t = time() + 1000; $data = array( 'Name' => 'foo', 'Value' => 'bar', 'Domain' => '.example.com', 'Path' => '/', 'Max-Age' => '86400', 'Secure' => true, 'Discard' => true, 'Expires' => $t ); // Make sure that the discard cookie is overridden with the non-discard $this->assertTrue($this->jar->setCookie(new SetCookie($data))); $data['Value'] = 'boo'; $this->assertTrue($this->jar->setCookie(new SetCookie($data))); $this->assertEquals(1, count($this->jar)); // Changing the value plus a parameter also must overwrite the existing one $data['Value'] = 'zoo'; $data['Secure'] = false; $this->assertTrue($this->jar->setCookie(new SetCookie($data))); $this->assertEquals(1, count($this->jar)); $c = $this->jar->getIterator()->getArrayCopy(); $this->assertEquals('zoo', $c[0]->getValue()); } public function testAddsCookiesFromResponseWithRequest() { $response = new Response(200, array( 'Set-Cookie' => "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT;" )); $request = new Request('GET', 'http://www.example.com'); $this->jar->extractCookies($request, $response); $this->assertEquals(1, count($this->jar)); } public function getMatchingCookiesDataProvider() { return array( array('https://example.com', 'foo=bar; baz=foobar'), array('http://example.com', ''), array('https://example.com:8912', 'foo=bar; baz=foobar'), array('https://foo.example.com', 'foo=bar; baz=foobar'), array('http://foo.example.com/test/acme/', 'googoo=gaga') ); } /** * @dataProvider getMatchingCookiesDataProvider */ public function testReturnsCookiesMatchingRequests($url, $cookies) { $bag = [ new SetCookie([ 'Name' => 'foo', 'Value' => 'bar', 'Domain' => 'example.com', 'Path' => '/', 'Max-Age' => '86400', 'Secure' => true ]), new SetCookie([ 'Name' => 'baz', 'Value' => 'foobar', 'Domain' => 'example.com', 'Path' => '/', 'Max-Age' => '86400', 'Secure' => true ]), new SetCookie([ 'Name' => 'test', 'Value' => '123', 'Domain' => 'www.foobar.com', 'Path' => '/path/', 'Discard' => true ]), new SetCookie([ 'Name' => 'muppet', 'Value' => 'cookie_monster', 'Domain' => '.y.example.com', 'Path' => '/acme/', 'Expires' => time() + 86400 ]), new SetCookie([ 'Name' => 'googoo', 'Value' => 'gaga', 'Domain' => '.example.com', 'Path' => '/test/acme/', 'Max-Age' => 1500 ]) ]; foreach ($bag as $cookie) { $this->jar->setCookie($cookie); } $request = new Request('GET', $url); $this->jar->addCookieHeader($request); $this->assertEquals($cookies, $request->getHeader('Cookie')); } /** * @expectedException \RuntimeException * @expectedExceptionMessage Invalid cookie: Cookie name must not cannot invalid characters: */ public function testThrowsExceptionWithStrictMode() { $a = new CookieJar(true); $a->setCookie(new SetCookie(['Name' => "abc\n", 'Value' => 'foo', 'Domain' => 'bar'])); } public function testDeletesCookiesByName() { $cookies = $this->getTestCookies(); $cookies[] = new SetCookie([ 'Name' => 'other', 'Value' => '123', 'Domain' => 'bar.com', 'Path' => '/boo', 'Expires' => time() + 1000 ]); $jar = new CookieJar(); foreach ($cookies as $cookie) { $jar->setCookie($cookie); } $this->assertCount(4, $jar); $jar->clear('bar.com', '/boo', 'other'); $this->assertCount(3, $jar); $names = array_map(function (SetCookie $c) { return $c->getName(); }, $jar->getIterator()->getArrayCopy()); $this->assertEquals(['foo', 'test', 'you'], $names); } public function testCanConvertToAndLoadFromArray() { $jar = new CookieJar(true); foreach ($this->getTestCookies() as $cookie) { $jar->setCookie($cookie); } $this->assertCount(3, $jar); $arr = $jar->toArray(); $this->assertCount(3, $arr); $newCookieJar = new CookieJar(false, $arr); $this->assertCount(3, $newCookieJar); $this->assertSame($jar->toArray(), $newCookieJar->toArray()); } } tests/Cookie/FileCookieJarTest.php000064400000003461151742731020013144 0ustar00file = tempnam('/tmp', 'file-cookies'); } /** * @expectedException \RuntimeException */ public function testValidatesCookieFile() { file_put_contents($this->file, 'true'); new FileCookieJar($this->file); } public function testLoadsFromFileFile() { $jar = new FileCookieJar($this->file); $this->assertEquals([], $jar->getIterator()->getArrayCopy()); unlink($this->file); } public function testPersistsToFileFile() { $jar = new FileCookieJar($this->file); $jar->setCookie(new SetCookie([ 'Name' => 'foo', 'Value' => 'bar', 'Domain' => 'foo.com', 'Expires' => time() + 1000 ])); $jar->setCookie(new SetCookie([ 'Name' => 'baz', 'Value' => 'bar', 'Domain' => 'foo.com', 'Expires' => time() + 1000 ])); $jar->setCookie(new SetCookie([ 'Name' => 'boo', 'Value' => 'bar', 'Domain' => 'foo.com', ])); $this->assertEquals(3, count($jar)); unset($jar); // Make sure it wrote to the file $contents = file_get_contents($this->file); $this->assertNotEmpty($contents); // Load the cookieJar from the file $jar = new FileCookieJar($this->file); // Weeds out temporary and session cookies $this->assertEquals(2, count($jar)); unset($jar); unlink($this->file); } } tests/Cookie/SessionCookieJarTest.php000064400000003726151742731020013714 0ustar00sessionVar = 'sessionKey'; if (!isset($_SESSION)) { $_SESSION = array(); } } /** * @expectedException \RuntimeException */ public function testValidatesCookieSession() { $_SESSION[$this->sessionVar] = 'true'; new SessionCookieJar($this->sessionVar); } public function testLoadsFromSession() { $jar = new SessionCookieJar($this->sessionVar); $this->assertEquals([], $jar->getIterator()->getArrayCopy()); unset($_SESSION[$this->sessionVar]); } public function testPersistsToSession() { $jar = new SessionCookieJar($this->sessionVar); $jar->setCookie(new SetCookie([ 'Name' => 'foo', 'Value' => 'bar', 'Domain' => 'foo.com', 'Expires' => time() + 1000 ])); $jar->setCookie(new SetCookie([ 'Name' => 'baz', 'Value' => 'bar', 'Domain' => 'foo.com', 'Expires' => time() + 1000 ])); $jar->setCookie(new SetCookie([ 'Name' => 'boo', 'Value' => 'bar', 'Domain' => 'foo.com', ])); $this->assertEquals(3, count($jar)); unset($jar); // Make sure it wrote to the sessionVar in $_SESSION $contents = $_SESSION[$this->sessionVar]; $this->assertNotEmpty($contents); // Load the cookieJar from the file $jar = new SessionCookieJar($this->sessionVar); // Weeds out temporary and session cookies $this->assertEquals(2, count($jar)); unset($jar); unset($_SESSION[$this->sessionVar]); } } tests/Cookie/SetCookieTest.php000064400000031741151742731020012365 0ustar00assertEquals('/', $cookie->getPath()); } public function testConvertsDateTimeMaxAgeToUnixTimestamp() { $cookie = new SetCookie(['Expires' => 'November 20, 1984']); $this->assertInternalType('integer', $cookie->getExpires()); } public function testAddsExpiresBasedOnMaxAge() { $t = time(); $cookie = new SetCookie(['Max-Age' => 100]); $this->assertEquals($t + 100, $cookie->getExpires()); } public function testHoldsValues() { $t = time(); $data = array( 'Name' => 'foo', 'Value' => 'baz', 'Path' => '/bar', 'Domain' => 'baz.com', 'Expires' => $t, 'Max-Age' => 100, 'Secure' => true, 'Discard' => true, 'HttpOnly' => true, 'foo' => 'baz', 'bar' => 'bam' ); $cookie = new SetCookie($data); $this->assertEquals($data, $cookie->toArray()); $this->assertEquals('foo', $cookie->getName()); $this->assertEquals('baz', $cookie->getValue()); $this->assertEquals('baz.com', $cookie->getDomain()); $this->assertEquals('/bar', $cookie->getPath()); $this->assertEquals($t, $cookie->getExpires()); $this->assertEquals(100, $cookie->getMaxAge()); $this->assertTrue($cookie->getSecure()); $this->assertTrue($cookie->getDiscard()); $this->assertTrue($cookie->getHttpOnly()); $this->assertEquals('baz', $cookie->toArray()['foo']); $this->assertEquals('bam', $cookie->toArray()['bar']); $cookie->setName('a'); $cookie->setValue('b'); $cookie->setPath('c'); $cookie->setDomain('bar.com'); $cookie->setExpires(10); $cookie->setMaxAge(200); $cookie->setSecure(false); $cookie->setHttpOnly(false); $cookie->setDiscard(false); $this->assertEquals('a', $cookie->getName()); $this->assertEquals('b', $cookie->getValue()); $this->assertEquals('c', $cookie->getPath()); $this->assertEquals('bar.com', $cookie->getDomain()); $this->assertEquals(10, $cookie->getExpires()); $this->assertEquals(200, $cookie->getMaxAge()); $this->assertFalse($cookie->getSecure()); $this->assertFalse($cookie->getDiscard()); $this->assertFalse($cookie->getHttpOnly()); } public function testDeterminesIfExpired() { $c = new SetCookie(); $c->setExpires(10); $this->assertTrue($c->isExpired()); $c->setExpires(time() + 10000); $this->assertFalse($c->isExpired()); } public function testMatchesDomain() { $cookie = new SetCookie(); $this->assertTrue($cookie->matchesDomain('baz.com')); $cookie->setDomain('baz.com'); $this->assertTrue($cookie->matchesDomain('baz.com')); $this->assertFalse($cookie->matchesDomain('bar.com')); $cookie->setDomain('.baz.com'); $this->assertTrue($cookie->matchesDomain('.baz.com')); $this->assertTrue($cookie->matchesDomain('foo.baz.com')); $this->assertFalse($cookie->matchesDomain('baz.bar.com')); $this->assertTrue($cookie->matchesDomain('baz.com')); $cookie->setDomain('.127.0.0.1'); $this->assertTrue($cookie->matchesDomain('127.0.0.1')); $cookie->setDomain('127.0.0.1'); $this->assertTrue($cookie->matchesDomain('127.0.0.1')); $cookie->setDomain('.com.'); $this->assertFalse($cookie->matchesDomain('baz.com')); $cookie->setDomain('.local'); $this->assertTrue($cookie->matchesDomain('example.local')); } public function testMatchesPath() { $cookie = new SetCookie(); $this->assertTrue($cookie->matchesPath('/foo')); $cookie->setPath('/foo'); $this->assertTrue($cookie->matchesPath('/foo')); $this->assertTrue($cookie->matchesPath('/foo/bar')); $this->assertFalse($cookie->matchesPath('/bar')); } public function cookieValidateProvider() { return array( array('foo', 'baz', 'bar', true), array('0', '0', '0', true), array('', 'baz', 'bar', 'The cookie name must not be empty'), array('foo', '', 'bar', 'The cookie value must not be empty'), array('foo', 'baz', '', 'The cookie domain must not be empty'), array("foo\r", 'baz', '0', 'Cookie name must not cannot invalid characters: =,; \t\r\n\013\014'), ); } /** * @dataProvider cookieValidateProvider */ public function testValidatesCookies($name, $value, $domain, $result) { $cookie = new SetCookie(array( 'Name' => $name, 'Value' => $value, 'Domain' => $domain )); $this->assertSame($result, $cookie->validate()); } public function testDoesNotMatchIp() { $cookie = new SetCookie(['Domain' => '192.168.16.']); $this->assertFalse($cookie->matchesDomain('192.168.16.121')); } public function testConvertsToString() { $t = 1382916008; $cookie = new SetCookie([ 'Name' => 'test', 'Value' => '123', 'Domain' => 'foo.com', 'Expires' => $t, 'Path' => '/abc', 'HttpOnly' => true, 'Secure' => true ]); $this->assertEquals( 'test=123; Domain=foo.com; Path=/abc; Expires=Sun, 27 Oct 2013 23:20:08 GMT; Secure; HttpOnly', (string) $cookie ); } /** * Provides the parsed information from a cookie * * @return array */ public function cookieParserDataProvider() { return array( array( 'ASIHTTPRequestTestCookie=This+is+the+value; expires=Sat, 26-Jul-2008 17:00:42 GMT; path=/tests; domain=allseeing-i.com; PHPSESSID=6c951590e7a9359bcedde25cda73e43c; path=/";', array( 'Domain' => 'allseeing-i.com', 'Path' => '/', 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c', 'Max-Age' => NULL, 'Expires' => 'Sat, 26-Jul-2008 17:00:42 GMT', 'Secure' => NULL, 'Discard' => NULL, 'Name' => 'ASIHTTPRequestTestCookie', 'Value' => 'This+is+the+value', 'HttpOnly' => false ) ), array('', []), array('foo', []), // Test setting a blank value for a cookie array(array( 'foo=', 'foo =', 'foo =;', 'foo= ;', 'foo =', 'foo= '), array( 'Name' => 'foo', 'Value' => '', 'Discard' => null, 'Domain' => null, 'Expires' => null, 'Max-Age' => null, 'Path' => '/', 'Secure' => null, 'HttpOnly' => false ) ), // Test setting a value and removing quotes array(array( 'foo=1', 'foo =1', 'foo =1;', 'foo=1 ;', 'foo =1', 'foo= 1', 'foo = 1 ;', 'foo="1"', 'foo="1";', 'foo= "1";'), array( 'Name' => 'foo', 'Value' => '1', 'Discard' => null, 'Domain' => null, 'Expires' => null, 'Max-Age' => null, 'Path' => '/', 'Secure' => null, 'HttpOnly' => false ) ), // Some of the following tests are based on http://framework.zend.com/svn/framework/standard/trunk/tests/Zend/Http/CookieTest.php array( 'justacookie=foo; domain=example.com', array( 'Name' => 'justacookie', 'Value' => 'foo', 'Domain' => 'example.com', 'Discard' => null, 'Expires' => null, 'Max-Age' => null, 'Path' => '/', 'Secure' => null, 'HttpOnly' => false ) ), array( 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com', array( 'Name' => 'expires', 'Value' => 'tomorrow', 'Domain' => '.example.com', 'Path' => '/Space Out/', 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', 'Discard' => null, 'Secure' => true, 'Max-Age' => null, 'HttpOnly' => false ) ), array( 'domain=unittests; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=example.com; path=/some value/', array( 'Name' => 'domain', 'Value' => 'unittests', 'Domain' => 'example.com', 'Path' => '/some value/', 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', 'Secure' => false, 'Discard' => null, 'Max-Age' => null, 'HttpOnly' => false ) ), array( 'path=indexAction; path=/; domain=.foo.com; expires=Tue, 21-Nov-2006 08:33:44 GMT', array( 'Name' => 'path', 'Value' => 'indexAction', 'Domain' => '.foo.com', 'Path' => '/', 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', 'Secure' => false, 'Discard' => null, 'Max-Age' => null, 'HttpOnly' => false ) ), array( 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com; version=1; Max-Age=86400', array( 'Name' => 'secure', 'Value' => 'sha1', 'Domain' => 'some.really.deep.domain.com', 'Path' => '/', 'Secure' => true, 'Discard' => null, 'Expires' => time() + 86400, 'Max-Age' => 86400, 'HttpOnly' => false, 'version' => '1' ) ), array( 'PHPSESSID=123456789+abcd%2Cef; secure; discard; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;', array( 'Name' => 'PHPSESSID', 'Value' => '123456789+abcd%2Cef', 'Domain' => '.localdomain', 'Path' => '/foo/baz', 'Expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', 'Secure' => true, 'Discard' => true, 'Max-Age' => null, 'HttpOnly' => false ) ), ); } /** * @dataProvider cookieParserDataProvider */ public function testParseCookie($cookie, $parsed) { foreach ((array) $cookie as $v) { $c = SetCookie::fromString($v); $p = $c->toArray(); if (isset($p['Expires'])) { // Remove expires values from the assertion if they are relatively equal if (abs($p['Expires'] != strtotime($parsed['Expires'])) < 40) { unset($p['Expires']); unset($parsed['Expires']); } } if (!empty($parsed)) { foreach ($parsed as $key => $value) { $this->assertEquals($parsed[$key], $p[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); } foreach ($p as $key => $value) { $this->assertEquals($p[$key], $parsed[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); } } else { $this->assertEquals([ 'Name' => null, 'Value' => null, 'Domain' => null, 'Path' => '/', 'Max-Age' => null, 'Expires' => null, 'Secure' => false, 'Discard' => false, 'HttpOnly' => false, ], $p); } } } } tests/Event/AbstractEventTest.php000064400000000634151742731020013112 0ustar00getMockBuilder('GuzzleHttp\Event\AbstractEvent') ->getMockForAbstractClass(); $this->assertFalse($e->isPropagationStopped()); $e->stopPropagation(); $this->assertTrue($e->isPropagationStopped()); } } tests/Event/AbstractRequestEventTest.php000064400000002055151742731020014462 0ustar00getMockBuilder('GuzzleHttp\Event\AbstractRequestEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $this->assertSame($t->client, $e->getClient()); $this->assertSame($t->request, $e->getRequest()); } public function testHasTransaction() { $t = new Transaction(new Client(), new Request('GET', '/')); $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRequestEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $r = new \ReflectionMethod($e, 'getTransaction'); $r->setAccessible(true); $this->assertSame($t, $r->invoke($e)); } } tests/Event/AbstractRetryableEventTest.php000064400000002321151742731020014757 0ustar00transferInfo = ['foo' => 'bar']; $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRetryableEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $e->retry(); $this->assertTrue($e->isPropagationStopped()); $this->assertEquals('before', $t->state); } public function testCanRetryAfterDelay() { $t = new Transaction(new Client(), new Request('GET', '/')); $t->transferInfo = ['foo' => 'bar']; $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractRetryableEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $e->retry(10); $this->assertTrue($e->isPropagationStopped()); $this->assertEquals('before', $t->state); $this->assertEquals(10, $t->request->getConfig()->get('delay')); } } tests/Event/AbstractTransferEventTest.php000064400000003301151742731020014611 0ustar00transferInfo = ['foo' => 'bar']; $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $this->assertNull($e->getTransferInfo('baz')); $this->assertEquals('bar', $e->getTransferInfo('foo')); $this->assertEquals($t->transferInfo, $e->getTransferInfo()); } public function testHasResponse() { $t = new Transaction(new Client(), new Request('GET', '/')); $t->response = new Response(200); $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $this->assertTrue($e->hasResponse()); $this->assertSame($t->response, $e->getResponse()); } public function testCanInterceptWithResponse() { $t = new Transaction(new Client(), new Request('GET', '/')); $r = new Response(200); $e = $this->getMockBuilder('GuzzleHttp\Event\AbstractTransferEvent') ->setConstructorArgs([$t]) ->getMockForAbstractClass(); $e->intercept($r); $this->assertSame($t->response, $r); $this->assertSame($t->response, $e->getResponse()); $this->assertTrue($e->isPropagationStopped()); } } tests/Event/BeforeEventTest.php000064400000001334151742731020012547 0ustar00exception = new \Exception('foo'); $e = new BeforeEvent($t); $response = new Response(200); $e->intercept($response); $this->assertTrue($e->isPropagationStopped()); $this->assertSame($t->response, $response); $this->assertNull($t->exception); } } tests/Event/EmitterTest.php000064400000030146151742731020011757 0ustar00emitter = new Emitter(); $this->listener = new TestEventListener(); } protected function tearDown() { $this->emitter = null; $this->listener = null; } public function testInitialState() { $this->assertEquals(array(), $this->emitter->listeners()); } public function testAddListener() { $this->emitter->on('pre.foo', array($this->listener, 'preFoo')); $this->emitter->on('post.foo', array($this->listener, 'postFoo')); $this->assertTrue($this->emitter->hasListeners(self::preFoo)); $this->assertTrue($this->emitter->hasListeners(self::preFoo)); $this->assertCount(1, $this->emitter->listeners(self::postFoo)); $this->assertCount(1, $this->emitter->listeners(self::postFoo)); $this->assertCount(2, $this->emitter->listeners()); } public function testGetListenersSortsByPriority() { $listener1 = new TestEventListener(); $listener2 = new TestEventListener(); $listener3 = new TestEventListener(); $listener1->name = '1'; $listener2->name = '2'; $listener3->name = '3'; $this->emitter->on('pre.foo', array($listener1, 'preFoo'), -10); $this->emitter->on('pre.foo', array($listener2, 'preFoo'), 10); $this->emitter->on('pre.foo', array($listener3, 'preFoo')); $expected = array( array($listener2, 'preFoo'), array($listener3, 'preFoo'), array($listener1, 'preFoo'), ); $this->assertSame($expected, $this->emitter->listeners('pre.foo')); } public function testGetAllListenersSortsByPriority() { $listener1 = new TestEventListener(); $listener2 = new TestEventListener(); $listener3 = new TestEventListener(); $listener4 = new TestEventListener(); $listener5 = new TestEventListener(); $listener6 = new TestEventListener(); $this->emitter->on('pre.foo', [$listener1, 'preFoo'], -10); $this->emitter->on('pre.foo', [$listener2, 'preFoo']); $this->emitter->on('pre.foo', [$listener3, 'preFoo'], 10); $this->emitter->on('post.foo', [$listener4, 'preFoo'], -10); $this->emitter->on('post.foo', [$listener5, 'preFoo']); $this->emitter->on('post.foo', [$listener6, 'preFoo'], 10); $expected = [ 'pre.foo' => [[$listener3, 'preFoo'], [$listener2, 'preFoo'], [$listener1, 'preFoo']], 'post.foo' => [[$listener6, 'preFoo'], [$listener5, 'preFoo'], [$listener4, 'preFoo']], ]; $this->assertSame($expected, $this->emitter->listeners()); } public function testDispatch() { $this->emitter->on('pre.foo', array($this->listener, 'preFoo')); $this->emitter->on('post.foo', array($this->listener, 'postFoo')); $this->emitter->emit(self::preFoo, $this->getEvent()); $this->assertTrue($this->listener->preFooInvoked); $this->assertFalse($this->listener->postFooInvoked); $this->assertInstanceOf('GuzzleHttp\Event\EventInterface', $this->emitter->emit(self::preFoo, $this->getEvent())); $event = $this->getEvent(); $return = $this->emitter->emit(self::preFoo, $event); $this->assertSame($event, $return); } public function testDispatchForClosure() { $invoked = 0; $listener = function () use (&$invoked) { $invoked++; }; $this->emitter->on('pre.foo', $listener); $this->emitter->on('post.foo', $listener); $this->emitter->emit(self::preFoo, $this->getEvent()); $this->assertEquals(1, $invoked); } public function testStopEventPropagation() { $otherListener = new TestEventListener(); // postFoo() stops the propagation, so only one listener should // be executed // Manually set priority to enforce $this->listener to be called first $this->emitter->on('post.foo', array($this->listener, 'postFoo'), 10); $this->emitter->on('post.foo', array($otherListener, 'preFoo')); $this->emitter->emit(self::postFoo, $this->getEvent()); $this->assertTrue($this->listener->postFooInvoked); $this->assertFalse($otherListener->postFooInvoked); } public function testDispatchByPriority() { $invoked = array(); $listener1 = function () use (&$invoked) { $invoked[] = '1'; }; $listener2 = function () use (&$invoked) { $invoked[] = '2'; }; $listener3 = function () use (&$invoked) { $invoked[] = '3'; }; $this->emitter->on('pre.foo', $listener1, -10); $this->emitter->on('pre.foo', $listener2); $this->emitter->on('pre.foo', $listener3, 10); $this->emitter->emit(self::preFoo, $this->getEvent()); $this->assertEquals(array('3', '2', '1'), $invoked); } public function testRemoveListener() { $this->emitter->on('pre.bar', [$this->listener, 'preFoo']); $this->assertNotEmpty($this->emitter->listeners(self::preBar)); $this->emitter->removeListener('pre.bar', [$this->listener, 'preFoo']); $this->assertEmpty($this->emitter->listeners(self::preBar)); $this->emitter->removeListener('notExists', [$this->listener, 'preFoo']); } public function testAddSubscriber() { $eventSubscriber = new TestEventSubscriber(); $this->emitter->attach($eventSubscriber); $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); $this->assertNotEmpty($this->emitter->listeners(self::postFoo)); } public function testAddSubscriberWithMultiple() { $eventSubscriber = new TestEventSubscriberWithMultiple(); $this->emitter->attach($eventSubscriber); $listeners = $this->emitter->listeners('pre.foo'); $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); $this->assertCount(2, $listeners); } public function testAddSubscriberWithPriorities() { $eventSubscriber = new TestEventSubscriber(); $this->emitter->attach($eventSubscriber); $eventSubscriber = new TestEventSubscriberWithPriorities(); $this->emitter->attach($eventSubscriber); $listeners = $this->emitter->listeners('pre.foo'); $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); $this->assertCount(2, $listeners); $this->assertInstanceOf('GuzzleHttp\Tests\Event\TestEventSubscriberWithPriorities', $listeners[0][0]); } public function testdetach() { $eventSubscriber = new TestEventSubscriber(); $this->emitter->attach($eventSubscriber); $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); $this->assertNotEmpty($this->emitter->listeners(self::postFoo)); $this->emitter->detach($eventSubscriber); $this->assertEmpty($this->emitter->listeners(self::preFoo)); $this->assertEmpty($this->emitter->listeners(self::postFoo)); } public function testdetachWithPriorities() { $eventSubscriber = new TestEventSubscriberWithPriorities(); $this->emitter->attach($eventSubscriber); $this->assertNotEmpty($this->emitter->listeners(self::preFoo)); $this->assertNotEmpty($this->emitter->listeners(self::postFoo)); $this->emitter->detach($eventSubscriber); $this->assertEmpty($this->emitter->listeners(self::preFoo)); $this->assertEmpty($this->emitter->listeners(self::postFoo)); } public function testEventReceivesEventNameAsArgument() { $listener = new TestWithDispatcher(); $this->emitter->on('test', array($listener, 'foo')); $this->assertNull($listener->name); $this->emitter->emit('test', $this->getEvent()); $this->assertEquals('test', $listener->name); } /** * @see https://bugs.php.net/bug.php?id=62976 * * This bug affects: * - The PHP 5.3 branch for versions < 5.3.18 * - The PHP 5.4 branch for versions < 5.4.8 * - The PHP 5.5 branch is not affected */ public function testWorkaroundForPhpBug62976() { $dispatcher = new Emitter(); $dispatcher->on('bug.62976', new CallableClass()); $dispatcher->removeListener('bug.62976', function () {}); $this->assertNotEmpty($dispatcher->listeners('bug.62976')); } public function testRegistersEventsOnce() { $this->emitter->once('pre.foo', array($this->listener, 'preFoo')); $this->emitter->on('pre.foo', array($this->listener, 'preFoo')); $this->assertCount(2, $this->emitter->listeners(self::preFoo)); $this->emitter->emit(self::preFoo, $this->getEvent()); $this->assertTrue($this->listener->preFooInvoked); $this->assertCount(1, $this->emitter->listeners(self::preFoo)); } public function testReturnsEmptyArrayForNonExistentEvent() { $this->assertEquals([], $this->emitter->listeners('doesnotexist')); } public function testCanAddFirstAndLastListeners() { $b = ''; $this->emitter->on('foo', function () use (&$b) { $b .= 'a'; }, 'first'); // 1 $this->emitter->on('foo', function () use (&$b) { $b .= 'b'; }, 'last'); // 0 $this->emitter->on('foo', function () use (&$b) { $b .= 'c'; }, 'first'); // 2 $this->emitter->on('foo', function () use (&$b) { $b .= 'd'; }, 'first'); // 3 $this->emitter->on('foo', function () use (&$b) { $b .= 'e'; }, 'first'); // 4 $this->emitter->on('foo', function () use (&$b) { $b .= 'f'; }); // 0 $this->emitter->emit('foo', $this->getEvent()); $this->assertEquals('edcabf', $b); } /** * @return \GuzzleHttp\Event\EventInterface */ private function getEvent() { return $this->getMockBuilder('GuzzleHttp\Event\AbstractEvent') ->getMockForAbstractClass(); } } class CallableClass { public function __invoke() { } } class TestEventListener { public $preFooInvoked = false; public $postFooInvoked = false; /* Listener methods */ public function preFoo(EventInterface $e) { $this->preFooInvoked = true; } public function postFoo(EventInterface $e) { $this->postFooInvoked = true; $e->stopPropagation(); } /** * @expectedException \PHPUnit_Framework_Error_Deprecated */ public function testHasDeprecatedAddListener() { $emitter = new Emitter(); $emitter->addListener('foo', function () {}); } /** * @expectedException \PHPUnit_Framework_Error_Deprecated */ public function testHasDeprecatedAddSubscriber() { $emitter = new Emitter(); $emitter->addSubscriber('foo', new TestEventSubscriber()); } } class TestWithDispatcher { public $name; public function foo(EventInterface $e, $name) { $this->name = $name; } } class TestEventSubscriber extends TestEventListener implements SubscriberInterface { public function getEvents() { return [ 'pre.foo' => ['preFoo'], 'post.foo' => ['postFoo'] ]; } } class TestEventSubscriberWithPriorities extends TestEventListener implements SubscriberInterface { public function getEvents() { return [ 'pre.foo' => ['preFoo', 10], 'post.foo' => ['postFoo'] ]; } } class TestEventSubscriberWithMultiple extends TestEventListener implements SubscriberInterface { public function getEvents() { return ['pre.foo' => [['preFoo', 10],['preFoo', 20]]]; } } tests/Event/ErrorEventTest.php000064400000001160151742731020012433 0ustar00request); $t->exception = $except; $e = new ErrorEvent($t); $this->assertSame($e->getException(), $t->exception); } } tests/Event/HasEmitterTraitTest.php000064400000001317151742731020013415 0ustar00getMockBuilder('GuzzleHttp\Tests\Event\AbstractHasEmitter') ->getMockForAbstractClass(); $result = $mock->getEmitter(); $this->assertInstanceOf('GuzzleHttp\Event\EmitterInterface', $result); $result2 = $mock->getEmitter(); $this->assertSame($result, $result2); } } tests/Event/ListenerAttacherTraitTest.php000064400000005600151742731020014610 0ustar00listeners = $this->prepareListeners($args, ['foo', 'bar']); $this->attachListeners($this, $this->listeners); } } class ListenerAttacherTraitTest extends \PHPUnit_Framework_TestCase { public function testRegistersEvents() { $fn = function () {}; $o = new ObjectWithEvents([ 'foo' => $fn, 'bar' => $fn, ]); $this->assertEquals([ ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false], ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false], ], $o->listeners); $this->assertCount(1, $o->getEmitter()->listeners('foo')); $this->assertCount(1, $o->getEmitter()->listeners('bar')); } public function testRegistersEventsWithPriorities() { $fn = function () {}; $o = new ObjectWithEvents([ 'foo' => ['fn' => $fn, 'priority' => 99, 'once' => true], 'bar' => ['fn' => $fn, 'priority' => 50], ]); $this->assertEquals([ ['name' => 'foo', 'fn' => $fn, 'priority' => 99, 'once' => true], ['name' => 'bar', 'fn' => $fn, 'priority' => 50, 'once' => false], ], $o->listeners); } public function testRegistersMultipleEvents() { $fn = function () {}; $eventArray = [['fn' => $fn], ['fn' => $fn]]; $o = new ObjectWithEvents([ 'foo' => $eventArray, 'bar' => $eventArray, ]); $this->assertEquals([ ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false], ['name' => 'foo', 'fn' => $fn, 'priority' => 0, 'once' => false], ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false], ['name' => 'bar', 'fn' => $fn, 'priority' => 0, 'once' => false], ], $o->listeners); $this->assertCount(2, $o->getEmitter()->listeners('foo')); $this->assertCount(2, $o->getEmitter()->listeners('bar')); } public function testRegistersEventsWithOnce() { $called = 0; $fn = function () use (&$called) { $called++; }; $o = new ObjectWithEvents(['foo' => ['fn' => $fn, 'once' => true]]); $ev = $this->getMock('GuzzleHttp\Event\EventInterface'); $o->getEmitter()->emit('foo', $ev); $o->getEmitter()->emit('foo', $ev); $this->assertEquals(1, $called); } /** * @expectedException \InvalidArgumentException */ public function testValidatesEvents() { $o = new ObjectWithEvents(['foo' => 'bar']); } } tests/Event/ProgressEventTest.php000064400000001373151742731020013154 0ustar00assertSame($t->request, $p->getRequest()); $this->assertSame($t->client, $p->getClient()); $this->assertEquals(2, $p->downloadSize); $this->assertEquals(1, $p->downloaded); $this->assertEquals(3, $p->uploadSize); $this->assertEquals(0, $p->uploaded); } } tests/Event/RequestEventsTest.php000064400000003345151742731020013164 0ustar00 [$cb]]], [ ['complete' => $cb], ['complete'], $cb, ['complete' => [$cb, $cb]] ], [ ['prepare' => []], ['error', 'foo'], $cb, [ 'prepare' => [], 'error' => [$cb], 'foo' => [$cb] ] ], [ ['prepare' => []], ['prepare'], $cb, [ 'prepare' => [$cb] ] ], [ ['prepare' => ['fn' => $cb]], ['prepare'], $cb, [ 'prepare' => [ ['fn' => $cb], $cb ] ] ], ]; } /** * @dataProvider prepareEventProvider */ public function testConvertsEventArrays( array $in, array $events, $add, array $out ) { $result = RequestEvents::convertEventArray($in, $events, $add); $this->assertEquals($out, $result); } /** * @expectedException \InvalidArgumentException */ public function testValidatesEventFormat() { RequestEvents::convertEventArray(['foo' => false], ['foo'], []); } } tests/Exception/ParseExceptionTest.php000064400000000731151742731020014151 0ustar00assertSame($res, $e->getResponse()); $this->assertEquals('foo', $e->getMessage()); } } tests/Exception/RequestExceptionTest.php000064400000005675151742731020014543 0ustar00assertSame($req, $e->getRequest()); $this->assertSame($res, $e->getResponse()); $this->assertTrue($e->hasResponse()); $this->assertEquals('foo', $e->getMessage()); } public function testCreatesGenerateException() { $e = RequestException::create(new Request('GET', '/')); $this->assertEquals('Error completing request', $e->getMessage()); $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e); } public function testCreatesClientErrorResponseException() { $e = RequestException::create(new Request('GET', '/'), new Response(400)); $this->assertEquals( 'Client error response [url] / [status code] 400 [reason phrase] Bad Request', $e->getMessage() ); $this->assertInstanceOf('GuzzleHttp\Exception\ClientException', $e); } public function testCreatesServerErrorResponseException() { $e = RequestException::create(new Request('GET', '/'), new Response(500)); $this->assertEquals( 'Server error response [url] / [status code] 500 [reason phrase] Internal Server Error', $e->getMessage() ); $this->assertInstanceOf('GuzzleHttp\Exception\ServerException', $e); } public function testCreatesGenericErrorResponseException() { $e = RequestException::create(new Request('GET', '/'), new Response(600)); $this->assertEquals( 'Unsuccessful response [url] / [status code] 600 [reason phrase] ', $e->getMessage() ); $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e); } public function testHasStatusCodeAsExceptionCode() { $e = RequestException::create(new Request('GET', '/'), new Response(442)); $this->assertEquals(442, $e->getCode()); } public function testWrapsRequestExceptions() { $e = new \Exception('foo'); $r = new Request('GET', 'http://www.oo.com'); $ex = RequestException::wrapException($r, $e); $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $ex); $this->assertSame($e, $ex->getPrevious()); } public function testWrapsConnectExceptions() { $e = new ConnectException('foo'); $r = new Request('GET', 'http://www.oo.com'); $ex = RequestException::wrapException($r, $e); $this->assertInstanceOf('GuzzleHttp\Exception\ConnectException', $ex); } } tests/Exception/XmlParseExceptionTest.php000064400000000725151742731020014635 0ustar00assertSame($error, $e->getError()); $this->assertEquals('foo', $e->getMessage()); } } tests/IntegrationTest.php000064400000004061151742731020011545 0ustar00createRequest( 'GET', Server::$url, [ 'timeout' => 1, 'connect_timeout' => 1, 'proxy' => 'http://127.0.0.1:123/foo' ] ); $events = []; $fn = function(AbstractTransferEvent $event) use (&$events) { $events[] = [ get_class($event), $event->hasResponse(), $event->getResponse() ]; }; $pool = new Pool($c, [$r], [ 'error' => $fn, 'end' => $fn ]); $pool->wait(); $this->assertCount(2, $events); $this->assertEquals('GuzzleHttp\Event\ErrorEvent', $events[0][0]); $this->assertFalse($events[0][1]); $this->assertNull($events[0][2]); $this->assertEquals('GuzzleHttp\Event\EndEvent', $events[1][0]); $this->assertFalse($events[1][1]); $this->assertNull($events[1][2]); } /** * @issue https://github.com/guzzle/guzzle/issues/866 */ public function testProperyGetsTransferStats() { $transfer = []; Server::enqueue([new Response(200)]); $c = new Client(); $response = $c->get(Server::$url . '/foo', [ 'events' => [ 'end' => function (EndEvent $e) use (&$transfer) { $transfer = $e->getTransferInfo(); } ] ]); $this->assertEquals(Server::$url . '/foo', $response->getEffectiveUrl()); $this->assertNotEmpty($transfer); $this->assertArrayHasKey('url', $transfer); } } tests/Message/AbstractMessageTest.php000064400000021306151742731020013717 0ustar00assertEquals(1.1, $m->getProtocolVersion()); } public function testHasHeaders() { $m = new Request('GET', 'http://foo.com'); $this->assertFalse($m->hasHeader('foo')); $m->addHeader('foo', 'bar'); $this->assertTrue($m->hasHeader('foo')); } public function testInitializesMessageWithProtocolVersionOption() { $m = new Request('GET', '/', [], null, [ 'protocol_version' => '10' ]); $this->assertEquals(10, $m->getProtocolVersion()); } public function testHasBody() { $m = new Request('GET', 'http://foo.com'); $this->assertNull($m->getBody()); $s = Stream::factory('test'); $m->setBody($s); $this->assertSame($s, $m->getBody()); $this->assertFalse($m->hasHeader('Content-Length')); } public function testCanRemoveBodyBySettingToNullAndRemovesCommonBodyHeaders() { $m = new Request('GET', 'http://foo.com'); $m->setBody(Stream::factory('foo')); $m->setHeader('Content-Length', 3); $m->setHeader('Transfer-Encoding', 'chunked'); $m->setBody(null); $this->assertNull($m->getBody()); $this->assertFalse($m->hasHeader('Content-Length')); $this->assertFalse($m->hasHeader('Transfer-Encoding')); } public function testCastsToString() { $m = new Request('GET', 'http://foo.com'); $m->setHeader('foo', 'bar'); $m->setBody(Stream::factory('baz')); $this->assertEquals("GET / HTTP/1.1\r\nHost: foo.com\r\nfoo: bar\r\n\r\nbaz", (string) $m); } public function parseParamsProvider() { $res1 = array( array( '', 'rel' => 'front', 'type' => 'image/jpeg', ), array( '', 'rel' => 'back', 'type' => 'image/jpeg', ), ); return array( array( '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"', $res1 ), array( '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"', $res1 ), array( 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"', array( array('foo' => 'baz', 'bar' => '123'), array('boo'), array('test' => '123'), array('foobar' => 'foo;bar') ) ), array( '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"', array( array('', 'rel' => 'side', 'type' => 'image/jpeg'), array('', 'rel' => 'side', 'type' => 'image/jpeg') ) ), array( '', array() ) ); } /** * @dataProvider parseParamsProvider */ public function testParseParams($header, $result) { $request = new Request('GET', '/', ['foo' => $header]); $this->assertEquals($result, Request::parseHeader($request, 'foo')); } public function testAddsHeadersWhenNotPresent() { $h = new Request('GET', 'http://foo.com'); $h->addHeader('foo', 'bar'); $this->assertInternalType('string', $h->getHeader('foo')); $this->assertEquals('bar', $h->getHeader('foo')); } public function testAddsHeadersWhenPresentSameCase() { $h = new Request('GET', 'http://foo.com'); $h->addHeader('foo', 'bar'); $h->addHeader('foo', 'baz'); $this->assertEquals('bar, baz', $h->getHeader('foo')); $this->assertEquals(['bar', 'baz'], $h->getHeaderAsArray('foo')); } public function testAddsMultipleHeaders() { $h = new Request('GET', 'http://foo.com'); $h->addHeaders([ 'foo' => ' bar', 'baz' => [' bam ', 'boo'] ]); $this->assertEquals([ 'foo' => ['bar'], 'baz' => ['bam', 'boo'], 'Host' => ['foo.com'] ], $h->getHeaders()); } public function testAddsHeadersWhenPresentDifferentCase() { $h = new Request('GET', 'http://foo.com'); $h->addHeader('Foo', 'bar'); $h->addHeader('fOO', 'baz'); $this->assertEquals('bar, baz', $h->getHeader('foo')); } public function testAddsHeadersWithArray() { $h = new Request('GET', 'http://foo.com'); $h->addHeader('Foo', ['bar', 'baz']); $this->assertEquals('bar, baz', $h->getHeader('foo')); } public function testGetHeadersReturnsAnArrayOfOverTheWireHeaderValues() { $h = new Request('GET', 'http://foo.com'); $h->addHeader('foo', 'bar'); $h->addHeader('Foo', 'baz'); $h->addHeader('boO', 'test'); $result = $h->getHeaders(); $this->assertInternalType('array', $result); $this->assertArrayHasKey('Foo', $result); $this->assertArrayNotHasKey('foo', $result); $this->assertArrayHasKey('boO', $result); $this->assertEquals(['bar', 'baz'], $result['Foo']); $this->assertEquals(['test'], $result['boO']); } public function testSetHeaderOverwritesExistingValues() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', 'bar'); $this->assertEquals('bar', $h->getHeader('foo')); $h->setHeader('Foo', 'baz'); $this->assertEquals('baz', $h->getHeader('foo')); $this->assertArrayHasKey('Foo', $h->getHeaders()); } public function testSetHeaderOverwritesExistingValuesUsingHeaderArray() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', ['bar']); $this->assertEquals('bar', $h->getHeader('foo')); } public function testSetHeaderOverwritesExistingValuesUsingArray() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', ['bar']); $this->assertEquals('bar', $h->getHeader('foo')); } public function testSetHeadersOverwritesAllHeaders() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', 'bar'); $h->setHeaders(['foo' => 'a', 'boo' => 'b']); $this->assertEquals(['foo' => ['a'], 'boo' => ['b']], $h->getHeaders()); } public function testChecksIfCaseInsensitiveHeaderIsPresent() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', 'bar'); $this->assertTrue($h->hasHeader('foo')); $this->assertTrue($h->hasHeader('Foo')); $h->setHeader('fOo', 'bar'); $this->assertTrue($h->hasHeader('Foo')); } public function testRemovesHeaders() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', 'bar'); $h->removeHeader('foo'); $this->assertFalse($h->hasHeader('foo')); $h->setHeader('Foo', 'bar'); $h->removeHeader('FOO'); $this->assertFalse($h->hasHeader('foo')); } public function testReturnsCorrectTypeWhenMissing() { $h = new Request('GET', 'http://foo.com'); $this->assertInternalType('string', $h->getHeader('foo')); $this->assertInternalType('array', $h->getHeaderAsArray('foo')); } public function testSetsIntegersAndFloatsAsHeaders() { $h = new Request('GET', 'http://foo.com'); $h->setHeader('foo', 10); $h->setHeader('bar', 10.5); $h->addHeader('foo', 10); $h->addHeader('bar', 10.5); $this->assertSame('10, 10', $h->getHeader('foo')); $this->assertSame('10.5, 10.5', $h->getHeader('bar')); } public function testGetsResponseStartLine() { $m = new Response(200); $this->assertEquals('HTTP/1.1 200 OK', Response::getStartLine($m)); } /** * @expectedException \InvalidArgumentException */ public function testThrowsWhenMessageIsUnknown() { $m = $this->getMockBuilder('GuzzleHttp\Message\AbstractMessage') ->getMockForAbstractClass(); AbstractMessage::getStartLine($m); } } tests/Message/FutureResponseTest.php000064400000012323151742731020013637 0ustar00foo; } public function testDoesTheSameAsResponseWhenDereferenced() { $str = Stream::factory('foo'); $response = new Response(200, ['Foo' => 'bar'], $str); $future = MockTest::createFuture(function () use ($response) { return $response; }); $this->assertFalse($this->readAttribute($future, 'isRealized')); $this->assertEquals(200, $future->getStatusCode()); $this->assertTrue($this->readAttribute($future, 'isRealized')); // Deref again does nothing. $future->wait(); $this->assertTrue($this->readAttribute($future, 'isRealized')); $this->assertEquals('bar', $future->getHeader('Foo')); $this->assertEquals(['bar'], $future->getHeaderAsarray('Foo')); $this->assertSame($response->getHeaders(), $future->getHeaders()); $this->assertSame( $response->getBody(), $future->getBody() ); $this->assertSame( $response->getProtocolVersion(), $future->getProtocolVersion() ); $this->assertSame( $response->getEffectiveUrl(), $future->getEffectiveUrl() ); $future->setEffectiveUrl('foo'); $this->assertEquals('foo', $response->getEffectiveUrl()); $this->assertSame( $response->getReasonPhrase(), $future->getReasonPhrase() ); $this->assertTrue($future->hasHeader('foo')); $future->removeHeader('Foo'); $this->assertFalse($future->hasHeader('foo')); $this->assertFalse($response->hasHeader('foo')); $future->setBody(Stream::factory('true')); $this->assertEquals('true', (string) $response->getBody()); $this->assertTrue($future->json()); $this->assertSame((string) $response, (string) $future); $future->setBody(Stream::factory('c')); $this->assertEquals('c', (string) $future->xml()->b); $future->addHeader('a', 'b'); $this->assertEquals('b', $future->getHeader('a')); $future->addHeaders(['a' => '2']); $this->assertEquals('b, 2', $future->getHeader('a')); $future->setHeader('a', '2'); $this->assertEquals('2', $future->getHeader('a')); $future->setHeaders(['a' => '3']); $this->assertEquals(['a' => ['3']], $future->getHeaders()); } public function testCanDereferenceManually() { $response = new Response(200, ['Foo' => 'bar']); $future = MockTest::createFuture(function () use ($response) { return $response; }); $this->assertSame($response, $future->wait()); $this->assertTrue($this->readAttribute($future, 'isRealized')); } public function testCanCancel() { $c = false; $deferred = new Deferred(); $future = new FutureResponse( $deferred->promise(), function () {}, function () use (&$c) { $c = true; return true; } ); $this->assertFalse($this->readAttribute($future, 'isRealized')); $future->cancel(); $this->assertTrue($this->readAttribute($future, 'isRealized')); $future->cancel(); } public function testCanCancelButReturnsFalseForNoCancelFunction() { $future = MockTest::createFuture(function () {}); $future->cancel(); $this->assertTrue($this->readAttribute($future, 'isRealized')); } /** * @expectedException \GuzzleHttp\Ring\Exception\CancelledFutureAccessException */ public function testAccessingCancelledResponseThrows() { $future = MockTest::createFuture(function () {}); $future->cancel(); $future->getStatusCode(); } public function testExceptionInToStringTriggersError() { $future = MockTest::createFuture(function () { throw new \Exception('foo'); }); $err = ''; set_error_handler(function () use (&$err) { $err = func_get_args()[1]; }); echo $future; restore_error_handler(); $this->assertContains('foo', $err); } public function testProxiesSetters() { $str = Stream::factory('foo'); $response = new Response(200, ['Foo' => 'bar'], $str); $future = MockTest::createFuture(function () use ($response) { return $response; }); $future->setStatusCode(202); $this->assertEquals(202, $future->getStatusCode()); $this->assertEquals(202, $response->getStatusCode()); $future->setReasonPhrase('foo'); $this->assertEquals('foo', $future->getReasonPhrase()); $this->assertEquals('foo', $response->getReasonPhrase()); } } tests/Message/MessageFactoryTest.php000064400000050460151742731020013566 0ustar00createResponse(200, ['foo' => 'bar'], 'test', [ 'protocol_version' => 1.0 ]); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals(['foo' => ['bar']], $response->getHeaders()); $this->assertEquals('test', $response->getBody()); $this->assertEquals(1.0, $response->getProtocolVersion()); } public function testCreatesRequestFromMessage() { $f = new MessageFactory(); $req = $f->fromMessage("GET / HTTP/1.1\r\nBaz: foo\r\n\r\n"); $this->assertEquals('GET', $req->getMethod()); $this->assertEquals('/', $req->getPath()); $this->assertEquals('foo', $req->getHeader('Baz')); $this->assertNull($req->getBody()); } public function testCreatesRequestFromMessageWithBody() { $req = (new MessageFactory())->fromMessage("GET / HTTP/1.1\r\nBaz: foo\r\n\r\ntest"); $this->assertEquals('test', $req->getBody()); } public function testCreatesRequestWithPostBody() { $req = (new MessageFactory())->createRequest('GET', 'http://www.foo.com', ['body' => ['abc' => '123']]); $this->assertEquals('abc=123', $req->getBody()); } public function testCreatesRequestWithPostBodyScalars() { $req = (new MessageFactory())->createRequest( 'GET', 'http://www.foo.com', ['body' => [ 'abc' => true, '123' => false, 'foo' => null, 'baz' => 10, 'bam' => 1.5, 'boo' => [1]] ] ); $this->assertEquals( 'abc=1&123=&foo&baz=10&bam=1.5&boo%5B0%5D=1', (string) $req->getBody() ); } public function testCreatesRequestWithPostBodyAndPostFiles() { $pf = fopen(__FILE__, 'r'); $pfi = new PostFile('ghi', 'abc', __FILE__); $req = (new MessageFactory())->createRequest('GET', 'http://www.foo.com', [ 'body' => [ 'abc' => '123', 'def' => $pf, 'ghi' => $pfi ] ]); $this->assertInstanceOf('GuzzleHttp\Post\PostBody', $req->getBody()); $s = (string) $req; $this->assertContains('testCreatesRequestWithPostBodyAndPostFiles', $s); $this->assertContains('multipart/form-data', $s); $this->assertTrue(in_array($pfi, $req->getBody()->getFiles(), true)); } public function testCreatesResponseFromMessage() { $response = (new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('OK', $response->getReasonPhrase()); $this->assertEquals('4', $response->getHeader('Content-Length')); $this->assertEquals('test', $response->getBody(true)); } public function testCanCreateHeadResponses() { $response = (new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n"); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('OK', $response->getReasonPhrase()); $this->assertEquals(null, $response->getBody()); $this->assertEquals('4', $response->getHeader('Content-Length')); } /** * @expectedException \InvalidArgumentException */ public function testFactoryRequiresMessageForRequest() { (new MessageFactory())->fromMessage(''); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage foo */ public function testValidatesOptionsAreImplemented() { (new MessageFactory())->createRequest('GET', 'http://test.com', ['foo' => 'bar']); } public function testOptionsAddsRequestOptions() { $request = (new MessageFactory())->createRequest( 'GET', 'http://test.com', ['config' => ['baz' => 'bar']] ); $this->assertEquals('bar', $request->getConfig()->get('baz')); } public function testCanDisableRedirects() { $request = (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => false]); $this->assertEmpty($request->getEmitter()->listeners('complete')); } /** * @expectedException \InvalidArgumentException */ public function testValidatesRedirects() { (new MessageFactory())->createRequest('GET', '/', ['allow_redirects' => []]); } public function testCanEnableStrictRedirectsAndSpecifyMax() { $request = (new MessageFactory())->createRequest('GET', '/', [ 'allow_redirects' => ['max' => 10, 'strict' => true] ]); $this->assertTrue($request->getConfig()['redirect']['strict']); $this->assertEquals(10, $request->getConfig()['redirect']['max']); } public function testCanAddCookiesFromHash() { $request = (new MessageFactory())->createRequest('GET', 'http://www.test.com/', [ 'cookies' => ['Foo' => 'Bar'] ]); $cookies = null; foreach ($request->getEmitter()->listeners('before') as $l) { if ($l[0] instanceof Cookie) { $cookies = $l[0]; break; } } if (!$cookies) { $this->fail('Did not add cookie listener'); } else { $this->assertCount(1, $cookies->getCookieJar()); } } public function testAddsCookieUsingTrue() { $factory = new MessageFactory(); $request1 = $factory->createRequest('GET', '/', ['cookies' => true]); $request2 = $factory->createRequest('GET', '/', ['cookies' => true]); $listeners = function ($r) { return array_filter($r->getEmitter()->listeners('before'), function ($l) { return $l[0] instanceof Cookie; }); }; $this->assertSame($listeners($request1), $listeners($request2)); } public function testAddsCookieFromCookieJar() { $jar = new CookieJar(); $request = (new MessageFactory())->createRequest('GET', '/', ['cookies' => $jar]); foreach ($request->getEmitter()->listeners('before') as $l) { if ($l[0] instanceof Cookie) { $this->assertSame($jar, $l[0]->getCookieJar()); } } } /** * @expectedException \InvalidArgumentException */ public function testValidatesCookies() { (new MessageFactory())->createRequest('GET', '/', ['cookies' => 'baz']); } public function testCanAddQuery() { $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ 'query' => ['Foo' => 'Bar'] ]); $this->assertEquals('Bar', $request->getQuery()->get('Foo')); } /** * @expectedException \InvalidArgumentException */ public function testValidatesQuery() { (new MessageFactory())->createRequest('GET', 'http://foo.com', [ 'query' => 'foo' ]); } public function testCanSetDefaultQuery() { $request = (new MessageFactory())->createRequest('GET', 'http://foo.com?test=abc', [ 'query' => ['Foo' => 'Bar', 'test' => 'def'] ]); $this->assertEquals('Bar', $request->getQuery()->get('Foo')); $this->assertEquals('abc', $request->getQuery()->get('test')); } public function testCanSetDefaultQueryWithObject() { $request = (new MessageFactory)->createRequest( 'GET', 'http://foo.com?test=abc', [ 'query' => new Query(['Foo' => 'Bar', 'test' => 'def']) ] ); $this->assertEquals('Bar', $request->getQuery()->get('Foo')); $this->assertEquals('abc', $request->getQuery()->get('test')); } public function testCanAddBasicAuth() { $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ 'auth' => ['michael', 'test'] ]); $this->assertTrue($request->hasHeader('Authorization')); } public function testCanAddDigestAuth() { $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ 'auth' => ['michael', 'test', 'digest'] ]); $this->assertEquals('michael:test', $request->getConfig()->getPath('curl/' . CURLOPT_USERPWD)); $this->assertEquals(CURLAUTH_DIGEST, $request->getConfig()->getPath('curl/' . CURLOPT_HTTPAUTH)); } public function testCanDisableAuth() { $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ 'auth' => false ]); $this->assertFalse($request->hasHeader('Authorization')); } public function testCanSetCustomAuth() { $request = (new MessageFactory())->createRequest('GET', 'http://foo.com', [ 'auth' => 'foo' ]); $this->assertEquals('foo', $request->getConfig()['auth']); } public function testCanAddEvents() { $foo = null; $client = new Client(); $client->getEmitter()->attach(new Mock([new Response(200)])); $client->get('http://test.com', [ 'events' => [ 'before' => function () use (&$foo) { $foo = true; } ] ]); $this->assertTrue($foo); } public function testCanAddEventsWithPriority() { $foo = null; $client = new Client(); $client->getEmitter()->attach(new Mock(array(new Response(200)))); $request = $client->createRequest('GET', 'http://test.com', [ 'events' => [ 'before' => [ 'fn' => function () use (&$foo) { $foo = true; }, 'priority' => 123 ] ] ]); $client->send($request); $this->assertTrue($foo); $l = $this->readAttribute($request->getEmitter(), 'listeners'); $this->assertArrayHasKey(123, $l['before']); } public function testCanAddEventsOnce() { $foo = 0; $client = new Client(); $client->getEmitter()->attach(new Mock([ new Response(200), new Response(200), ])); $fn = function () use (&$foo) { ++$foo; }; $request = $client->createRequest('GET', 'http://test.com', [ 'events' => ['before' => ['fn' => $fn, 'once' => true]] ]); $client->send($request); $this->assertEquals(1, $foo); $client->send($request); $this->assertEquals(1, $foo); } /** * @expectedException \InvalidArgumentException */ public function testValidatesEventContainsFn() { $client = new Client(['base_url' => 'http://test.com']); $client->createRequest('GET', '/', ['events' => ['before' => ['foo' => 'bar']]]); } /** * @expectedException \InvalidArgumentException */ public function testValidatesEventIsArray() { $client = new Client(['base_url' => 'http://test.com']); $client->createRequest('GET', '/', ['events' => ['before' => '123']]); } public function testCanAddSubscribers() { $mock = new Mock([new Response(200)]); $client = new Client(); $client->getEmitter()->attach($mock); $request = $client->get('http://test.com', ['subscribers' => [$mock]]); } public function testCanDisableExceptions() { $client = new Client(); $this->assertEquals(500, $client->get('http://test.com', [ 'subscribers' => [new Mock([new Response(500)])], 'exceptions' => false ])->getStatusCode()); } public function testCanChangeSaveToLocation() { $saveTo = Stream::factory(); $request = (new MessageFactory())->createRequest('GET', '/', ['save_to' => $saveTo]); $this->assertSame($saveTo, $request->getConfig()->get('save_to')); } public function testCanSetProxy() { $request = (new MessageFactory())->createRequest('GET', '/', ['proxy' => '192.168.16.121']); $this->assertEquals('192.168.16.121', $request->getConfig()->get('proxy')); } public function testCanSetHeadersOption() { $request = (new MessageFactory())->createRequest('GET', '/', ['headers' => ['Foo' => 'Bar']]); $this->assertEquals('Bar', (string) $request->getHeader('Foo')); } public function testCanSetHeaders() { $request = (new MessageFactory())->createRequest('GET', '/', [ 'headers' => ['Foo' => ['Baz', 'Bar'], 'Test' => '123'] ]); $this->assertEquals('Baz, Bar', $request->getHeader('Foo')); $this->assertEquals('123', $request->getHeader('Test')); } public function testCanSetTimeoutOption() { $request = (new MessageFactory())->createRequest('GET', '/', ['timeout' => 1.5]); $this->assertEquals(1.5, $request->getConfig()->get('timeout')); } public function testCanSetConnectTimeoutOption() { $request = (new MessageFactory())->createRequest('GET', '/', ['connect_timeout' => 1.5]); $this->assertEquals(1.5, $request->getConfig()->get('connect_timeout')); } public function testCanSetDebug() { $request = (new MessageFactory())->createRequest('GET', '/', ['debug' => true]); $this->assertTrue($request->getConfig()->get('debug')); } public function testCanSetVerifyToOff() { $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => false]); $this->assertFalse($request->getConfig()->get('verify')); } public function testCanSetVerifyToOn() { $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => true]); $this->assertTrue($request->getConfig()->get('verify')); } public function testCanSetVerifyToPath() { $request = (new MessageFactory())->createRequest('GET', '/', ['verify' => '/foo.pem']); $this->assertEquals('/foo.pem', $request->getConfig()->get('verify')); } public function inputValidation() { return array_map(function ($option) { return array($option); }, array( 'headers', 'events', 'subscribers', 'params' )); } /** * @dataProvider inputValidation * @expectedException \InvalidArgumentException */ public function testValidatesInput($option) { (new MessageFactory())->createRequest('GET', '/', [$option => 'foo']); } public function testCanAddSslKey() { $request = (new MessageFactory())->createRequest('GET', '/', ['ssl_key' => '/foo.pem']); $this->assertEquals('/foo.pem', $request->getConfig()->get('ssl_key')); } public function testCanAddSslKeyPassword() { $request = (new MessageFactory())->createRequest('GET', '/', ['ssl_key' => ['/foo.pem', 'bar']]); $this->assertEquals(['/foo.pem', 'bar'], $request->getConfig()->get('ssl_key')); } public function testCanAddSslCert() { $request = (new MessageFactory())->createRequest('GET', '/', ['cert' => '/foo.pem']); $this->assertEquals('/foo.pem', $request->getConfig()->get('cert')); } public function testCanAddSslCertPassword() { $request = (new MessageFactory())->createRequest('GET', '/', ['cert' => ['/foo.pem', 'bar']]); $this->assertEquals(['/foo.pem', 'bar'], $request->getConfig()->get('cert')); } public function testCreatesBodyWithoutZeroString() { $request = (new MessageFactory())->createRequest('PUT', 'http://test.com', ['body' => '0']); $this->assertSame('0', (string) $request->getBody()); } public function testCanSetProtocolVersion() { $request = (new MessageFactory())->createRequest('GET', 'http://t.com', ['version' => 1.0]); $this->assertEquals(1.0, $request->getProtocolVersion()); } public function testCanAddJsonData() { $request = (new MessageFactory())->createRequest('PUT', 'http://f.com', [ 'json' => ['foo' => 'bar'] ]); $this->assertEquals( 'application/json', $request->getHeader('Content-Type') ); $this->assertEquals('{"foo":"bar"}', (string) $request->getBody()); } public function testCanAddJsonDataToAPostRequest() { $request = (new MessageFactory())->createRequest('POST', 'http://f.com', [ 'json' => ['foo' => 'bar'] ]); $this->assertEquals( 'application/json', $request->getHeader('Content-Type') ); $this->assertEquals('{"foo":"bar"}', (string) $request->getBody()); } public function testCanAddJsonDataAndNotOverwriteContentType() { $request = (new MessageFactory())->createRequest('PUT', 'http://f.com', [ 'headers' => ['Content-Type' => 'foo'], 'json' => null ]); $this->assertEquals('foo', $request->getHeader('Content-Type')); $this->assertEquals('null', (string) $request->getBody()); } public function testCanUseCustomRequestOptions() { $c = false; $f = new MessageFactory([ 'foo' => function (RequestInterface $request, $value) use (&$c) { $c = true; $this->assertEquals('bar', $value); } ]); $f->createRequest('PUT', 'http://f.com', [ 'headers' => ['Content-Type' => 'foo'], 'foo' => 'bar' ]); $this->assertTrue($c); } /** * @ticket https://github.com/guzzle/guzzle/issues/706 */ public function testDoesNotApplyPostBodyRightAway() { $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ 'body' => ['foo' => ['bar', 'baz']] ]); $this->assertEquals('', $request->getHeader('Content-Type')); $this->assertEquals('', $request->getHeader('Content-Length')); $request->getBody()->setAggregator(Query::duplicateAggregator()); $request->getBody()->applyRequestHeaders($request); $this->assertEquals('foo=bar&foo=baz', $request->getBody()); } public function testCanForceMultipartUploadWithContentType() { $client = new Client(); $client->getEmitter()->attach(new Mock([new Response(200)])); $history = new History(); $client->getEmitter()->attach($history); $client->post('http://foo.com', [ 'headers' => ['Content-Type' => 'multipart/form-data'], 'body' => ['foo' => 'bar'] ]); $this->assertContains( 'multipart/form-data; boundary=', $history->getLastRequest()->getHeader('Content-Type') ); $this->assertContains( "Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar", (string) $history->getLastRequest()->getBody() ); } public function testDecodeDoesNotForceAcceptHeader() { $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ 'decode_content' => true ]); $this->assertEquals('', $request->getHeader('Accept-Encoding')); $this->assertTrue($request->getConfig()->get('decode_content')); } public function testDecodeCanAddAcceptHeader() { $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ 'decode_content' => 'gzip' ]); $this->assertEquals('gzip', $request->getHeader('Accept-Encoding')); $this->assertTrue($request->getConfig()->get('decode_content')); } public function testCanDisableDecoding() { $request = (new MessageFactory())->createRequest('POST', 'http://f.cn', [ 'decode_content' => false ]); $this->assertEquals('', $request->getHeader('Accept-Encoding')); $this->assertNull($request->getConfig()->get('decode_content')); } } class ExtendedFactory extends MessageFactory { protected function add_foo() {} } tests/Message/MessageParserTest.php000064400000023555151742731020013420 0ustar00compareRequestResults($parts, $parser->parseRequest($message)); } /** * @dataProvider responseProvider */ public function testParsesResponses($message, $parts) { $parser = new MessageParser(); $this->compareResponseResults($parts, $parser->parseResponse($message)); } public function testParsesRequestsWithMissingProtocol() { $parser = new MessageParser(); $parts = $parser->parseRequest("GET /\r\nHost: Foo.com\r\n\r\n"); $this->assertEquals('GET', $parts['method']); $this->assertEquals('HTTP', $parts['protocol']); $this->assertEquals('1.1', $parts['protocol_version']); } public function testParsesRequestsWithMissingVersion() { $parser = new MessageParser(); $parts = $parser->parseRequest("GET / HTTP\r\nHost: Foo.com\r\n\r\n"); $this->assertEquals('GET', $parts['method']); $this->assertEquals('HTTP', $parts['protocol']); $this->assertEquals('1.1', $parts['protocol_version']); } public function testParsesResponsesWithMissingReasonPhrase() { $parser = new MessageParser(); $parts = $parser->parseResponse("HTTP/1.1 200\r\n\r\n"); $this->assertEquals('200', $parts['code']); $this->assertEquals('', $parts['reason_phrase']); $this->assertEquals('HTTP', $parts['protocol']); $this->assertEquals('1.1', $parts['protocol_version']); } public function requestProvider() { $auth = base64_encode('michael:foo'); return array( // Empty request array('', false), // Converts casing of request. Does not require host header. array("GET / HTTP/1.1\r\n\r\n", array( 'method' => 'GET', 'protocol' => 'HTTP', 'protocol_version' => '1.1', 'request_url' => array( 'scheme' => 'http', 'host' => '', 'port' => '', 'path' => '/', 'query' => '' ), 'headers' => array(), 'body' => '' )), // Path and query string, multiple header values per header and case sensitive storage array("HEAD /path?query=foo HTTP/1.0\r\nHost: example.com\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\nX-Foo: Baz\r\n\r\n", array( 'method' => 'HEAD', 'protocol' => 'HTTP', 'protocol_version' => '1.0', 'request_url' => array( 'scheme' => 'http', 'host' => 'example.com', 'port' => '', 'path' => '/path', 'query' => 'query=foo' ), 'headers' => array( 'Host' => 'example.com', 'X-Foo' => array('foo', 'foo', 'Baz'), 'x-foo' => 'Bar' ), 'body' => '' )), // Includes a body array("PUT / HTTP/1.0\r\nhost: example.com:443\r\nContent-Length: 4\r\n\r\ntest", array( 'method' => 'PUT', 'protocol' => 'HTTP', 'protocol_version' => '1.0', 'request_url' => array( 'scheme' => 'https', 'host' => 'example.com', 'port' => '443', 'path' => '/', 'query' => '' ), 'headers' => array( 'host' => 'example.com:443', 'Content-Length' => '4' ), 'body' => 'test' )), // Includes Authorization headers array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nAuthorization: Basic {$auth}\r\n\r\n", array( 'method' => 'GET', 'protocol' => 'HTTP', 'protocol_version' => '1.1', 'request_url' => array( 'scheme' => 'http', 'host' => 'example.com', 'port' => '8080', 'path' => '/', 'query' => '' ), 'headers' => array( 'Host' => 'example.com:8080', 'Authorization' => "Basic {$auth}" ), 'body' => '' )), // Include authorization header array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nauthorization: Basic {$auth}\r\n\r\n", array( 'method' => 'GET', 'protocol' => 'HTTP', 'protocol_version' => '1.1', 'request_url' => array( 'scheme' => 'http', 'host' => 'example.com', 'port' => '8080', 'path' => '/', 'query' => '' ), 'headers' => array( 'Host' => 'example.com:8080', 'authorization' => "Basic {$auth}" ), 'body' => '' )), ); } public function responseProvider() { return array( // Empty request array('', false), array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", array( 'protocol' => 'HTTP', 'protocol_version' => '1.1', 'code' => '200', 'reason_phrase' => 'OK', 'headers' => array( 'Content-Length' => 0 ), 'body' => '' )), array("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n", array( 'protocol' => 'HTTP', 'protocol_version' => '1.0', 'code' => '400', 'reason_phrase' => 'Bad Request', 'headers' => array( 'Content-Length' => 0 ), 'body' => '' )), array("HTTP/1.0 100 Continue\r\n\r\n", array( 'protocol' => 'HTTP', 'protocol_version' => '1.0', 'code' => '100', 'reason_phrase' => 'Continue', 'headers' => array(), 'body' => '' )), array("HTTP/1.1 204 No Content\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\n\r\n", array( 'protocol' => 'HTTP', 'protocol_version' => '1.1', 'code' => '204', 'reason_phrase' => 'No Content', 'headers' => array( 'X-Foo' => array('foo', 'foo'), 'x-foo' => 'Bar' ), 'body' => '' )), array("HTTP/1.1 200 Ok that is great!\r\nContent-Length: 4\r\n\r\nTest", array( 'protocol' => 'HTTP', 'protocol_version' => '1.1', 'code' => '200', 'reason_phrase' => 'Ok that is great!', 'headers' => array( 'Content-Length' => 4 ), 'body' => 'Test' )), ); } public function compareRequestResults($result, $expected) { if (!$result) { $this->assertFalse($expected); return; } $this->assertEquals($result['method'], $expected['method']); $this->assertEquals($result['protocol'], $expected['protocol']); $this->assertEquals($result['protocol_version'], $expected['protocol_version']); $this->assertEquals($result['request_url'], $expected['request_url']); $this->assertEquals($result['body'], $expected['body']); $this->compareHttpHeaders($result['headers'], $expected['headers']); } public function compareResponseResults($result, $expected) { if (!$result) { $this->assertFalse($expected); return; } $this->assertEquals($result['protocol'], $expected['protocol']); $this->assertEquals($result['protocol_version'], $expected['protocol_version']); $this->assertEquals($result['code'], $expected['code']); $this->assertEquals($result['reason_phrase'], $expected['reason_phrase']); $this->assertEquals($result['body'], $expected['body']); $this->compareHttpHeaders($result['headers'], $expected['headers']); } protected function normalizeHeaders($headers) { $normalized = array(); foreach ($headers as $key => $value) { $key = strtolower($key); if (!isset($normalized[$key])) { $normalized[$key] = $value; } elseif (!is_array($normalized[$key])) { $normalized[$key] = array($value); } else { $normalized[$key][] = $value; } } foreach ($normalized as $key => &$value) { if (is_array($value)) { sort($value); } } return $normalized; } public function compareHttpHeaders($result, $expected) { // Aggregate all headers case-insensitively $result = $this->normalizeHeaders($result); $expected = $this->normalizeHeaders($expected); $this->assertEquals($result, $expected); } } tests/Message/RequestTest.php000064400000010571151742731020012301 0ustar00 '123'], Stream::factory('foo')); $this->assertEquals('PUT', $r->getMethod()); $this->assertEquals('/test', $r->getUrl()); $this->assertEquals('123', $r->getHeader('test')); $this->assertEquals('foo', $r->getBody()); } public function testConstructorInitializesMessageWithProtocolVersion() { $r = new Request('GET', '', [], null, ['protocol_version' => 10]); $this->assertEquals(10, $r->getProtocolVersion()); } public function testConstructorInitializesMessageWithEmitter() { $e = new Emitter(); $r = new Request('GET', '', [], null, ['emitter' => $e]); $this->assertSame($r->getEmitter(), $e); } public function testCloneIsDeep() { $r = new Request('GET', '/test', ['foo' => 'baz'], Stream::factory('foo')); $r2 = clone $r; $this->assertNotSame($r->getEmitter(), $r2->getEmitter()); $this->assertEquals('foo', $r2->getBody()); $r->getConfig()->set('test', 123); $this->assertFalse($r2->getConfig()->hasKey('test')); $r->setPath('/abc'); $this->assertEquals('/test', $r2->getPath()); } public function testCastsToString() { $r = new Request('GET', 'http://test.com/test', ['foo' => 'baz'], Stream::factory('body')); $s = explode("\r\n", (string) $r); $this->assertEquals("GET /test HTTP/1.1", $s[0]); $this->assertContains('Host: test.com', $s); $this->assertContains('foo: baz', $s); $this->assertContains('', $s); $this->assertContains('body', $s); } public function testSettingUrlOverridesHostHeaders() { $r = new Request('GET', 'http://test.com/test'); $r->setUrl('https://baz.com/bar'); $this->assertEquals('baz.com', $r->getHost()); $this->assertEquals('baz.com', $r->getHeader('Host')); $this->assertEquals('/bar', $r->getPath()); $this->assertEquals('https', $r->getScheme()); } public function testQueryIsMutable() { $r = new Request('GET', 'http://www.foo.com?baz=bar'); $this->assertEquals('baz=bar', $r->getQuery()); $this->assertInstanceOf('GuzzleHttp\Query', $r->getQuery()); $r->getQuery()->set('hi', 'there'); $this->assertEquals('/?baz=bar&hi=there', $r->getResource()); } public function testQueryCanChange() { $r = new Request('GET', 'http://www.foo.com?baz=bar'); $r->setQuery(new Query(['foo' => 'bar'])); $this->assertEquals('foo=bar', $r->getQuery()); } public function testCanChangeMethod() { $r = new Request('GET', 'http://www.foo.com'); $r->setMethod('put'); $this->assertEquals('PUT', $r->getMethod()); } public function testCanChangeSchemeWithPort() { $r = new Request('GET', 'http://www.foo.com:80'); $r->setScheme('https'); $this->assertEquals('https://www.foo.com', $r->getUrl()); } public function testCanChangeScheme() { $r = new Request('GET', 'http://www.foo.com'); $r->setScheme('https'); $this->assertEquals('https://www.foo.com', $r->getUrl()); } public function testCanChangeHost() { $r = new Request('GET', 'http://www.foo.com:222'); $r->setHost('goo'); $this->assertEquals('http://goo:222', $r->getUrl()); $this->assertEquals('goo:222', $r->getHeader('host')); $r->setHost('goo:80'); $this->assertEquals('http://goo', $r->getUrl()); $this->assertEquals('goo', $r->getHeader('host')); } public function testCanChangePort() { $r = new Request('GET', 'http://www.foo.com:222'); $this->assertSame(222, $r->getPort()); $this->assertEquals('www.foo.com', $r->getHost()); $this->assertEquals('www.foo.com:222', $r->getHeader('host')); $r->setPort(80); $this->assertSame(80, $r->getPort()); $this->assertEquals('www.foo.com', $r->getHost()); $this->assertEquals('www.foo.com', $r->getHeader('host')); } } tests/Message/ResponseTest.php000064400000010461151742731020012445 0ustar00 'hi!']); $this->assertEquals(999, $response->getStatusCode()); $this->assertEquals('hi!', $response->getReasonPhrase()); } public function testConvertsToString() { $response = new Response(200); $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", (string) $response); // Add another header $response = new Response(200, ['X-Test' => 'Guzzle']); $this->assertEquals("HTTP/1.1 200 OK\r\nX-Test: Guzzle\r\n\r\n", (string) $response); $response = new Response(200, ['Content-Length' => 4], Stream::factory('test')); $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", (string) $response); } public function testConvertsToStringAndSeeksToByteZero() { $response = new Response(200); $s = Stream::factory('foo'); $s->read(1); $response->setBody($s); $this->assertEquals("HTTP/1.1 200 OK\r\n\r\nfoo", (string) $response); } public function testParsesJsonResponses() { $json = '{"foo": "bar"}'; $response = new Response(200, [], Stream::factory($json)); $this->assertEquals(['foo' => 'bar'], $response->json()); $this->assertEquals(json_decode($json), $response->json(['object' => true])); $response = new Response(200); $this->assertEquals(null, $response->json()); } /** * @expectedException \GuzzleHttp\Exception\ParseException * @expectedExceptionMessage Unable to parse JSON data: JSON_ERROR_SYNTAX - Syntax error, malformed JSON */ public function testThrowsExceptionWhenFailsToParseJsonResponse() { $response = new Response(200, [], Stream::factory('{"foo": "')); $response->json(); } public function testParsesXmlResponses() { $response = new Response(200, [], Stream::factory('bar')); $this->assertEquals('bar', (string) $response->xml()->foo); // Always return a SimpleXMLElement from the xml method $response = new Response(200); $this->assertEmpty((string) $response->xml()->foo); } /** * @expectedException \GuzzleHttp\Exception\XmlParseException * @expectedExceptionMessage Unable to parse response body into XML: String could not be parsed as XML */ public function testThrowsExceptionWhenFailsToParseXmlResponse() { $response = new Response(200, [], Stream::factory('xml(); } catch (XmlParseException $e) { $xmlParseError = $e->getError(); $this->assertInstanceOf('\LibXMLError', $xmlParseError); $this->assertContains("Couldn't find end of Start Tag abc line 1", $xmlParseError->message); throw $e; } } public function testHasEffectiveUrl() { $r = new Response(200); $this->assertNull($r->getEffectiveUrl()); $r->setEffectiveUrl('http://www.test.com'); $this->assertEquals('http://www.test.com', $r->getEffectiveUrl()); } public function testPreventsComplexExternalEntities() { $xml = ']>&test;'; $response = new Response(200, [], Stream::factory($xml)); $oldCwd = getcwd(); chdir(__DIR__); try { $xml = $response->xml(); chdir($oldCwd); $this->markTestIncomplete('Did not throw the expected exception! XML resolved as: ' . $xml->asXML()); } catch (\Exception $e) { chdir($oldCwd); } } public function testStatusAndReasonAreMutable() { $response = new Response(200); $response->setStatusCode(201); $this->assertEquals(201, $response->getStatusCode()); $response->setReasonPhrase('Foo'); $this->assertEquals('Foo', $response->getReasonPhrase()); } } tests/MimetypesTest.php000064400000001414151742731020011235 0ustar00assertEquals('text/x-php', Mimetypes::getInstance()->fromExtension('php')); } public function testGetsFromFilename() { $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(__FILE__)); } public function testGetsFromCaseInsensitiveFilename() { $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(strtoupper(__FILE__))); } public function testReturnsNullWhenNoMatchFound() { $this->assertNull(Mimetypes::getInstance()->fromExtension('foobar')); } } tests/PoolTest.php000064400000017023151742731020010175 0ustar00 10]); $this->assertSame($c, $this->readAttribute($p, 'client')); $this->assertEquals(10, $this->readAttribute($p, 'poolSize')); } /** * @expectedException \InvalidArgumentException */ public function testValidatesEachElement() { $c = new Client(); $requests = ['foo']; $p = new Pool($c, new \ArrayIterator($requests)); $p->wait(); } public function testSendsAndRealizesFuture() { $c = $this->getClient(); $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]); $this->assertTrue($p->wait()); $this->assertFalse($p->wait()); $this->assertTrue($this->readAttribute($p, 'isRealized')); $this->assertFalse($p->cancel()); } public function testSendsManyRequestsInCappedPool() { $c = $this->getClient(); $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]); $this->assertTrue($p->wait()); $this->assertFalse($p->wait()); } public function testSendsRequestsThatHaveNotBeenRealized() { $c = $this->getClient(); $p = new Pool($c, [$c->createRequest('GET', 'http://foo.com')]); $this->assertTrue($p->wait()); $this->assertFalse($p->wait()); $this->assertFalse($p->cancel()); } public function testCancelsInFlightRequests() { $c = $this->getClient(); $h = new History(); $c->getEmitter()->attach($h); $p = new Pool($c, [ $c->createRequest('GET', 'http://foo.com'), $c->createRequest('GET', 'http://foo.com', [ 'events' => [ 'before' => [ 'fn' => function () use (&$p) { $this->assertTrue($p->cancel()); }, 'priority' => RequestEvents::EARLY ] ] ]) ]); ob_start(); $p->wait(); $contents = ob_get_clean(); $this->assertEquals(1, count($h)); $this->assertEquals('Cancelling', $contents); } private function getClient() { $deferred = new Deferred(); $future = new FutureArray( $deferred->promise(), function() use ($deferred) { $deferred->resolve(['status' => 200, 'headers' => []]); }, function () { echo 'Cancelling'; } ); return new Client(['handler' => new MockHandler($future)]); } public function testBatchesRequests() { $client = new Client(['handler' => function () { throw new \RuntimeException('No network access'); }]); $responses = [ new Response(301, ['Location' => 'http://foo.com/bar']), new Response(200), new Response(200), new Response(404) ]; $client->getEmitter()->attach(new Mock($responses)); $requests = [ $client->createRequest('GET', 'http://foo.com/baz'), $client->createRequest('HEAD', 'http://httpbin.org/get'), $client->createRequest('PUT', 'http://httpbin.org/put'), ]; $a = $b = $c = $d = 0; $result = Pool::batch($client, $requests, [ 'before' => function (BeforeEvent $e) use (&$a) { $a++; }, 'complete' => function (CompleteEvent $e) use (&$b) { $b++; }, 'error' => function (ErrorEvent $e) use (&$c) { $c++; }, 'end' => function (EndEvent $e) use (&$d) { $d++; } ]); $this->assertEquals(4, $a); $this->assertEquals(2, $b); $this->assertEquals(1, $c); $this->assertEquals(3, $d); $this->assertCount(3, $result); $this->assertInstanceOf('GuzzleHttp\BatchResults', $result); // The first result is actually the second (redirect) response. $this->assertSame($responses[1], $result[0]); // The second result is a 1:1 request:response map $this->assertSame($responses[2], $result[1]); // The third entry is the 404 RequestException $this->assertSame($responses[3], $result[2]->getResponse()); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Each event listener must be a callable or */ public function testBatchValidatesTheEventFormat() { $client = new Client(); $requests = [$client->createRequest('GET', 'http://foo.com/baz')]; Pool::batch($client, $requests, ['complete' => 'foo']); } public function testEmitsProgress() { $client = new Client(['handler' => function () { throw new \RuntimeException('No network access'); }]); $responses = [new Response(200), new Response(404)]; $client->getEmitter()->attach(new Mock($responses)); $requests = [ $client->createRequest('GET', 'http://foo.com/baz'), $client->createRequest('HEAD', 'http://httpbin.org/get') ]; $pool = new Pool($client, $requests); $count = 0; $thenned = null; $pool->then( function ($value) use (&$thenned) { $thenned = $value; }, null, function ($result) use (&$count, $requests) { $this->assertSame($requests[$count], $result['request']); if ($count == 0) { $this->assertNull($result['error']); $this->assertEquals(200, $result['response']->getStatusCode()); } else { $this->assertInstanceOf( 'GuzzleHttp\Exception\ClientException', $result['error'] ); } $count++; } ); $pool->wait(); $this->assertEquals(2, $count); $this->assertEquals(true, $thenned); } public function testDoesNotThrowInErrorEvent() { $client = new Client(); $responses = [new Response(404)]; $client->getEmitter()->attach(new Mock($responses)); $requests = [$client->createRequest('GET', 'http://foo.com/baz')]; $result = Pool::batch($client, $requests); $this->assertCount(1, $result); $this->assertInstanceOf('GuzzleHttp\Exception\ClientException', $result[0]); } public function testHasSendMethod() { $client = new Client(); $responses = [new Response(404)]; $history = new History(); $client->getEmitter()->attach($history); $client->getEmitter()->attach(new Mock($responses)); $requests = [$client->createRequest('GET', 'http://foo.com/baz')]; Pool::send($client, $requests); $this->assertCount(1, $history); } } tests/Post/MultipartBodyTest.php000064400000007361151742731020013014 0ustar00 'bar'], [ new PostFile('foo', 'abc', 'foo.txt') ], 'abcdef'); } public function testConstructorAddsFieldsAndFiles() { $b = $this->getTestBody(); $this->assertEquals('abcdef', $b->getBoundary()); $c = (string) $b; $this->assertContains("--abcdef\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n", $c); $this->assertContains("--abcdef\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"foo.txt\"\r\n" . "Content-Type: text/plain\r\n\r\nabc\r\n--abcdef--", $c); } public function testDoesNotModifyFieldFormat() { $m = new MultipartBody(['foo+baz' => 'bar+bam %20 boo'], [ new PostFile('foo+bar', 'abc %20 123', 'foo.txt') ], 'abcdef'); $this->assertContains('name="foo+baz"', (string) $m); $this->assertContains('name="foo+bar"', (string) $m); $this->assertContains('bar+bam %20 boo', (string) $m); $this->assertContains('abc %20 123', (string) $m); } /** * @expectedException \InvalidArgumentException */ public function testConstructorValidatesFiles() { new MultipartBody([], ['bar']); } public function testConstructorCanCreateBoundary() { $b = new MultipartBody(); $this->assertNotNull($b->getBoundary()); } public function testWrapsStreamMethods() { $b = $this->getTestBody(); $this->assertFalse($b->write('foo')); $this->assertFalse($b->isWritable()); $this->assertTrue($b->isReadable()); $this->assertTrue($b->isSeekable()); $this->assertEquals(0, $b->tell()); } public function testCanDetachFieldsAndFiles() { $b = $this->getTestBody(); $b->detach(); $b->close(); $this->assertEquals('', (string) $b); } public function testIsSeekableReturnsTrueIfAllAreSeekable() { $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isSeekable', 'isReadable']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('isSeekable') ->will($this->returnValue(false)); $s->expects($this->once()) ->method('isReadable') ->will($this->returnValue(true)); $p = new PostFile('foo', $s, 'foo.php'); $b = new MultipartBody([], [$p]); $this->assertFalse($b->isSeekable()); $this->assertFalse($b->seek(10)); } public function testReadsFromBuffer() { $b = $this->getTestBody(); $c = $b->read(1); $c .= $b->read(1); $c .= $b->read(1); $c .= $b->read(1); $c .= $b->read(1); $this->assertEquals('--abc', $c); } public function testCalculatesSize() { $b = $this->getTestBody(); $this->assertEquals(strlen($b), $b->getSize()); } public function testCalculatesSizeAndReturnsNullForUnknown() { $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['getSize', 'isReadable']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('getSize') ->will($this->returnValue(null)); $s->expects($this->once()) ->method('isReadable') ->will($this->returnValue(true)); $b = new MultipartBody([], [new PostFile('foo', $s, 'foo.php')]); $this->assertNull($b->getSize()); } } tests/Post/PostBodyTest.php000064400000020073151742731020011753 0ustar00assertTrue($b->isSeekable()); $this->assertTrue($b->isReadable()); $this->assertFalse($b->isWritable()); $this->assertFalse($b->write('foo')); } public function testApplyingWithNothingDoesNothing() { $b = new PostBody(); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertFalse($m->hasHeader('Content-Length')); $this->assertFalse($m->hasHeader('Content-Type')); } public function testCanForceMultipartUploadsWhenApplying() { $b = new PostBody(); $b->forceMultipartUpload(true); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertContains( 'multipart/form-data', $m->getHeader('Content-Type') ); } public function testApplyingWithFilesAddsMultipartUpload() { $b = new PostBody(); $p = new PostFile('foo', fopen(__FILE__, 'r')); $b->addFile($p); $this->assertEquals([$p], $b->getFiles()); $this->assertNull($b->getFile('missing')); $this->assertSame($p, $b->getFile('foo')); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertContains( 'multipart/form-data', $m->getHeader('Content-Type') ); $this->assertTrue($m->hasHeader('Content-Length')); } public function testApplyingWithFieldsAddsMultipartUpload() { $b = new PostBody(); $b->setField('foo', 'bar'); $this->assertEquals(['foo' => 'bar'], $b->getFields()); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertContains( 'application/x-www-form', $m->getHeader('Content-Type') ); $this->assertTrue($m->hasHeader('Content-Length')); } public function testMultipartWithNestedFields() { $b = new PostBody(); $b->setField('foo', ['bar' => 'baz']); $b->forceMultipartUpload(true); $this->assertEquals(['foo' => ['bar' => 'baz']], $b->getFields()); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertContains( 'multipart/form-data', $m->getHeader('Content-Type') ); $this->assertTrue($m->hasHeader('Content-Length')); $contents = $b->getContents(); $this->assertContains('name="foo[bar]"', $contents); $this->assertNotContains('name="foo"', $contents); } public function testCountProvidesFieldsAndFiles() { $b = new PostBody(); $b->setField('foo', 'bar'); $b->addFile(new PostFile('foo', fopen(__FILE__, 'r'))); $this->assertEquals(2, count($b)); $b->clearFiles(); $b->removeField('foo'); $this->assertEquals(0, count($b)); $this->assertEquals([], $b->getFiles()); $this->assertEquals([], $b->getFields()); } public function testHasFields() { $b = new PostBody(); $b->setField('foo', 'bar'); $b->setField('baz', '123'); $this->assertEquals('bar', $b->getField('foo')); $this->assertEquals('123', $b->getField('baz')); $this->assertNull($b->getField('ahh')); $this->assertTrue($b->hasField('foo')); $this->assertFalse($b->hasField('test')); $b->replaceFields(['abc' => '123']); $this->assertFalse($b->hasField('foo')); $this->assertTrue($b->hasField('abc')); } public function testConvertsFieldsToQueryStyleBody() { $b = new PostBody(); $b->setField('foo', 'bar'); $b->setField('baz', '123'); $this->assertEquals('foo=bar&baz=123', $b); $this->assertEquals(15, $b->getSize()); $b->seek(0); $this->assertEquals('foo=bar&baz=123', $b->getContents()); $b->seek(0); $this->assertEquals('foo=bar&baz=123', $b->read(1000)); $this->assertEquals(15, $b->tell()); $this->assertTrue($b->eof()); } public function testCanSpecifyQueryAggregator() { $b = new PostBody(); $b->setField('foo', ['baz', 'bar']); $this->assertEquals('foo%5B0%5D=baz&foo%5B1%5D=bar', (string) $b); $b = new PostBody(); $b->setField('foo', ['baz', 'bar']); $agg = Query::duplicateAggregator(); $b->setAggregator($agg); $this->assertEquals('foo=baz&foo=bar', (string) $b); } public function testDetachesAndCloses() { $b = new PostBody(); $b->setField('foo', 'bar'); $b->detach(); $b->close(); $this->assertEquals('', $b->read(10)); } public function testDetachesWhenBodyIsPresent() { $b = new PostBody(); $b->setField('foo', 'bar'); $b->getContents(); $b->detach(); } public function testFlushAndMetadataPlaceholders() { $b = new PostBody(); $this->assertEquals([], $b->getMetadata()); $this->assertNull($b->getMetadata('foo')); } public function testCreatesMultipartUploadWithMultiFields() { $b = new PostBody(); $b->setField('testing', ['baz', 'bar']); $b->setField('other', 'hi'); $b->setField('third', 'there'); $b->addFile(new PostFile('foo', fopen(__FILE__, 'r'))); $s = (string) $b; $this->assertContains(file_get_contents(__FILE__), $s); $this->assertContains('testing=bar', $s); $this->assertContains( 'Content-Disposition: form-data; name="third"', $s ); $this->assertContains( 'Content-Disposition: form-data; name="other"', $s ); } public function testMultipartWithBase64Fields() { $b = new PostBody(); $b->setField('foo64', '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc='); $b->forceMultipartUpload(true); $this->assertEquals( ['foo64' => '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc='], $b->getFields() ); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertContains( 'multipart/form-data', $m->getHeader('Content-Type') ); $this->assertTrue($m->hasHeader('Content-Length')); $contents = $b->getContents(); $this->assertContains('name="foo64"', $contents); $this->assertContains( '/xA2JhWEqPcgyLRDdir9WSRi/khpb2Lh3ooqv+5VYoc=', $contents ); } public function testMultipartWithAmpersandInValue() { $b = new PostBody(); $b->setField('a', 'b&c=d'); $b->forceMultipartUpload(true); $this->assertEquals(['a' => 'b&c=d'], $b->getFields()); $m = new Request('POST', '/'); $b->applyRequestHeaders($m); $this->assertContains( 'multipart/form-data', $m->getHeader('Content-Type') ); $this->assertTrue($m->hasHeader('Content-Length')); $contents = $b->getContents(); $this->assertContains('name="a"', $contents); $this->assertContains('b&c=d', $contents); } /** * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException */ public function testCannotAttach() { $b = new PostBody(); $b->attach('foo'); } public function testDoesNotOverwriteExistingHeaderForUrlencoded() { $m = new Request('POST', 'http://foo.com', [ 'content-type' => 'application/x-www-form-urlencoded; charset=utf-8' ]); $b = new PostBody(); $b->setField('foo', 'bar'); $b->applyRequestHeaders($m); $this->assertEquals( 'application/x-www-form-urlencoded; charset=utf-8', $m->getHeader('Content-Type') ); } } tests/Post/PostFileTest.php000064400000003513151742731020011735 0ustar00assertInstanceOf('GuzzleHttp\Post\PostFileInterface', $p); $this->assertEquals('hi', $p->getContent()); $this->assertEquals('foo', $p->getName()); $this->assertEquals('/path/to/test.php', $p->getFilename()); $this->assertEquals( 'form-data; name="foo"; filename="test.php"', $p->getHeaders()['Content-Disposition'] ); } public function testGetsFilenameFromMetadata() { $p = new PostFile('foo', fopen(__FILE__, 'r')); $this->assertEquals(__FILE__, $p->getFilename()); } public function testDefaultsToNameWhenNoFilenameExists() { $p = new PostFile('foo', 'bar'); $this->assertEquals('foo', $p->getFilename()); } public function testCreatesFromMultipartFormData() { $mp = new MultipartBody([], [], 'baz'); $p = new PostFile('foo', $mp); $this->assertEquals( 'form-data; name="foo"', $p->getHeaders()['Content-Disposition'] ); $this->assertEquals( 'multipart/form-data; boundary=baz', $p->getHeaders()['Content-Type'] ); } public function testCanAddHeaders() { $p = new PostFile('foo', Stream::factory('hi'), 'test.php', [ 'X-Foo' => '123', 'Content-Disposition' => 'bar' ]); $this->assertEquals('bar', $p->getHeaders()['Content-Disposition']); $this->assertEquals('123', $p->getHeaders()['X-Foo']); } } tests/QueryParserTest.php000064400000005751151742731020011553 0ustar00 ['a', 'b']]], // Can parse multi-valued items that use numeric indices ['q[0]=a&q[1]=b', ['q' => ['a', 'b']]], // Can parse duplicates and does not include numeric indices ['q[]=a&q[]=b', ['q' => ['a', 'b']]], // Ensures that the value of "q" is an array even though one value ['q[]=a', ['q' => ['a']]], // Does not modify "." to "_" like PHP's parse_str() ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']], // Can decode %20 to " " ['q%20a=a%20b', ['q a' => 'a b']], // Can parse funky strings with no values by assigning each to null ['q&a', ['q' => null, 'a' => null]], // Does not strip trailing equal signs ['data=abc=', ['data' => 'abc=']], // Can store duplicates without affecting other values ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']], // Sets value to null when no "=" is present ['foo', ['foo' => null]], // Preserves "0" keys. ['0', ['0' => null]], // Sets the value to an empty string when "=" is present ['0=', ['0' => '']], // Preserves falsey keys ['var=0', ['var' => '0']], // Can deeply nest and store duplicate PHP values ['a[b][c]=1&a[b][c]=2', [ 'a' => ['b' => ['c' => ['1', '2']]] ]], // Can parse PHP style arrays ['a[b]=c&a[d]=e', ['a' => ['b' => 'c', 'd' => 'e']]], // Ensure it doesn't leave things behind with repeated values // Can parse mult-values items ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]], ]; } /** * @dataProvider parseQueryProvider */ public function testParsesQueries($input, $output) { $query = Query::fromString($input); $this->assertEquals($output, $query->toArray()); // Normalize the input and output $query->setEncodingType(false); $this->assertEquals(rawurldecode($input), (string) $query); } public function testConvertsPlusSymbolsToSpacesByDefault() { $query = Query::fromString('var=foo+bar', true); $this->assertEquals('foo bar', $query->get('var')); } public function testCanControlDecodingType() { $qp = new QueryParser(); $q = new Query(); $qp->parseInto($q, 'var=foo+bar', Query::RFC3986); $this->assertEquals('foo+bar', $q->get('var')); $qp->parseInto($q, 'var=foo+bar', Query::RFC1738); $this->assertEquals('foo bar', $q->get('var')); } } tests/QueryTest.php000064400000012035151742731020010367 0ustar00 'baz', 'bar' => 'bam boozle']); $this->assertEquals('foo=baz&bar=bam%20boozle', (string) $q); } public function testCanDisableUrlEncoding() { $q = new Query(['bar' => 'bam boozle']); $q->setEncodingType(false); $this->assertEquals('bar=bam boozle', (string) $q); } public function testCanSpecifyRfc1783UrlEncodingType() { $q = new Query(['bar abc' => 'bam boozle']); $q->setEncodingType(Query::RFC1738); $this->assertEquals('bar+abc=bam+boozle', (string) $q); } public function testCanSpecifyRfc3986UrlEncodingType() { $q = new Query(['bar abc' => 'bam boozle', 'ሴ' => 'hi']); $q->setEncodingType(Query::RFC3986); $this->assertEquals('bar%20abc=bam%20boozle&%E1%88%B4=hi', (string) $q); } /** * @expectedException \InvalidArgumentException */ public function testValidatesEncodingType() { (new Query(['bar' => 'bam boozle']))->setEncodingType('foo'); } public function testAggregatesMultipleValues() { $q = new Query(['foo' => ['bar', 'baz']]); $this->assertEquals('foo%5B0%5D=bar&foo%5B1%5D=baz', (string) $q); } public function testCanSetAggregator() { $q = new Query(['foo' => ['bar', 'baz']]); $q->setAggregator(function (array $data) { return ['foo' => ['barANDbaz']]; }); $this->assertEquals('foo=barANDbaz', (string) $q); } public function testAllowsMultipleValuesPerKey() { $q = new Query(); $q->add('facet', 'size'); $q->add('facet', 'width'); $q->add('facet.field', 'foo'); // Use the duplicate aggregator $q->setAggregator($q::duplicateAggregator()); $this->assertEquals('facet=size&facet=width&facet.field=foo', (string) $q); } public function testAllowsZeroValues() { $query = new Query(array( 'foo' => 0, 'baz' => '0', 'bar' => null, 'boo' => false )); $this->assertEquals('foo=0&baz=0&bar&boo=', (string) $query); } private $encodeData = [ 't' => [ 'v1' => ['a', '1'], 'v2' => 'b', 'v3' => ['v4' => 'c', 'v5' => 'd'] ] ]; public function testEncodesDuplicateAggregator() { $agg = Query::duplicateAggregator(); $result = $agg($this->encodeData); $this->assertEquals(array( 't[v1]' => ['a', '1'], 't[v2]' => ['b'], 't[v3][v4]' => ['c'], 't[v3][v5]' => ['d'], ), $result); } public function testDuplicateEncodesNoNumericIndices() { $agg = Query::duplicateAggregator(); $result = $agg($this->encodeData); $this->assertEquals(array( 't[v1]' => ['a', '1'], 't[v2]' => ['b'], 't[v3][v4]' => ['c'], 't[v3][v5]' => ['d'], ), $result); } public function testEncodesPhpAggregator() { $agg = Query::phpAggregator(); $result = $agg($this->encodeData); $this->assertEquals(array( 't[v1][0]' => ['a'], 't[v1][1]' => ['1'], 't[v2]' => ['b'], 't[v3][v4]' => ['c'], 't[v3][v5]' => ['d'], ), $result); } public function testPhpEncodesNoNumericIndices() { $agg = Query::phpAggregator(false); $result = $agg($this->encodeData); $this->assertEquals(array( 't[v1][]' => ['a', '1'], 't[v2]' => ['b'], 't[v3][v4]' => ['c'], 't[v3][v5]' => ['d'], ), $result); } public function testCanDisableUrlEncodingDecoding() { $q = Query::fromString('foo=bar+baz boo%20', false); $this->assertEquals('bar+baz boo%20', $q['foo']); $this->assertEquals('foo=bar+baz boo%20', (string) $q); } public function testCanChangeUrlEncodingDecodingToRfc1738() { $q = Query::fromString('foo=bar+baz', Query::RFC1738); $this->assertEquals('bar baz', $q['foo']); $this->assertEquals('foo=bar+baz', (string) $q); } public function testCanChangeUrlEncodingDecodingToRfc3986() { $q = Query::fromString('foo=bar%20baz', Query::RFC3986); $this->assertEquals('bar baz', $q['foo']); $this->assertEquals('foo=bar%20baz', (string) $q); } public function testQueryStringsAllowSlashButDoesNotDecodeWhenDisable() { $q = Query::fromString('foo=bar%2Fbaz&bam=boo%20boo', Query::RFC3986); $q->setEncodingType(false); $this->assertEquals('foo=bar/baz&bam=boo boo', (string) $q); } public function testQueryStringsAllowDecodingEncodingCompletelyDisabled() { $q = Query::fromString('foo=bar%2Fbaz&bam=boo boo!', false); $this->assertEquals('foo=bar%2Fbaz&bam=boo boo!', (string) $q); } } tests/RequestFsmTest.php000064400000015162151742731020011364 0ustar00mf = new MessageFactory(); } public function testEmitsBeforeEventInTransition() { $fsm = new RequestFsm(function () {}, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $c = false; $t->request->getEmitter()->on('before', function (BeforeEvent $e) use (&$c) { $c = true; }); $fsm($t, 'send'); $this->assertTrue($c); } public function testEmitsCompleteEventInTransition() { $fsm = new RequestFsm(function () {}, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $t->response = new Response(200); $t->state = 'complete'; $c = false; $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) { $c = true; }); $fsm($t, 'end'); $this->assertTrue($c); } public function testDoesNotEmitCompleteForFuture() { $fsm = new RequestFsm(function () {}, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $deferred = new Deferred(); $t->response = new FutureResponse($deferred->promise()); $t->state = 'complete'; $c = false; $t->request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$c) { $c = true; }); $fsm($t, 'end'); $this->assertFalse($c); } public function testDoesNotEmitEndForFuture() { $fsm = new RequestFsm(function () {}, $this->mf); $t = new Transaction(new Client(), new Request('GET', 'http://foo.com')); $deferred = new Deferred(); $t->response = new FutureResponse($deferred->promise()); $t->state = 'end'; $c = false; $t->request->getEmitter()->on('end', function (EndEvent $e) use (&$c) { $c = true; }); $fsm($t); $this->assertFalse($c); } public function testTransitionsThroughSuccessfulTransfer() { $client = new Client(); $client->getEmitter()->attach(new Mock([new Response(200)])); $request = $client->createRequest('GET', 'http://ewfewwef.com'); $this->addListeners($request, $calls); $client->send($request); $this->assertEquals(['before', 'complete', 'end'], $calls); } public function testTransitionsThroughErrorsInBefore() { $fsm = new RequestFsm(function () {}, $this->mf); $client = new Client(); $request = $client->createRequest('GET', 'http://ewfewwef.com'); $t = new Transaction($client, $request); $calls = []; $this->addListeners($t->request, $calls); $t->request->getEmitter()->on('before', function (BeforeEvent $e) { throw new \Exception('foo'); }); try { $fsm($t, 'send'); $this->fail('did not throw'); } catch (RequestException $e) { $this->assertContains('foo', $t->exception->getMessage()); $this->assertEquals(['before', 'error', 'end'], $calls); } } public function testTransitionsThroughErrorsInComplete() { $client = new Client(); $client->getEmitter()->attach(new Mock([new Response(200)])); $request = $client->createRequest('GET', 'http://ewfewwef.com'); $this->addListeners($request, $calls); $request->getEmitter()->once('complete', function (CompleteEvent $e) { throw new \Exception('foo'); }); try { $client->send($request); $this->fail('did not throw'); } catch (RequestException $e) { $this->assertContains('foo', $e->getMessage()); $this->assertEquals(['before', 'complete', 'error', 'end'], $calls); } } public function testTransitionsThroughErrorInterception() { $fsm = new RequestFsm(function () {}, $this->mf); $client = new Client(); $request = $client->createRequest('GET', 'http://ewfewwef.com'); $t = new Transaction($client, $request); $calls = []; $this->addListeners($t->request, $calls); $t->request->getEmitter()->on('error', function (ErrorEvent $e) { $e->intercept(new Response(200)); }); $fsm($t, 'send'); $t->response = new Response(404); $t->state = 'complete'; $fsm($t); $this->assertEquals(200, $t->response->getStatusCode()); $this->assertNull($t->exception); $this->assertEquals(['before', 'complete', 'error', 'complete', 'end'], $calls); } private function addListeners(RequestInterface $request, &$calls) { $request->getEmitter()->on('before', function (BeforeEvent $e) use (&$calls) { $calls[] = 'before'; }, RequestEvents::EARLY); $request->getEmitter()->on('complete', function (CompleteEvent $e) use (&$calls) { $calls[] = 'complete'; }, RequestEvents::EARLY); $request->getEmitter()->on('error', function (ErrorEvent $e) use (&$calls) { $calls[] = 'error'; }, RequestEvents::EARLY); $request->getEmitter()->on('end', function (EndEvent $e) use (&$calls) { $calls[] = 'end'; }, RequestEvents::EARLY); } /** * @expectedException \GuzzleHttp\Exception\RequestException * @expectedExceptionMessage Too many state transitions */ public function testDetectsInfiniteLoops() { $client = new Client([ 'fsm' => $fsm = new RequestFsm( function () {}, new MessageFactory(), 3 ) ]); $request = $client->createRequest('GET', 'http://foo.com:123'); $request->getEmitter()->on('before', function () { throw new \Exception('foo'); }); $request->getEmitter()->on('error', function ($e) { $e->retry(); }); $client->send($request); } } tests/RingBridgeTest.php000064400000017063151742731020011304 0ustar00 'hello' ], $stream); $request->getConfig()->set('foo', 'bar'); $trans = new Transaction(new Client(), $request); $factory = new MessageFactory(); $fsm = new RequestFsm(function () {}, new MessageFactory()); $r = RingBridge::prepareRingRequest($trans, $factory, $fsm); $this->assertEquals('http', $r['scheme']); $this->assertEquals('1.1', $r['version']); $this->assertEquals('GET', $r['http_method']); $this->assertEquals('http://httpbin.org/get?a=b', $r['url']); $this->assertEquals('/get', $r['uri']); $this->assertEquals('a=b', $r['query_string']); $this->assertEquals([ 'Host' => ['httpbin.org'], 'test' => ['hello'] ], $r['headers']); $this->assertSame($stream, $r['body']); $this->assertEquals(['foo' => 'bar'], $r['client']); $this->assertFalse($r['future']); } public function testCreatesRingRequestsWithNullQueryString() { $request = new Request('GET', 'http://httpbin.org'); $trans = new Transaction(new Client(), $request); $factory = new MessageFactory(); $fsm = new RequestFsm(function () {}, new MessageFactory()); $r = RingBridge::prepareRingRequest($trans, $factory, $fsm); $this->assertNull($r['query_string']); $this->assertEquals('/', $r['uri']); $this->assertEquals(['Host' => ['httpbin.org']], $r['headers']); $this->assertNull($r['body']); $this->assertEquals([], $r['client']); } public function testAddsProgress() { Server::enqueue([new Response(200)]); $client = new Client(['base_url' => Server::$url]); $request = $client->createRequest('GET'); $called = false; $request->getEmitter()->on( 'progress', function (ProgressEvent $e) use (&$called) { $called = true; } ); $this->assertEquals(200, $client->send($request)->getStatusCode()); $this->assertTrue($called); } public function testGetsResponseProtocolVersionAndEffectiveUrlAndReason() { $client = new Client([ 'handler' => new MockHandler([ 'status' => 200, 'reason' => 'test', 'headers' => [], 'version' => '1.0', 'effective_url' => 'http://foo.com' ]) ]); $request = $client->createRequest('GET', 'http://foo.com'); $response = $client->send($request); $this->assertEquals('1.0', $response->getProtocolVersion()); $this->assertEquals('http://foo.com', $response->getEffectiveUrl()); $this->assertEquals('test', $response->getReasonPhrase()); } public function testGetsStreamFromResponse() { $res = fopen('php://temp', 'r+'); fwrite($res, 'foo'); rewind($res); $client = new Client([ 'handler' => new MockHandler([ 'status' => 200, 'headers' => [], 'body' => $res ]) ]); $request = $client->createRequest('GET', 'http://foo.com'); $response = $client->send($request); $this->assertEquals('foo', (string) $response->getBody()); } public function testEmitsCompleteEventOnSuccess() { $c = false; $trans = new Transaction(new Client(), new Request('GET', 'http://f.co')); $trans->request->getEmitter()->on('complete', function () use (&$c) { $c = true; }); $f = new MessageFactory(); $res = ['status' => 200, 'headers' => []]; $fsm = new RequestFsm(function () {}, new MessageFactory()); RingBridge::completeRingResponse($trans, $res, $f, $fsm); $this->assertInstanceOf( 'GuzzleHttp\Message\ResponseInterface', $trans->response ); $this->assertTrue($c); } public function testEmitsErrorEventOnError() { $client = new Client(['base_url' => 'http://127.0.0.1:123']); $request = $client->createRequest('GET'); $called = false; $request->getEmitter()->on('error', function () use (&$called) { $called = true; }); $request->getConfig()['timeout'] = 0.001; $request->getConfig()['connect_timeout'] = 0.001; try { $client->send($request); $this->fail('did not throw'); } catch (RequestException $e) { $this->assertSame($request, $e->getRequest()); $this->assertContains('cURL error', $e->getMessage()); $this->assertTrue($called); } } /** * @expectedException \InvalidArgumentException */ public function testValidatesRingRequest() { RingBridge::fromRingRequest([]); } public function testCreatesRequestFromRing() { $request = RingBridge::fromRingRequest([ 'http_method' => 'GET', 'uri' => '/', 'headers' => [ 'foo' => ['bar'], 'host' => ['foo.com'] ], 'body' => 'test', 'version' => '1.0' ]); $this->assertEquals('GET', $request->getMethod()); $this->assertEquals('http://foo.com/', $request->getUrl()); $this->assertEquals('1.0', $request->getProtocolVersion()); $this->assertEquals('test', (string) $request->getBody()); $this->assertEquals('bar', $request->getHeader('foo')); } public function testCanInterceptException() { $client = new Client(['base_url' => 'http://127.0.0.1:123']); $request = $client->createRequest('GET'); $called = false; $request->getEmitter()->on( 'error', function (ErrorEvent $e) use (&$called) { $called = true; $e->intercept(new Response(200)); } ); $request->getConfig()['timeout'] = 0.001; $request->getConfig()['connect_timeout'] = 0.001; $this->assertEquals(200, $client->send($request)->getStatusCode()); $this->assertTrue($called); } public function testCreatesLongException() { $r = new Request('GET', 'http://www.google.com'); $e = RingBridge::getNoRingResponseException($r); $this->assertInstanceOf('GuzzleHttp\Exception\RequestException', $e); $this->assertSame($r, $e->getRequest()); } public function testEnsuresResponseOrExceptionWhenCompletingResponse() { $trans = new Transaction(new Client(), new Request('GET', 'http://f.co')); $f = new MessageFactory(); $fsm = new RequestFsm(function () {}, new MessageFactory()); try { RingBridge::completeRingResponse($trans, [], $f, $fsm); } catch (RequestException $e) { $this->assertSame($trans->request, $e->getRequest()); $this->assertContains('RingPHP', $e->getMessage()); } } } tests/Server.php000064400000005536151742731020007700 0ustar00fromMessage($response); } elseif (!($response instanceof ResponseInterface)) { throw new \Exception('Responses must be strings or Responses'); } $data[] = self::convertResponse($response); } TestServer::enqueue($data); } /** * Get all of the received requests * * @param bool $hydrate Set to TRUE to turn the messages into * actual {@see RequestInterface} objects. If $hydrate is FALSE, * requests will be returned as strings. * * @return array * @throws \RuntimeException */ public static function received($hydrate = false) { $response = TestServer::received(); if ($hydrate) { $c = new Client(); $factory = new MessageFactory(); $response = array_map(function($message) use ($factory, $c) { return RingBridge::fromRingRequest($message); }, $response); } return $response; } public static function flush() { TestServer::flush(); } public static function stop() { TestServer::stop(); } public static function wait($maxTries = 5) { TestServer::wait($maxTries); } public static function start() { TestServer::start(); } private static function convertResponse(Response $response) { $headers = array_map(function ($h) { return implode(', ', $h); }, $response->getHeaders()); return [ 'status' => $response->getStatusCode(), 'reason' => $response->getReasonPhrase(), 'headers' => $headers, 'body' => base64_encode((string) $response->getBody()) ]; } } tests/Subscriber/CookieTest.php000064400000004722151742731020012602 0ustar00getMockBuilder('GuzzleHttp\Cookie\CookieJar') ->setMethods(array('extractCookies')) ->getMock(); $mock->expects($this->exactly(1)) ->method('extractCookies') ->with($request, $response); $plugin = new Cookie($mock); $t = new Transaction(new Client(), $request); $t->response = $response; $plugin->onComplete(new CompleteEvent($t)); } public function testProvidesCookieJar() { $jar = new CookieJar(); $plugin = new Cookie($jar); $this->assertSame($jar, $plugin->getCookieJar()); } public function testCookiesAreExtractedFromRedirectResponses() { $jar = new CookieJar(); $cookie = new Cookie($jar); $history = new History(); $mock = new Mock([ "HTTP/1.1 302 Moved Temporarily\r\n" . "Set-Cookie: test=583551; Domain=www.foo.com; Expires=Wednesday, 23-Mar-2050 19:49:45 GMT; Path=/\r\n" . "Location: /redirect\r\n\r\n", "HTTP/1.1 200 OK\r\n" . "Content-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\n" . "Content-Length: 0\r\n\r\n" ]); $client = new Client(['base_url' => 'http://www.foo.com']); $client->getEmitter()->attach($cookie); $client->getEmitter()->attach($mock); $client->getEmitter()->attach($history); $client->get(); $request = $client->createRequest('GET', '/'); $client->send($request); $this->assertEquals('test=583551', $request->getHeader('Cookie')); $requests = $history->getRequests(); // Confirm subsequent requests have the cookie. $this->assertEquals('test=583551', $requests[2]->getHeader('Cookie')); // Confirm the redirected request has the cookie. $this->assertEquals('test=583551', $requests[1]->getHeader('Cookie')); } } tests/Subscriber/HistoryTest.php000064400000010565151742731020013034 0ustar00response = $response; $e = new RequestException('foo', $request, $response); $ev = new ErrorEvent($t, $e); $h = new History(2); $h->onError($ev); // Only tracks when no response is present $this->assertEquals([], $h->getRequests()); } public function testLogsConnectionErrors() { $request = new Request('GET', '/'); $t = new Transaction(new Client(), $request); $e = new RequestException('foo', $request); $ev = new ErrorEvent($t, $e); $h = new History(); $h->onError($ev); $this->assertEquals([$request], $h->getRequests()); } public function testMaintainsLimitValue() { $request = new Request('GET', '/'); $response = new Response(200); $t = new Transaction(new Client(), $request); $t->response = $response; $ev = new CompleteEvent($t); $h = new History(2); $h->onComplete($ev); $h->onComplete($ev); $h->onComplete($ev); $this->assertEquals(2, count($h)); $this->assertSame($request, $h->getLastRequest()); $this->assertSame($response, $h->getLastResponse()); foreach ($h as $trans) { $this->assertInstanceOf('GuzzleHttp\Message\RequestInterface', $trans['request']); $this->assertInstanceOf('GuzzleHttp\Message\ResponseInterface', $trans['response']); } return $h; } /** * @depends testMaintainsLimitValue */ public function testClearsHistory($h) { $this->assertEquals(2, count($h)); $h->clear(); $this->assertEquals(0, count($h)); } public function testWorksWithMock() { $client = new Client(['base_url' => 'http://localhost/']); $h = new History(); $client->getEmitter()->attach($h); $mock = new Mock([new Response(200), new Response(201), new Response(202)]); $client->getEmitter()->attach($mock); $request = $client->createRequest('GET', '/'); $client->send($request); $request->setMethod('PUT'); $client->send($request); $request->setMethod('POST'); $client->send($request); $this->assertEquals(3, count($h)); $result = implode("\n", array_map(function ($line) { return strpos($line, 'User-Agent') === 0 ? 'User-Agent:' : trim($line); }, explode("\n", $h))); $this->assertEquals("> GET / HTTP/1.1 Host: localhost User-Agent: < HTTP/1.1 200 OK > PUT / HTTP/1.1 Host: localhost User-Agent: < HTTP/1.1 201 Created > POST / HTTP/1.1 Host: localhost User-Agent: < HTTP/1.1 202 Accepted ", $result); } public function testCanCastToString() { $client = new Client(['base_url' => 'http://localhost/']); $h = new History(); $client->getEmitter()->attach($h); $mock = new Mock(array( new Response(301, array('Location' => '/redirect1', 'Content-Length' => 0)), new Response(307, array('Location' => '/redirect2', 'Content-Length' => 0)), new Response(200, array('Content-Length' => '2'), Stream::factory('HI')) )); $client->getEmitter()->attach($mock); $request = $client->createRequest('GET', '/'); $client->send($request); $this->assertEquals(3, count($h)); $h = str_replace("\r", '', $h); $this->assertContains("> GET / HTTP/1.1\nHost: localhost\nUser-Agent:", $h); $this->assertContains("< HTTP/1.1 301 Moved Permanently\nLocation: /redirect1", $h); $this->assertContains("< HTTP/1.1 307 Temporary Redirect\nLocation: /redirect2", $h); $this->assertContains("< HTTP/1.1 200 OK\nContent-Length: 2\n\nHI", $h); } } tests/Subscriber/HttpErrorTest.php000064400000003034151742731020013315 0ustar00getEvent(); $event->intercept(new Response(200)); (new HttpError())->onComplete($event); } /** * @expectedException \GuzzleHttp\Exception\ClientException */ public function testThrowsClientExceptionOnFailure() { $event = $this->getEvent(); $event->intercept(new Response(403)); (new HttpError())->onComplete($event); } /** * @expectedException \GuzzleHttp\Exception\ServerException */ public function testThrowsServerExceptionOnFailure() { $event = $this->getEvent(); $event->intercept(new Response(500)); (new HttpError())->onComplete($event); } private function getEvent() { return new CompleteEvent(new Transaction(new Client(), new Request('PUT', '/'))); } /** * @expectedException \GuzzleHttp\Exception\ClientException */ public function testFullTransaction() { $client = new Client(); $client->getEmitter()->attach(new Mock([ new Response(403) ])); $client->get('http://httpbin.org'); } } tests/Subscriber/MockTest.php000064400000014276151742731020012267 0ustar00promise(), function () use ($deferred, $wait) { $deferred->resolve($wait()); }, $cancel ); } public function testDescribesSubscribedEvents() { $mock = new Mock(); $this->assertInternalType('array', $mock->getEvents()); } public function testIsCountable() { $plugin = new Mock(); $plugin->addResponse((new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); $this->assertEquals(1, count($plugin)); } public function testCanClearQueue() { $plugin = new Mock(); $plugin->addResponse((new MessageFactory())->fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); $plugin->clearQueue(); $this->assertEquals(0, count($plugin)); } public function testRetrievesResponsesFromFiles() { $tmp = tempnam('/tmp', 'tfile'); file_put_contents($tmp, "HTTP/1.1 201 OK\r\nContent-Length: 0\r\n\r\n"); $plugin = new Mock(); $plugin->addResponse($tmp); unlink($tmp); $this->assertEquals(1, count($plugin)); $q = $this->readAttribute($plugin, 'queue'); $this->assertEquals(201, $q[0]->getStatusCode()); } /** * @expectedException \InvalidArgumentException */ public function testThrowsExceptionWhenInvalidResponse() { (new Mock())->addResponse(false); } public function testAddsMockResponseToRequestFromClient() { $response = new Response(200); $t = new Transaction(new Client(), new Request('GET', '/')); $m = new Mock([$response]); $ev = new BeforeEvent($t); $m->onBefore($ev); $this->assertSame($response, $t->response); } /** * @expectedException \OutOfBoundsException */ public function testUpdateThrowsExceptionWhenEmpty() { $p = new Mock(); $ev = new BeforeEvent(new Transaction(new Client(), new Request('GET', '/'))); $p->onBefore($ev); } public function testReadsBodiesFromMockedRequests() { $m = new Mock([new Response(200)]); $client = new Client(['base_url' => 'http://test.com']); $client->getEmitter()->attach($m); $body = Stream::factory('foo'); $client->put('/', ['body' => $body]); $this->assertEquals(3, $body->tell()); } public function testCanMockBadRequestExceptions() { $client = new Client(['base_url' => 'http://test.com']); $request = $client->createRequest('GET', '/'); $ex = new RequestException('foo', $request); $mock = new Mock([$ex]); $this->assertCount(1, $mock); $request->getEmitter()->attach($mock); try { $client->send($request); $this->fail('Did not dequeue an exception'); } catch (RequestException $e) { $this->assertSame($e, $ex); $this->assertSame($request, $ex->getRequest()); } } public function testCanMockFutureResponses() { $client = new Client(['base_url' => 'http://test.com']); $request = $client->createRequest('GET', '/', ['future' => true]); $response = new Response(200); $future = self::createFuture(function () use ($response) { return $response; }); $mock = new Mock([$future]); $this->assertCount(1, $mock); $request->getEmitter()->attach($mock); $res = $client->send($request); $this->assertSame($future, $res); $this->assertFalse($this->readAttribute($res, 'isRealized')); $this->assertSame($response, $res->wait()); } public function testCanMockExceptionFutureResponses() { $client = new Client(['base_url' => 'http://test.com']); $request = $client->createRequest('GET', '/', ['future' => true]); $future = self::createFuture(function () use ($request) { throw new RequestException('foo', $request); }); $mock = new Mock([$future]); $request->getEmitter()->attach($mock); $response = $client->send($request); $this->assertSame($future, $response); $this->assertFalse($this->readAttribute($response, 'isRealized')); try { $response->wait(); $this->fail('Did not throw'); } catch (RequestException $e) { $this->assertContains('foo', $e->getMessage()); } } public function testCanMockFailedFutureResponses() { $client = new Client(['base_url' => 'http://test.com']); $request = $client->createRequest('GET', '/', ['future' => true]); // The first mock will be a mocked future response. $future = self::createFuture(function () use ($client) { // When dereferenced, we will set a mocked response and send // another request. $client->get('http://httpbin.org', ['events' => [ 'before' => function (BeforeEvent $e) { $e->intercept(new Response(404)); } ]]); }); $mock = new Mock([$future]); $request->getEmitter()->attach($mock); $response = $client->send($request); $this->assertSame($future, $response); $this->assertFalse($this->readAttribute($response, 'isRealized')); try { $response->wait(); $this->fail('Did not throw'); } catch (RequestException $e) { $this->assertEquals(404, $e->getResponse()->getStatusCode()); } } } tests/Subscriber/PrepareTest.php000064400000015605151742731020012771 0ustar00getTrans(); $s->onBefore(new BeforeEvent($t)); $this->assertFalse($t->request->hasHeader('Expect')); } public function testAppliesPostBody() { $s = new Prepare(); $t = $this->getTrans(); $p = $this->getMockBuilder('GuzzleHttp\Post\PostBody') ->setMethods(['applyRequestHeaders']) ->getMockForAbstractClass(); $p->expects($this->once()) ->method('applyRequestHeaders'); $t->request->setBody($p); $s->onBefore(new BeforeEvent($t)); } public function testAddsExpectHeaderWithTrue() { $s = new Prepare(); $t = $this->getTrans(); $t->request->getConfig()->set('expect', true); $t->request->setBody(Stream::factory('foo')); $s->onBefore(new BeforeEvent($t)); $this->assertEquals('100-Continue', $t->request->getHeader('Expect')); } public function testAddsExpectHeaderBySize() { $s = new Prepare(); $t = $this->getTrans(); $t->request->getConfig()->set('expect', 2); $t->request->setBody(Stream::factory('foo')); $s->onBefore(new BeforeEvent($t)); $this->assertTrue($t->request->hasHeader('Expect')); } public function testDoesNotModifyExpectHeaderIfPresent() { $s = new Prepare(); $t = $this->getTrans(); $t->request->setHeader('Expect', 'foo'); $t->request->setBody(Stream::factory('foo')); $s->onBefore(new BeforeEvent($t)); $this->assertEquals('foo', $t->request->getHeader('Expect')); } public function testDoesAddExpectHeaderWhenSetToFalse() { $s = new Prepare(); $t = $this->getTrans(); $t->request->getConfig()->set('expect', false); $t->request->setBody(Stream::factory('foo')); $s->onBefore(new BeforeEvent($t)); $this->assertFalse($t->request->hasHeader('Expect')); } public function testDoesNotAddExpectHeaderBySize() { $s = new Prepare(); $t = $this->getTrans(); $t->request->getConfig()->set('expect', 10); $t->request->setBody(Stream::factory('foo')); $s->onBefore(new BeforeEvent($t)); $this->assertFalse($t->request->hasHeader('Expect')); } public function testAddsExpectHeaderForNonSeekable() { $s = new Prepare(); $t = $this->getTrans(); $t->request->setBody(new NoSeekStream(Stream::factory('foo'))); $s->onBefore(new BeforeEvent($t)); $this->assertTrue($t->request->hasHeader('Expect')); } public function testRemovesContentLengthWhenSendingWithChunked() { $s = new Prepare(); $t = $this->getTrans(); $t->request->setBody(Stream::factory('foo')); $t->request->setHeader('Transfer-Encoding', 'chunked'); $s->onBefore(new BeforeEvent($t)); $this->assertFalse($t->request->hasHeader('Content-Length')); } public function testUsesProvidedContentLengthAndRemovesXferEncoding() { $s = new Prepare(); $t = $this->getTrans(); $t->request->setBody(Stream::factory('foo')); $t->request->setHeader('Content-Length', '3'); $t->request->setHeader('Transfer-Encoding', 'chunked'); $s->onBefore(new BeforeEvent($t)); $this->assertEquals(3, $t->request->getHeader('Content-Length')); $this->assertFalse($t->request->hasHeader('Transfer-Encoding')); } public function testSetsContentTypeIfPossibleFromStream() { $body = $this->getMockBody(); $sub = new Prepare(); $t = $this->getTrans(); $t->request->setBody($body); $sub->onBefore(new BeforeEvent($t)); $this->assertEquals( 'image/jpeg', $t->request->getHeader('Content-Type') ); $this->assertEquals(4, $t->request->getHeader('Content-Length')); } public function testDoesNotOverwriteExistingContentType() { $s = new Prepare(); $t = $this->getTrans(); $t->request->setBody($this->getMockBody()); $t->request->setHeader('Content-Type', 'foo/baz'); $s->onBefore(new BeforeEvent($t)); $this->assertEquals( 'foo/baz', $t->request->getHeader('Content-Type') ); } public function testSetsContentLengthIfPossible() { $s = new Prepare(); $t = $this->getTrans(); $t->request->setBody($this->getMockBody()); $s->onBefore(new BeforeEvent($t)); $this->assertEquals(4, $t->request->getHeader('Content-Length')); } public function testSetsTransferEncodingChunkedIfNeeded() { $r = new Request('PUT', '/'); $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['getSize']) ->getMockForAbstractClass(); $s->expects($this->exactly(2)) ->method('getSize') ->will($this->returnValue(null)); $r->setBody($s); $t = $this->getTrans($r); $s = new Prepare(); $s->onBefore(new BeforeEvent($t)); $this->assertEquals('chunked', $r->getHeader('Transfer-Encoding')); } public function testContentLengthIntegrationTest() { Server::flush(); Server::enqueue([new Response(200)]); $client = new Client(['base_url' => Server::$url]); $this->assertEquals(200, $client->put('/', [ 'body' => 'test' ])->getStatusCode()); $request = Server::received(true)[0]; $this->assertEquals('PUT', $request->getMethod()); $this->assertEquals('4', $request->getHeader('Content-Length')); $this->assertEquals('test', (string) $request->getBody()); } private function getTrans($request = null) { return new Transaction( new Client(), $request ?: new Request('PUT', '/') ); } /** * @return \GuzzleHttp\Stream\StreamInterface */ private function getMockBody() { $s = $this->getMockBuilder('GuzzleHttp\Stream\MetadataStreamInterface') ->setMethods(['getMetadata', 'getSize']) ->getMockForAbstractClass(); $s->expects($this->any()) ->method('getMetadata') ->with('uri') ->will($this->returnValue('/foo/baz/bar.jpg')); $s->expects($this->exactly(2)) ->method('getSize') ->will($this->returnValue(4)); return $s; } } tests/Subscriber/RedirectTest.php000064400000026423151742731020013134 0ustar00addMultiple([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ]); $client = new Client(['base_url' => 'http://test.com']); $client->getEmitter()->attach($history); $client->getEmitter()->attach($mock); $request = $client->createRequest('GET', '/foo'); // Ensure "end" is called only once $called = 0; $request->getEmitter()->on('end', function () use (&$called) { $called++; }); $response = $client->send($request); $this->assertEquals(200, $response->getStatusCode()); $this->assertContains('/redirect2', $response->getEffectiveUrl()); // Ensure that two requests were sent $requests = $history->getRequests(true); $this->assertEquals('/foo', $requests[0]->getPath()); $this->assertEquals('GET', $requests[0]->getMethod()); $this->assertEquals('/redirect1', $requests[1]->getPath()); $this->assertEquals('GET', $requests[1]->getMethod()); $this->assertEquals('/redirect2', $requests[2]->getPath()); $this->assertEquals('GET', $requests[2]->getMethod()); $this->assertEquals(1, $called); } /** * @expectedException \GuzzleHttp\Exception\TooManyRedirectsException * @expectedExceptionMessage Will not follow more than */ public function testCanLimitNumberOfRedirects() { $mock = new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect3\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect4\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect5\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect6\r\nContent-Length: 0\r\n\r\n" ]); $client = new Client(); $client->getEmitter()->attach($mock); $client->get('http://www.example.com/foo'); } public function testDefaultBehaviorIsToRedirectWithGetForEntityEnclosingRequests() { $h = new History(); $mock = new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ]); $client = new Client(); $client->getEmitter()->attach($mock); $client->getEmitter()->attach($h); $client->post('http://test.com/foo', [ 'headers' => ['X-Baz' => 'bar'], 'body' => 'testing' ]); $requests = $h->getRequests(true); $this->assertEquals('POST', $requests[0]->getMethod()); $this->assertEquals('GET', $requests[1]->getMethod()); $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); $this->assertEquals('GET', $requests[2]->getMethod()); } public function testCanRedirectWithStrictRfcCompliance() { $h = new History(); $mock = new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ]); $client = new Client(['base_url' => 'http://test.com']); $client->getEmitter()->attach($mock); $client->getEmitter()->attach($h); $client->post('/foo', [ 'headers' => ['X-Baz' => 'bar'], 'body' => 'testing', 'allow_redirects' => ['max' => 10, 'strict' => true] ]); $requests = $h->getRequests(true); $this->assertEquals('POST', $requests[0]->getMethod()); $this->assertEquals('POST', $requests[1]->getMethod()); $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); $this->assertEquals('POST', $requests[2]->getMethod()); } public function testRewindsStreamWhenRedirectingIfNeeded() { $h = new History(); $mock = new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ]); $client = new Client(['base_url' => 'http://test.com']); $client->getEmitter()->attach($mock); $client->getEmitter()->attach($h); $body = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['seek', 'read', 'eof', 'tell']) ->getMockForAbstractClass(); $body->expects($this->once())->method('tell')->will($this->returnValue(1)); $body->expects($this->once())->method('seek')->will($this->returnValue(true)); $body->expects($this->any())->method('eof')->will($this->returnValue(true)); $body->expects($this->any())->method('read')->will($this->returnValue('foo')); $client->post('/foo', [ 'body' => $body, 'allow_redirects' => ['max' => 5, 'strict' => true] ]); } /** * @expectedException \GuzzleHttp\Exception\CouldNotRewindStreamException * @expectedExceptionMessage Unable to rewind the non-seekable request body after redirecting */ public function testThrowsExceptionWhenStreamCannotBeRewound() { $h = new History(); $mock = new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ]); $client = new Client(); $client->getEmitter()->attach($mock); $client->getEmitter()->attach($h); $body = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['seek', 'read', 'eof', 'tell']) ->getMockForAbstractClass(); $body->expects($this->once())->method('tell')->will($this->returnValue(1)); $body->expects($this->once())->method('seek')->will($this->returnValue(false)); $body->expects($this->any())->method('eof')->will($this->returnValue(true)); $body->expects($this->any())->method('read')->will($this->returnValue('foo')); $client->post('http://example.com/foo', [ 'body' => $body, 'allow_redirects' => ['max' => 10, 'strict' => true] ]); } public function testRedirectsCanBeDisabledPerRequest() { $client = new Client(['base_url' => 'http://test.com']); $client->getEmitter()->attach(new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ])); $response = $client->put('/', ['body' => 'test', 'allow_redirects' => false]); $this->assertEquals(301, $response->getStatusCode()); } public function testCanRedirectWithNoLeadingSlashAndQuery() { $h = new History(); $client = new Client(['base_url' => 'http://www.foo.com']); $client->getEmitter()->attach(new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect?foo=bar\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ])); $client->getEmitter()->attach($h); $client->get('?foo=bar'); $requests = $h->getRequests(true); $this->assertEquals('http://www.foo.com?foo=bar', $requests[0]->getUrl()); $this->assertEquals('http://www.foo.com/redirect?foo=bar', $requests[1]->getUrl()); } public function testHandlesRedirectsWithSpacesProperly() { $client = new Client(['base_url' => 'http://www.foo.com']); $client->getEmitter()->attach(new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect 1\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" ])); $h = new History(); $client->getEmitter()->attach($h); $client->get('/foo'); $reqs = $h->getRequests(true); $this->assertEquals('/redirect%201', $reqs[1]->getResource()); } public function testAddsRefererWhenPossible() { $client = new Client(['base_url' => 'http://www.foo.com']); $client->getEmitter()->attach(new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: /bar\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" ])); $h = new History(); $client->getEmitter()->attach($h); $client->get('/foo', ['allow_redirects' => ['max' => 5, 'referer' => true]]); $reqs = $h->getRequests(true); $this->assertEquals('http://www.foo.com/foo', $reqs[1]->getHeader('Referer')); } public function testDoesNotAddRefererWhenChangingProtocols() { $client = new Client(['base_url' => 'https://www.foo.com']); $client->getEmitter()->attach(new Mock([ "HTTP/1.1 301 Moved Permanently\r\n" . "Location: http://www.foo.com/foo\r\n" . "Content-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" ])); $h = new History(); $client->getEmitter()->attach($h); $client->get('/foo', ['allow_redirects' => ['max' => 5, 'referer' => true]]); $reqs = $h->getRequests(true); $this->assertFalse($reqs[1]->hasHeader('Referer')); } public function testRedirectsWithGetOn303() { $h = new History(); $mock = new Mock([ "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", ]); $client = new Client(); $client->getEmitter()->attach($mock); $client->getEmitter()->attach($h); $client->post('http://test.com/foo', ['body' => 'testing']); $requests = $h->getRequests(true); $this->assertEquals('POST', $requests[0]->getMethod()); $this->assertEquals('GET', $requests[1]->getMethod()); } public function testRelativeLinkBasedLatestRequest() { $client = new Client(['base_url' => 'http://www.foo.com']); $client->getEmitter()->attach(new Mock([ "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.bar.com\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" ])); $response = $client->get('/'); $this->assertEquals( 'http://www.bar.com/redirect', $response->getEffectiveUrl() ); } } tests/TransactionTest.php000064400000001163151742731020011547 0ustar00assertSame($client, $t->client); $this->assertSame($request, $t->request); $response = new Response(200); $t->response = $response; $this->assertSame($response, $t->response); } } tests/UriTemplateTest.php000064400000020325151742731020011516 0ustar00 'value', 'hello' => 'Hello World!', 'empty' => '', 'path' => '/foo/bar', 'x' => '1024', 'y' => '768', 'null' => null, 'list' => array('red', 'green', 'blue'), 'keys' => array( "semi" => ';', "dot" => '.', "comma" => ',' ), 'empty_keys' => array(), ); return array_map(function ($t) use ($params) { $t[] = $params; return $t; }, array( array('foo', 'foo'), array('{var}', 'value'), array('{hello}', 'Hello%20World%21'), array('{+var}', 'value'), array('{+hello}', 'Hello%20World!'), array('{+path}/here', '/foo/bar/here'), array('here?ref={+path}', 'here?ref=/foo/bar'), array('X{#var}', 'X#value'), array('X{#hello}', 'X#Hello%20World!'), array('map?{x,y}', 'map?1024,768'), array('{x,hello,y}', '1024,Hello%20World%21,768'), array('{+x,hello,y}', '1024,Hello%20World!,768'), array('{+path,x}/here', '/foo/bar,1024/here'), array('{#x,hello,y}', '#1024,Hello%20World!,768'), array('{#path,x}/here', '#/foo/bar,1024/here'), array('X{.var}', 'X.value'), array('X{.x,y}', 'X.1024.768'), array('{/var}', '/value'), array('{/var,x}/here', '/value/1024/here'), array('{;x,y}', ';x=1024;y=768'), array('{;x,y,empty}', ';x=1024;y=768;empty'), array('{?x,y}', '?x=1024&y=768'), array('{?x,y,empty}', '?x=1024&y=768&empty='), array('?fixed=yes{&x}', '?fixed=yes&x=1024'), array('{&x,y,empty}', '&x=1024&y=768&empty='), array('{var:3}', 'val'), array('{var:30}', 'value'), array('{list}', 'red,green,blue'), array('{list*}', 'red,green,blue'), array('{keys}', 'semi,%3B,dot,.,comma,%2C'), array('{keys*}', 'semi=%3B,dot=.,comma=%2C'), array('{+path:6}/here', '/foo/b/here'), array('{+list}', 'red,green,blue'), array('{+list*}', 'red,green,blue'), array('{+keys}', 'semi,;,dot,.,comma,,'), array('{+keys*}', 'semi=;,dot=.,comma=,'), array('{#path:6}/here', '#/foo/b/here'), array('{#list}', '#red,green,blue'), array('{#list*}', '#red,green,blue'), array('{#keys}', '#semi,;,dot,.,comma,,'), array('{#keys*}', '#semi=;,dot=.,comma=,'), array('X{.var:3}', 'X.val'), array('X{.list}', 'X.red,green,blue'), array('X{.list*}', 'X.red.green.blue'), array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'), array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'), array('{/var:1,var}', '/v/value'), array('{/list}', '/red,green,blue'), array('{/list*}', '/red/green/blue'), array('{/list*,path:4}', '/red/green/blue/%2Ffoo'), array('{/keys}', '/semi,%3B,dot,.,comma,%2C'), array('{/keys*}', '/semi=%3B/dot=./comma=%2C'), array('{;hello:5}', ';hello=Hello'), array('{;list}', ';list=red,green,blue'), array('{;list*}', ';list=red;list=green;list=blue'), array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'), array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'), array('{?var:3}', '?var=val'), array('{?list}', '?list=red,green,blue'), array('{?list*}', '?list=red&list=green&list=blue'), array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'), array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'), array('{&var:3}', '&var=val'), array('{&list}', '&list=red,green,blue'), array('{&list*}', '&list=red&list=green&list=blue'), array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'), array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'), array('{.null}', ''), array('{.null,var}', '.value'), array('X{.empty_keys*}', 'X'), array('X{.empty_keys}', 'X'), // Test that missing expansions are skipped array('test{&missing*}', 'test'), // Test that multiple expansions can be set array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'), // Test more complex query string stuff array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C') )); } /** * @dataProvider templateProvider */ public function testExpandsUriTemplates($template, $expansion, $params) { $uri = new UriTemplate($template); $this->assertEquals($expansion, $uri->expand($template, $params)); } public function expressionProvider() { return array( array( '{+var*}', array( 'operator' => '+', 'values' => array( array('value' => 'var', 'modifier' => '*') ) ), ), array( '{?keys,var,val}', array( 'operator' => '?', 'values' => array( array('value' => 'keys', 'modifier' => ''), array('value' => 'var', 'modifier' => ''), array('value' => 'val', 'modifier' => '') ) ), ), array( '{+x,hello,y}', array( 'operator' => '+', 'values' => array( array('value' => 'x', 'modifier' => ''), array('value' => 'hello', 'modifier' => ''), array('value' => 'y', 'modifier' => '') ) ) ) ); } /** * @dataProvider expressionProvider */ public function testParsesExpressions($exp, $data) { $template = new UriTemplate($exp); // Access the config object $class = new \ReflectionClass($template); $method = $class->getMethod('parseExpression'); $method->setAccessible(true); $exp = substr($exp, 1, -1); $this->assertEquals($data, $method->invokeArgs($template, array($exp))); } /** * @ticket https://github.com/guzzle/guzzle/issues/90 */ public function testAllowsNestedArrayExpansion() { $template = new UriTemplate(); $result = $template->expand('http://example.com{+path}{/segments}{?query,data*,foo*}', array( 'path' => '/foo/bar', 'segments' => array('one', 'two'), 'query' => 'test', 'data' => array( 'more' => array('fun', 'ice cream') ), 'foo' => array( 'baz' => array( 'bar' => 'fizz', 'test' => 'buzz' ), 'bam' => 'boo' ) )); $this->assertEquals('http://example.com/foo/bar/one,two?query=test&more%5B0%5D=fun&more%5B1%5D=ice%20cream&baz%5Bbar%5D=fizz&baz%5Btest%5D=buzz&bam=boo', $result); } } tests/UrlTest.php000064400000033616151742731020010034 0ustar00assertEquals('', (string) $url); } public function testPortIsDeterminedFromScheme() { $this->assertEquals(80, Url::fromString('http://www.test.com/')->getPort()); $this->assertEquals(443, Url::fromString('https://www.test.com/')->getPort()); $this->assertEquals(21, Url::fromString('ftp://www.test.com/')->getPort()); $this->assertEquals(8192, Url::fromString('http://www.test.com:8192/')->getPort()); $this->assertEquals(null, Url::fromString('foo://www.test.com/')->getPort()); } public function testRemovesDefaultPortWhenSettingScheme() { $url = Url::fromString('http://www.test.com/'); $url->setPort(80); $url->setScheme('https'); $this->assertEquals(443, $url->getPort()); } public function testCloneCreatesNewInternalObjects() { $u1 = Url::fromString('http://www.test.com/'); $u2 = clone $u1; $this->assertNotSame($u1->getQuery(), $u2->getQuery()); } public function testValidatesUrlPartsInFactory() { $url = Url::fromString('/index.php'); $this->assertEquals('/index.php', (string) $url); $this->assertFalse($url->isAbsolute()); $url = 'http://michael:test@test.com:80/path/123?q=abc#test'; $u = Url::fromString($url); $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u); $this->assertTrue($u->isAbsolute()); } public function testAllowsFalsyUrlParts() { $url = Url::fromString('http://a:50/0?0#0'); $this->assertSame('a', $url->getHost()); $this->assertEquals(50, $url->getPort()); $this->assertSame('/0', $url->getPath()); $this->assertEquals('0', (string) $url->getQuery()); $this->assertSame('0', $url->getFragment()); $this->assertEquals('http://a:50/0?0#0', (string) $url); $url = Url::fromString(''); $this->assertSame('', (string) $url); $url = Url::fromString('0'); $this->assertSame('0', (string) $url); } public function testBuildsRelativeUrlsWithFalsyParts() { $url = Url::buildUrl(['path' => '/0']); $this->assertSame('/0', $url); $url = Url::buildUrl(['path' => '0']); $this->assertSame('0', $url); $url = Url::buildUrl(['host' => '', 'path' => '0']); $this->assertSame('0', $url); } public function testUrlStoresParts() { $url = Url::fromString('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment'); $this->assertEquals('http', $url->getScheme()); $this->assertEquals('test', $url->getUsername()); $this->assertEquals('pass', $url->getPassword()); $this->assertEquals('www.test.com', $url->getHost()); $this->assertEquals(8081, $url->getPort()); $this->assertEquals('/path/path2/', $url->getPath()); $this->assertEquals('fragment', $url->getFragment()); $this->assertEquals('a=1&b=2', (string) $url->getQuery()); $this->assertEquals(array( 'fragment' => 'fragment', 'host' => 'www.test.com', 'pass' => 'pass', 'path' => '/path/path2/', 'port' => 8081, 'query' => 'a=1&b=2', 'scheme' => 'http', 'user' => 'test' ), $url->getParts()); } public function testHandlesPathsCorrectly() { $url = Url::fromString('http://www.test.com'); $this->assertEquals('', $url->getPath()); $url->setPath('test'); $this->assertEquals('test', $url->getPath()); $url->setPath('/test/123/abc'); $this->assertEquals(array('', 'test', '123', 'abc'), $url->getPathSegments()); $parts = parse_url('http://www.test.com/test'); $parts['path'] = ''; $this->assertEquals('http://www.test.com', Url::buildUrl($parts)); $parts['path'] = 'test'; $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts)); } public function testAddsQueryIfPresent() { $this->assertEquals('?foo=bar', Url::buildUrl(array( 'query' => 'foo=bar' ))); } public function testAddsToPath() { // Does nothing here $url = Url::fromString('http://e.com/base?a=1'); $url->addPath(false); $this->assertEquals('http://e.com/base?a=1', $url); $url = Url::fromString('http://e.com/base?a=1'); $url->addPath(''); $this->assertEquals('http://e.com/base?a=1', $url); $url = Url::fromString('http://e.com/base?a=1'); $url->addPath('/'); $this->assertEquals('http://e.com/base?a=1', $url); $url = Url::fromString('http://e.com/base'); $url->addPath('0'); $this->assertEquals('http://e.com/base/0', $url); $url = Url::fromString('http://e.com/base?a=1'); $url->addPath('relative'); $this->assertEquals('http://e.com/base/relative?a=1', $url); $url = Url::fromString('http://e.com/base?a=1'); $url->addPath('/relative'); $this->assertEquals('http://e.com/base/relative?a=1', $url); } /** * URL combination data provider * * @return array */ public function urlCombineDataProvider() { return [ // Specific test cases ['http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'], ['http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'], ['http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'], ['http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'], ['http://www.example.com/path', 'http://test.com', 'http://test.com'], ['http://www.example.com:8080/path', 'http://test.com', 'http://test.com'], ['http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'], ['http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'], ['/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'], ['http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'], ['https://www.example.com/path', '//foo.com/abc', 'https://foo.com/abc'], ['https://www.example.com/0/', 'relative/foo', 'https://www.example.com/0/relative/foo'], ['', '0', '0'], // RFC 3986 test cases [self::RFC3986_BASE, 'g:h', 'g:h'], [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], [self::RFC3986_BASE, './g', 'http://a/b/c/g'], [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], [self::RFC3986_BASE, '/g', 'http://a/g'], [self::RFC3986_BASE, '//g', 'http://g'], [self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'], [self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'], [self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'], [self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'], [self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'], [self::RFC3986_BASE, ';x', 'http://a/b/c/;x'], [self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'], [self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'], [self::RFC3986_BASE, '', self::RFC3986_BASE], [self::RFC3986_BASE, '.', 'http://a/b/c/'], [self::RFC3986_BASE, './', 'http://a/b/c/'], [self::RFC3986_BASE, '..', 'http://a/b/'], [self::RFC3986_BASE, '../', 'http://a/b/'], [self::RFC3986_BASE, '../g', 'http://a/b/g'], [self::RFC3986_BASE, '../..', 'http://a/'], [self::RFC3986_BASE, '../../', 'http://a/'], [self::RFC3986_BASE, '../../g', 'http://a/g'], [self::RFC3986_BASE, '../../../g', 'http://a/g'], [self::RFC3986_BASE, '../../../../g', 'http://a/g'], [self::RFC3986_BASE, '/./g', 'http://a/g'], [self::RFC3986_BASE, '/../g', 'http://a/g'], [self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'], [self::RFC3986_BASE, '.g', 'http://a/b/c/.g'], [self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'], [self::RFC3986_BASE, '..g', 'http://a/b/c/..g'], [self::RFC3986_BASE, './../g', 'http://a/b/g'], [self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'], [self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'], [self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'], [self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'], [self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'], [self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'], [self::RFC3986_BASE, 'http:g', 'http:g'], ]; } /** * @dataProvider urlCombineDataProvider */ public function testCombinesUrls($a, $b, $c) { $this->assertEquals($c, (string) Url::fromString($a)->combine($b)); } public function testHasGettersAndSetters() { $url = Url::fromString('http://www.test.com/'); $url->setHost('example.com'); $this->assertEquals('example.com', $url->getHost()); $url->setPort(8080); $this->assertEquals('8080', $url->getPort()); $url->setPath('/foo/bar'); $this->assertEquals('/foo/bar', $url->getPath()); $url->setPassword('a'); $this->assertEquals('a', $url->getPassword()); $url->setUsername('b'); $this->assertEquals('b', $url->getUsername()); $url->setFragment('abc'); $this->assertEquals('abc', $url->getFragment()); $url->setScheme('https'); $this->assertEquals('https', $url->getScheme()); $url->setQuery('a=123'); $this->assertEquals('a=123', (string) $url->getQuery()); $this->assertEquals( 'https://b:a@example.com:8080/foo/bar?a=123#abc', (string) $url ); $url->setQuery(new Query(['b' => 'boo'])); $this->assertEquals('b=boo', $url->getQuery()); $this->assertEquals( 'https://b:a@example.com:8080/foo/bar?b=boo#abc', (string) $url ); $url->setQuery('a%20=bar!', true); $this->assertEquals( 'https://b:a@example.com:8080/foo/bar?a%20=bar!#abc', (string) $url ); } public function testSetQueryAcceptsArray() { $url = Url::fromString('http://www.test.com'); $url->setQuery(array('a' => 'b')); $this->assertEquals('http://www.test.com?a=b', (string) $url); } /** * @expectedException \InvalidArgumentException */ public function testQueryMustBeValid() { $url = Url::fromString('http://www.test.com'); $url->setQuery(false); } public function testDefersParsingAndEncodingQueryUntilNecessary() { $url = Url::fromString('http://www.test.com'); // Note that invalid characters are encoded. $url->setQuery('foo#bar/', true); $this->assertEquals('http://www.test.com?foo%23bar/', (string) $url); $this->assertInternalType('string', $this->readAttribute($url, 'query')); $this->assertEquals('foo%23bar%2F', (string) $url->getQuery()); $this->assertInstanceOf('GuzzleHttp\Query', $this->readAttribute($url, 'query')); } public function urlProvider() { return array( array('/foo/..', '/'), array('//foo//..', '//foo/'), array('/foo//', '/foo//'), array('/foo/../..', '/'), array('/foo/../.', '/'), array('/./foo/..', '/'), array('/./foo', '/foo'), array('/./foo/', '/foo/'), array('*', '*'), array('/foo', '/foo'), array('/abc/123/../foo/', '/abc/foo/'), array('/a/b/c/./../../g', '/a/g'), array('/b/c/./../../g', '/g'), array('/b/c/./../../g', '/g'), array('/c/./../../g', '/g'), array('/./../../g', '/g'), array('foo', 'foo'), ); } /** * @dataProvider urlProvider */ public function testRemoveDotSegments($path, $result) { $url = Url::fromString('http://www.example.com'); $url->setPath($path); $url->removeDotSegments(); $this->assertEquals($result, $url->getPath()); } public function testSettingHostWithPortModifiesPort() { $url = Url::fromString('http://www.example.com'); $url->setHost('foo:8983'); $this->assertEquals('foo', $url->getHost()); $this->assertEquals(8983, $url->getPort()); } /** * @expectedException \InvalidArgumentException */ public function testValidatesUrlCanBeParsed() { Url::fromString('foo:////'); } public function testConvertsSpecialCharsInPathWhenCastingToString() { $url = Url::fromString('http://foo.com/baz bar?a=b'); $url->addPath('?'); $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url); } public function testCorrectlyEncodesPathWithoutDoubleEncoding() { $url = Url::fromString('http://foo.com/baz%20 bar:boo/baz!'); $this->assertEquals('/baz%20%20bar:boo/baz!', $url->getPath()); } } tests/UtilsTest.php000064400000001412151742731020010357 0ustar00assertEquals( 'foo/123', Utils::uriTemplate('foo/{bar}', ['bar' => '123']) ); } public function noBodyProvider() { return [['get'], ['head'], ['delete']]; } public function testJsonDecodes() { $this->assertTrue(Utils::jsonDecode('true')); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Unable to parse JSON data: JSON_ERROR_SYNTAX - Syntax error, malformed JSON */ public function testJsonDecodesWithErrorMessages() { Utils::jsonDecode('!narf!'); } } tests/bootstrap.php000064400000000360151742731020010435 0ustar00 Server::$url]); $t = microtime(true); for ($i = 0; $i < $total; $i++) { $client->get('/guzzle-server/perf'); } $totalTime = microtime(true) - $t; $perRequest = ($totalTime / $total) * 1000; printf("Serial: %f (%f ms / request) %d total\n", $totalTime, $perRequest, $total); // Create a generator used to yield batches of requests $reqs = function () use ($client, $total) { for ($i = 0; $i < $total; $i++) { yield $client->createRequest('GET', '/guzzle-server/perf'); } }; $t = microtime(true); Pool::send($client, $reqs(), ['parallel' => $parallel]); $totalTime = microtime(true) - $t; $perRequest = ($totalTime / $total) * 1000; printf("Batch: %f (%f ms / request) %d total with %d in parallel\n", $totalTime, $perRequest, $total, $parallel); $handler = new CurlMultiHandler(['max_handles' => $parallel]); $client = new Client(['handler' => $handler, 'base_url' => Server::$url]); $t = microtime(true); for ($i = 0; $i < $total; $i++) { $client->get('/guzzle-server/perf'); } unset($client); $totalTime = microtime(true) - $t; $perRequest = ($totalTime / $total) * 1000; printf("Future: %f (%f ms / request) %d total\n", $totalTime, $perRequest, $total);