summaryrefslogtreecommitdiff
path: root/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache')
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php11
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php353
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php43
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php53
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php30
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php46
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php266
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php174
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php32
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php19
-rw-r--r--vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json28
13 files changed, 1106 insertions, 0 deletions
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php
new file mode 100644
index 0000000..7790f88
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+\Guzzle\Common\Version::warn('Guzzle\Plugin\Cache\CacheKeyProviderInterface is no longer used');
+
+/**
+ * @deprecated This is no longer used
+ * @codeCoverageIgnore
+ */
+interface CacheKeyProviderInterface {}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php
new file mode 100644
index 0000000..ce4b317
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CachePlugin.php
@@ -0,0 +1,353 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Cache\CacheAdapterFactory;
+use Guzzle\Cache\CacheAdapterInterface;
+use Guzzle\Common\Event;
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Common\Version;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Cache\DoctrineCacheAdapter;
+use Guzzle\Http\Exception\CurlException;
+use Doctrine\Common\Cache\ArrayCache;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Plugin to enable the caching of GET and HEAD requests. Caching can be done on all requests passing through this
+ * plugin or only after retrieving resources with cacheable response headers.
+ *
+ * This is a simple implementation of RFC 2616 and should be considered a private transparent proxy cache, meaning
+ * authorization and private data can be cached.
+ *
+ * It also implements RFC 5861's `stale-if-error` Cache-Control extension, allowing stale cache responses to be used
+ * when an error is encountered (such as a `500 Internal Server Error` or DNS failure).
+ */
+class CachePlugin implements EventSubscriberInterface
+{
+ /** @var RevalidationInterface Cache revalidation strategy */
+ protected $revalidation;
+
+ /** @var CanCacheStrategyInterface Object used to determine if a request can be cached */
+ protected $canCache;
+
+ /** @var CacheStorageInterface $cache Object used to cache responses */
+ protected $storage;
+
+ /** @var bool */
+ protected $autoPurge;
+
+ /**
+ * @param array|CacheAdapterInterface|CacheStorageInterface $options Array of options for the cache plugin,
+ * cache adapter, or cache storage object.
+ * - CacheStorageInterface storage: Adapter used to cache responses
+ * - RevalidationInterface revalidation: Cache revalidation strategy
+ * - CanCacheInterface can_cache: Object used to determine if a request can be cached
+ * - bool auto_purge Set to true to automatically PURGE resources when non-idempotent
+ * requests are sent to a resource. Defaults to false.
+ * @throws InvalidArgumentException if no cache is provided and Doctrine cache is not installed
+ */
+ public function __construct($options = null)
+ {
+ if (!is_array($options)) {
+ if ($options instanceof CacheAdapterInterface) {
+ $options = array('storage' => new DefaultCacheStorage($options));
+ } elseif ($options instanceof CacheStorageInterface) {
+ $options = array('storage' => $options);
+ } elseif ($options) {
+ $options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options)));
+ } elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) {
+ // @codeCoverageIgnoreStart
+ throw new InvalidArgumentException('No cache was provided and Doctrine is not installed');
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ $this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false;
+
+ // Add a cache storage if a cache adapter was provided
+ $this->storage = isset($options['storage'])
+ ? $options['storage']
+ : new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()));
+
+ if (!isset($options['can_cache'])) {
+ $this->canCache = new DefaultCanCacheStrategy();
+ } else {
+ $this->canCache = is_callable($options['can_cache'])
+ ? new CallbackCanCacheStrategy($options['can_cache'])
+ : $options['can_cache'];
+ }
+
+ // Use the provided revalidation strategy or the default
+ $this->revalidation = isset($options['revalidation'])
+ ? $options['revalidation']
+ : new DefaultRevalidation($this->storage, $this->canCache);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'request.before_send' => array('onRequestBeforeSend', -255),
+ 'request.sent' => array('onRequestSent', 255),
+ 'request.error' => array('onRequestError', 0),
+ 'request.exception' => array('onRequestException', 0),
+ );
+ }
+
+ /**
+ * Check if a response in cache will satisfy the request before sending
+ *
+ * @param Event $event
+ */
+ public function onRequestBeforeSend(Event $event)
+ {
+ $request = $event['request'];
+ $request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
+
+ if (!$this->canCache->canCacheRequest($request)) {
+ switch ($request->getMethod()) {
+ case 'PURGE':
+ $this->purge($request);
+ $request->setResponse(new Response(200, array(), 'purged'));
+ break;
+ case 'PUT':
+ case 'POST':
+ case 'DELETE':
+ case 'PATCH':
+ if ($this->autoPurge) {
+ $this->purge($request);
+ }
+ }
+ return;
+ }
+
+ if ($response = $this->storage->fetch($request)) {
+ $params = $request->getParams();
+ $params['cache.lookup'] = true;
+ $response->setHeader(
+ 'Age',
+ time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now')
+ );
+ // Validate that the response satisfies the request
+ if ($this->canResponseSatisfyRequest($request, $response)) {
+ if (!isset($params['cache.hit'])) {
+ $params['cache.hit'] = true;
+ }
+ $request->setResponse($response);
+ }
+ }
+ }
+
+ /**
+ * If possible, store a response in cache after sending
+ *
+ * @param Event $event
+ */
+ public function onRequestSent(Event $event)
+ {
+ $request = $event['request'];
+ $response = $event['response'];
+
+ if ($request->getParams()->get('cache.hit') === null &&
+ $this->canCache->canCacheRequest($request) &&
+ $this->canCache->canCacheResponse($response)
+ ) {
+ $this->storage->cache($request, $response);
+ }
+
+ $this->addResponseHeaders($request, $response);
+ }
+
+ /**
+ * If possible, return a cache response on an error
+ *
+ * @param Event $event
+ */
+ public function onRequestError(Event $event)
+ {
+ $request = $event['request'];
+
+ if (!$this->canCache->canCacheRequest($request)) {
+ return;
+ }
+
+ if ($response = $this->storage->fetch($request)) {
+ $response->setHeader(
+ 'Age',
+ time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now')
+ );
+
+ if ($this->canResponseSatisfyFailedRequest($request, $response)) {
+ $request->getParams()->set('cache.hit', 'error');
+ $this->addResponseHeaders($request, $response);
+ $event['response'] = $response;
+ $event->stopPropagation();
+ }
+ }
+ }
+
+ /**
+ * If possible, set a cache response on a cURL exception
+ *
+ * @param Event $event
+ *
+ * @return null
+ */
+ public function onRequestException(Event $event)
+ {
+ if (!$event['exception'] instanceof CurlException) {
+ return;
+ }
+
+ $request = $event['request'];
+ if (!$this->canCache->canCacheRequest($request)) {
+ return;
+ }
+
+ if ($response = $this->storage->fetch($request)) {
+ $response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now'));
+ if (!$this->canResponseSatisfyFailedRequest($request, $response)) {
+ return;
+ }
+ $request->getParams()->set('cache.hit', 'error');
+ $request->setResponse($response);
+ $this->addResponseHeaders($request, $response);
+ $event->stopPropagation();
+ }
+ }
+
+ /**
+ * Check if a cache response satisfies a request's caching constraints
+ *
+ * @param RequestInterface $request Request to validate
+ * @param Response $response Response to validate
+ *
+ * @return bool
+ */
+ public function canResponseSatisfyRequest(RequestInterface $request, Response $response)
+ {
+ $responseAge = $response->calculateAge();
+ $reqc = $request->getHeader('Cache-Control');
+ $resc = $response->getHeader('Cache-Control');
+
+ // Check the request's max-age header against the age of the response
+ if ($reqc && $reqc->hasDirective('max-age') &&
+ $responseAge > $reqc->getDirective('max-age')) {
+ return false;
+ }
+
+ // Check the response's max-age header
+ if ($response->isFresh() === false) {
+ $maxStale = $reqc ? $reqc->getDirective('max-stale') : null;
+ if (null !== $maxStale) {
+ if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) {
+ return false;
+ }
+ } elseif ($resc && $resc->hasDirective('max-age')
+ && $responseAge > $resc->getDirective('max-age')
+ ) {
+ return false;
+ }
+ }
+
+ if ($this->revalidation->shouldRevalidate($request, $response)) {
+ try {
+ return $this->revalidation->revalidate($request, $response);
+ } catch (CurlException $e) {
+ $request->getParams()->set('cache.hit', 'error');
+ return $this->canResponseSatisfyFailedRequest($request, $response);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a cache response satisfies a failed request's caching constraints
+ *
+ * @param RequestInterface $request Request to validate
+ * @param Response $response Response to validate
+ *
+ * @return bool
+ */
+ public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response)
+ {
+ $reqc = $request->getHeader('Cache-Control');
+ $resc = $response->getHeader('Cache-Control');
+ $requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null;
+ $responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null;
+
+ if (!$requestStaleIfError && !$responseStaleIfError) {
+ return false;
+ }
+
+ if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) {
+ return false;
+ }
+
+ if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Purge all cache entries for a given URL
+ *
+ * @param string $url URL to purge
+ */
+ public function purge($url)
+ {
+ // BC compatibility with previous version that accepted a Request object
+ $url = $url instanceof RequestInterface ? $url->getUrl() : $url;
+ $this->storage->purge($url);
+ }
+
+ /**
+ * Add the plugin's headers to a response
+ *
+ * @param RequestInterface $request Request
+ * @param Response $response Response to add headers to
+ */
+ protected function addResponseHeaders(RequestInterface $request, Response $response)
+ {
+ $params = $request->getParams();
+ $response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION));
+
+ $lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache';
+ if ($header = $response->getHeader('X-Cache-Lookup')) {
+ // Don't add duplicates
+ $values = $header->toArray();
+ $values[] = $lookup;
+ $response->setHeader('X-Cache-Lookup', array_unique($values));
+ } else {
+ $response->setHeader('X-Cache-Lookup', $lookup);
+ }
+
+ if ($params['cache.hit'] === true) {
+ $xcache = 'HIT from GuzzleCache';
+ } elseif ($params['cache.hit'] == 'error') {
+ $xcache = 'HIT_ERROR from GuzzleCache';
+ } else {
+ $xcache = 'MISS from GuzzleCache';
+ }
+
+ if ($header = $response->getHeader('X-Cache')) {
+ // Don't add duplicates
+ $values = $header->toArray();
+ $values[] = $xcache;
+ $response->setHeader('X-Cache', array_unique($values));
+ } else {
+ $response->setHeader('X-Cache', $xcache);
+ }
+
+ if ($response->isFresh() === false) {
+ $response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION));
+ if ($params['cache.hit'] === 'error') {
+ $response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION));
+ }
+ }
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php
new file mode 100644
index 0000000..f3d9154
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Interface used to cache HTTP requests
+ */
+interface CacheStorageInterface
+{
+ /**
+ * Get a Response from the cache for a request
+ *
+ * @param RequestInterface $request
+ *
+ * @return null|Response
+ */
+ public function fetch(RequestInterface $request);
+
+ /**
+ * Cache an HTTP request
+ *
+ * @param RequestInterface $request Request being cached
+ * @param Response $response Response to cache
+ */
+ public function cache(RequestInterface $request, Response $response);
+
+ /**
+ * Deletes cache entries that match a request
+ *
+ * @param RequestInterface $request Request to delete from cache
+ */
+ public function delete(RequestInterface $request);
+
+ /**
+ * Purge all cache entries for a given URL
+ *
+ * @param string $url
+ */
+ public function purge($url);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php
new file mode 100644
index 0000000..2d271e3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CallbackCanCacheStrategy.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Common\Exception\InvalidArgumentException;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Determines if a request can be cached using a callback
+ */
+class CallbackCanCacheStrategy extends DefaultCanCacheStrategy
+{
+ /** @var callable Callback for request */
+ protected $requestCallback;
+
+ /** @var callable Callback for response */
+ protected $responseCallback;
+
+ /**
+ * @param \Closure|array|mixed $requestCallback Callable method to invoke for requests
+ * @param \Closure|array|mixed $responseCallback Callable method to invoke for responses
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($requestCallback = null, $responseCallback = null)
+ {
+ if ($requestCallback && !is_callable($requestCallback)) {
+ throw new InvalidArgumentException('Method must be callable');
+ }
+
+ if ($responseCallback && !is_callable($responseCallback)) {
+ throw new InvalidArgumentException('Method must be callable');
+ }
+
+ $this->requestCallback = $requestCallback;
+ $this->responseCallback = $responseCallback;
+ }
+
+ public function canCacheRequest(RequestInterface $request)
+ {
+ return $this->requestCallback
+ ? call_user_func($this->requestCallback, $request)
+ : parent::canCacheRequest($request);
+ }
+
+ public function canCacheResponse(Response $response)
+ {
+ return $this->responseCallback
+ ? call_user_func($this->responseCallback, $response)
+ : parent::canCacheResponse($response);
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php
new file mode 100644
index 0000000..6e01a8e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Strategy used to determine if a request can be cached
+ */
+interface CanCacheStrategyInterface
+{
+ /**
+ * Determine if a request can be cached
+ *
+ * @param RequestInterface $request Request to determine
+ *
+ * @return bool
+ */
+ public function canCacheRequest(RequestInterface $request);
+
+ /**
+ * Determine if a response can be cached
+ *
+ * @param Response $response Response to determine
+ *
+ * @return bool
+ */
+ public function canCacheResponse(Response $response);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php
new file mode 100644
index 0000000..ec0dc4e
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheKeyProvider.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+
+\Guzzle\Common\Version::warn('Guzzle\Plugin\Cache\DefaultCacheKeyProvider is no longer used');
+
+/**
+ * @deprecated This class is no longer used
+ * @codeCoverageIgnore
+ */
+class DefaultCacheKeyProvider implements CacheKeyProviderInterface
+{
+ public function getCacheKey(RequestInterface $request)
+ {
+ // See if the key has already been calculated
+ $key = $request->getParams()->get(self::CACHE_KEY);
+
+ if (!$key) {
+
+ $cloned = clone $request;
+ $cloned->removeHeader('Cache-Control');
+
+ // Check to see how and if the key should be filtered
+ foreach (explode(';', $request->getParams()->get(self::CACHE_KEY_FILTER)) as $part) {
+ $pieces = array_map('trim', explode('=', $part));
+ if (isset($pieces[1])) {
+ foreach (array_map('trim', explode(',', $pieces[1])) as $remove) {
+ if ($pieces[0] == 'header') {
+ $cloned->removeHeader($remove);
+ } elseif ($pieces[0] == 'query') {
+ $cloned->getQuery()->remove($remove);
+ }
+ }
+ }
+ }
+
+ $raw = (string) $cloned;
+ $key = 'GZ' . md5($raw);
+ $request->getParams()->set(self::CACHE_KEY, $key)->set(self::CACHE_KEY_RAW, $raw);
+ }
+
+ return $key;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php
new file mode 100644
index 0000000..26d7a8b
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php
@@ -0,0 +1,266 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Cache\CacheAdapterFactory;
+use Guzzle\Cache\CacheAdapterInterface;
+use Guzzle\Http\EntityBodyInterface;
+use Guzzle\Http\Message\MessageInterface;
+use Guzzle\Http\Message\Request;
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Default cache storage implementation
+ */
+class DefaultCacheStorage implements CacheStorageInterface
+{
+ /** @var string */
+ protected $keyPrefix;
+
+ /** @var CacheAdapterInterface Cache used to store cache data */
+ protected $cache;
+
+ /** @var int Default cache TTL */
+ protected $defaultTtl;
+
+ /**
+ * @param mixed $cache Cache used to store cache data
+ * @param string $keyPrefix Provide an optional key prefix to prefix on all cache keys
+ * @param int $defaultTtl Default cache TTL
+ */
+ public function __construct($cache, $keyPrefix = '', $defaultTtl = 3600)
+ {
+ $this->cache = CacheAdapterFactory::fromCache($cache);
+ $this->defaultTtl = $defaultTtl;
+ $this->keyPrefix = $keyPrefix;
+ }
+
+ public function cache(RequestInterface $request, Response $response)
+ {
+ $currentTime = time();
+
+ $overrideTtl = $request->getParams()->get('cache.override_ttl');
+ if ($overrideTtl) {
+ $ttl = $overrideTtl;
+ } else {
+ $maxAge = $response->getMaxAge();
+ if ($maxAge !== null) {
+ $ttl = $maxAge;
+ } else {
+ $ttl = $this->defaultTtl;
+ }
+ }
+
+ if ($cacheControl = $response->getHeader('Cache-Control')) {
+ $stale = $cacheControl->getDirective('stale-if-error');
+ if ($stale === true) {
+ $ttl += $ttl;
+ } else if (is_numeric($stale)) {
+ $ttl += $stale;
+ }
+ }
+
+ // Determine which manifest key should be used
+ $key = $this->getCacheKey($request);
+ $persistedRequest = $this->persistHeaders($request);
+ $entries = array();
+
+ if ($manifest = $this->cache->fetch($key)) {
+ // Determine which cache entries should still be in the cache
+ $vary = $response->getVary();
+ foreach (unserialize($manifest) as $entry) {
+ // Check if the entry is expired
+ if ($entry[4] < $currentTime) {
+ continue;
+ }
+ $entry[1]['vary'] = isset($entry[1]['vary']) ? $entry[1]['vary'] : '';
+ if ($vary != $entry[1]['vary'] || !$this->requestsMatch($vary, $entry[0], $persistedRequest)) {
+ $entries[] = $entry;
+ }
+ }
+ }
+
+ // Persist the response body if needed
+ $bodyDigest = null;
+ if ($response->getBody() && $response->getBody()->getContentLength() > 0) {
+ $bodyDigest = $this->getBodyKey($request->getUrl(), $response->getBody());
+ $this->cache->save($bodyDigest, (string) $response->getBody(), $ttl);
+ }
+
+ array_unshift($entries, array(
+ $persistedRequest,
+ $this->persistHeaders($response),
+ $response->getStatusCode(),
+ $bodyDigest,
+ $currentTime + $ttl
+ ));
+
+ $this->cache->save($key, serialize($entries));
+ }
+
+ public function delete(RequestInterface $request)
+ {
+ $key = $this->getCacheKey($request);
+ if ($entries = $this->cache->fetch($key)) {
+ // Delete each cached body
+ foreach (unserialize($entries) as $entry) {
+ if ($entry[3]) {
+ $this->cache->delete($entry[3]);
+ }
+ }
+ $this->cache->delete($key);
+ }
+ }
+
+ public function purge($url)
+ {
+ foreach (array('GET', 'HEAD', 'POST', 'PUT', 'DELETE') as $method) {
+ $this->delete(new Request($method, $url));
+ }
+ }
+
+ public function fetch(RequestInterface $request)
+ {
+ $key = $this->getCacheKey($request);
+ if (!($entries = $this->cache->fetch($key))) {
+ return null;
+ }
+
+ $match = null;
+ $headers = $this->persistHeaders($request);
+ $entries = unserialize($entries);
+ foreach ($entries as $index => $entry) {
+ if ($this->requestsMatch(isset($entry[1]['vary']) ? $entry[1]['vary'] : '', $headers, $entry[0])) {
+ $match = $entry;
+ break;
+ }
+ }
+
+ if (!$match) {
+ return null;
+ }
+
+ // Ensure that the response is not expired
+ $response = null;
+ if ($match[4] < time()) {
+ $response = -1;
+ } else {
+ $response = new Response($match[2], $match[1]);
+ if ($match[3]) {
+ if ($body = $this->cache->fetch($match[3])) {
+ $response->setBody($body);
+ } else {
+ // The response is not valid because the body was somehow deleted
+ $response = -1;
+ }
+ }
+ }
+
+ if ($response === -1) {
+ // Remove the entry from the metadata and update the cache
+ unset($entries[$index]);
+ if ($entries) {
+ $this->cache->save($key, serialize($entries));
+ } else {
+ $this->cache->delete($key);
+ }
+ return null;
+ }
+
+ return $response;
+ }
+
+ /**
+ * Hash a request URL into a string that returns cache metadata
+ *
+ * @param RequestInterface $request
+ *
+ * @return string
+ */
+ protected function getCacheKey(RequestInterface $request)
+ {
+ // Allow cache.key_filter to trim down the URL cache key by removing generate query string values (e.g. auth)
+ if ($filter = $request->getParams()->get('cache.key_filter')) {
+ $url = $request->getUrl(true);
+ foreach (explode(',', $filter) as $remove) {
+ $url->getQuery()->remove(trim($remove));
+ }
+ } else {
+ $url = $request->getUrl();
+ }
+
+ return $this->keyPrefix . md5($request->getMethod() . ' ' . $url);
+ }
+
+ /**
+ * Create a cache key for a response's body
+ *
+ * @param string $url URL of the entry
+ * @param EntityBodyInterface $body Response body
+ *
+ * @return string
+ */
+ protected function getBodyKey($url, EntityBodyInterface $body)
+ {
+ return $this->keyPrefix . md5($url) . $body->getContentMd5();
+ }
+
+ /**
+ * Determines whether two Request HTTP header sets are non-varying
+ *
+ * @param string $vary Response vary header
+ * @param array $r1 HTTP header array
+ * @param array $r2 HTTP header array
+ *
+ * @return bool
+ */
+ private function requestsMatch($vary, $r1, $r2)
+ {
+ if ($vary) {
+ foreach (explode(',', $vary) as $header) {
+ $key = trim(strtolower($header));
+ $v1 = isset($r1[$key]) ? $r1[$key] : null;
+ $v2 = isset($r2[$key]) ? $r2[$key] : null;
+ if ($v1 !== $v2) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Creates an array of cacheable and normalized message headers
+ *
+ * @param MessageInterface $message
+ *
+ * @return array
+ */
+ private function persistHeaders(MessageInterface $message)
+ {
+ // Headers are excluded from the caching (see RFC 2616:13.5.1)
+ static $noCache = array(
+ 'age' => true,
+ 'connection' => true,
+ 'keep-alive' => true,
+ 'proxy-authenticate' => true,
+ 'proxy-authorization' => true,
+ 'te' => true,
+ 'trailers' => true,
+ 'transfer-encoding' => true,
+ 'upgrade' => true,
+ 'set-cookie' => true,
+ 'set-cookie2' => true
+ );
+
+ // Clone the response to not destroy any necessary headers when caching
+ $headers = $message->getHeaders()->getAll();
+ $headers = array_diff_key($headers, $noCache);
+ // Cast the headers to a string
+ $headers = array_map(function ($h) { return (string) $h; }, $headers);
+
+ return $headers;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php
new file mode 100644
index 0000000..3ca1fbf
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Default strategy used to determine of an HTTP request can be cached
+ */
+class DefaultCanCacheStrategy implements CanCacheStrategyInterface
+{
+ public function canCacheRequest(RequestInterface $request)
+ {
+ // Only GET and HEAD requests can be cached
+ if ($request->getMethod() != RequestInterface::GET && $request->getMethod() != RequestInterface::HEAD) {
+ return false;
+ }
+
+ // Never cache requests when using no-store
+ if ($request->hasHeader('Cache-Control') && $request->getHeader('Cache-Control')->hasDirective('no-store')) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function canCacheResponse(Response $response)
+ {
+ return $response->isSuccessful() && $response->canCache();
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php
new file mode 100644
index 0000000..af33234
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+use Guzzle\Http\Exception\BadResponseException;
+
+/**
+ * Default revalidation strategy
+ */
+class DefaultRevalidation implements RevalidationInterface
+{
+ /** @var CacheStorageInterface Cache object storing cache data */
+ protected $storage;
+
+ /** @var CanCacheStrategyInterface */
+ protected $canCache;
+
+ /**
+ * @param CacheStorageInterface $cache Cache storage
+ * @param CanCacheStrategyInterface $canCache Determines if a message can be cached
+ */
+ public function __construct(CacheStorageInterface $cache, CanCacheStrategyInterface $canCache = null)
+ {
+ $this->storage = $cache;
+ $this->canCache = $canCache ?: new DefaultCanCacheStrategy();
+ }
+
+ public function revalidate(RequestInterface $request, Response $response)
+ {
+ try {
+ $revalidate = $this->createRevalidationRequest($request, $response);
+ $validateResponse = $revalidate->send();
+ if ($validateResponse->getStatusCode() == 200) {
+ return $this->handle200Response($request, $validateResponse);
+ } elseif ($validateResponse->getStatusCode() == 304) {
+ return $this->handle304Response($request, $validateResponse, $response);
+ }
+ } catch (BadResponseException $e) {
+ $this->handleBadResponse($e);
+ }
+
+ // Other exceptions encountered in the revalidation request are ignored
+ // in hopes that sending a request to the origin server will fix it
+ return false;
+ }
+
+ public function shouldRevalidate(RequestInterface $request, Response $response)
+ {
+ if ($request->getMethod() != RequestInterface::GET) {
+ return false;
+ }
+
+ $reqCache = $request->getHeader('Cache-Control');
+ $resCache = $response->getHeader('Cache-Control');
+
+ $revalidate = $request->getHeader('Pragma') == 'no-cache' ||
+ ($reqCache && ($reqCache->hasDirective('no-cache') || $reqCache->hasDirective('must-revalidate'))) ||
+ ($resCache && ($resCache->hasDirective('no-cache') || $resCache->hasDirective('must-revalidate')));
+
+ // Use the strong ETag validator if available and the response contains no Cache-Control directive
+ if (!$revalidate && !$resCache && $response->hasHeader('ETag')) {
+ $revalidate = true;
+ }
+
+ return $revalidate;
+ }
+
+ /**
+ * Handles a bad response when attempting to revalidate
+ *
+ * @param BadResponseException $e Exception encountered
+ *
+ * @throws BadResponseException
+ */
+ protected function handleBadResponse(BadResponseException $e)
+ {
+ // 404 errors mean the resource no longer exists, so remove from
+ // cache, and prevent an additional request by throwing the exception
+ if ($e->getResponse()->getStatusCode() == 404) {
+ $this->storage->delete($e->getRequest());
+ throw $e;
+ }
+ }
+
+ /**
+ * Creates a request to use for revalidation
+ *
+ * @param RequestInterface $request Request
+ * @param Response $response Response to revalidate
+ *
+ * @return RequestInterface returns a revalidation request
+ */
+ protected function createRevalidationRequest(RequestInterface $request, Response $response)
+ {
+ $revalidate = clone $request;
+ $revalidate->removeHeader('Pragma')->removeHeader('Cache-Control');
+
+ if ($response->getLastModified()) {
+ $revalidate->setHeader('If-Modified-Since', $response->getLastModified());
+ }
+
+ if ($response->getEtag()) {
+ $revalidate->setHeader('If-None-Match', $response->getEtag());
+ }
+
+ // Remove any cache plugins that might be on the request to prevent infinite recursive revalidations
+ $dispatcher = $revalidate->getEventDispatcher();
+ foreach ($dispatcher->getListeners() as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ if (is_array($listener) && $listener[0] instanceof CachePlugin) {
+ $dispatcher->removeListener($eventName, $listener);
+ }
+ }
+ }
+
+ return $revalidate;
+ }
+
+ /**
+ * Handles a 200 response response from revalidating. The server does not support validation, so use this response.
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param Response $validateResponse Response received
+ *
+ * @return bool Returns true if valid, false if invalid
+ */
+ protected function handle200Response(RequestInterface $request, Response $validateResponse)
+ {
+ $request->setResponse($validateResponse);
+ if ($this->canCache->canCacheResponse($validateResponse)) {
+ $this->storage->cache($request, $validateResponse);
+ }
+
+ return false;
+ }
+
+ /**
+ * Handle a 304 response and ensure that it is still valid
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param Response $validateResponse Response received
+ * @param Response $response Original cached response
+ *
+ * @return bool Returns true if valid, false if invalid
+ */
+ protected function handle304Response(RequestInterface $request, Response $validateResponse, Response $response)
+ {
+ static $replaceHeaders = array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified');
+
+ // Make sure that this response has the same ETag
+ if ($validateResponse->getEtag() != $response->getEtag()) {
+ return false;
+ }
+
+ // Replace cached headers with any of these headers from the
+ // origin server that might be more up to date
+ $modified = false;
+ foreach ($replaceHeaders as $name) {
+ if ($validateResponse->hasHeader($name)) {
+ $modified = true;
+ $response->setHeader($name, $validateResponse->getHeader($name));
+ }
+ }
+
+ // Store the updated response in cache
+ if ($modified && $this->canCache->canCacheResponse($response)) {
+ $this->storage->cache($request, $response);
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php
new file mode 100644
index 0000000..88b86f3
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Never performs cache revalidation and just assumes the request is invalid
+ */
+class DenyRevalidation extends DefaultRevalidation
+{
+ public function __construct() {}
+
+ public function revalidate(RequestInterface $request, Response $response)
+ {
+ return false;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php
new file mode 100644
index 0000000..52353d8
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/RevalidationInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Cache revalidation interface
+ */
+interface RevalidationInterface
+{
+ /**
+ * Performs a cache revalidation
+ *
+ * @param RequestInterface $request Request to revalidate
+ * @param Response $response Response that was received
+ *
+ * @return bool Returns true if the request can be cached
+ */
+ public function revalidate(RequestInterface $request, Response $response);
+
+ /**
+ * Returns true if the response should be revalidated
+ *
+ * @param RequestInterface $request Request to check
+ * @param Response $response Response to check
+ *
+ * @return bool
+ */
+ public function shouldRevalidate(RequestInterface $request, Response $response);
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php
new file mode 100644
index 0000000..10b5c11
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/SkipRevalidation.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Guzzle\Plugin\Cache;
+
+use Guzzle\Http\Message\RequestInterface;
+use Guzzle\Http\Message\Response;
+
+/**
+ * Never performs cache revalidation and just assumes the request is still ok
+ */
+class SkipRevalidation extends DefaultRevalidation
+{
+ public function __construct() {}
+
+ public function revalidate(RequestInterface $request, Response $response)
+ {
+ return true;
+ }
+}
diff --git a/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json
new file mode 100644
index 0000000..674d8c4
--- /dev/null
+++ b/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "guzzle/plugin-cache",
+ "description": "Guzzle HTTP cache plugin",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["plugin", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.2",
+ "guzzle/http": "self.version",
+ "guzzle/cache": "self.version"
+ },
+ "autoload": {
+ "psr-0": { "Guzzle\\Plugin\\Cache": "" }
+ },
+ "target-dir": "Guzzle/Plugin/Cache",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.7-dev"
+ }
+ }
+}