Update AWS sdk to 2.6.15

This commit is contained in:
Johan Björk 2014-08-28 00:10:31 +02:00 committed by Johan Björk
parent cb0da1178b
commit dadb1fad2a
263 changed files with 5931 additions and 9805 deletions

View file

@ -16,6 +16,7 @@
namespace Aws\Common;
use Aws\Common\Facade\Facade;
use Guzzle\Service\Builder\ServiceBuilder;
use Guzzle\Service\Builder\ServiceBuilderLoader;
@ -27,7 +28,7 @@ class Aws extends ServiceBuilder
/**
* @var string Current version of the SDK
*/
const VERSION = '2.4.0';
const VERSION = '2.6.15';
/**
* Create a new service locator for the AWS SDK
@ -97,10 +98,7 @@ class Aws extends ServiceBuilder
*/
public function enableFacades($namespace = null)
{
$facadeClass = 'Aws\\Common\\Facade\\Facade';
if (class_exists($facadeClass)) {
$facadeClass::mountFacades($this, $namespace);
}
Facade::mountFacades($this, $namespace);
return $this;
}

View file

@ -19,8 +19,10 @@ namespace Aws\Common\Client;
use Aws\Common\Aws;
use Aws\Common\Credentials\Credentials;
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Credentials\NullCredentials;
use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\TransferException;
use Aws\Common\Signature\EndpointSignatureInterface;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Signature\SignatureListener;
@ -29,6 +31,7 @@ use Aws\Common\Waiter\CompositeWaiterFactory;
use Aws\Common\Waiter\WaiterFactoryInterface;
use Aws\Common\Waiter\WaiterConfigFactory;
use Guzzle\Common\Collection;
use Guzzle\Http\Exception\CurlException;
use Guzzle\Service\Client;
use Guzzle\Service\Description\ServiceDescriptionInterface;
@ -82,7 +85,9 @@ abstract class AbstractClient extends Client implements AwsClientInterface
// Add the event listener so that requests are signed before they are sent
$dispatcher = $this->getEventDispatcher();
$dispatcher->addSubscriber(new SignatureListener($credentials, $signature));
if (!$credentials instanceof NullCredentials) {
$dispatcher->addSubscriber(new SignatureListener($credentials, $signature));
}
if ($backoff = $config->get(Options::BACKOFF)) {
$dispatcher->addSubscriber($backoff, -255);
@ -133,17 +138,11 @@ abstract class AbstractClient extends Client implements AwsClientInterface
return $scheme . '://' . $regions[$region]['hostname'];
}
/**
* {@inheritdoc}
*/
public function getCredentials()
{
return $this->credentials;
}
/**
* {@inheritdoc}
*/
public function setCredentials(CredentialsInterface $credentials)
{
$formerCredentials = $this->credentials;
@ -158,33 +157,21 @@ abstract class AbstractClient extends Client implements AwsClientInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function getSignature()
{
return $this->signature;
}
/**
* {@inheritdoc}
*/
public function getRegions()
{
return $this->serviceDescription->getData('regions');
}
/**
* {@inheritdoc}
*/
public function getRegion()
{
return $this->getConfig(Options::REGION);
}
/**
* {@inheritdoc}
*/
public function setRegion($region)
{
$config = $this->getConfig();
@ -214,9 +201,6 @@ abstract class AbstractClient extends Client implements AwsClientInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function waitUntil($waiter, array $input = array())
{
$this->getWaiter($waiter, $input)->wait();
@ -224,9 +208,6 @@ abstract class AbstractClient extends Client implements AwsClientInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function getWaiter($waiter, array $input = array())
{
return $this->getWaiterFactory()->build($waiter)
@ -234,9 +215,6 @@ abstract class AbstractClient extends Client implements AwsClientInterface
->setConfig($input);
}
/**
* {@inheritdoc}
*/
public function setWaiterFactory(WaiterFactoryInterface $waiterFactory)
{
$this->waiterFactory = $waiterFactory;
@ -244,9 +222,6 @@ abstract class AbstractClient extends Client implements AwsClientInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function getWaiterFactory()
{
if (!$this->waiterFactory) {
@ -256,18 +231,34 @@ abstract class AbstractClient extends Client implements AwsClientInterface
new WaiterClassFactory(substr($clientClass, 0, strrpos($clientClass, '\\')) . '\\Waiter')
));
if ($this->getDescription()) {
$this->waiterFactory->addFactory(new WaiterConfigFactory($this->getDescription()->getData('waiters')));
$waiterConfig = $this->getDescription()->getData('waiters') ?: array();
$this->waiterFactory->addFactory(new WaiterConfigFactory($waiterConfig));
}
}
return $this->waiterFactory;
}
/**
* {@inheritdoc}
*/
public function getApiVersion()
{
return $this->serviceDescription->getApiVersion();
}
/**
* {@inheritdoc}
* @throws \Aws\Common\Exception\TransferException
*/
public function send($requests)
{
try {
return parent::send($requests);
} catch (CurlException $e) {
$wrapped = new TransferException($e->getMessage(), null, $e);
$wrapped->setCurlHandle($e->getCurlHandle())
->setCurlInfo($e->getCurlInfo())
->setError($e->getError(), $e->getErrorNo())
->setRequest($e->getRequest());
throw $wrapped;
}
}
}

View file

@ -17,6 +17,8 @@
namespace Aws\Common\Client;
use Aws\Common\Credentials\Credentials;
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Credentials\NullCredentials;
use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Enum\Region;
use Aws\Common\Exception\ExceptionListener;
@ -28,7 +30,6 @@ use Aws\Common\Iterator\AwsResourceIteratorFactory;
use Aws\Common\Signature\EndpointSignatureInterface;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Signature\SignatureV2;
use Aws\Common\Signature\SignatureV3;
use Aws\Common\Signature\SignatureV3Https;
use Aws\Common\Signature\SignatureV4;
use Guzzle\Common\Collection;
@ -199,14 +200,10 @@ class ClientBuilder
(self::$commonConfigRequirements + $this->configRequirements)
);
// Resolve endpoint and signature from the config and service description
// Resolve the endpoint, signature, and credentials
$description = $this->updateConfigFromDescription($config);
$signature = $this->getSignature($description, $config);
// Resolve credentials
if (!$credentials = $config->get('credentials')) {
$credentials = Credentials::factory($config);
}
$credentials = $this->getCredentials($config);
// Resolve exception parser
if (!$this->exceptionParser) {
@ -221,10 +218,10 @@ class ClientBuilder
new TruncatedBackoffStrategy(3,
// Retry failed requests with 400-level responses due to throttling
new ThrottlingErrorChecker($this->exceptionParser,
// Retry failed requests with 500-level responses
new HttpBackoffStrategy(array(500, 503, 509),
// Retry failed requests due to transient network or cURL problems
new CurlBackoffStrategy(null,
// Retry failed requests due to transient network or cURL problems
new CurlBackoffStrategy(null,
// Retry failed requests with 500-level responses
new HttpBackoffStrategy(array(500, 503, 509),
// Retry requests that failed due to expired credentials
new ExpiredCredentialsChecker($this->exceptionParser,
new ExponentialBackoffStrategy()
@ -402,7 +399,10 @@ class ClientBuilder
}
/**
* Return an appropriate signature object for a a client based on a description
* Return an appropriate signature object for a a client based on the
* "signature" configuration setting, or the default signature specified in
* a service description. The signature can be set to a valid signature
* version identifier string or an instance of Aws\Common\Signature\SignatureInterface.
*
* @param ServiceDescription $description Description that holds a signature option
* @param Collection $config Configuration options
@ -412,43 +412,50 @@ class ClientBuilder
*/
protected function getSignature(ServiceDescription $description, Collection $config)
{
if (!$signature = $config->get(Options::SIGNATURE)) {
switch ($description->getData('signatureVersion')) {
case 'v2':
$signature = new SignatureV2();
break;
case 'v3':
$signature = new SignatureV3();
break;
case 'v3https':
$signature = new SignatureV3Https();
break;
case 'v4':
$signature = new SignatureV4();
break;
default:
throw new InvalidArgumentException('Service description does not specify a valid signatureVersion');
// If a custom signature has not been provided, then use the default
// signature setting specified in the service description.
$signature = $config->get(Options::SIGNATURE) ?: $description->getData('signatureVersion');
if (is_string($signature)) {
if ($signature == 'v4') {
$signature = new SignatureV4();
} elseif ($signature == 'v2') {
$signature = new SignatureV2();
} elseif ($signature == 'v3https') {
$signature = new SignatureV3Https();
} else {
throw new InvalidArgumentException("Invalid signature type: {$signature}");
}
} elseif (!($signature instanceof SignatureInterface)) {
throw new InvalidArgumentException('The provided signature is not '
. 'a signature version string or an instance of '
. 'Aws\\Common\\Signature\\SignatureInterface');
}
// Allow a custom service name or region value to be provided
if ($signature instanceof EndpointSignatureInterface) {
// Determine the service name to use when signing
if (!$service = $config->get(Options::SIGNATURE_SERVICE)) {
if (!$service = $description->getData('signingName')) {
$service = $description->getData('endpointPrefix');
}
}
$signature->setServiceName($service);
$signature->setServiceName($config->get(Options::SIGNATURE_SERVICE)
?: $description->getData('signingName')
?: $description->getData('endpointPrefix'));
// Determine the region to use when signing requests
if (!$region = $config->get(Options::SIGNATURE_REGION)) {
$region = $config->get(Options::REGION);
}
$signature->setRegionName($region);
$signature->setRegionName($config->get(Options::SIGNATURE_REGION) ?: $config->get(Options::REGION));
}
return $signature;
}
protected function getCredentials(Collection $config)
{
$credentials = $config->get(Options::CREDENTIALS);
if ($credentials === false) {
$credentials = new NullCredentials();
} elseif (!$credentials instanceof CredentialsInterface) {
$credentials = Credentials::factory($config);
}
return $credentials;
}
}

View file

@ -29,38 +29,29 @@ class DefaultClient extends AbstractClient
*
* The following array keys and values are available options:
*
* - Credential options (`key`, `secret`, and optional `token` OR `credentials` is required)
* - key: AWS Access Key ID
* - secret: AWS secret access key
* - credentials: You can optionally provide a custom `Aws\Common\Credentials\CredentialsInterface` object
* - token: Custom AWS security token to use with request authentication
* - token.ttd: UNIX timestamp for when the custom credentials expire
* - credentials.cache: Used to cache credentials when using providers that require HTTP requests. Set the true
* to use the default APC cache or provide a `Guzzle\Cache\CacheAdapterInterface` object.
* - credentials.cache.key: Optional custom cache key to use with the credentials
* - credentials.client: Pass this option to specify a custom `Guzzle\Http\ClientInterface` to use if your
* credentials require a HTTP request (e.g. RefreshableInstanceProfileCredentials)
* - Region and Endpoint options (a `region` and optional `scheme` OR a `base_url` is required)
* - region: Region name (e.g. 'us-east-1', 'us-west-1', 'us-west-2', 'eu-west-1', etc...)
* - scheme: URI Scheme of the base URL (e.g. 'https', 'http').
* - service: Specify the name of the service
* - base_url: Instead of using a `region` and `scheme`, you can specify a custom base URL for the client
* - Signature options
* - signature: You can optionally provide a custom signature implementation used to sign requests
* - signature.service: Set to explicitly override the service name used in signatures
* - signature.region: Set to explicitly override the region name used in signatures
* - Exponential backoff options
* - client.backoff.logger: `Guzzle\Log\LogAdapterInterface` object used to log backoff retries. Use
* 'debug' to emit PHP warnings when a retry is issued.
* - client.backoff.logger.template: Optional template to use for exponential backoff log messages. See
* `Guzzle\Plugin\Backoff\BackoffLogger` for formatting information.
* - Generic client options
* - ssl.certificate_authority: Set to true to use the bundled CA cert (default), system to use the certificate
* bundled with your system, or pass the full path to an SSL certificate bundle. This option should be used
* when you encounter curl error code 60.
* - curl.CURLOPT_VERBOSE: Set to true to output curl debug information during transfers
* - curl.*: Prefix any available cURL option with `curl.` to add cURL options to each request.
* See: http://www.php.net/manual/en/function.curl-setopt.php
* Credential options ((`key`, `secret`, and optional `token`) OR `credentials` is required):
*
* - key: AWS Access Key ID
* - secret: AWS secret access key
* - credentials: You can optionally provide a custom `Aws\Common\Credentials\CredentialsInterface` object
* - token: Custom AWS security token to use with request authentication. Please note that not all services accept temporary credentials. See http://docs.aws.amazon.com/STS/latest/UsingSTS/UsingTokens.html
* - token.ttd: UNIX timestamp for when the custom credentials expire
* - credentials.cache.key: Optional custom cache key to use with the credentials
* - credentials.client: Pass this option to specify a custom `Guzzle\Http\ClientInterface` to use if your credentials require a HTTP request (e.g. RefreshableInstanceProfileCredentials)
*
* Region and endpoint options (Some services do not require a region while others do. Check the service specific user guide documentation for details):
*
* - region: Region name (e.g. 'us-east-1', 'us-west-1', 'us-west-2', 'eu-west-1', etc...)
* - scheme: URI Scheme of the base URL (e.g. 'https', 'http') used when base_url is not supplied
* - base_url: Allows you to specify a custom endpoint instead of building one from the region and scheme
*
* Generic client options:
*
* - signature: Overrides the signature used by the client. Clients will always choose an appropriate default signature. However, it can be useful to override this with a custom setting. This can be set to "v4", "v3https", "v2" or an instance of Aws\Common\Signature\SignatureInterface.
* - ssl.certificate_authority: Set to true to use the bundled CA cert or pass the full path to an SSL certificate bundle
* - curl.options: Associative of CURLOPT_* cURL options to add to each request
* - client.backoff.logger: `Guzzle\Log\LogAdapterInterface` object used to log backoff retries. Use 'debug' to emit PHP warnings when a retry is issued.
* - client.backoff.logger.template: Optional template to use for exponential backoff log messages. See `Guzzle\Plugin\Backoff\BackoffLogger` for formatting information.
*
* @param array|Collection $config Client configuration data
*

View file

@ -16,6 +16,7 @@
namespace Aws\Common\Client;
use Aws\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Event;
use Guzzle\Http\EntityBody;
use Guzzle\Service\Command\AbstractCommand as Command;
@ -66,6 +67,7 @@ class UploadBodyListener implements EventSubscriberInterface
* Converts filenames and file handles into EntityBody objects before the command is validated
*
* @param Event $event Event emitted
* @throws InvalidArgumentException
*/
public function onCommandBeforePrepare(Event $event)
{
@ -81,13 +83,13 @@ class UploadBodyListener implements EventSubscriberInterface
$body = fopen($source, 'r');
}
if (null !== $body) {
$body = EntityBody::factory($body);
}
// Prepare the body parameter and remove the source file parameter
$command->remove($this->sourceParameter);
$command->set($this->bodyParameter, $body);
if (null !== $body) {
$command->remove($this->sourceParameter);
$command->set($this->bodyParameter, EntityBody::factory($body));
} else {
throw new InvalidArgumentException("You must specify a non-null value for the {$this->bodyParameter} or {$this->sourceParameter} parameters.");
}
}
}
}

View file

@ -12,11 +12,11 @@ use Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor;
*/
class AwsQueryVisitor extends AbstractRequestVisitor
{
/**
* {@inheritdoc}
*/
private $fqname;
public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value)
{
$this->fqname = $command->getName();
$query = array();
$this->customResolver($value, $param, $query, $param->getWireName());
$request->addPostFields($query);
@ -66,8 +66,11 @@ class AwsQueryVisitor extends AbstractRequestVisitor
} elseif ($hasAdditionalProperties) {
// Handle map cases like &Attribute.1.Name=<name>&Attribute.1.Value=<value>
$additionalPropertyCount++;
$query["{$prefix}.{$additionalPropertyCount}.Name"] = $name;
$newPrefix = "{$prefix}.{$additionalPropertyCount}.Value";
$data = $param->getData();
$keyName = isset($data['keyName']) ? $data['keyName'] : 'key';
$valueName = isset($data['valueName']) ? $data['valueName'] : 'value';
$query["{$prefix}.{$additionalPropertyCount}.{$keyName}"] = $name;
$newPrefix = "{$prefix}.{$additionalPropertyCount}.{$valueName}";
if (is_array($v)) {
$this->customResolver($v, $param->getAdditionalProperties(), $query, $newPrefix);
} else {
@ -87,6 +90,20 @@ class AwsQueryVisitor extends AbstractRequestVisitor
*/
protected function resolveArray(Parameter $param, array $value, $prefix, array &$query)
{
static $serializeEmpty = array(
'SetLoadBalancerPoliciesForBackendServer' => 1,
'SetLoadBalancerPoliciesOfListener' => 1,
'UpdateStack' => 1
);
// For BC, serialize empty lists for specific operations
if (!$value) {
if (isset($serializeEmpty[$this->fqname])) {
$query[$prefix] = '';
}
return;
}
$offset = $param->getData('offset') ?: 1;
foreach ($value as $index => $v) {
$index += $offset;

View file

@ -20,10 +20,10 @@ use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\RequiredExtensionNotLoadedException;
use Aws\Common\Exception\RuntimeException;
use Guzzle\Http\ClientInterface;
use Guzzle\Common\FromConfigInterface;
use Guzzle\Cache\CacheAdapterInterface;
use Guzzle\Cache\DoctrineCacheAdapter;
use Guzzle\Common\Collection;
/**
* Basic implementation of the AWSCredentials interface that allows callers to
@ -33,25 +33,19 @@ class Credentials implements CredentialsInterface, FromConfigInterface
{
const ENV_KEY = 'AWS_ACCESS_KEY_ID';
const ENV_SECRET = 'AWS_SECRET_KEY';
const ENV_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY';
const ENV_PROFILE = 'AWS_PROFILE';
/**
* @var string AWS Access key ID
*/
/** @var string AWS Access Key ID */
protected $key;
/**
* @var string AWS Secret access key
*/
/** @var string AWS Secret Access Key */
protected $secret;
/**
* @var string Security token
*/
/** @var string AWS Security Token */
protected $token;
/**
* @var int Time to die of token
*/
/** @var int Time to die of token */
protected $ttd;
/**
@ -66,6 +60,7 @@ class Credentials implements CredentialsInterface, FromConfigInterface
Options::SECRET => null,
Options::TOKEN => null,
Options::TOKEN_TTD => null,
Options::PROFILE => null,
Options::CREDENTIALS_CACHE => null,
Options::CREDENTIALS_CACHE_KEY => null,
Options::CREDENTIALS_CLIENT => null
@ -97,19 +92,7 @@ class Credentials implements CredentialsInterface, FromConfigInterface
// Create the credentials object
if (!$config[Options::KEY] || !$config[Options::SECRET]) {
// No keys were provided, so attempt to retrieve some from the environment
$envKey = isset($_SERVER[self::ENV_KEY]) ? $_SERVER[self::ENV_KEY] : getenv(self::ENV_KEY);
$envSecret = isset($_SERVER[self::ENV_SECRET]) ? $_SERVER[self::ENV_SECRET] : getenv(self::ENV_SECRET);
if ($envKey && $envSecret) {
// Use credentials set in the environment variables
$credentials = new static($envKey, $envSecret);
} else {
// Use instance profile credentials (available on EC2 instances)
$credentials = new RefreshableInstanceProfileCredentials(
new static('', '', '', 1),
$config[Options::CREDENTIALS_CLIENT]
);
}
$credentials = self::createFromEnvironment($config);
// If no cache key was set, use the crc32 hostname of the server
$cacheKey = $cacheKey ?: 'credentials_' . crc32(gethostname());
} else {
@ -127,31 +110,56 @@ class Credentials implements CredentialsInterface, FromConfigInterface
// Check if the credentials are refreshable, and if so, configure caching
$cache = $config[Options::CREDENTIALS_CACHE];
if ($cacheKey && $cache) {
if ($cache === 'true' || $cache === true) {
// If no cache adapter was provided, then create one for the user
// @codeCoverageIgnoreStart
if (!extension_loaded('apc')) {
throw new RequiredExtensionNotLoadedException('PHP has not been compiled with APC. Unable to cache '
. 'the credentials.');
} elseif (!class_exists('Doctrine\Common\Cache\ApcCache')) {
throw new RuntimeException(
'Cannot set ' . Options::CREDENTIALS_CACHE . ' to true because the Doctrine cache component is '
. 'not installed. Either install doctrine/cache or pass in an instantiated '
. 'Guzzle\Cache\CacheAdapterInterface object'
);
}
// @codeCoverageIgnoreEnd
$cache = new DoctrineCacheAdapter(new \Doctrine\Common\Cache\ApcCache());
} elseif (!($cache instanceof CacheAdapterInterface)) {
throw new InvalidArgumentException('Unable to utilize caching with the specified options');
}
// Decorate the credentials with a cache
$credentials = new CacheableCredentials($credentials, $cache, $cacheKey);
$credentials = self::createCache($credentials, $cache, $cacheKey);
}
return $credentials;
}
/**
* Create credentials from the credentials ini file in the HOME directory.
*
* @param string|null $profile Pass a specific profile to use. If no
* profile is specified we will attempt to use
* the value specified in the AWS_PROFILE
* environment variable. If AWS_PROFILE is not
* set, the "default" profile is used.
* @param string|null $filename Pass a string to specify the location of the
* credentials files. If null is passed, the
* SDK will attempt to find the configuration
* file at in your HOME directory at
* ~/.aws/credentials.
* @return CredentialsInterface
* @throws \RuntimeException if the file cannot be found, if the file is
* invalid, or if the profile is invalid.
*/
public static function fromIni($profile = null, $filename = null)
{
if (!$filename) {
$filename = self::getHomeDir() . '/.aws/credentials';
}
if (!$profile) {
$profile = self::getEnvVar(self::ENV_PROFILE) ?: 'default';
}
if (!file_exists($filename) || !($data = parse_ini_file($filename, true))) {
throw new \RuntimeException("Invalid AWS credentials file: {$filename}.");
}
if (empty($data[$profile])) {
throw new \RuntimeException("Invalid AWS credentials profile {$profile} in {$filename}.");
}
return new self(
$data[$profile]['aws_access_key_id'],
$data[$profile]['aws_secret_access_key'],
isset($data[$profile]['aws_security_token'])
? $data[$profile]['aws_security_token']
: null
);
}
/**
* Constructs a new BasicAWSCredentials object, with the specified AWS
* access key and AWS secret key
@ -169,9 +177,6 @@ class Credentials implements CredentialsInterface, FromConfigInterface
$this->ttd = $expiration;
}
/**
* {@inheritdoc}
*/
public function serialize()
{
return json_encode(array(
@ -182,9 +187,6 @@ class Credentials implements CredentialsInterface, FromConfigInterface
));
}
/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
$data = json_decode($serialized, true);
@ -194,49 +196,31 @@ class Credentials implements CredentialsInterface, FromConfigInterface
$this->ttd = $data[Options::TOKEN_TTD];
}
/**
* {@inheritdoc}
*/
public function getAccessKeyId()
{
return $this->key;
}
/**
* {@inheritdoc}
*/
public function getSecretKey()
{
return $this->secret;
}
/**
* {@inheritdoc}
*/
public function getSecurityToken()
{
return $this->token;
}
/**
* {@inheritdoc}
*/
public function getExpiration()
{
return $this->ttd;
}
/**
* {@inheritdoc}
*/
public function isExpired()
{
return $this->ttd !== null && time() >= $this->ttd;
}
/**
* {@inheritdoc}
*/
public function setAccessKeyId($key)
{
$this->key = $key;
@ -244,9 +228,6 @@ class Credentials implements CredentialsInterface, FromConfigInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function setSecretKey($secret)
{
$this->secret = $secret;
@ -254,9 +235,6 @@ class Credentials implements CredentialsInterface, FromConfigInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function setSecurityToken($token)
{
$this->token = $token;
@ -264,13 +242,96 @@ class Credentials implements CredentialsInterface, FromConfigInterface
return $this;
}
/**
* {@inheritdoc}
*/
public function setExpiration($timestamp)
{
$this->ttd = $timestamp;
return $this;
}
/**
* When no keys are provided, attempt to create them based on the
* environment or instance profile credentials.
*
* @param array|Collection $config
*
* @return CredentialsInterface
*/
private static function createFromEnvironment($config)
{
// Get key and secret from ENV variables
$envKey = self::getEnvVar(self::ENV_KEY);
if (!($envSecret = self::getEnvVar(self::ENV_SECRET))) {
// Use AWS_SECRET_ACCESS_KEY if AWS_SECRET_KEY was not set.
$envSecret = self::getEnvVar(self::ENV_SECRET_ACCESS_KEY);
}
// Use credentials from the environment variables if available
if ($envKey && $envSecret) {
return new static($envKey, $envSecret);
}
// Use credentials from the ini file in HOME directory if available
$home = self::getHomeDir();
if ($home && file_exists("{$home}/.aws/credentials")) {
return self::fromIni($config[Options::PROFILE], "{$home}/.aws/credentials");
}
// Use instance profile credentials (available on EC2 instances)
return new RefreshableInstanceProfileCredentials(
new static('', '', '', 1),
$config[Options::CREDENTIALS_CLIENT]
);
}
private static function createCache(CredentialsInterface $credentials, $cache, $cacheKey)
{
if ($cache === 'true' || $cache === true) {
// If no cache adapter was provided, then create one for the user
// @codeCoverageIgnoreStart
if (!extension_loaded('apc')) {
throw new RequiredExtensionNotLoadedException('PHP has not been compiled with APC. Unable to cache '
. 'the credentials.');
} elseif (!class_exists('Doctrine\Common\Cache\ApcCache')) {
throw new RuntimeException(
'Cannot set ' . Options::CREDENTIALS_CACHE . ' to true because the Doctrine cache component is '
. 'not installed. Either install doctrine/cache or pass in an instantiated '
. 'Guzzle\Cache\CacheAdapterInterface object'
);
}
// @codeCoverageIgnoreEnd
$cache = new DoctrineCacheAdapter(new \Doctrine\Common\Cache\ApcCache());
} elseif (!($cache instanceof CacheAdapterInterface)) {
throw new InvalidArgumentException('Unable to utilize caching with the specified options');
}
// Decorate the credentials with a cache
return new CacheableCredentials($credentials, $cache, $cacheKey);
}
private static function getHomeDir()
{
// On Linux/Unix-like systems, use the HOME environment variable
if ($homeDir = self::getEnvVar('HOME')) {
return $homeDir;
}
// Get the HOMEDRIVE and HOMEPATH values for Windows hosts
$homeDrive = self::getEnvVar('HOMEDRIVE');
$homePath = self::getEnvVar('HOMEPATH');
return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
}
/**
* Fetches the value of an environment variable by checking $_SERVER and getenv().
*
* @param string $var Name of the environment variable
*
* @return mixed|null
*/
private static function getEnvVar($var)
{
return isset($_SERVER[$var]) ? $_SERVER[$var] : getenv($var);
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Aws\Common\Credentials;
/**
* A blank set of credentials. AWS clients must be provided credentials, but
* there are some types of requests that do not need authentication. This class
* can be used to pivot on that scenario, and also serve as a mock credentials
* object when testing
*
* @codeCoverageIgnore
*/
class NullCredentials implements CredentialsInterface
{
public function getAccessKeyId()
{
return '';
}
public function getSecretKey()
{
return '';
}
public function getSecurityToken()
{
return null;
}
public function getExpiration()
{
return null;
}
public function isExpired()
{
return false;
}
public function serialize()
{
return 'N;';
}
public function unserialize($serialized)
{
// Nothing to do here.
}
public function setAccessKeyId($key)
{
// Nothing to do here.
}
public function setSecretKey($secret)
{
// Nothing to do here.
}
public function setSecurityToken($token)
{
// Nothing to do here.
}
public function setExpiration($timestamp)
{
// Nothing to do here.
}
}

View file

@ -38,6 +38,11 @@ class ClientOptions extends Enum
*/
const CREDENTIALS = 'credentials';
/**
* @var string Name of a credential profile to read from your ~/.aws/credentials file
*/
const PROFILE = 'profile';
/**
* @var string Custom AWS security token to use with request authentication
*/

View file

@ -22,7 +22,7 @@ use Aws\Common\Enum;
* Contains enumerable region code values. These should be useful in most cases,
* with Amazon S3 being the most notable exception
*
* @link http://docs.amazonwebservices.com/general/latest/gr/rande.html AWS Regions and Endpoints
* @link http://docs.aws.amazon.com/general/latest/gr/rande.html AWS Regions and Endpoints
*/
class Region extends Enum
{
@ -52,6 +52,9 @@ class Region extends Enum
const SA_EAST_1 = 'sa-east-1';
const SAO_PAULO = 'sa-east-1';
const CN_NORTH_1 = 'cn-north-1';
const BEIJING = 'cn-north-1';
const US_GOV_WEST_1 = 'us-gov-west-1';
const GOV_CLOUD_US = 'us-gov-west-1';
}

View file

@ -0,0 +1,24 @@
<?php
/**
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
namespace Aws\Common\Exception;
use Guzzle\Http\Exception\CurlException;
/**
* Transfer request exception
*/
class TransferException extends CurlException implements AwsExceptionInterface {}

View file

@ -41,7 +41,7 @@ abstract class Facade implements FacadeInterface
if (isset($service['alias'], $service['class'])) {
$facadeClass = __NAMESPACE__ . '\\' . $service['alias'];
$facadeAlias = ltrim($targetNamespace . '\\' . $service['alias'], '\\');
if (!class_exists($facadeAlias)) {
if (!class_exists($facadeAlias) && class_exists($facadeClass)) {
// @codeCoverageIgnoreStart
class_alias($facadeClass, $facadeAlias);
// @codeCoverageIgnoreEnd

View file

@ -56,6 +56,14 @@ class CloudSearch extends Facade
}
}
class CloudTrail extends Facade
{
public static function getServiceBuilderKey()
{
return 'cloudtrail';
}
}
class CloudWatch extends Facade
{
public static function getServiceBuilderKey()
@ -160,6 +168,14 @@ class ImportExport extends Facade
}
}
class Kinesis extends Facade
{
public static function getServiceBuilderKey()
{
return 'kinesis';
}
}
class OpsWorks extends Facade
{
public static function getServiceBuilderKey()

View file

@ -33,7 +33,7 @@ class HostNameUtils
* @param Url $url HTTP URL
*
* @return string
* @link http://docs.amazonwebservices.com/general/latest/gr/rande.html
* @link http://docs.aws.amazon.com/general/latest/gr/rande.html
*/
public static function parseRegionName(Url $url)
{
@ -68,7 +68,7 @@ class HostNameUtils
* @param Url $url HTTP URL
*
* @return string Returns a service name (or empty string)
* @link http://docs.amazonwebservices.com/general/latest/gr/rande.html
* @link http://docs.aws.amazon.com/general/latest/gr/rande.html
*/
public static function parseServiceName(Url $url)
{

View file

@ -45,6 +45,10 @@ class InstanceMetadataClient extends AbstractClient
$config = Collection::fromConfig($config, array(
Options::BASE_URL => 'http://169.254.169.254/{version}/',
'version' => 'latest',
'request.options' => array(
'connect_timeout' => 5,
'timeout' => 10
)
), array('base_url', 'version'));
return new self($config);
@ -71,15 +75,14 @@ class InstanceMetadataClient extends AbstractClient
{
try {
$request = $this->get('meta-data/iam/security-credentials/');
$request->getCurlOptions()->set(CURLOPT_TIMEOUT, 1)->set(CURLOPT_CONNECTTIMEOUT, 1);
$credentials = trim($request->send()->getBody(true));
$result = $this->get("meta-data/iam/security-credentials/{$credentials}")->send()->json();
} catch (\Exception $e) {
$message = 'Error retrieving credentials from the instance profile metadata server. When you are not'
. ' running inside of Amazon EC2, you must provide your AWS access key ID and secret access key in'
$message = sprintf('Error retrieving credentials from the instance profile metadata server. When you are'
. ' not running inside of Amazon EC2, you must provide your AWS access key ID and secret access key in'
. ' the "key" and "secret" options when creating a client or provide an instantiated'
. ' Aws\\Common\\Credentials\\CredentialsInterface object.';
throw new InstanceProfileCredentialsException($message, $e->getCode(), $e);
. ' Aws\\Common\\Credentials\\CredentialsInterface object. (%s)', $e->getMessage());
throw new InstanceProfileCredentialsException($message, $e->getCode());
}
// Ensure that the status code was successful

View file

@ -32,7 +32,8 @@ class AwsResourceIterator extends ResourceIterator
protected $lastResult = null;
/**
* Provides access to the most recent result obtained by the iterator.
* Provides access to the most recent result obtained by the iterator. This makes it easier to extract any
* additional information from the result which you do not have access to from the values emitted by the iterator
*
* @return Model|null
*/
@ -71,45 +72,37 @@ class AwsResourceIterator extends ResourceIterator
return $resources;
}
/**
* {@inheritdoc}
*/
protected function prepareRequest()
{
// Get the limit parameter key to set
$param = $this->get('limit_param');
if ($param && ($limit = $this->command->get($param))) {
$limitKey = $this->get('limit_key');
if ($limitKey && ($limit = $this->command->get($limitKey))) {
$pageSize = $this->calculatePageSize();
// If the limit of the command is different than the pageSize of the iterator, use the smaller value
if ($limit && $pageSize) {
$this->command->set('limit', min($limit, $pageSize));
$realLimit = min($limit, $pageSize);
$this->command->set($limitKey, $realLimit);
}
}
}
/**
* {@inheritdoc}
*/
protected function handleResults(Model $result)
{
$results = array();
// Get the result key that contains the results
if ($resultKey = $this->get('result_key')) {
$results = $result->getPath($resultKey) ?: array();
$results = $this->getValueFromResult($result, $resultKey) ?: array();
}
return $results;
}
/**
* {@inheritdoc}
*/
protected function applyNextToken()
{
// Get the token parameter key to set
if ($tokenParam = $this->get('token_param')) {
if ($tokenParam = $this->get('input_token')) {
// Set the next token. Works with multi-value tokens
if (is_array($tokenParam)) {
if (is_array($this->nextToken) && count($tokenParam) === count($this->nextToken)) {
@ -126,24 +119,51 @@ class AwsResourceIterator extends ResourceIterator
}
}
/**
* {@inheritdoc}
*/
protected function determineNextToken(Model $result)
{
$this->nextToken = null;
// If the value of "more key" is true or there is no "more key" to check, then try to get the next token
$moreKey = $this->get('more_key');
if ($moreKey === null || $result->getPath($moreKey)) {
// If the value of "more_results" is true or there is no "more_results" to check, then try to get the next token
$moreKey = $this->get('more_results');
if ($moreKey === null || $this->getValueFromResult($result, $moreKey)) {
// Get the token key to check
if ($tokenKey = $this->get('token_key')) {
if ($tokenKey = $this->get('output_token')) {
// Get the next token's value. Works with multi-value tokens
$getToken = function ($key) use ($result) {
return $result->getPath((string) $key);
};
$this->nextToken = is_array($tokenKey) ? array_map($getToken, $tokenKey) : $getToken($tokenKey);
if (is_array($tokenKey)) {
$this->nextToken = array();
foreach ($tokenKey as $key) {
$this->nextToken[] = $this->getValueFromResult($result, $key);
}
} else {
$this->nextToken = $this->getValueFromResult($result, $tokenKey);
}
}
}
}
/**
* Extracts the value from the result using Collection::getPath. Also adds some additional logic for keys that need
* to access n-1 indexes (e.g., ImportExport, Kinesis). The n-1 logic only works for the known cases. We will switch
* to a jmespath implementation in the future to cover all cases
*
* @param Model $result
* @param string $key
*
* @return mixed|null
*/
protected function getValueFromResult(Model $result, $key)
{
// Special handling for keys that need to access n-1 indexes
if (strpos($key, '#') !== false) {
$keyParts = explode('#', $key, 2);
$items = $result->getPath(trim($keyParts[0], '/'));
if ($items && is_array($items)) {
$index = count($items) - 1;
$key = strtr($key, array('#' => $index));
}
}
// Get the value
return $result->getPath($key);
}
}

View file

@ -16,24 +16,28 @@ class AwsResourceIteratorFactory implements ResourceIteratorFactoryInterface
/**
* @var array Default configuration values for iterators
*/
protected static $defaultConfig = array(
'limit_key' => null,
'limit_param' => null,
'more_key' => null,
'token_key' => null,
'token_param' => null,
'operations' => array(),
protected static $defaultIteratorConfig = array(
'input_token' => null,
'output_token' => null,
'limit_key' => null,
'result_key' => null,
'more_results' => null,
);
/**
* @var Collection The configuration for the iterators
* @var array Legacy configuration options mapped to their new names
*/
protected $config;
private static $legacyConfigOptions = array(
'token_param' => 'input_token',
'token_key' => 'output_token',
'limit_param' => 'limit_key',
'more_key' => 'more_results',
);
/**
* @var Collection Additional configurations for specific iterators
* @var array Iterator configuration for each iterable operation
*/
protected $operations;
protected $config;
/**
* @var ResourceIteratorFactoryInterface Another factory that will be used first to instantiate the iterator
@ -43,59 +47,60 @@ class AwsResourceIteratorFactory implements ResourceIteratorFactoryInterface
/**
* @param array $config An array of configuration values for the factory
* @param ResourceIteratorFactoryInterface $primaryIteratorFactory Another factory to use for chain of command
*
* @throws InvalidArgumentException
*/
public function __construct(array $config, ResourceIteratorFactoryInterface $primaryIteratorFactory = null)
{
$this->primaryIteratorFactory = $primaryIteratorFactory;
// Set up the config with default values
$this->config = Collection::fromConfig($config, self::$defaultConfig);
// Pull out the operation-specific configurations
$this->operations = new Collection();
$potentialOperations = $this->config->get('operations') ?: array();
$this->config->remove('operations');
foreach ($potentialOperations as $key => $value) {
if (is_int($key) && is_string($value)) {
$this->operations->set($value, array());
} elseif (is_string($key) && is_array($value)) {
$this->operations->set($key, $value);
} else {
throw new InvalidArgumentException('The iterator factory configuration was invalid.');
}
$this->config = array();
foreach ($config as $name => $operation) {
$this->config[$name] = $operation + self::$defaultIteratorConfig;
}
}
/**
* {@inheritdoc}
*/
public function build(CommandInterface $command, array $options = array())
{
// Get the configuration data for the command
$commandName = $command->getName();
$iteratorConfig = $this->operations->get($commandName) ?: array();
$options = array_replace($this->config->getAll(), $iteratorConfig, $options);
$commandSupported = isset($this->config[$commandName]);
$options = $this->translateLegacyConfigOptions($options);
$options += $commandSupported ? $this->config[$commandName] : array();
// Instantiate the iterator using the primary factory (if there is one)
// Instantiate the iterator using the primary factory (if one was provided)
if ($this->primaryIteratorFactory && $this->primaryIteratorFactory->canBuild($command)) {
$iterator = $this->primaryIteratorFactory->build($command, $options);
} elseif (!$this->operations->hasKey($commandName)) {
} elseif (!$commandSupported) {
throw new InvalidArgumentException("Iterator was not found for {$commandName}.");
} else {
// Fallback to this factory for creating the iterator if the primary factory did not work
// Instantiate a generic AWS resource iterator
$iterator = new AwsResourceIterator($command, $options);
}
return $iterator;
}
/**
* {@inheritdoc}
*/
public function canBuild(CommandInterface $command)
{
return ($this->primaryIteratorFactory && $this->primaryIteratorFactory->canBuild($command))
|| $this->operations->hasKey($command->getName());
if ($this->primaryIteratorFactory) {
return $this->primaryIteratorFactory->canBuild($command);
} else {
return isset($this->config[$command->getName()]);
}
}
/**
* @param array $config The config for a single operation
*
* @return array The modified config with legacy options translated
*/
private function translateLegacyConfigOptions($config)
{
foreach (self::$legacyConfigOptions as $legacyOption => $newOption) {
if (isset($config[$legacyOption])) {
$config[$newOption] = $config[$legacyOption];
unset($config[$legacyOption]);
}
}
return $config;
}
}

View file

@ -53,12 +53,53 @@ return array(
'class' => 'Aws\CloudSearch\CloudSearchClient'
),
'cloudsearch_20110201' => array(
'extends' => 'cloudsearch',
'params' => array(
'version' => '2011-02-01'
)
),
'cloudsearchdomain' => array(
'alias' => 'CloudSearchDomain',
'extends' => 'default_settings',
'class' => 'Aws\CloudSearchDomain\CloudSearchDomainClient'
),
'cloudtrail' => array(
'alias' => 'CloudTrail',
'extends' => 'default_settings',
'class' => 'Aws\CloudTrail\CloudTrailClient'
),
'cloudwatch' => array(
'alias' => 'CloudWatch',
'extends' => 'default_settings',
'class' => 'Aws\CloudWatch\CloudWatchClient'
),
'cognito-identity' => array(
'alias' => 'CognitoIdentity',
'extends' => 'default_settings',
'class' => 'Aws\CognitoIdentity\CognitoIdentityClient'
),
'cognitoidentity' => array('extends' => 'cognito-identity'),
'cognito-sync' => array(
'alias' => 'CognitoSync',
'extends' => 'default_settings',
'class' => 'Aws\CognitoSync\CognitoSyncClient'
),
'cognitosync' => array('extends' => 'cognito-sync'),
'cloudwatchlogs' => array(
'alias' => 'CloudWatchLogs',
'extends' => 'default_settings',
'class' => 'Aws\CloudWatchLogs\CloudWatchLogsClient'
),
'datapipeline' => array(
'alias' => 'DataPipeline',
'extends' => 'default_settings',
@ -126,6 +167,12 @@ return array(
'class' => 'Aws\Glacier\GlacierClient'
),
'kinesis' => array(
'alias' => 'Kinesis',
'extends' => 'default_settings',
'class' => 'Aws\Kinesis\KinesisClient'
),
'iam' => array(
'alias' => 'Iam',
'extends' => 'default_settings',
@ -162,6 +209,12 @@ return array(
'class' => 'Aws\Route53\Route53Client'
),
'route53domains' => array(
'alias' => 'Route53Domains',
'extends' => 'default_settings',
'class' => 'Aws\Route53Domains\Route53DomainsClient'
),
's3' => array(
'alias' => 'S3',
'extends' => 'default_settings',

View file

@ -16,75 +16,29 @@
namespace Aws\Common\Signature;
use Aws\Common\Credentials\CredentialsInterface;
use Guzzle\Http\Message\RequestInterface;
/**
* Abstract signature class that can be used when implementing new concrete
* AWS signature protocol strategies
*/
abstract class AbstractSignature implements SignatureInterface
{
/**
* @var int Timestamp
*/
private $timestamp;
/**
* Get the canonicalized query string for a request
*
* @param RequestInterface $request
* @return string
*/
protected function getCanonicalizedQueryString(RequestInterface $request)
{
$queryParams = $request->getQuery()->getAll();
unset($queryParams['X-Amz-Signature']);
if (empty($queryParams)) {
return '';
}
$qs = '';
ksort($queryParams);
foreach ($queryParams as $key => $values) {
if (is_array($values)) {
sort($values);
} elseif (!$values) {
$values = array('');
}
foreach ((array) $values as $value) {
$qs .= rawurlencode($key) . '=' . rawurlencode($value) . '&';
}
}
return substr($qs, 0, -1);
}
/**
* Provides the timestamp used for the class
*
* @param bool $refresh Set to TRUE to refresh the cached timestamp
* Provides the timestamp used for the class (used for mocking PHP's time() function)
*
* @return int
*/
protected function getTimestamp($refresh = false)
protected function getTimestamp()
{
if (!$this->timestamp || $refresh) {
$this->timestamp = time();
}
return $this->timestamp;
return time();
}
/**
* Get a date for one of the parts of the requests
*
* @param string $format Date format
*
* @return string
* @codeCoverageIgnore
*/
protected function getDateTime($format)
{
return gmdate($format, $this->getTimestamp());
public function createPresignedUrl(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
) {
throw new \BadMethodCallException(__METHOD__ . ' not implemented');
}
}

View file

@ -34,4 +34,19 @@ interface SignatureInterface
* @param CredentialsInterface $credentials Signing credentials
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials);
/**
* Create a pre-signed URL
*
* @param RequestInterface $request Request to sign
* @param CredentialsInterface $credentials Credentials used to sign
* @param int|string|\DateTime $expires The time at which the URL should expire. This can be a Unix timestamp, a
* PHP DateTime object, or a string that can be evaluated by strtotime
* @return string
*/
public function createPresignedUrl(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
);
}

View file

@ -25,16 +25,13 @@ use Guzzle\Http\Message\RequestInterface;
*/
class SignatureV2 extends AbstractSignature
{
/**
* {@inheritDoc}
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
// refresh the cached timestamp
$this->getTimestamp(true);
$timestamp = $this->getTimestamp(true);
// set values we need in CanonicalizedParameterString
$this->addParameter($request, 'Timestamp', $this->getDateTime('c'));
$this->addParameter($request, 'Timestamp', gmdate('c', $timestamp));
$this->addParameter($request, 'SignatureVersion', '2');
$this->addParameter($request, 'SignatureMethod', 'HmacSHA256');
$this->addParameter($request, 'AWSAccessKeyId', $credentials->getAccessKeyId());
@ -90,7 +87,7 @@ class SignatureV2 extends AbstractSignature
*
* @return string
*/
public function getCanonicalizedParameterString(RequestInterface $request)
private function getCanonicalizedParameterString(RequestInterface $request)
{
if ($request->getMethod() == 'POST') {
$params = $request->getPostFields()->toArray();

View file

@ -1,102 +0,0 @@
<?php
/**
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
namespace Aws\Common\Signature;
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Enum\DateFormat;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
/**
* Implementation of Signature Version 3
* @link http://docs.amazonwebservices.com/amazonswf/latest/developerguide/HMACAuth-swf.html
*/
class SignatureV3 extends AbstractSignature
{
/**
* Get an array of headers to be signed
*
* @param RequestInterface $request Request to get headers from
*
* @return array
*/
protected function getHeadersToSign(RequestInterface $request)
{
$headers = array();
foreach ($request->getHeaders()->toArray() as $k => $v) {
$k = strtolower($k);
if ($k == 'host' || strpos($k, 'x-amz-') !== false) {
$headers[$k] = implode(',', $v);
}
}
// Sort the headers alphabetically and add them to the string to sign
ksort($headers);
return $headers;
}
/**
* {@inheritdoc}
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
// Refresh the cached timestamp
$this->getTimestamp(true);
// Add default headers
$request->setHeader('x-amz-date', $this->getDateTime(DateFormat::RFC1123));
// Add the security token if one is present
if ($credentials->getSecurityToken()) {
$request->setHeader('x-amz-security-token', $credentials->getSecurityToken());
}
// Grab the path and ensure that it is absolute
$path = '/' . ltrim($request->getUrl(true)->normalizePath()->getPath(), '/');
// Begin building the string to sign
$sign = $request->getMethod() . "\n"
. "{$path}\n"
. $this->getCanonicalizedQueryString($request) . "\n";
// Get all of the headers that must be signed (host and x-amz-*)
$headers = $this->getHeadersToSign($request);
foreach ($headers as $key => $value) {
$sign .= $key . ':' . $value . "\n";
}
$sign .= "\n";
// Add the body of the request if a body is present
if ($request instanceof EntityEnclosingRequestInterface) {
$sign .= (string) $request->getBody();
}
// Add the string to sign to the request for debugging purposes
$request->getParams()->set('aws.string_to_sign', $sign);
$signature = base64_encode(hash_hmac('sha256',
hash('sha256', $sign, true), $credentials->getSecretKey(), true));
// Add the authorization header to the request
$request->setHeader('x-amzn-authorization', sprintf('AWS3 AWSAccessKeyId=%s,Algorithm=HmacSHA256,SignedHeaders=%s,Signature=%s',
$credentials->getAccessKeyId(),
implode(';', array_keys($headers)),
$signature));
}
}

View file

@ -22,18 +22,15 @@ use Guzzle\Http\Message\RequestInterface;
/**
* Implementation of Signature Version 3 HTTPS
* @link http://docs.amazonwebservices.com/Route53/latest/DeveloperGuide/RESTAuthentication.html
* @link http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/RESTAuthentication.html
*/
class SignatureV3Https extends AbstractSignature
{
/**
* {@inheritdoc}
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
// Add a date header if one is not set
if (!$request->hasHeader('date') && !$request->hasHeader('x-amz-date')) {
$request->setHeader('Date', $this->getDateTime(DateFormat::RFC1123));
$request->setHeader('Date', gmdate(DateFormat::RFC1123, $this->getTimestamp()));
}
// Add the security token if one is present
@ -42,7 +39,7 @@ class SignatureV3Https extends AbstractSignature
}
// Determine the string to sign
$stringToSign = $request->getHeader('Date', true) ?: $request->getHeader('x-amz-date', true);
$stringToSign = (string) ($request->getHeader('Date') ?: $request->getHeader('x-amz-date'));
$request->getParams()->set('aws.string_to_sign', $stringToSign);
// Calculate the signature

View file

@ -19,46 +19,47 @@ namespace Aws\Common\Signature;
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Enum\DateFormat;
use Aws\Common\HostNameUtils;
use Guzzle\Http\Message\EntityEnclosingRequest;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
use Guzzle\Http\Message\RequestFactory;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\QueryString;
use Guzzle\Http\Url;
/**
* Signature Version 4
* @link http://docs.amazonwebservices.com/general/latest/gr/signature-version-4.html
* @link http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
*/
class SignatureV4 extends AbstractSignature implements EndpointSignatureInterface
{
/**
* @var string Cache of the default empty entity-body payload
*/
/** @var string Cache of the default empty entity-body payload */
const DEFAULT_PAYLOAD = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
/**
* @var string Explicitly set service name
*/
/** @var string Explicitly set service name */
protected $serviceName;
/**
* @var string Explicitly set region name
*/
/** @var string Explicitly set region name */
protected $regionName;
/**
* @var int Maximum number of hashes to cache
*/
/** @var int Maximum number of hashes to cache */
protected $maxCacheSize = 50;
/**
* @var array Cache of previously signed values
*/
/** @var array Cache of previously signed values */
protected $hashCache = array();
/**
* @var int Size of the hash cache
*/
/** @var int Size of the hash cache */
protected $cacheSize = 0;
/**
* @param string $serviceName Bind the signing to a particular service name
* @param string $regionName Bind the signing to a particular region name
*/
public function __construct($serviceName = null, $regionName = null)
{
$this->serviceName = $serviceName;
$this->regionName = $regionName;
}
/**
* Set the service name instead of inferring it from a request URL
*
@ -101,26 +102,20 @@ class SignatureV4 extends AbstractSignature implements EndpointSignatureInterfac
return $this;
}
/**
* {@inheritdoc}
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
// Refresh the cached timestamp
$this->getTimestamp(true);
$timestamp = $this->getTimestamp();
$longDate = gmdate(DateFormat::ISO8601, $timestamp);
$shortDate = substr($longDate, 0, 8);
$longDate = $this->getDateTime(DateFormat::ISO8601);
$shortDate = $this->getDateTime(DateFormat::SHORT);
// Remove any previously set Authorization headers so that
// exponential backoff works correctly
// Remove any previously set Authorization headers so that retries work
$request->removeHeader('Authorization');
// Requires a x-amz-date header or Date
if ($request->hasHeader('x-amz-date') || !$request->hasHeader('Date')) {
$request->setHeader('x-amz-date', $longDate);
} else {
$request->setHeader('Date', $this->getDateTime(DateFormat::RFC1123));
$request->setHeader('Date', gmdate(DateFormat::RFC1123, $timestamp));
}
// Add the security token if one is present
@ -129,22 +124,22 @@ class SignatureV4 extends AbstractSignature implements EndpointSignatureInterfac
}
// Parse the service and region or use one that is explicitly set
$url = null;
if (!$this->regionName || !$this->serviceName) {
$region = $this->regionName;
$service = $this->serviceName;
if (!$region || !$service) {
$url = Url::factory($request->getUrl());
}
if (!$region = $this->regionName) {
$region = HostNameUtils::parseRegionName($url);
}
if (!$service = $this->serviceName) {
$service = HostNameUtils::parseServiceName($url);
$region = $region ?: HostNameUtils::parseRegionName($url);
$service = $service ?: HostNameUtils::parseServiceName($url);
}
$credentialScope = "{$shortDate}/{$region}/{$service}/aws4_request";
$signingContext = $this->createCanonicalRequest($request);
$signingContext['string_to_sign'] = "AWS4-HMAC-SHA256\n{$longDate}\n{$credentialScope}\n"
. hash('sha256', $signingContext['canonical_request']);
$credentialScope = $this->createScope($shortDate, $region, $service);
$payload = $this->getPayload($request);
$signingContext = $this->createSigningContext($request, $payload);
$signingContext['string_to_sign'] = $this->createStringToSign(
$longDate,
$credentialScope,
$signingContext['canonical_request']
);
// Calculate the signing key using a series of derived keys
$signingKey = $this->getSigningKey($shortDate, $region, $service, $credentials->getSecretKey());
@ -158,32 +153,175 @@ class SignatureV4 extends AbstractSignature implements EndpointSignatureInterfac
$request->getParams()->set('aws.signature', $signingContext);
}
public function createPresignedUrl(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
) {
$request = $this->createPresignedRequest($request, $credentials);
$query = $request->getQuery();
$httpDate = gmdate(DateFormat::ISO8601, $this->getTimestamp());
$shortDate = substr($httpDate, 0, 8);
$scope = $this->createScope(
$shortDate,
$this->regionName,
$this->serviceName
);
$this->addQueryValues($scope, $request, $credentials, $expires);
$payload = $this->getPresignedPayload($request);
$context = $this->createSigningContext($request, $payload);
$stringToSign = $this->createStringToSign(
$httpDate,
$scope,
$context['canonical_request']
);
$key = $this->getSigningKey(
$shortDate,
$this->regionName,
$this->serviceName,
$credentials->getSecretKey()
);
$query['X-Amz-Signature'] = hash_hmac('sha256', $stringToSign, $key);
return $request->getUrl();
}
/**
* Converts a POST request to a GET request by moving POST fields into the
* query string.
*
* Useful for pre-signing query protocol requests.
*
* @param EntityEnclosingRequestInterface $request Request to clone
*
* @return RequestInterface
* @throws \InvalidArgumentException if the method is not POST
*/
public static function convertPostToGet(EntityEnclosingRequestInterface $request)
{
if ($request->getMethod() !== 'POST') {
throw new \InvalidArgumentException('Expected a POST request but '
. 'received a ' . $request->getMethod() . ' request.');
}
$cloned = RequestFactory::getInstance()
->cloneRequestWithMethod($request, 'GET');
// Move POST fields to the query if they are present
foreach ($request->getPostFields() as $name => $value) {
$cloned->getQuery()->set($name, $value);
}
return $cloned;
}
/**
* Get the payload part of a signature from a request.
*
* @param RequestInterface $request
*
* @return string
*/
protected function getPayload(RequestInterface $request)
{
// Calculate the request signature payload
if ($request->hasHeader('x-amz-content-sha256')) {
// Handle streaming operations (e.g. Glacier.UploadArchive)
return (string) $request->getHeader('x-amz-content-sha256');
}
if ($request instanceof EntityEnclosingRequestInterface) {
return hash(
'sha256',
$request->getMethod() == 'POST' && count($request->getPostFields())
? (string) $request->getPostFields()
: (string) $request->getBody()
);
}
return self::DEFAULT_PAYLOAD;
}
/**
* Get the payload of a request for use with pre-signed URLs.
*
* @param RequestInterface $request
*
* @return string
*/
protected function getPresignedPayload(RequestInterface $request)
{
return $this->getPayload($request);
}
protected function createCanonicalizedPath(RequestInterface $request)
{
$doubleEncoded = rawurlencode(ltrim($request->getPath(), '/'));
return '/' . str_replace('%2F', '/', $doubleEncoded);
}
private function createStringToSign($longDate, $credentialScope, $creq)
{
return "AWS4-HMAC-SHA256\n{$longDate}\n{$credentialScope}\n"
. hash('sha256', $creq);
}
private function createPresignedRequest(
RequestInterface $request,
CredentialsInterface $credentials
) {
$sr = RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET');
// Move POST fields to the query if they are present
if ($request instanceof EntityEnclosingRequestInterface) {
foreach ($request->getPostFields() as $name => $value) {
$sr->getQuery()->set($name, $value);
}
}
// Make sure to handle temporary credentials
if ($token = $credentials->getSecurityToken()) {
$sr->setHeader('X-Amz-Security-Token', $token);
$sr->getQuery()->set('X-Amz-Security-Token', $token);
}
$this->moveHeadersToQuery($sr);
return $sr;
}
/**
* Create the canonical representation of a request
*
* @param RequestInterface $request Request to canonicalize
* @param string $payload Request payload (typically the value
* of the x-amz-content-sha256 header.
*
* @return array Returns an array of context information
* @return array Returns an array of context information including:
* - canonical_request
* - signed_headers
*/
private function createCanonicalRequest(RequestInterface $request)
private function createSigningContext(RequestInterface $request, $payload)
{
// Normalize the path as required by SigV4 and ensure it's absolute
$method = $request->getMethod();
$canon = $method . "\n"
. '/' . ltrim($request->getUrl(true)->normalizePath()->getPath(), '/') . "\n"
$canon = $request->getMethod() . "\n"
. $this->createCanonicalizedPath($request) . "\n"
. $this->getCanonicalizedQueryString($request) . "\n";
// Create the canonical headers
$headers = array();
foreach ($request->getHeaders()->getAll() as $key => $values) {
if ($key != 'User-Agent') {
$key = strtolower($key);
if (!isset($headers[$key])) {
$headers[$key] = array();
}
$key = strtolower($key);
if ($key != 'user-agent') {
$headers[$key] = array();
foreach ($values as $value) {
$headers[$key][] = preg_replace('/\s+/', ' ', trim($value));
}
// Sort the value if there is more than one
if (count($values) > 1) {
sort($headers[$key]);
}
}
}
@ -192,30 +330,13 @@ class SignatureV4 extends AbstractSignature implements EndpointSignatureInterfac
// Continue to build the canonical request by adding headers
foreach ($headers as $key => $values) {
// Combine multi-value headers into a sorted comma separated list
if (count($values) > 1) {
sort($values);
}
// Combine multi-value headers into a comma separated list
$canon .= $key . ':' . implode(',', $values) . "\n";
}
// Create the signed headers
$signedHeaders = implode(';', array_keys($headers));
$canon .= "\n{$signedHeaders}\n";
// Create the payload if this request has an entity body
if ($request->hasHeader('x-amz-content-sha256')) {
// Handle streaming operations (e.g. Glacier.UploadArchive)
$canon .= $request->getHeader('x-amz-content-sha256');
} elseif ($request instanceof EntityEnclosingRequestInterface) {
$canon .= hash(
'sha256',
$method == 'POST' && count($request->getPostFields())
? (string) $request->getPostFields() : (string) $request->getBody()
);
} else {
$canon .= self::DEFAULT_PAYLOAD;
}
$canon .= "\n{$signedHeaders}\n{$payload}";
return array(
'canonical_request' => $canon,
@ -253,4 +374,97 @@ class SignatureV4 extends AbstractSignature implements EndpointSignatureInterfac
return $this->hashCache[$cacheKey];
}
/**
* Get the canonicalized query string for a request
*
* @param RequestInterface $request
* @return string
*/
private function getCanonicalizedQueryString(RequestInterface $request)
{
$queryParams = $request->getQuery()->getAll();
unset($queryParams['X-Amz-Signature']);
if (empty($queryParams)) {
return '';
}
$qs = '';
ksort($queryParams);
foreach ($queryParams as $key => $values) {
if (is_array($values)) {
sort($values);
} elseif (!$values) {
$values = array('');
}
foreach ((array) $values as $value) {
if ($value === QueryString::BLANK) {
$value = '';
}
$qs .= rawurlencode($key) . '=' . rawurlencode($value) . '&';
}
}
return substr($qs, 0, -1);
}
private function convertExpires($expires)
{
if ($expires instanceof \DateTime) {
$expires = $expires->getTimestamp();
} elseif (!is_numeric($expires)) {
$expires = strtotime($expires);
}
$duration = $expires - time();
// Ensure that the duration of the signature is not longer than a week
if ($duration > 604800) {
throw new \InvalidArgumentException('The expiration date of a '
. 'signature version 4 presigned URL must be less than one '
. 'week');
}
return $duration;
}
private function createScope($shortDate, $region, $service)
{
return $shortDate
. '/' . $region
. '/' . $service
. '/aws4_request';
}
private function addQueryValues(
$scope,
RequestInterface $request,
CredentialsInterface $credentials,
$expires
) {
$credential = $credentials->getAccessKeyId() . '/' . $scope;
// Set query params required for pre-signed URLs
$request->getQuery()
->set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256')
->set('X-Amz-Credential', $credential)
->set('X-Amz-Date', gmdate('Ymd\THis\Z', $this->getTimestamp()))
->set('X-Amz-SignedHeaders', 'Host')
->set('X-Amz-Expires', $this->convertExpires($expires));
}
private function moveHeadersToQuery(RequestInterface $request)
{
$query = $request->getQuery();
foreach ($request->getHeaders() as $name => $header) {
if (substr($name, 0, 5) == 'x-amz') {
$query[$header->getName()] = (string) $header;
}
if ($name !== 'host') {
$request->removeHeader($name);
}
}
}
}

View file

@ -89,6 +89,16 @@ abstract class AbstractWaiter extends AbstractHasDispatcher implements WaiterInt
*/
public function setConfig(array $config)
{
if (isset($config['waiter.before_attempt'])) {
$this->getEventDispatcher()->addListener('waiter.before_attempt', $config['waiter.before_attempt']);
unset($config['waiter.before_attempt']);
}
if (isset($config['waiter.before_wait'])) {
$this->getEventDispatcher()->addListener('waiter.before_wait', $config['waiter.before_wait']);
unset($config['waiter.before_wait']);
}
$this->config = $config;
return $this;

View file

@ -1,141 +0,0 @@
# Apache License
Version 2.0, January 2004
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
## 1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1
through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the
License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled
by, or are under common control with that entity. For the purposes of this definition, "control" means
(i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract
or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software
source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form,
including but not limited to compiled object code, generated documentation, and conversions to other media
types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License,
as indicated by a copyright notice that is included in or attached to the work (an example is provided in the
Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from)
the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent,
as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not
include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work
and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any
modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to
Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to
submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of
electronic, verbal, or written communication sent to the Licensor or its representatives, including but not
limited to communication on electronic mailing lists, source code control systems, and issue tracking systems
that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been
received by Licensor and subsequently incorporated within the Work.
## 2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
## 3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such
license applies only to those patent claims licensable by such Contributor that are necessarily infringed by
their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim
or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work
constitutes direct or contributory patent infringement, then any patent licenses granted to You under this
License for that Work shall terminate as of the date such litigation is filed.
## 4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You meet the following conditions:
1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
2. You must cause any modified files to carry prominent notices stating that You changed the files; and
3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent,
trademark, and attribution notices from the Source form of the Work, excluding those notices that do
not pertain to any part of the Derivative Works; and
4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that
You distribute must include a readable copy of the attribution notices contained within such NOTICE
file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed as part of the Derivative Works; within
the Source form or documentation, if provided along with the Derivative Works; or, within a display
generated by the Derivative Works, if and wherever such third-party notices normally appear. The
contents of the NOTICE file are for informational purposes only and do not modify the License. You may
add Your own attribution notices within Derivative Works that You distribute, alongside or as an
addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be
construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license
terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative
Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the
conditions stated in this License.
## 5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by
You to the Licensor shall be under the terms and conditions of this License, without any additional terms or
conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate
license agreement you may have executed with Licensor regarding such Contributions.
## 6. Trademarks.
This License does not grant permission to use the trade names, trademarks, service marks, or product names of
the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
## 7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor
provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
## 8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless
required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any
Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential
damages of any character arising as a result of this License or out of the use or inability to use the Work
(including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has been advised of the possibility
of such damages.
## 9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole
responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold
each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -1,112 +0,0 @@
# AWS SDK for PHP
<http://aws.amazon.com/php>
Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
A copy of the License is located at
<http://aws.amazon.com/apache2.0>
or in the "license" file accompanying this file. This file is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing
permissions and limitations under the License.
# Guzzle
<https://github.com/guzzle/guzzle>
Copyright (c) 2011 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.
# Symfony
<https://github.com/symfony/symfony>
Copyright (c) 2004-2012 Fabien Potencier
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.
# Doctrine Common
<https://github.com/doctrine/common>
Copyright (c) 2006-2012 Doctrine Project
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.
# Monolog
<https://github.com/Seldaek/monolog>
Copyright (c) Jordi Boggiano
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.

View file

@ -16,7 +16,6 @@
namespace Aws\S3\Command;
use Aws\S3\Exception\RedirectException;
use Guzzle\Service\Command\OperationCommand;
use Guzzle\Service\Resource\Model;
use Guzzle\Common\Event;

View file

@ -0,0 +1,27 @@
<?php
/**
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
namespace Aws\S3\Enum;
use Aws\Common\Enum;
/**
* Contains enumerable EncodingType values
*/
class EncodingType extends Enum
{
const URL = 'url';
}

View file

@ -24,5 +24,5 @@ use Aws\Common\Enum;
class Status extends Enum
{
const ENABLED = 'Enabled';
const DISABLED = 'Disabled';
const SUSPENDED = 'Suspended';
}

View file

@ -23,7 +23,8 @@ use Guzzle\Service\Resource\Model;
* Iterator for the S3 ListBuckets command
*
* This iterator includes the following additional options:
* @option bool names_only Set to true to receive only the object/prefix names
*
* - names_only: Set to true to receive only the object/prefix names
*/
class ListBucketsIterator extends AwsResourceIterator
{

View file

@ -23,7 +23,8 @@ use Aws\Common\Iterator\AwsResourceIterator;
* Iterator for the S3 ListMultipartUploads command
*
* This iterator includes the following additional options:
* @option bool return_prefixes Set to true to return both prefixes and uploads
*
* - return_prefixes: Set to true to return both prefixes and uploads
*/
class ListMultipartUploadsIterator extends AwsResourceIterator
{

View file

@ -23,7 +23,8 @@ use Guzzle\Service\Resource\Model;
* Iterator for an S3 ListObjectVersions command
*
* This iterator includes the following additional options:
* @option bool return_prefixes Set to true to receive both prefixes and versions in results
*
* - return_prefixes: Set to true to receive both prefixes and versions in results
*/
class ListObjectVersionsIterator extends AwsResourceIterator
{

View file

@ -23,23 +23,21 @@ use Guzzle\Service\Resource\Model;
* Iterator for an S3 ListObjects command
*
* This iterator includes the following additional options:
* @option bool return_prefixes Set to true to receive both prefixes and objects in results
* @option bool sort_results Set to true to sort mixed (object/prefix) results
* @option bool names_only Set to true to receive only the object/prefix names
*
* - return_prefixes: Set to true to receive both prefixes and objects in results
* - sort_results: Set to true to sort mixed (object/prefix) results
* - names_only: Set to true to receive only the object/prefix names
*/
class ListObjectsIterator extends AwsResourceIterator
{
/**
* {@inheritdoc}
*/
protected function handleResults(Model $result)
{
// Get the list of objects and record the last key
$objects = $result->get('Contents') ?: array();
$numObjects = count($objects);
$lastKey = $numObjects ? $objects[$numObjects - 1]['Key'] : false;
if ($lastKey && !$result->hasKey($this->get('token_key'))) {
$result->set($this->get('token_key'), $lastKey);
if ($lastKey && !$result->hasKey($this->get('output_token'))) {
$result->set($this->get('output_token'), $lastKey);
}
// Closure for getting the name of an object or prefix

View file

@ -17,7 +17,6 @@
namespace Aws\S3\Model;
use Aws\Common\Client\AwsClientInterface;
use Aws\Common\Iterator\AwsResourceIterator;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Batch\FlushingBatch;
use Guzzle\Batch\ExceptionBufferingBatch;

View file

@ -20,7 +20,6 @@ use Aws\Common\Enum\UaString as Ua;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Model\MultipartUpload\AbstractUploadBuilder;
use Aws\S3\Model\Acp;
use Guzzle\Common\Collection;
/**
* Easily create a multipart uploader used to quickly and reliably upload a
@ -266,8 +265,10 @@ class UploadBuilder extends AbstractUploadBuilder
protected function initiateMultipartUpload()
{
// Determine Content-Type
if ($mimeType = $this->source->getContentType()) {
$this->commandOptions['ContentType'] = $mimeType;
if (!isset($this->commandOptions['ContentType'])) {
if ($mimeType = $this->source->getContentType()) {
$this->commandOptions['ContentType'] = $mimeType;
}
}
$params = array_replace(array(

View file

@ -16,7 +16,6 @@
namespace Aws\S3\Model;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Enum\DateFormat;
use Aws\S3\S3Client;
use Guzzle\Common\Collection;
@ -68,12 +67,19 @@ class PostObject extends Collection
* - key: The location where the file should be uploaded to. The default value is
* `^${filename}` which will use the name of the uploaded file
* - policy: A raw policy in JSON format. By default, the PostObject creates one for you
* - policy_callback: A callback used to modify the policy before encoding and signing it. The
* method signature for the callback should accept an array of the policy data as
* the 1st argument, (optionally) the PostObject as the 2nd argument, and return
* the policy data with the desired modifications.
* - success_action_redirect: The URI for Amazon S3 to redirect to upon successful upload
* - success_action_status: The status code for Amazon S3 to return upon successful upload
* - ttd: The expiration time for the generated upload form data
* - x-amz-meta-*: Any custom meta tag that should be set to the object
* - x-amz-server-side-encryption: The server-side encryption mechanism to use
* - x-amz-storage-class: The storage setting to apply to the object
* - x-amz-meta-*: Any custom meta tag that should be set to the object
* - x-amz-server-side-encryption-customer-algorithm: The SSE-C algorithm
* - x-amz-server-side-encryption-customer-key: The SSE-C customer secret key
* - x-amz-server-side-encryption-customer-key-MD5: The MD5 hash of the SSE-C customer secret key
*
* For the Cache-Control, Content-Disposition, Content-Encoding,
* Content-Type, Expires, and key options, to use a "starts-with" comparison
@ -110,9 +116,10 @@ class PostObject extends Collection
$ttd = is_numeric($ttd) ? (int) $ttd : strtotime($ttd);
unset($options['ttd']);
// Save policy if passed in
$rawPolicy = $options['policy'];
unset($options['policy']);
// If a policy or policy callback were provided, extract those from the options
$rawJsonPolicy = $options['policy'];
$policyCallback = $options['policy_callback'];
unset($options['policy'], $options['policy_callback']);
// Setup policy document
$policy = array(
@ -122,7 +129,13 @@ class PostObject extends Collection
// Configure the endpoint/action
$url = Url::factory($this->client->getBaseUrl());
$url->setHost($this->bucket . '.' . $url->getHost());
if ($url->getScheme() === 'https' && strpos($this->bucket, '.') !== false) {
// Use path-style URLs
$url->setPath($this->bucket);
} else {
// Use virtual-style URLs
$url->setHost($this->bucket . '.' . $url->getHost());
}
// Setup basic form
$this->formAttributes = array(
@ -141,7 +154,7 @@ class PostObject extends Collection
$policy['conditions'][] = array(
'success_action_status' => (string) $status
);
$options->remove('success_action_status');
unset($options['success_action_status']);
}
// Add other options
@ -158,18 +171,10 @@ class PostObject extends Collection
}
}
// Add policy
$this->jsonPolicy = $rawPolicy ?: json_encode($policy);
$jsonPolicy64 = base64_encode($this->jsonPolicy);
$this->formInputs['policy'] = $jsonPolicy64;
// Add signature
$this->formInputs['signature'] = base64_encode(hash_hmac(
'sha1',
$jsonPolicy64,
$this->client->getCredentials()->getSecretKey(),
true
));
// Handle the policy
$policy = is_callable($policyCallback) ? $policyCallback($policy, $this) : $policy;
$this->jsonPolicy = $rawJsonPolicy ?: json_encode($policy);
$this->applyPolicy();
return $this;
}
@ -251,4 +256,20 @@ class PostObject extends Collection
{
return $this->jsonPolicy;
}
/**
* Handles the encoding, singing, and injecting of the policy
*/
protected function applyPolicy()
{
$jsonPolicy64 = base64_encode($this->jsonPolicy);
$this->formInputs['policy'] = $jsonPolicy64;
$this->formInputs['signature'] = base64_encode(hash_hmac(
'sha1',
$jsonPolicy64,
$this->client->getCredentials()->getSecretKey(),
true
));
}
}

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,8 @@ use Aws\Common\Client\UploadBodyListener;
use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Exception\RuntimeException;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Signature\SignatureV4;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Model\MultipartUpload\AbstractTransfer;
use Aws\S3\Exception\AccessDeniedException;
use Aws\S3\Exception\Parser\S3ExceptionParser;
@ -30,10 +32,8 @@ use Aws\S3\Exception\S3Exception;
use Aws\S3\Model\ClearBucket;
use Aws\S3\Model\MultipartUpload\AbstractTransfer as AbstractMulti;
use Aws\S3\Model\MultipartUpload\UploadBuilder;
use Aws\S3\S3Signature;
use Aws\S3\Sync\DownloadSyncBuilder;
use Aws\S3\Sync\UploadSyncBuilder;
use Aws\S3\Sync\AbstractSync;
use Guzzle\Common\Collection;
use Guzzle\Http\EntityBody;
use Guzzle\Http\Message\RequestInterface;
@ -43,7 +43,6 @@ use Guzzle\Plugin\Backoff\CurlBackoffStrategy;
use Guzzle\Plugin\Backoff\ExponentialBackoffStrategy;
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
use Guzzle\Plugin\Backoff\TruncatedBackoffStrategy;
use Guzzle\Plugin\Md5\CommandContentMd5Plugin;
use Guzzle\Service\Command\CommandInterface;
use Guzzle\Service\Command\Factory\AliasFactory;
use Guzzle\Service\Command\Factory\CompositeFactory;
@ -53,6 +52,7 @@ use Guzzle\Service\Resource\ResourceIteratorInterface;
/**
* Client to interact with Amazon Simple Storage Service
*
* @method S3SignatureInterface getSignature() Returns the signature implementation used with the client
* @method Model abortMultipartUpload(array $args = array()) {@command S3 AbortMultipartUpload}
* @method Model completeMultipartUpload(array $args = array()) {@command S3 CompleteMultipartUpload}
* @method Model copyObject(array $args = array()) {@command S3 CopyObject}
@ -102,17 +102,17 @@ use Guzzle\Service\Resource\ResourceIteratorInterface;
* @method Model restoreObject(array $args = array()) {@command S3 RestoreObject}
* @method Model uploadPart(array $args = array()) {@command S3 UploadPart}
* @method Model uploadPartCopy(array $args = array()) {@command S3 UploadPartCopy}
* @method waitUntilBucketExists(array $input) Wait until a bucket exists. The input array uses the parameters of the HeadBucket operation and waiter specific settings
* @method waitUntilBucketNotExists(array $input) Wait until a bucket does not exist. The input array uses the parameters of the HeadBucket operation and waiter specific settings
* @method waitUntilObjectExists(array $input) Wait until an object exists. The input array uses the parameters of the HeadObject operation and waiter specific settings
* @method waitUntilBucketExists(array $input) The input array uses the parameters of the HeadBucket operation and waiter specific settings
* @method waitUntilBucketNotExists(array $input) The input array uses the parameters of the HeadBucket operation and waiter specific settings
* @method waitUntilObjectExists(array $input) The input array uses the parameters of the HeadObject operation and waiter specific settings
* @method ResourceIteratorInterface getListBucketsIterator(array $args = array()) The input array uses the parameters of the ListBuckets operation
* @method ResourceIteratorInterface getListMultipartUploadsIterator(array $args = array()) The input array uses the parameters of the ListMultipartUploads operation
* @method ResourceIteratorInterface getListObjectsIterator(array $args = array()) The input array uses the parameters of the ListObjects operation
* @method ResourceIteratorInterface getListObjectVersionsIterator(array $args = array()) The input array uses the parameters of the ListObjectVersions operation
* @method ResourceIteratorInterface getListObjectsIterator(array $args = array()) The input array uses the parameters of the ListObjects operation
* @method ResourceIteratorInterface getListPartsIterator(array $args = array()) The input array uses the parameters of the ListParts operation
*
* @link http://docs.aws.amazon.com/aws-sdk-php-2/guide/latest/service-s3.html User guide
* @link http://docs.aws.amazon.com/aws-sdk-php-2/latest/class-Aws.S3.S3Client.html API docs
* @link http://docs.aws.amazon.com/aws-sdk-php/guide/latest/service-s3.html User guide
* @link http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.S3.S3Client.html API docs
*/
class S3Client extends AbstractClient
{
@ -149,51 +149,15 @@ class S3Client extends AbstractClient
'DeleteObjectExpirationConfig' => 'DeleteBucketLifecycle',
);
/**
* @inheritdoc
*/
protected $directory = __DIR__;
/**
* Factory method to create a new Amazon S3 client using an array of configuration options.
*
* The following array keys and values are available options:
*
* Credential options (key, secret, and optional token OR credentials is required)
*
* - key - AWS Access Key ID
* - secret - AWS secret access key
* - credentials - You can optionally provide a custom `Aws\Common\Credentials\CredentialsInterface` object
* - token - Custom AWS security token to use with request authentication
* - token.ttd - UNIX timestamp for when the custom credentials expire
* - credentials.cache - Used to cache credentials when using providers that require HTTP requests. Set the true
* to use the default APC cache or provide a `Guzzle\Cache\CacheAdapterInterface` object.
* - credentials.cache.key - Optional custom cache key to use with the credentials
* - credentials.client - Pass this option to specify a custom `Guzzle\Http\ClientInterface` to use if your
* credentials require a HTTP request (e.g. RefreshableInstanceProfileCredentials)
*
* Region and Endpoint options (a `region` and optional `scheme` OR a `base_url` is required)
*
* - region - Region name (e.g. 'us-east-1', 'us-west-1', 'us-west-2', 'eu-west-1', etc...)
* - scheme - URI Scheme of the base URL (e.g. 'https', 'http').
* - base_url - Instead of using a `region` and `scheme`, you can specify a custom base URL for the client
*
* Generic client options
*
* - ssl.certificate_authority: Set to true to use the bundled CA cert (default), system to use the certificate
* bundled with your system, or pass the full path to an SSL certificate bundle. This option should be used when
* you encounter curl error code 60.
* - curl.options - Array of cURL options to apply to every request.
* See http://www.php.net/manual/en/function.curl-setopt.php for a list of available options
* - signature - You can optionally provide a custom signature implementation used to sign requests
* - client.backoff.logger - `Guzzle\Log\LogAdapterInterface` object used to log backoff retries. Use
* 'debug' to emit PHP warnings when a retry is issued.
* - client.backoff.logger.template - Optional template to use for exponential backoff log messages. See
* `Guzzle\Plugin\Backoff\BackoffLogger` for formatting information.
*
* @param array|Collection $config Client configuration data
*
* @return self
* @link http://docs.aws.amazon.com/aws-sdk-php/guide/latest/configuration.html#client-configuration-options
*/
public static function factory($config = array())
{
@ -201,25 +165,14 @@ class S3Client extends AbstractClient
// Configure the custom exponential backoff plugin for retrying S3 specific errors
if (!isset($config[Options::BACKOFF])) {
$config[Options::BACKOFF] = new BackoffPlugin(
new TruncatedBackoffStrategy(3,
new HttpBackoffStrategy(null,
new SocketTimeoutChecker(
new CurlBackoffStrategy(null,
new ExpiredCredentialsChecker($exceptionParser,
new ExponentialBackoffStrategy()
)
)
)
)
)
);
$config[Options::BACKOFF] = self::createBackoffPlugin($exceptionParser);
}
$config[Options::SIGNATURE] = $signature = self::createSignature($config);
$client = ClientBuilder::factory(__NAMESPACE__)
->setConfig($config)
->setConfigDefaults(array(
Options::SIGNATURE => new S3Signature(),
Options::VERSION => self::LATEST_API_VERSION,
Options::SERVICE_DESCRIPTION => __DIR__ . '/Resources/s3-%s.php'
))
@ -255,16 +208,17 @@ class S3Client extends AbstractClient
// Use virtual hosted buckets when possible
$client->addSubscriber(new BucketStyleListener());
// Ensure that ACP headers are applied when needed
$client->addSubscriber(new AcpListener());
// Validate and add Content-MD5 hashes
$client->addSubscriber(new CommandContentMd5Plugin());
// Validate and add required Content-MD5 hashes (e.g. DeleteObjects)
$client->addSubscriber(new S3Md5Listener($signature));
// Allow for specifying bodies with file paths and file handles
$client->addSubscriber(new UploadBodyListener(array('PutObject', 'UploadPart')));
// Ensures that if a SSE-CPK key is provided, the key and md5 are formatted correctly
$client->addSubscriber(new SseCpkListener);
// Add aliases for some S3 operations
$default = CompositeFactory::getDefaultChain($client);
$default->add(
@ -277,7 +231,66 @@ class S3Client extends AbstractClient
}
/**
* Find out if a string is a valid name for an Amazon S3 bucket.
* Create an Amazon S3 specific backoff plugin
*
* @param S3ExceptionParser $exceptionParser
*
* @return BackoffPlugin
*/
private static function createBackoffPlugin(S3ExceptionParser $exceptionParser)
{
return new BackoffPlugin(
new TruncatedBackoffStrategy(3,
new CurlBackoffStrategy(null,
new HttpBackoffStrategy(null,
new SocketTimeoutChecker(
new ExpiredCredentialsChecker($exceptionParser,
new ExponentialBackoffStrategy()
)
)
)
)
)
);
}
/**
* Create an appropriate signature based on the configuration settings
*
* @param $config
*
* @return \Aws\Common\Signature\SignatureInterface
* @throws InvalidArgumentException
*/
private static function createSignature($config)
{
$currentValue = isset($config[Options::SIGNATURE]) ? $config[Options::SIGNATURE] : null;
// Use the Amazon S3 signature V4 when the value is set to "v4" or when
// the value is not set and the region starts with "cn-".
if ($currentValue == 'v4' ||
(!$currentValue && isset($config['region']) && substr($config['region'], 0, 3) == 'cn-')
) {
// Force SignatureV4 for specific regions or if specified in the config
$currentValue = new S3SignatureV4('s3');
} elseif (!$currentValue || $currentValue == 's3') {
// Use the Amazon S3 signature by default
$currentValue = new S3Signature();
}
// A region is require with v4
if ($currentValue instanceof SignatureV4 && !isset($config['region'])) {
throw new InvalidArgumentException('A region must be specified '
. 'when using signature version 4');
}
return $currentValue;
}
/**
* Determine if a string is a valid name for a DNS compatible Amazon S3
* bucket, meaning the bucket can be used as a subdomain in a URL (e.g.,
* "<bucket>.s3.amazonaws.com").
*
* @param string $bucket The name of the bucket to check.
*
@ -286,14 +299,12 @@ class S3Client extends AbstractClient
public static function isValidBucketName($bucket)
{
$bucketLen = strlen($bucket);
if (!$bucket || $bucketLen < 3 || $bucketLen > 63
// Cannot start or end with a '.'
|| $bucket[0] == '.'
|| $bucket[$bucketLen - 1] == '.'
if ($bucketLen < 3 || $bucketLen > 63 ||
// Cannot look like an IP address
|| preg_match('/^\d+\.\d+\.\d+\.\d+$/', $bucket)
preg_match('/(\d+\.){3}\d+$/', $bucket) ||
// Cannot include special characters, must start and end with lower alnum
|| !preg_match('/^[a-z0-9][a-z0-9\-.]*[a-z0-9]?$/', $bucket)) {
!preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/', $bucket)
) {
return false;
}
@ -319,36 +330,7 @@ class S3Client extends AbstractClient
. 'request object');
}
if ($expires instanceof \DateTime) {
$expires = $expires->getTimestamp();
} elseif (!is_numeric($expires)) {
$expires = strtotime($expires);
}
// Operate on a clone of the request, so the original is not altered
$request = clone $request;
// URL encoding already occurs in the URI template expansion. Undo that and encode using the same encoding as
// GET object, PUT object, etc.
$path = $this->encodeKey(rawurldecode($request->getPath()));
$request->setPath($path);
// Make sure to handle temporary credentials
if ($token = $this->credentials->getSecurityToken()) {
$request->setHeader('x-amz-security-token', $token);
$request->getQuery()->set('x-amz-security-token', $token);
}
// Set query params required for pre-signed URLs
$request->getQuery()
->set('AWSAccessKeyId', $this->credentials->getAccessKeyId())
->set('Expires', $expires)
->set('Signature', $this->signature->signString(
$this->signature->createCanonicalizedString($request, $expires),
$this->credentials
));
return $request->getUrl();
return $this->signature->createPresignedUrl($request, $this->credentials, $expires);
}
/**
@ -565,7 +547,13 @@ class S3Client extends AbstractClient
*/
public function uploadDirectory($directory, $bucket, $keyPrefix = null, array $options = array())
{
$options = Collection::fromConfig($options, array('base_dir' => $directory));
$options = Collection::fromConfig(
$options,
array(
'base_dir' => realpath($directory) ?: $directory
)
);
$builder = $options['builder'] ?: UploadSyncBuilder::getInstance();
$builder->uploadFromDirectory($directory)
->setClient($this)

View file

@ -0,0 +1,73 @@
<?php
/**
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
namespace Aws\S3;
use Aws\Common\Signature\SignatureV4;
use Aws\Common\Signature\SignatureInterface;
use Guzzle\Common\Event;
use Guzzle\Service\Command\CommandInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Adds required and optional Content-MD5 headers
*/
class S3Md5Listener implements EventSubscriberInterface
{
/** @var S3SignatureInterface */
private $signature;
public static function getSubscribedEvents()
{
return array('command.after_prepare' => 'onCommandAfterPrepare');
}
public function __construct(SignatureInterface $signature)
{
$this->signature = $signature;
}
public function onCommandAfterPrepare(Event $event)
{
$command = $event['command'];
$operation = $command->getOperation();
if ($operation->getData('contentMd5')) {
// Add the MD5 if it is required for all signers
$this->addMd5($command);
} elseif ($operation->hasParam('ContentMD5')) {
$value = $command['ContentMD5'];
// Add a computed MD5 if the parameter is set to true or if
// not using Signature V4 and the value is not set (null).
if ($value === true ||
($value === null && !($this->signature instanceof SignatureV4))
) {
$this->addMd5($command);
}
}
}
private function addMd5(CommandInterface $command)
{
$request = $command->getRequest();
$body = $request->getBody();
if ($body && $body->getSize() > 0) {
if (false !== ($md5 = $body->getContentMd5(true, true))) {
$request->setHeader('Content-MD5', $md5);
}
}
}
}

View file

@ -17,39 +17,55 @@
namespace Aws\S3;
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Enum\DateFormat;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\QueryString;
use Guzzle\Http\Url;
/**
* Default Amazon S3 signature implementation
* @link http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
* @link http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
*/
class S3Signature implements S3SignatureInterface
{
/**
* @var array Query string values that must be signed
*/
protected $signableQueryString = array(
'acl', 'delete', 'lifecycle', 'location', 'logging', 'notification',
'partNumber', 'policy', 'requestPayment', 'torrent', 'uploadId',
'uploads', 'versionId', 'versioning', 'versions', 'website',
'response-cache-control', 'response-content-disposition',
'response-content-encoding', 'response-content-language',
'response-content-type', 'response-expires', 'restore', 'tagging', 'cors'
protected $signableQueryString = array (
'acl',
'cors',
'delete',
'lifecycle',
'location',
'logging',
'notification',
'partNumber',
'policy',
'requestPayment',
'response-cache-control',
'response-content-disposition',
'response-content-encoding',
'response-content-language',
'response-content-type',
'response-expires',
'restore',
'tagging',
'torrent',
'uploadId',
'uploads',
'versionId',
'versioning',
'versions',
'website',
);
/**
* @var array Sorted headers that must be signed
*/
protected $signableHeaders = array('Content-MD5', 'Content-Type');
/** @var array Sorted headers that must be signed */
private $signableHeaders = array('Content-MD5', 'Content-Type');
/**
* {@inheritdoc}
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
// Ensure that the signable query string parameters are sorted
sort($this->signableQueryString);
// Add the security token header if one is being used by the credentials
if ($token = $credentials->getSecurityToken()) {
$request->setHeader('x-amz-security-token', $token);
@ -57,7 +73,7 @@ class S3Signature implements S3SignatureInterface
// Add a date header if one is not set
if (!$request->hasHeader('date') && !$request->hasHeader('x-amz-date')) {
$request->setHeader('Date', gmdate(DateFormat::RFC2822));
$request->setHeader('Date', gmdate(\DateTime::RFC2822));
}
$stringToSign = $this->createCanonicalizedString($request);
@ -69,17 +85,57 @@ class S3Signature implements S3SignatureInterface
);
}
/**
* {@inheritdoc}
*/
public function createPresignedUrl(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
) {
if ($expires instanceof \DateTime) {
$expires = $expires->getTimestamp();
} elseif (!is_numeric($expires)) {
$expires = strtotime($expires);
}
// Operate on a clone of the request, so the original is not altered
$request = clone $request;
// URL encoding already occurs in the URI template expansion. Undo that and encode using the same encoding as
// GET object, PUT object, etc.
$path = S3Client::encodeKey(rawurldecode($request->getPath()));
$request->setPath($path);
// Make sure to handle temporary credentials
if ($token = $credentials->getSecurityToken()) {
$request->setHeader('x-amz-security-token', $token);
$request->getQuery()->set('x-amz-security-token', $token);
}
// Set query params required for pre-signed URLs
$request->getQuery()
->set('AWSAccessKeyId', $credentials->getAccessKeyId())
->set('Expires', $expires)
->set('Signature', $this->signString(
$this->createCanonicalizedString($request, $expires),
$credentials
));
// Move X-Amz-* headers to the query string
foreach ($request->getHeaders() as $name => $header) {
$name = strtolower($name);
if (strpos($name, 'x-amz-') === 0) {
$request->getQuery()->set($name, (string) $header);
$request->removeHeader($name);
}
}
return $request->getUrl();
}
public function signString($string, CredentialsInterface $credentials)
{
return base64_encode(hash_hmac('sha1', $string, $credentials->getSecretKey(), true));
}
/**
* {@inheritdoc}
*/
public function createCanonicalizedString(RequestInterface $request, $expires = null)
{
$buffer = $request->getMethod() . "\n";
@ -106,12 +162,11 @@ class S3Signature implements S3SignatureInterface
*
* @return string Returns canonicalized AMZ headers.
*/
protected function createCanonicalizedAmzHeaders(RequestInterface $request)
private function createCanonicalizedAmzHeaders(RequestInterface $request)
{
$headers = array();
foreach ($request->getHeaders(true) as $header) {
/** @var $header \Guzzle\Http\Message\Header */
$name = strtolower($header->getName());
foreach ($request->getHeaders() as $name => $header) {
$name = strtolower($name);
if (strpos($name, 'x-amz-') === 0) {
$value = trim((string) $header);
if ($value || $value === '0') {
@ -120,13 +175,13 @@ class S3Signature implements S3SignatureInterface
}
}
if (empty($headers)) {
if (!$headers) {
return '';
} else {
ksort($headers);
return implode("\n", $headers) . "\n";
}
ksort($headers);
return implode("\n", $headers) . "\n";
}
/**
@ -136,7 +191,7 @@ class S3Signature implements S3SignatureInterface
*
* @return string
*/
protected function createCanonicalizedResource(RequestInterface $request)
private function createCanonicalizedResource(RequestInterface $request)
{
$buffer = $request->getParams()->get('s3.resource');
// When sending a raw HTTP request (e.g. $client->get())
@ -157,11 +212,17 @@ class S3Signature implements S3SignatureInterface
$query = $request->getQuery();
$first = true;
foreach ($this->signableQueryString as $key) {
if ($value = $query->get($key)) {
if ($query->hasKey($key)) {
$value = $query[$key];
$buffer .= $first ? '?' : '&';
$first = false;
$buffer .= $key;
if ($value !== QueryString::BLANK) {
// Don't add values for empty sub-resources
if ($value !== '' &&
$value !== false &&
$value !== null &&
$value !== QueryString::BLANK
) {
$buffer .= "={$value}";
}
}
@ -177,7 +238,7 @@ class S3Signature implements S3SignatureInterface
*
* @return string
*/
protected function parseBucketName(RequestInterface $request)
private function parseBucketName(RequestInterface $request)
{
$baseUrl = Url::factory($request->getClient()->getBaseUrl());
$baseHost = $baseUrl->getHost();

View file

@ -17,32 +17,8 @@
namespace Aws\S3;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Credentials\CredentialsInterface;
use Guzzle\Http\Message\RequestInterface;
/**
* Amazon S3 signature interface
* @link http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
* @deprecated
*/
interface S3SignatureInterface extends SignatureInterface
{
/**
* Sign a string for Amazon S3
*
* @param string $string String to sign
* @param CredentialsInterface $credentials Credentials used to sign
*
* @return string
*/
public function signString($string, CredentialsInterface $credentials);
/**
* Create a canonicalized string for a signature.
*
* @param RequestInterface $request Base on the request
* @param string $expires Pass a UNIX timestamp if creating a query signature
*
* @return string Returns a canonicalized string for an Amazon S3 signature.
*/
public function createCanonicalizedString(RequestInterface $request, $expires = null);
}
interface S3SignatureInterface extends SignatureInterface {}

View file

@ -0,0 +1,64 @@
<?php
/**
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
namespace Aws\S3;
use Aws\Common\Signature\SignatureV4;
use Aws\Common\Credentials\CredentialsInterface;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
use Guzzle\Http\Message\RequestInterface;
/**
* Amazon S3 signature version 4 overrides.
*/
class S3SignatureV4 extends SignatureV4 implements S3SignatureInterface
{
/**
* Always add a x-amz-content-sha-256 for data integrity.
*/
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
if (!$request->hasHeader('x-amz-content-sha256')) {
$request->setHeader('x-amz-content-sha256', $this->getPresignedPayload($request));
}
parent::signRequest($request, $credentials);
}
/**
* Override used to allow pre-signed URLs to be created for an
* in-determinate request payload.
*/
protected function getPresignedPayload(RequestInterface $request)
{
$result = parent::getPresignedPayload($request);
// If the body is empty, then sign with 'UNSIGNED-PAYLOAD'
if ($result === self::DEFAULT_PAYLOAD) {
$result = hash('sha256', 'UNSIGNED-PAYLOAD');
}
return $result;
}
/**
* Amazon S3 does not double-encode the path component in the canonical req
*/
protected function createCanonicalizedPath(RequestInterface $request)
{
return '/' . ltrim($request->getPath(), '/');
}
}

View file

@ -65,18 +65,6 @@ class SocketTimeoutChecker extends AbstractBackoffStrategy
&& $response->getStatusCode() == 400
&& strpos($response->getBody(), self::ERR)
) {
// Check if the request is sending a local file, and if so, clear the stat cache and recalculate the size.
if ($request instanceof EntityEnclosingRequestInterface) {
if ($request->getBody()->getWrapper() == 'plainfile') {
$filename = $request->getBody()->getUri();
// Clear the cache so that we send accurate file sizes
clearstatcache(true, $filename);
$length = filesize($filename);
$request->getBody()->setSize($length);
$request->setHeader('Content-Length', $length);
}
}
return true;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Aws\S3;
use Aws\Common\Exception\RuntimeException;
use Guzzle\Common\Event;
use Guzzle\Service\Command\CommandInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* This listener simplifies the SSE-C process by encoding and hashing the key.
*/
class SseCpkListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array('command.before_prepare' => 'onCommandBeforePrepare');
}
public function onCommandBeforePrepare(Event $event)
{
/** @var CommandInterface $command */
$command = $event['command'];
// Allows only HTTPS connections when using SSE-C
if ($command['SSECustomerKey'] ||
$command['CopySourceSSECustomerKey']
) {
$this->validateScheme($command);
}
// Prepare the normal SSE-CPK headers
if ($command['SSECustomerKey']) {
$this->prepareSseParams($command);
}
// If it's a copy operation, prepare the SSE-CPK headers for the source.
if ($command['CopySourceSSECustomerKey']) {
$this->prepareSseParams($command, true);
}
}
private function validateScheme(CommandInterface $command)
{
if ($command->getClient()->getConfig('scheme') !== 'https') {
throw new RuntimeException('You must configure your S3 client to '
. 'use HTTPS in order to use the SSE-C features.');
}
}
private function prepareSseParams(
CommandInterface $command,
$isCopy = false
) {
$prefix = $isCopy ? 'CopySource' : '';
// Base64 encode the provided key
$key = $command[$prefix . 'SSECustomerKey'];
$command[$prefix . 'SSECustomerKey'] = base64_encode($key);
// Base64 the provided MD5 or, generate an MD5 if not provided
if ($md5 = $command[$prefix . 'SSECustomerKeyMD5']) {
$command[$prefix . 'SSECustomerKeyMD5'] = base64_encode($md5);
} else {
$command[$prefix . 'SSECustomerKeyMD5'] = base64_encode(md5($key, true));
}
}
}

View file

@ -20,9 +20,10 @@ use Aws\Common\Exception\RuntimeException;
use Aws\S3\Exception\S3Exception;
use Aws\S3\Exception\NoSuchKeyException;
use Aws\S3\Iterator\ListObjectsIterator;
use Guzzle\Http\QueryString;
use Guzzle\Http\EntityBody;
use Guzzle\Http\CachingEntityBody;
use Guzzle\Http\Mimetypes;
use Guzzle\Iterator\FilterIterator;
use Guzzle\Stream\PhpStreamRequestFactory;
use Guzzle\Service\Command\CommandInterface;
@ -71,7 +72,6 @@ use Guzzle\Service\Command\CommandInterface;
* Stream context options:
*
* - "seekable": Set to true to create a seekable "r" (read only) stream by using a php://temp stream buffer
* - "throw_exceptions": Set to true to throw exceptions instead of trigger_errors
* - For "unlink" only: Any option that can be passed to the DeleteObject operation
*/
class StreamWrapper
@ -132,7 +132,7 @@ class StreamWrapper
stream_wrapper_unregister('s3');
}
stream_wrapper_register('s3', __CLASS__, STREAM_IS_URL);
stream_wrapper_register('s3', get_called_class(), STREAM_IS_URL);
self::$client = $client;
}
@ -172,8 +172,8 @@ class StreamWrapper
}
// When using mode "x" validate if the file exists before attempting to read
if ($mode == 'x' && !self::$client->doesObjectExist($params['Bucket'], $params['Key'], $this->getOptions())) {
$errors[] = "{$path} does not exist on Amazon S3";
if ($mode == 'x' && self::$client->doesObjectExist($params['Bucket'], $params['Key'], $this->getOptions())) {
$errors[] = "{$path} already exists on Amazon S3";
}
if (!$errors) {
@ -210,6 +210,14 @@ class StreamWrapper
$params = $this->params;
$params['Body'] = $this->body;
// Attempt to guess the ContentType of the upload based on the
// file extension of the key
if (!isset($params['ContentType']) &&
($type = Mimetypes::getInstance()->fromFilename($params['Key']))
) {
$params['ContentType'] = $type;
}
try {
self::$client->putObject($params);
return true;
@ -314,25 +322,30 @@ class StreamWrapper
$parts = $this->getParams($path);
// Stat a bucket or just s3://
if (!$parts['Key'] && (!$parts['Bucket'] || self::$client->doesBucketExist($parts['Bucket']))) {
return $this->formatUrlStat($path);
}
// You must pass either a bucket or a bucket + key
if (!$parts['Key']) {
return $this->triggerError("File or directory not found: {$path}", $flags);
// Stat "directories": buckets, or "s3://"
if (!$parts['Bucket'] || self::$client->doesBucketExist($parts['Bucket'])) {
return $this->formatUrlStat($path);
} else {
return $this->triggerError("File or directory not found: {$path}", $flags);
}
}
try {
try {
// Attempt to stat and cache regular object
return $this->formatUrlStat(self::$client->headObject($parts)->toArray());
$result = self::$client->headObject($parts)->toArray();
if (substr($parts['Key'], -1, 1) == '/' && $result['ContentLength'] == 0) {
// Return as if it is a bucket to account for console bucket objects (e.g., zero-byte object "foo/")
return $this->formatUrlStat($path);
} else {
// Attempt to stat and cache regular object
return $this->formatUrlStat($result);
}
} catch (NoSuchKeyException $e) {
// Maybe this isn't an actual key, but a prefix. Do a prefix listing of objects to determine.
$result = self::$client->listObjects(array(
'Bucket' => $parts['Bucket'],
'Prefix' => $parts['Key'],
'Prefix' => rtrim($parts['Key'], '/') . '/',
'MaxKeys' => 1
));
if (!$result['Contents'] && !$result['CommonPrefixes']) {
@ -352,7 +365,7 @@ class StreamWrapper
* @param string $path Directory which should be created.
* @param int $mode Permissions. 700-range permissions map to ACL_PUBLIC. 600-range permissions map to
* ACL_AUTH_READ. All other permissions map to ACL_PRIVATE. Expects octal form.
* @param int $options A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE. (unused)
* @param int $options A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE.
*
* @return bool
* @link http://www.php.net/manual/en/streamwrapper.mkdir.php
@ -360,28 +373,17 @@ class StreamWrapper
public function mkdir($path, $mode, $options)
{
$params = $this->getParams($path);
$this->clearStatInfo($path);
if (!$params['Bucket'] || $params['Key']) {
if (!$params['Bucket']) {
return false;
}
try {
if (!isset($params['ACL'])) {
$mode = decoct($mode);
if ($mode >= 700 and $mode <= 799) {
$params['ACL'] = 'public-read';
} elseif ($mode >= 600 && $mode <= 699) {
$params['ACL'] = 'authenticated-read';
} else {
$params['ACL'] = 'private';
}
}
self::$client->createBucket($params);
return true;
} catch (\Exception $e) {
return $this->triggerError($e->getMessage());
if (!isset($params['ACL'])) {
$params['ACL'] = $this->determineAcl($mode);
}
return !isset($params['Key']) || $params['Key'] === '/'
? $this->createBucket($path, $params)
: $this->createPseudoDirectory($path, $params);
}
/**
@ -397,14 +399,40 @@ class StreamWrapper
$params = $this->getParams($path);
if (!$params['Bucket']) {
return $this->triggerError('You cannot delete s3://. Please specify a bucket.');
} elseif ($params['Key']) {
return $this->triggerError('rmdir() only supports bucket deletion');
}
try {
self::$client->deleteBucket(array('Bucket' => $params['Bucket']));
$this->clearStatInfo($path);
return true;
if (!$params['Key']) {
self::$client->deleteBucket(array('Bucket' => $params['Bucket']));
$this->clearStatInfo($path);
return true;
}
// Use a key that adds a trailing slash if needed.
$prefix = rtrim($params['Key'], '/') . '/';
$result = self::$client->listObjects(array(
'Bucket' => $params['Bucket'],
'Prefix' => $prefix,
'MaxKeys' => 1
));
// Check if the bucket contains keys other than the placeholder
if ($result['Contents']) {
foreach ($result['Contents'] as $key) {
if ($key['Key'] == $prefix) {
continue;
}
return $this->triggerError('Psuedo folder is not empty');
}
return $this->unlink(rtrim($path, '/') . '/');
}
return $result['CommonPrefixes']
? $this->triggerError('Pseudo folder contains nested folders')
: true;
} catch (\Exception $e) {
return $this->triggerError($e->getMessage());
}
@ -413,6 +441,11 @@ class StreamWrapper
/**
* Support for opendir().
*
* The opendir() method of the Amazon S3 stream wrapper supports a stream
* context option of "listFilter". listFilter must be a callable that
* accepts an associative array of object data and returns true if the
* object should be yielded when iterating the keys in a bucket.
*
* @param string $path The path to the directory (e.g. "s3://dir[</prefix>]")
* @param string $options Whether or not to enforce safe_mode (0x04). Unused.
*
@ -425,14 +458,14 @@ class StreamWrapper
$this->clearStatInfo();
$params = $this->getParams($path);
$delimiter = $this->getOption('delimiter');
$filterFn = $this->getOption('listFilter');
if ($delimiter === null) {
$delimiter = '/';
}
if ($params['Key']) {
$suffix = $delimiter ?: '/';
$params['Key'] = rtrim($params['Key'], $suffix) . $suffix;
$params['Key'] = rtrim($params['Key'], $delimiter) . $delimiter;
}
$this->openedBucket = $params['Bucket'];
@ -443,11 +476,22 @@ class StreamWrapper
$operationParams['Delimiter'] = $delimiter;
}
$this->objectIterator = self::$client->getIterator('ListObjects', $operationParams, array(
$objectIterator = self::$client->getIterator('ListObjects', $operationParams, array(
'return_prefixes' => true,
'sort_results' => true
));
// Filter our "/" keys added by the console as directories, and ensure
// that if a filter function is provided that it passes the filter.
$this->objectIterator = new FilterIterator(
$objectIterator,
function ($key) use ($filterFn) {
// Each yielded results can contain a "Key" or "Prefix"
return (!$filterFn || call_user_func($filterFn, $key)) &&
(!isset($key['Key']) || substr($key['Key'], -1, 1) !== '/');
}
);
$this->objectIterator->next();
return true;
@ -487,26 +531,32 @@ class StreamWrapper
*/
public function dir_readdir()
{
$result = false;
if ($this->objectIterator->valid()) {
$current = $this->objectIterator->current();
if (isset($current['Prefix'])) {
// Include "directories"
$result = str_replace($this->openedBucketPrefix, '', $current['Prefix']);
$key = "s3://{$this->openedBucket}/{$current['Prefix']}";
$stat = $this->formatUrlStat($current['Prefix']);
} else {
// Remove the prefix from the result to emulate other stream wrappers
$result = str_replace($this->openedBucketPrefix, '', $current['Key']);
$key = "s3://{$this->openedBucket}/{$current['Key']}";
$stat = $this->formatUrlStat($current);
}
// Cache the object data for quick url_stat lookups used with RecursiveDirectoryIterator
self::$nextStat = array($key => $stat);
$this->objectIterator->next();
// Skip empty result keys
if (!$this->objectIterator->valid()) {
return false;
}
$current = $this->objectIterator->current();
if (isset($current['Prefix'])) {
// Include "directories". Be sure to strip a trailing "/"
// on prefixes.
$prefix = rtrim($current['Prefix'], '/');
$result = str_replace($this->openedBucketPrefix, '', $prefix);
$key = "s3://{$this->openedBucket}/{$prefix}";
$stat = $this->formatUrlStat($prefix);
} else {
// Remove the prefix from the result to emulate other
// stream wrappers.
$result = str_replace($this->openedBucketPrefix, '', $current['Key']);
$key = "s3://{$this->openedBucket}/{$current['Key']}";
$stat = $this->formatUrlStat($current);
}
// Cache the object data for quick url_stat lookups used with
// RecursiveDirectoryIterator.
self::$nextStat = array($key => $stat);
$this->objectIterator->next();
return $result;
}
@ -602,7 +652,6 @@ class StreamWrapper
$params = $this->getOptions();
unset($params['seekable']);
unset($params['throw_exceptions']);
return array(
'Bucket' => $parts[0],
@ -695,14 +744,19 @@ class StreamWrapper
*/
protected function triggerError($errors, $flags = null)
{
if ($flags != STREAM_URL_STAT_QUIET) {
if ($this->getOption('throw_exceptions')) {
throw new RuntimeException(implode("\n", (array) $errors));
} else {
trigger_error(implode("\n", (array) $errors), E_USER_WARNING);
}
if ($flags & STREAM_URL_STAT_QUIET) {
// This is triggered with things like file_exists()
if ($flags & STREAM_URL_STAT_LINK) {
// This is triggered for things like is_link()
return $this->formatUrlStat(false);
}
return false;
}
// This is triggered when doing things like lstat() or stat()
trigger_error(implode("\n", (array) $errors), E_USER_WARNING);
return false;
}
@ -732,19 +786,18 @@ class StreamWrapper
);
$stat = $statTemplate;
$type = gettype($result);
// Determine what type of data is being cached
if (!$result || is_string($result)) {
if ($type == 'NULL' || $type == 'string') {
// Directory with 0777 access - see "man 2 stat".
$stat['mode'] = $stat[2] = 0040777;
} elseif (is_array($result) && isset($result['LastModified'])) {
} elseif ($type == 'array' && isset($result['LastModified'])) {
// ListObjects or HeadObject result
$stat['mtime'] = $stat[9] = $stat['ctime'] = $stat[10] = strtotime($result['LastModified']);
$stat['size'] = $stat[7] = (isset($result['ContentLength']) ? $result['ContentLength'] : $result['Size']);
// Regular file with 0777 access - see "man 2 stat".
$stat['mode'] = $stat[2] = 0100777;
} else {
$stat['mode'] = $stat[2] = 0100777;
}
return $stat;
@ -762,4 +815,77 @@ class StreamWrapper
clearstatcache(true, $path);
}
}
/**
* Creates a bucket for the given parameters.
*
* @param string $path Stream wrapper path
* @param array $params A result of StreamWrapper::getParams()
*
* @return bool Returns true on success or false on failure
*/
private function createBucket($path, array $params)
{
if (self::$client->doesBucketExist($params['Bucket'])) {
return $this->triggerError("Directory already exists: {$path}");
}
try {
self::$client->createBucket($params);
$this->clearStatInfo($path);
return true;
} catch (\Exception $e) {
return $this->triggerError($e->getMessage());
}
}
/**
* Creates a pseudo-folder by creating an empty "/" suffixed key
*
* @param string $path Stream wrapper path
* @param array $params A result of StreamWrapper::getParams()
*
* @return bool
*/
private function createPseudoDirectory($path, array $params)
{
// Ensure the path ends in "/" and the body is empty.
$params['Key'] = rtrim($params['Key'], '/') . '/';
$params['Body'] = '';
// Fail if this pseudo directory key already exists
if (self::$client->doesObjectExist($params['Bucket'], $params['Key'])) {
return $this->triggerError("Directory already exists: {$path}");
}
try {
self::$client->putObject($params);
$this->clearStatInfo($path);
return true;
} catch (\Exception $e) {
return $this->triggerError($e->getMessage());
}
}
/**
* Determine the most appropriate ACL based on a file mode.
*
* @param int $mode File mode
*
* @return string
*/
private function determineAcl($mode)
{
$mode = decoct($mode);
if ($mode >= 700 && $mode <= 799) {
return 'public-read';
}
if ($mode >= 600 && $mode <= 699) {
return 'authenticated-read';
}
return 'private';
}
}

View file

@ -17,10 +17,8 @@
namespace Aws\S3\Sync;
use Aws\S3\S3Client;
use Aws\S3\Model\MultipartUpload\AbstractTransfer;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Common\Collection;
use Guzzle\Http\EntityBody;
use Guzzle\Iterator\ChunkedIterator;
use Guzzle\Service\Command\CommandInterface;

View file

@ -18,7 +18,6 @@ namespace Aws\S3\Sync;
use Aws\Common\Exception\RuntimeException;
use Aws\Common\Exception\UnexpectedValueException;
use Aws\Common\Model\MultipartUpload\AbstractTransfer;
use Aws\Common\Model\MultipartUpload\TransferInterface;
use Aws\S3\S3Client;
use Aws\S3\Iterator\OpendirIterator;
@ -169,7 +168,8 @@ abstract class AbstractSyncBuilder
*/
public function setKeyPrefix($keyPrefix)
{
$this->keyPrefix = $keyPrefix;
// Removing leading slash
$this->keyPrefix = ltrim($keyPrefix, '/');
return $this;
}
@ -275,6 +275,7 @@ abstract class AbstractSyncBuilder
// Only wrap the source iterator in a changed files iterator if we are not forcing the transfers
if (!$this->forcing) {
$this->sourceIterator->rewind();
$this->sourceIterator = new ChangedFilesIterator(
new \NoRewindIterator($this->sourceIterator),
$this->getTargetIterator(),
@ -391,11 +392,6 @@ abstract class AbstractSyncBuilder
function (Event $e) use ($params) {
if ($e['command'] instanceof CommandInterface) {
$e['command']->overwriteWith($params);
} elseif ($e['command'] instanceof TransferInterface) {
// Multipart upload transfer object
foreach ($params as $k => $v) {
$e['command']->setOption($k, $v);
}
}
}
);
@ -410,12 +406,30 @@ abstract class AbstractSyncBuilder
{
// Ensure that the stream wrapper is registered
$this->client->registerStreamWrapper();
// Calculate the opendir() bucket and optional key prefix location
// Remove the delimiter as it is not needed for this
$dir = rtrim('s3://' . $this->bucket . ($this->keyPrefix ? ('/' . $this->keyPrefix) : ''), '/');
// Use opendir so that we can pass stream context to the iterator
$dh = opendir($dir, stream_context_create(array('s3' => array('delimiter' => ''))));
return $this->filterIterator(new \NoRewindIterator(new OpendirIterator($dh, $dir . '/')));
// Calculate the opendir() bucket and optional key prefix location
$dir = "s3://{$this->bucket}";
if ($this->keyPrefix) {
$dir .= '/' . ltrim($this->keyPrefix, '/ ');
}
// Use opendir so that we can pass stream context to the iterator
$dh = opendir($dir, stream_context_create(array(
's3' => array(
'delimiter' => '',
'listFilter' => function ($obj) {
// Ensure that we do not try to download a glacier object.
return !isset($obj['StorageClass']) ||
$obj['StorageClass'] != 'GLACIER';
}
)
)));
// Add the trailing slash for the OpendirIterator concatenation
if (!$this->keyPrefix) {
$dir .= '/';
}
return $this->filterIterator(new \NoRewindIterator(new OpendirIterator($dh, $dir)));
}
}

View file

@ -16,8 +16,6 @@
namespace Aws\S3\Sync;
use Aws\S3\S3Client;
/**
* Iterator used to filter an internal iterator to only yield files that do not exist in the target iterator or files
* that have changed
@ -60,7 +58,7 @@ class ChangedFilesIterator extends \FilterIterator
public function accept()
{
$current = $this->current();
$key = $this->sourceConverter->convert((string) $current);
$key = $this->sourceConverter->convert($this->normalize($current));
if (!($data = $this->getTargetData($key))) {
return true;
}
@ -88,6 +86,8 @@ class ChangedFilesIterator extends \FilterIterator
*/
protected function getTargetData($key)
{
$key = $this->cleanKey($key);
if (isset($this->cache[$key])) {
$result = $this->cache[$key];
unset($this->cache[$key]);
@ -99,14 +99,27 @@ class ChangedFilesIterator extends \FilterIterator
while ($it->valid()) {
$value = $it->current();
$data = array($value->getSize(), $value->getMTime());
$filename = $this->targetConverter->convert((string) $value);
$filename = $this->targetConverter->convert($this->normalize($value));
$filename = $this->cleanKey($filename);
if ($filename == $key) {
return $data;
}
$this->cache[$filename] = $data;
$it->next();
}
return false;
}
private function normalize($current)
{
return $current->getRealPath() ?: (string) $current;
}
private function cleanKey($key)
{
return ltrim($key, '/');
}
}

View file

@ -18,9 +18,6 @@ namespace Aws\S3\Sync;
use Aws\Common\Exception\RuntimeException;
use Aws\S3\ResumableDownload;
use Aws\S3\S3Client;
use Aws\S3\Model\MultipartUpload\AbstractTransfer;
use Guzzle\Http\EntityBody;
/**
* Downloads and Amazon S3 bucket to a local directory
@ -31,8 +28,7 @@ class DownloadSync extends AbstractSync
{
$sourceFilename = $file->getPathname();
list($bucket, $key) = explode('/', substr($sourceFilename, 5), 2);
$filename = '/' . ltrim($this->options['source_converter']->convert($sourceFilename), '/');
$filename = $this->options['source_converter']->convert($sourceFilename);
$this->createDirectory($filename);
// Some S3 buckets contains nested files under the same name as a directory

View file

@ -17,9 +17,7 @@
namespace Aws\S3\Sync;
use Aws\Common\Exception\RuntimeException;
use Aws\Common\Model\MultipartUpload\AbstractTransfer;
use Aws\S3\ResumableDownload;
use Aws\S3\S3Client;
use Guzzle\Common\Event;
use Guzzle\Http\EntityBodyInterface;
use Guzzle\Service\Command\CommandInterface;

View file

@ -37,20 +37,31 @@ class KeyConverter implements FilenameConverterInterface
*/
public function __construct($baseDir = '', $prefix = '', $delimiter = '/')
{
$this->baseDir = $baseDir;
$this->baseDir = (string) $baseDir;
$this->prefix = $prefix;
$this->delimiter = $delimiter;
}
public function convert($filename)
{
// Remove base directory from the key
$key = str_replace($this->baseDir, '', $filename);
$key = $filename;
// Remove base directory from the key (only the first occurrence)
if ($this->baseDir && (false !== $pos = strpos($filename, $this->baseDir))) {
$key = substr_replace($key, '', $pos, strlen($this->baseDir));
}
// Replace Windows directory separators to become Unix style, and convert that to the custom dir separator
$key = str_replace('/', $this->delimiter, str_replace('\\', '/', $key));
// Add the key prefix and remove double slashes
$key = str_replace($this->delimiter . $this->delimiter, $this->delimiter, $this->prefix . $key);
return ltrim($key, $this->delimiter);
// Add the key prefix and remove double slashes that are not in the protocol (e.g. prefixed with ":")
$delim = preg_quote($this->delimiter);
$key = preg_replace(
"#(?<!:){$delim}{$delim}#",
$this->delimiter,
$this->prefix . $key
);
return $key;
}
}

View file

@ -17,7 +17,6 @@
namespace Aws\S3\Sync;
use Aws\Common\Exception\RuntimeException;
use Aws\S3\S3Client;
use Aws\S3\Model\MultipartUpload\UploadBuilder;
use Aws\S3\Model\MultipartUpload\AbstractTransfer;
use Guzzle\Http\EntityBody;
@ -27,6 +26,8 @@ use Guzzle\Http\EntityBody;
*/
class UploadSync extends AbstractSync
{
const BEFORE_MULTIPART_BUILD = 's3.sync.before_multipart_build';
protected function init()
{
if (null == $this->options['multipart_upload_size']) {
@ -37,10 +38,11 @@ class UploadSync extends AbstractSync
protected function createTransferAction(\SplFileInfo $file)
{
// Open the file for reading
$filename = $file->getPathName();
$filename = $file->getRealPath() ?: $file->getPathName();
if (!($resource = fopen($filename, 'r'))) {
// @codeCoverageIgnoreStart
throw new RuntimeException("Could not open {$filename} for reading");
throw new RuntimeException('Could not open ' . $file->getPathname() . ' for reading');
// @codeCoverageIgnoreEnd
}
@ -57,15 +59,21 @@ class UploadSync extends AbstractSync
// Use a multi-part upload if the file is larger than the cutoff size and is a regular file
if ($body->getWrapper() == 'plainfile' && $file->getSize() >= $this->options['multipart_upload_size']) {
return UploadBuilder::newInstance()
$builder = UploadBuilder::newInstance()
->setBucket($this->options['bucket'])
->setKey($key)
->setMinPartSize($this->options['multipart_upload_size'])
->setOption($aclType, $acl)
->setClient($this->options['client'])
->setSource($body)
->setConcurrency($this->options['concurrency'])
->build();
->setConcurrency($this->options['concurrency']);
$this->dispatch(
self::BEFORE_MULTIPART_BUILD,
array('builder' => $builder, 'file' => $file)
);
return $builder->build();
}
return $this->options['client']->getCommand('PutObject', array(

View file

@ -19,7 +19,7 @@ namespace Aws\S3\Sync;
use \FilesystemIterator as FI;
use Aws\Common\Model\MultipartUpload\AbstractTransfer;
use Aws\S3\Model\Acp;
use Aws\S3\S3Client;
use Guzzle\Common\HasDispatcherInterface;
use Guzzle\Common\Event;
use Guzzle\Service\Command\CommandInterface;
@ -40,7 +40,7 @@ class UploadSyncBuilder extends AbstractSyncBuilder
*/
public function uploadFromDirectory($path)
{
$this->baseDir = $path;
$this->baseDir = realpath($path);
$this->sourceIterator = $this->filterIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
$path,
FI::SKIP_DOTS | FI::UNIX_PATHS | FI::FOLLOW_SYMLINKS
@ -125,6 +125,21 @@ class UploadSyncBuilder extends AbstractSyncBuilder
return $sync;
}
protected function addCustomParamListener(HasDispatcherInterface $sync)
{
// Handle the special multi-part upload event
parent::addCustomParamListener($sync);
$params = $this->params;
$sync->getEventDispatcher()->addListener(
UploadSync::BEFORE_MULTIPART_BUILD,
function (Event $e) use ($params) {
foreach ($params as $k => $v) {
$e['builder']->setOption($k, $v);
}
}
);
}
protected function getTargetIterator()
{
return $this->createS3Iterator();
@ -167,7 +182,7 @@ class UploadSyncBuilder extends AbstractSyncBuilder
$size = $command['Body']->getContentLength();
$percentage = number_format(($progress / $totalSize) * 100, 2);
fwrite($resource, "- Part {$command['PartNumber']} ({$size} bytes, {$percentage}%)\n");
$progress .= $size;
$progress += $size;
}
);
});

View file

@ -1,93 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* APC cache provider.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class ApcCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return apc_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return apc_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return (bool) apc_store($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return apc_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return apc_clear_cache() && apc_clear_cache('user');
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = apc_cache_info();
$sma = apc_sma_info();
return array(
Cache::STATS_HITS => $info['num_hits'],
Cache::STATS_MISSES => $info['num_misses'],
Cache::STATS_UPTIME => $info['start_time'],
Cache::STATS_MEMORY_USAGE => $info['mem_size'],
Cache::STATS_MEMORY_AVAILIABLE => $sma['avail_mem'],
);
}
}

View file

@ -1,96 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Array cache driver.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class ArrayCache extends CacheProvider
{
/**
* @var array $data
*/
private $data = array();
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return (isset($this->data[$id])) ? $this->data[$id] : false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return isset($this->data[$id]);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$this->data[$id] = $data;
return true;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
unset($this->data[$id]);
return true;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->data = array();
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return null;
}
}

View file

@ -1,102 +0,0 @@
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface Cache
{
const STATS_HITS = 'hits';
const STATS_MISSES = 'misses';
const STATS_UPTIME = 'uptime';
const STATS_MEMORY_USAGE = 'memory_usage';
const STATS_MEMORY_AVAILIABLE = 'memory_available';
/**
* Fetches an entry from the cache.
*
* @param string $id cache id The id of the cache entry to fetch.
* @return mixed The cached data or FALSE, if no cache entry exists for the given id.
*/
function fetch($id);
/**
* Test if an entry exists in the cache.
*
* @param string $id cache id The cache id of the entry to check for.
* @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
function contains($id);
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param mixed $data The cache entry/data.
* @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime).
* @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
function save($id, $data, $lifeTime = 0);
/**
* Deletes a cache entry.
*
* @param string $id cache id
* @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
function delete($id);
/**
* Retrieves cached information from data store
*
* The server's statistics array has the following values:
*
* - <b>hits</b>
* Number of keys that have been requested and found present.
*
* - <b>misses</b>
* Number of items that have been requested and not found.
*
* - <b>uptime</b>
* Time that the server is running.
*
* - <b>memory_usage</b>
* Memory used by this server to store items.
*
* - <b>memory_available</b>
* Memory allowed to use for storage.
*
* @since 2.2
* @return array Associative array with server's statistics if available, NULL otherwise.
*/
function getStats();
}

View file

@ -1,231 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Base class for cache provider implementations.
*
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class CacheProvider implements Cache
{
const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]';
/**
* @var string The namespace to prefix all cache ids with
*/
private $namespace = '';
/**
* @var string The namespace version
*/
private $namespaceVersion;
/**
* Set the namespace to prefix all cache ids with.
*
* @param string $namespace
* @return void
*/
public function setNamespace($namespace)
{
$this->namespace = (string) $namespace;
}
/**
* Retrieve the namespace that prefixes all cache ids.
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* {@inheritdoc}
*/
public function fetch($id)
{
return $this->doFetch($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
return $this->doContains($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function save($id, $data, $lifeTime = 0)
{
return $this->doSave($this->getNamespacedId($id), $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
return $this->doDelete($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function getStats()
{
return $this->doGetStats();
}
/**
* Deletes all cache entries.
*
* @return boolean TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
public function flushAll()
{
return $this->doFlush();
}
/**
* Delete all cache entries.
*
* @return boolean TRUE if the cache entries were successfully deleted, FALSE otherwise.
*/
public function deleteAll()
{
$namespaceCacheKey = $this->getNamespaceCacheKey();
$namespaceVersion = $this->getNamespaceVersion() + 1;
$this->namespaceVersion = $namespaceVersion;
return $this->doSave($namespaceCacheKey, $namespaceVersion);
}
/**
* Prefix the passed id with the configured namespace value
*
* @param string $id The id to namespace
* @return string $id The namespaced id
*/
private function getNamespacedId($id)
{
$namespaceVersion = $this->getNamespaceVersion();
return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion);
}
/**
* Namespace cache key
*
* @return string $namespaceCacheKey
*/
private function getNamespaceCacheKey()
{
return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace);
}
/**
* Namespace version
*
* @return string $namespaceVersion
*/
private function getNamespaceVersion()
{
if (null !== $this->namespaceVersion) {
return $this->namespaceVersion;
}
$namespaceCacheKey = $this->getNamespaceCacheKey();
$namespaceVersion = $this->doFetch($namespaceCacheKey);
if (false === $namespaceVersion) {
$namespaceVersion = 1;
$this->doSave($namespaceCacheKey, $namespaceVersion);
}
$this->namespaceVersion = $namespaceVersion;
return $this->namespaceVersion;
}
/**
* Fetches an entry from the cache.
*
* @param string $id cache id The id of the cache entry to fetch.
* @return string The cached data or FALSE, if no cache entry exists for the given id.
*/
abstract protected function doFetch($id);
/**
* Test if an entry exists in the cache.
*
* @param string $id cache id The cache id of the entry to check for.
* @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
abstract protected function doContains($id);
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param string $data The cache entry/data.
* @param bool|int $lifeTime The lifetime. If != false, sets a specific lifetime for this
* cache entry (null => infinite lifeTime).
*
* @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
abstract protected function doSave($id, $data, $lifeTime = false);
/**
* Deletes a cache entry.
*
* @param string $id cache id
* @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
abstract protected function doDelete($id);
/**
* Deletes all cache entries.
*
* @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
abstract protected function doFlush();
/**
* Retrieves cached information from data store
*
* @since 2.2
* @return array An associative array with server's statistics if available, NULL otherwise.
*/
abstract protected function doGetStats();
}

View file

@ -1,123 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Couchbase;
/**
* Couchbase cache provider.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.4
* @author Michael Nitschinger <michael@nitschinger.at>
*/
class CouchbaseCache extends CacheProvider
{
/**
* @var Couchbase
*/
private $couchbase;
/**
* Sets the Couchbase instance to use.
*
* @param Couchbase $couchbase
*/
public function setCouchbase(Couchbase $couchbase)
{
$this->couchbase = $couchbase;
}
/**
* Gets the Couchbase instance used by the cache.
*
* @return Couchbase
*/
public function getCouchbase()
{
return $this->couchbase;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->couchbase->get($id) ?: false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (null !== $this->couchbase->get($id));
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->couchbase->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->couchbase->delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->couchbase->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->couchbase->getStats();
$servers = $this->couchbase->getServers();
$server = explode(":", $servers[0]);
$key = $server[0] . ":" . "11210";
$stats = $stats[$key];
return array(
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILIABLE => $stats['limit_maxbytes'],
);
}
}

View file

@ -1,132 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Base file cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class FileCache extends CacheProvider
{
/**
* @var string Cache directory.
*/
protected $directory;
/**
* @var string Cache file extension.
*/
protected $extension;
/**
* Constructor
*
* @param string $directory Cache directory.
* @param string $directory Cache file extension.
*
* @throws \InvalidArgumentException
*/
public function __construct($directory, $extension = null)
{
if ( ! is_dir($directory) && ! @mkdir($directory, 0777, true)) {
throw new \InvalidArgumentException(sprintf(
'The directory "%s" does not exist and could not be created.',
$directory
));
}
if ( ! is_writable($directory)) {
throw new \InvalidArgumentException(sprintf(
'The directory "%s" is not writable.',
$directory
));
}
$this->directory = realpath($directory);
$this->extension = $extension ?: $this->extension;
}
/**
* Gets the cache directory.
*
* @return string
*/
public function getDirectory()
{
return $this->directory;
}
/**
* Gets the cache file extension.
*
* @return string
*/
public function getExtension()
{
return $this->extension;
}
/**
* @return string
*/
protected function getFilename($id)
{
$path = implode(str_split(md5($id), 12), DIRECTORY_SEPARATOR);
$path = $this->directory . DIRECTORY_SEPARATOR . $path;
return $path . DIRECTORY_SEPARATOR . $id . $this->extension;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return @unlink($this->getFilename($id));
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$pattern = '/^.+\\' . $this->extension . '$/i';
$iterator = new \RecursiveDirectoryIterator($this->directory);
$iterator = new \RecursiveIteratorIterator($iterator);
$iterator = new \RegexIterator($iterator, $pattern);
foreach ($iterator as $name => $file) {
@unlink($name);
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return null;
}
}

View file

@ -1,114 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Filesystem cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class FilesystemCache extends FileCache
{
const EXTENSION = '.doctrinecache.data';
/**
* {@inheritdoc}
*/
protected $extension = self::EXTENSION;
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$data = '';
$lifetime = -1;
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
return false;
}
$resource = fopen($filename, "r");
if (false !== ($line = fgets($resource))) {
$lifetime = (integer) $line;
}
if ($lifetime !== 0 && $lifetime < time()) {
fclose($resource);
return false;
}
while (false !== ($line = fgets($resource))) {
$data .= $line;
}
fclose($resource);
return unserialize($data);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$lifetime = -1;
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
return false;
}
$resource = fopen($filename, "r");
if (false !== ($line = fgets($resource))) {
$lifetime = (integer) $line;
}
fclose($resource);
return $lifetime === 0 || $lifetime > time();
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
$lifeTime = time() + $lifeTime;
}
$data = serialize($data);
$filename = $this->getFilename($id);
$filepath = pathinfo($filename, PATHINFO_DIRNAME);
if ( ! is_dir($filepath)) {
mkdir($filepath, 0777, true);
}
return file_put_contents($filename, $lifeTime . PHP_EOL . $data);
}
}

View file

@ -1,121 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Memcache;
/**
* Memcache cache provider.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class MemcacheCache extends CacheProvider
{
/**
* @var Memcache
*/
private $memcache;
/**
* Sets the memcache instance to use.
*
* @param Memcache $memcache
*/
public function setMemcache(Memcache $memcache)
{
$this->memcache = $memcache;
}
/**
* Gets the memcache instance used by the cache.
*
* @return Memcache
*/
public function getMemcache()
{
return $this->memcache;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->memcache->get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (bool) $this->memcache->get($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcache->set($id, $data, 0, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->memcache->delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->memcache->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->memcache->getStats();
return array(
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILIABLE => $stats['limit_maxbytes'],
);
}
}

View file

@ -1,124 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Memcached;
/**
* Memcached cache provider.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class MemcachedCache extends CacheProvider
{
/**
* @var Memcached
*/
private $memcached;
/**
* Sets the memcache instance to use.
*
* @param Memcached $memcached
*/
public function setMemcached(Memcached $memcached)
{
$this->memcached = $memcached;
}
/**
* Gets the memcached instance used by the cache.
*
* @return Memcached
*/
public function getMemcached()
{
return $this->memcached;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->memcached->get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (false !== $this->memcached->get($id));
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcached->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->memcached->delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->memcached->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->memcached->getStats();
$servers = $this->memcached->getServerList();
$key = $servers[0]['host'] . ':' . $servers[0]['port'];
$stats = $stats[$key];
return array(
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILIABLE => $stats['limit_maxbytes'],
);
}
}

View file

@ -1,108 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Php file cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class PhpFileCache extends FileCache
{
const EXTENSION = '.doctrinecache.php';
/**
* {@inheritdoc}
*/
protected $extension = self::EXTENSION;
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
return false;
}
$value = include $filename;
if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) {
return false;
}
return $value['data'];
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
return false;
}
$value = include $filename;
return $value['lifetime'] === 0 || $value['lifetime'] > time();
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
$lifeTime = time() + $lifeTime;
}
if (is_object($data) && ! method_exists($data, '__set_state')) {
throw new \InvalidArgumentException(
"Invalid argument given, PhpFileCache only allows objects that implement __set_state() " .
"and fully support var_export(). You can use the FilesystemCache to save arbitrary object " .
"graphs using serialize()/deserialize()."
);
}
$filename = $this->getFilename($id);
$filepath = pathinfo($filename, PATHINFO_DIRNAME);
if ( ! is_dir($filepath)) {
mkdir($filepath, 0777, true);
}
$value = array(
'lifetime' => $lifeTime,
'data' => $data
);
$value = var_export($value, true);
$code = sprintf('<?php return %s;', $value);
return file_put_contents($filename, $code);
}
}

View file

@ -1,119 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use Redis;
/**
* Redis cache provider.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.2
* @author Osman Ungur <osmanungur@gmail.com>
*/
class RedisCache extends CacheProvider
{
/**
* @var Redis
*/
private $redis;
/**
* Sets the redis instance to use.
*
* @param Redis $redis
*/
public function setRedis(Redis $redis)
{
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);
$this->redis = $redis;
}
/**
* Gets the redis instance used by the cache.
*
* @return Redis
*/
public function getRedis()
{
return $this->redis;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->redis->get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return $this->redis->exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$result = $this->redis->set($id, $data);
if ($lifeTime > 0) {
$this->redis->expire($id, $lifeTime);
}
return $result;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->redis->delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->redis->flushDB();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = $this->redis->info();
return array(
Cache::STATS_HITS => false,
Cache::STATS_MISSES => false,
Cache::STATS_UPTIME => $info['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['used_memory'],
Cache::STATS_MEMORY_AVAILIABLE => false
);
}
}

View file

@ -1,93 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* WinCache cache provider.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class WinCacheCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return wincache_ucache_get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return wincache_ucache_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return (bool) wincache_ucache_set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return wincache_ucache_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return wincache_ucache_clear();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = wincache_ucache_info();
$meminfo = wincache_ucache_meminfo();
return array(
Cache::STATS_HITS => $info['total_hit_count'],
Cache::STATS_MISSES => $info['total_miss_count'],
Cache::STATS_UPTIME => $info['total_cache_uptime'],
Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'],
Cache::STATS_MEMORY_AVAILIABLE => $meminfo['memory_free'],
);
}
}

View file

@ -1,110 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Xcache cache driver.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class XcacheCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->doContains($id) ? unserialize(xcache_get($id)) : false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return xcache_isset($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return xcache_set($id, serialize($data), (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return xcache_unset($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->checkAuthorization();
xcache_clear_cache(XC_TYPE_VAR, 0);
return true;
}
/**
* Checks that xcache.admin.enable_auth is Off
*
* @throws \BadMethodCallException When xcache.admin.enable_auth is On
* @return void
*/
protected function checkAuthorization()
{
if (ini_get('xcache.admin.enable_auth')) {
throw new \BadMethodCallException('To use all features of \Doctrine\Common\Cache\XcacheCache, you must set "xcache.admin.enable_auth" to "Off" in your php.ini.');
}
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$this->checkAuthorization();
$info = xcache_info(XC_TYPE_VAR, 0);
return array(
Cache::STATS_HITS => $info['hits'],
Cache::STATS_MISSES => $info['misses'],
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $info['size'],
Cache::STATS_MEMORY_AVAILIABLE => $info['avail'],
);
}
}

View file

@ -1,84 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Zend Data Cache cache driver.
*
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.doctrine-project.org
* @since 2.0
* @author Ralph Schindler <ralph.schindler@zend.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class ZendDataCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return zend_shm_cache_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (false !== zend_shm_cache_fetch($id));
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return zend_shm_cache_store($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return zend_shm_cache_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$namespace = $this->getNamespace();
if (empty($namespace)) {
return zend_shm_cache_clear();
}
return zend_shm_cache_clear($namespace);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return null;
}
}

View file

@ -0,0 +1,31 @@
{
"name": "guzzle/batch",
"description": "Guzzle batch component for batching requests, commands, or custom transfers",
"homepage": "http://guzzlephp.org/",
"keywords": ["batch", "HTTP", "REST", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2",
"guzzle/common": "self.version"
},
"autoload": {
"psr-0": { "Guzzle\\Batch": "" }
},
"suggest": {
"guzzle/http": "self.version",
"guzzle/service": "self.version"
},
"target-dir": "Guzzle/Batch",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View file

@ -5,6 +5,7 @@ namespace Guzzle\Cache;
use Doctrine\Common\Cache\Cache;
use Guzzle\Common\Version;
use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Exception\RuntimeException;
use Guzzle\Common\FromConfigInterface;
use Zend\Cache\Storage\StorageInterface;

View file

@ -0,0 +1,27 @@
{
"name": "guzzle/cache",
"description": "Guzzle cache adapter component",
"homepage": "http://guzzlephp.org/",
"keywords": ["cache", "adapter", "zf", "doctrine", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.3.2",
"guzzle/common": "self.version"
},
"autoload": {
"psr-0": { "Guzzle\\Cache": "" }
},
"target-dir": "Guzzle/Cache",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View file

@ -37,7 +37,7 @@ class AbstractHasDispatcher implements HasDispatcherInterface
public function dispatch($eventName, array $context = array())
{
$this->getEventDispatcher()->dispatch($eventName, new Event($context));
return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
}
public function addSubscriber(EventSubscriberInterface $subscriber)

View file

@ -10,6 +10,15 @@ class ExceptionCollection extends \Exception implements GuzzleException, \Iterat
/** @var array Array of Exceptions */
protected $exceptions = array();
/** @var string Succinct exception message not including sub-exceptions */
private $shortMessage;
public function __construct($message = '', $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->shortMessage = $message;
}
/**
* Set all of the exceptions
*
@ -36,19 +45,12 @@ class ExceptionCollection extends \Exception implements GuzzleException, \Iterat
*/
public function add($e)
{
$this->exceptions[] = $e;
if ($this->message) {
$this->message .= "\n";
}
if ($e instanceof self) {
$this->message .= '(' . get_class($e) . ")";
foreach (explode("\n", $e->getMessage()) as $message) {
$this->message .= "\n {$message}";
}
} elseif ($e instanceof \Exception) {
$this->exceptions[] = $e;
$this->message .= '(' . get_class($e) . ') ' . $e->getMessage();
}
$this->message .= $this->getExceptionMessage($e, 0);
return $this;
}
@ -82,4 +84,25 @@ class ExceptionCollection extends \Exception implements GuzzleException, \Iterat
{
return $this->exceptions ? $this->exceptions[0] : null;
}
private function getExceptionMessage(\Exception $e, $depth = 0)
{
static $sp = ' ';
$prefix = $depth ? str_repeat($sp, $depth) : '';
$message = "{$prefix}(" . get_class($e) . ') ' . $e->getFile() . ' line ' . $e->getLine() . "\n";
if ($e instanceof self) {
if ($e->shortMessage) {
$message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->shortMessage) . "\n";
}
foreach ($e as $ee) {
$message .= "\n" . $this->getExceptionMessage($ee, $depth + 1);
}
} else {
$message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getMessage()) . "\n";
$message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getTraceAsString()) . "\n";
}
return str_replace(getcwd(), '.', $message);
}
}

View file

@ -38,6 +38,8 @@ interface HasDispatcherInterface
*
* @param string $eventName Name of the event to dispatch
* @param array $context Context of the event
*
* @return Event Returns the created event object
*/
public function dispatch($eventName, array $context = array());

View file

@ -7,7 +7,7 @@ namespace Guzzle\Common;
*/
class Version
{
const VERSION = '3.7.0';
const VERSION = '3.9.2';
/**
* @var bool Set this value to true to enable warnings for deprecated functionality use. This should be on in your

View file

@ -0,0 +1,20 @@
{
"name": "guzzle/common",
"homepage": "http://guzzlephp.org/",
"description": "Common libraries used by Guzzle",
"keywords": ["common", "event", "exception", "collection"],
"license": "MIT",
"require": {
"php": ">=5.3.2",
"symfony/event-dispatcher": ">=2.1"
},
"autoload": {
"psr-0": { "Guzzle\\Common": "" }
},
"target-dir": "Guzzle/Common",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
}
}

View file

@ -30,6 +30,8 @@ class Client extends AbstractHasDispatcher implements ClientInterface
const CURL_OPTIONS = 'curl.options';
const SSL_CERT_AUTHORITY = 'ssl.certificate_authority';
const DISABLE_REDIRECTS = RedirectPlugin::DISABLE;
const DEFAULT_SELECT_TIMEOUT = 1.0;
const MAX_HANDLES = 3;
/** @var Collection Default HTTP headers to set on each request */
protected $defaultHeaders;
@ -109,11 +111,8 @@ class Client extends AbstractHasDispatcher implements ClientInterface
*/
public function setDefaultOption($keyOrPath, $value)
{
if (strpos($keyOrPath, '/')) {
$this->config->setPath($keyOrPath, $value);
} else {
$this->config[$keyOrPath] = $value;
}
$keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
$this->config->setPath($keyOrPath, $value);
return $this;
}
@ -127,7 +126,9 @@ class Client extends AbstractHasDispatcher implements ClientInterface
*/
public function getDefaultOption($keyOrPath)
{
return strpos($keyOrPath, '/') ? $this->config->getPath($keyOrPath) : $this->config[$keyOrPath];
$keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath;
return $this->config->getPath($keyOrPath);
}
final public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2)
@ -142,7 +143,7 @@ class Client extends AbstractHasDispatcher implements ClientInterface
} elseif ($certificateAuthority === false) {
unset($opts[CURLOPT_CAINFO]);
$opts[CURLOPT_SSL_VERIFYPEER] = false;
$opts[CURLOPT_SSL_VERIFYHOST] = 2;
$opts[CURLOPT_SSL_VERIFYHOST] = 0;
} elseif ($verifyPeer !== true && $verifyPeer !== false && $verifyPeer !== 1 && $verifyPeer !== 0) {
throw new InvalidArgumentException('verifyPeer must be 1, 0 or boolean');
} elseif ($verifyHost !== 0 && $verifyHost !== 1 && $verifyHost !== 2) {
@ -178,7 +179,7 @@ class Client extends AbstractHasDispatcher implements ClientInterface
} else {
list($uri, $templateVars) = $uri;
}
if (substr($uri, 0, 4) === 'http') {
if (strpos($uri, '://')) {
// Use absolute URLs as-is
$url = $this->expandTemplate($uri, $templateVars);
} else {
@ -244,7 +245,7 @@ class Client extends AbstractHasDispatcher implements ClientInterface
public function head($uri = null, $headers = null, array $options = array())
{
return $this->createRequest('HEAD', $uri, $headers, $options);
return $this->createRequest('HEAD', $uri, $headers, null, $options);
}
public function delete($uri = null, $headers = null, $body = null, array $options = array())
@ -307,7 +308,10 @@ class Client extends AbstractHasDispatcher implements ClientInterface
public function getCurlMulti()
{
if (!$this->curlMulti) {
$this->curlMulti = new CurlMultiProxy();
$this->curlMulti = new CurlMultiProxy(
self::MAX_HANDLES,
$this->getConfig('select_timeout') ?: self::DEFAULT_SELECT_TIMEOUT
);
}
return $this->curlMulti;
@ -334,31 +338,6 @@ class Client extends AbstractHasDispatcher implements ClientInterface
return $this;
}
/**
* Copy the cacert.pem file from the phar if it is not in the temp folder and validate the MD5 checksum
*
* @param bool $md5Check Set to false to not perform the MD5 validation
*
* @return string Returns the path to the extracted cacert
* @throws RuntimeException if the file cannot be copied or there is a MD5 mismatch
*/
public function preparePharCacert($md5Check = true)
{
$from = __DIR__ . '/Resources/cacert.pem';
$certFile = sys_get_temp_dir() . '/guzzle-cacert.pem';
if (!file_exists($certFile) && !copy($from, $certFile)) {
throw new RuntimeException("Could not copy {$from} to {$certFile}: " . var_export(error_get_last(), true));
} elseif ($md5Check) {
$actualMd5 = md5_file($certFile);
$expectedMd5 = trim(file_get_contents("{$from}.md5"));
if ($actualMd5 != $expectedMd5) {
throw new RuntimeException("{$certFile} MD5 mismatch: expected {$expectedMd5} but got {$actualMd5}");
}
}
return $certFile;
}
/**
* Expand a URI template while merging client config settings into the template variables
*
@ -458,7 +437,9 @@ class Client extends AbstractHasDispatcher implements ClientInterface
*/
protected function initSsl()
{
if ('system' == ($authority = $this->config[self::SSL_CERT_AUTHORITY])) {
$authority = $this->config[self::SSL_CERT_AUTHORITY];
if ($authority === 'system') {
return;
}
@ -467,13 +448,7 @@ class Client extends AbstractHasDispatcher implements ClientInterface
}
if ($authority === true && substr(__FILE__, 0, 7) == 'phar://') {
$authority = $this->preparePharCacert();
$that = $this;
$this->getEventDispatcher()->addListener('request.before_send', function ($event) use ($authority, $that) {
if ($authority == $event['request']->getCurlOptions()->get(CURLOPT_CAINFO)) {
$that->preparePharCacert(false);
}
});
$authority = self::extractPharCacert(__DIR__ . '/Resources/cacert.pem');
}
$this->setSslVerification($authority);
@ -504,4 +479,46 @@ class Client extends AbstractHasDispatcher implements ClientInterface
return $this;
}
/**
* @deprecated
*/
public function preparePharCacert($md5Check = true)
{
return sys_get_temp_dir() . '/guzzle-cacert.pem';
}
/**
* Copies the phar cacert from a phar into the temp directory.
*
* @param string $pharCacertPath Path to the phar cacert. For example:
* 'phar://aws.phar/Guzzle/Http/Resources/cacert.pem'
*
* @return string Returns the path to the extracted cacert file.
* @throws \RuntimeException Throws if the phar cacert cannot be found or
* the file cannot be copied to the temp dir.
*/
public static function extractPharCacert($pharCacertPath)
{
// Copy the cacert.pem file from the phar if it is not in the temp
// folder.
$certFile = sys_get_temp_dir() . '/guzzle-cacert.pem';
if (!file_exists($pharCacertPath)) {
throw new \RuntimeException("Could not find $pharCacertPath");
}
if (!file_exists($certFile) ||
filesize($certFile) != filesize($pharCacertPath)
) {
if (!copy($pharCacertPath, $certFile)) {
throw new \RuntimeException(
"Could not copy {$pharCacertPath} to {$certFile}: "
. var_export(error_get_last(), true)
);
}
}
return $certFile;
}
}

View file

@ -48,9 +48,16 @@ class CurlHandle
$method = $request->getMethod();
$bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
// Prepare url
$url = (string)$request->getUrl();
if(($pos = strpos($url, '#')) !== false ){
// strip fragment from url
$url = substr($url, 0, $pos);
}
// Array of default cURL options.
$curlOptions = array(
CURLOPT_URL => $request->getUrl(),
CURLOPT_URL => $url,
CURLOPT_CONNECTTIMEOUT => 150,
CURLOPT_RETURNTRANSFER => false,
CURLOPT_HEADER => false,
@ -200,6 +207,12 @@ class CurlHandle
$curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
$args = func_get_args();
$args[] = $handle;
// PHP 5.5 pushed the handle onto the start of the args
if (is_resource($args[0])) {
array_shift($args);
}
call_user_func_array(array($mediator, 'progress'), $args);
};
$curlOptions[CURLOPT_NOPROGRESS] = false;

View file

@ -7,6 +7,8 @@ use Guzzle\Common\Event;
use Guzzle\Http\Exception\MultiTransferException;
use Guzzle\Http\Exception\CurlException;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
use Guzzle\Http\Exception\RequestException;
/**
* Send {@see RequestInterface} objects in parallel using curl_multi
@ -39,8 +41,12 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!')
);
public function __construct()
/** @var float */
protected $selectTimeout;
public function __construct($selectTimeout = 1.0)
{
$this->selectTimeout = $selectTimeout;
$this->multiHandle = curl_multi_init();
// @codeCoverageIgnoreStart
if ($this->multiHandle === false) {
@ -76,13 +82,12 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
public function remove(RequestInterface $request)
{
$this->removeHandle($request);
foreach ($this->requests as $i => $r) {
if ($request === $r) {
unset($this->requests[$i]);
$this->requests = array_values($this->requests);
$this->dispatch(self::REMOVE_REQUEST, array('request' => $request));
return true;
}
if (($index = array_search($request, $this->requests, true)) !== false) {
$request = $this->requests[$index];
unset($this->requests[$index]);
$this->requests = array_values($this->requests);
$this->dispatch(self::REMOVE_REQUEST, array('request' => $request));
return true;
}
return false;
@ -130,8 +135,7 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
$multiException = new MultiTransferException('Errors during multi transfer');
while ($e = array_shift($exceptions)) {
$multiException->add($e['exception']);
$multiException->addFailedRequest($e['request']);
$multiException->addFailedRequestWithException($e['request'], $e['exception']);
}
// Add successful requests
@ -155,10 +159,10 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
try {
$state = $request->setState(RequestInterface::STATE_TRANSFER);
if ($state == RequestInterface::STATE_TRANSFER) {
// Add the request curl handle to the multi handle
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $this->createCurlHandle($request)->getHandle()));
$this->addHandle($request);
} else {
// Requests might decide they don't need to be sent just before transfer (e.g. CachePlugin)
// Requests might decide they don't need to be sent just before
// transfer (e.g. CachePlugin)
$this->remove($request);
if ($state == RequestInterface::STATE_COMPLETE) {
$this->successful[] = $request;
@ -170,6 +174,14 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
}
}
private function addHandle(RequestInterface $request)
{
$handle = $this->createCurlHandle($request)->getHandle();
$this->checkCurlResult(
curl_multi_add_handle($this->multiHandle, $handle)
);
}
/**
* Create a curl handle for a request
*
@ -191,18 +203,9 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
*/
protected function perform()
{
if (!$this->requests) {
return;
}
// Initialize the handles with a very quick select timeout
$active = $mrc = null;
$this->executeHandles($active, $mrc, 0.001);
$event = new Event(array('curl_multi' => $this));
$this->processMessages();
while ($this->requests) {
// Notify each request as polling
$blocking = $total = 0;
foreach ($this->requests as $request) {
@ -214,77 +217,60 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
++$blocking;
}
}
if ($blocking == $total) {
// Sleep to prevent eating CPU because no requests are actually pending a select call
usleep(500);
} else {
do {
$this->executeHandles($active, $mrc, 1);
} while ($active);
$this->executeHandles();
}
$this->processMessages();
}
}
/**
* Execute and select curl handles
*/
private function executeHandles()
{
// The first curl_multi_select often times out no matter what, but is usually required for fast transfers
$selectTimeout = 0.001;
$active = false;
do {
while (($mrc = curl_multi_exec($this->multiHandle, $active)) == CURLM_CALL_MULTI_PERFORM);
$this->checkCurlResult($mrc);
$this->processMessages();
if ($active && curl_multi_select($this->multiHandle, $selectTimeout) === -1) {
// Perform a usleep if a select returns -1: https://bugs.php.net/bug.php?id=61141
usleep(150);
}
$selectTimeout = $this->selectTimeout;
} while ($active);
}
/**
* Process any received curl multi messages
*/
private function processMessages()
{
// Get messages from curl handles
while ($done = curl_multi_info_read($this->multiHandle)) {
$request = $this->resourceHash[(int) $done['handle']];
try {
$request = $this->resourceHash[(int) $done['handle']];
$this->processResponse($request, $this->handles[$request], $done);
$this->successful[] = $request;
} catch (MultiTransferException $e) {
$this->removeErroredRequest($request, $e, false);
throw $e;
} catch (\Exception $e) {
$this->removeErroredRequest($request, $e);
}
}
}
/**
* Execute and select curl handles until there is activity
*
* @param int $active Active value to update
* @param int $mrc Multi result value to update
* @param int $timeout Select timeout in seconds
*/
private function executeHandles(&$active, &$mrc, $timeout = 1)
{
do {
$mrc = curl_multi_exec($this->multiHandle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM && $active);
$this->checkCurlResult($mrc);
// @codeCoverageIgnoreStart
// Select the curl handles until there is any activity on any of the open file descriptors
// See https://github.com/php/php-src/blob/master/ext/curl/multi.c#L170
if ($active && $mrc == CURLM_OK && curl_multi_select($this->multiHandle, $timeout) == -1) {
// Perform a usleep if a previously executed select returned -1
// @see https://bugs.php.net/bug.php?id=61141
usleep(100);
}
// @codeCoverageIgnoreEnd
}
/**
* Remove a request that encountered an exception
*
* @param RequestInterface $request Request to remove
* @param \Exception $e Exception encountered
* @param bool $buffer Set to false to not buffer the exception
*/
protected function removeErroredRequest(RequestInterface $request, \Exception $e = null, $buffer = true)
protected function removeErroredRequest(RequestInterface $request, \Exception $e = null)
{
if ($buffer) {
$this->exceptions[] = array('request' => $request, 'exception' => $e);
}
$this->exceptions[] = array('request' => $request, 'exception' => $e);
$this->remove($request);
$this->dispatch(self::MULTI_EXCEPTION, array('exception' => $e, 'all_exceptions' => $this->exceptions));
}
@ -310,23 +296,31 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
$this->removeHandle($request);
if (!$curlException) {
$state = $request->setState(RequestInterface::STATE_COMPLETE, array('handle' => $handle));
// Only remove the request if it wasn't resent as a result of the state change
if ($state != RequestInterface::STATE_TRANSFER) {
$this->remove($request);
}
} else {
// Set the state of the request to an error
$state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $curlException));
// Allow things to ignore the error if possible
if ($state != RequestInterface::STATE_TRANSFER) {
$this->remove($request);
}
// The error was not handled, so fail
if ($state == RequestInterface::STATE_ERROR) {
/** @var CurlException $curlException */
throw $curlException;
if ($this->validateResponseWasSet($request)) {
$state = $request->setState(
RequestInterface::STATE_COMPLETE,
array('handle' => $handle)
);
// Only remove the request if it wasn't resent as a result of
// the state change
if ($state != RequestInterface::STATE_TRANSFER) {
$this->remove($request);
}
}
return;
}
// Set the state of the request to an error
$state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $curlException));
// Allow things to ignore the error if possible
if ($state != RequestInterface::STATE_TRANSFER) {
$this->remove($request);
}
// The error was not handled, so fail
if ($state == RequestInterface::STATE_ERROR) {
/** @var CurlException $curlException */
throw $curlException;
}
}
@ -339,9 +333,9 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
{
if (isset($this->handles[$request])) {
$handle = $this->handles[$request];
curl_multi_remove_handle($this->multiHandle, $handle->getHandle());
unset($this->handles[$request]);
unset($this->resourceHash[(int) $handle->getHandle()]);
curl_multi_remove_handle($this->multiHandle, $handle->getHandle());
$handle->close();
}
}
@ -387,4 +381,43 @@ class CurlMulti extends AbstractHasDispatcher implements CurlMultiInterface
);
}
}
/**
* @link https://github.com/guzzle/guzzle/issues/710
*/
private function validateResponseWasSet(RequestInterface $request)
{
if ($request->getResponse()) {
return true;
}
$body = $request instanceof EntityEnclosingRequestInterface
? $request->getBody()
: null;
if (!$body) {
$rex = new RequestException(
'No response was received for a request with no body. This'
. ' could mean that you are saturating your network.'
);
$rex->setRequest($request);
$this->removeErroredRequest($request, $rex);
} elseif (!$body->isSeekable() || !$body->seek(0)) {
// Nothing we can do with this. Sorry!
$rex = new RequestException(
'The connection was unexpectedly closed. The request would'
. ' have been retried, but attempting to rewind the'
. ' request body failed.'
);
$rex->setRequest($request);
$this->removeErroredRequest($request, $rex);
} else {
$this->remove($request);
// Add the request back to the batch to retry automatically.
$this->requests[] = $request;
$this->addHandle($request);
}
return false;
}
}

View file

@ -15,13 +15,16 @@ class CurlMultiProxy extends AbstractHasDispatcher implements CurlMultiInterface
protected $groups = array();
protected $queued = array();
protected $maxHandles;
protected $selectTimeout;
/**
* @param int $maxHandles The maximum number of idle CurlMulti handles to allow to remain open
* @param int $maxHandles The maximum number of idle CurlMulti handles to allow to remain open
* @param float $selectTimeout timeout for curl_multi_select
*/
public function __construct($maxHandles = 3)
public function __construct($maxHandles = 3, $selectTimeout = 1.0)
{
$this->maxHandles = $maxHandles;
$this->selectTimeout = $selectTimeout;
// You can get some weird "Too many open files" errors when sending a large amount of requests in parallel.
// These two statements autoload classes before a system runs out of file descriptors so that you can get back
// valuable error messages if you run out.
@ -122,7 +125,7 @@ class CurlMultiProxy extends AbstractHasDispatcher implements CurlMultiInterface
}
// All are claimed, so create one
$handle = new CurlMulti();
$handle = new CurlMulti($this->selectTimeout);
$handle->setEventDispatcher($this->getEventDispatcher());
$this->handles[] = $handle;

View file

@ -114,7 +114,12 @@ class RequestMediator
));
}
return $this->request->getResponse()->getBody()->write($write);
if ($response = $this->request->getResponse()) {
return $response->getBody()->write($write);
} else {
// Unexpected data received before response headers - abort transfer
return 0;
}
}
/**
@ -128,18 +133,15 @@ class RequestMediator
*/
public function readRequestBody($ch, $fd, $length)
{
$read = '';
if ($this->request->getBody()) {
$read = $this->request->getBody()->read($length);
if ($this->emitIo) {
$this->request->dispatch('curl.callback.read', array(
'request' => $this->request,
'read' => $read
));
}
if (!($body = $this->request->getBody())) {
return '';
}
return !$read ? '' : $read;
$read = (string) $body->read($length);
if ($this->emitIo) {
$this->request->dispatch('curl.callback.read', array('request' => $this->request, 'read' => $read));
}
return $read;
}
}

View file

@ -126,9 +126,11 @@ class EntityBody extends Stream implements EntityBodyInterface
public function getContentMd5($rawOutput = false, $base64Encode = false)
{
$hash = self::getHash($this, 'md5', $rawOutput);
return $hash && $base64Encode ? base64_encode($hash) : $hash;
if ($hash = self::getHash($this, 'md5', $rawOutput)) {
return $hash && $base64Encode ? base64_encode($hash) : $hash;
} else {
return false;
}
}
/**

View file

@ -32,7 +32,6 @@ class BadResponseException extends RequestException
} else {
$label = 'Unsuccessful response';
$class = __CLASS__;
$e = new self();
}
$message = $label . PHP_EOL . implode(PHP_EOL, array(

View file

@ -12,6 +12,7 @@ class MultiTransferException extends ExceptionCollection
{
protected $successfulRequests = array();
protected $failedRequests = array();
protected $exceptionForRequest = array();
/**
* Get all of the requests in the transfer
@ -51,6 +52,37 @@ class MultiTransferException extends ExceptionCollection
return $this;
}
/**
* Add to the array of failed requests and associate with exceptions
*
* @param RequestInterface $request Failed request
* @param \Exception $exception Exception to add and associate with
*
* @return self
*/
public function addFailedRequestWithException(RequestInterface $request, \Exception $exception)
{
$this->add($exception)
->addFailedRequest($request)
->exceptionForRequest[spl_object_hash($request)] = $exception;
return $this;
}
/**
* Get the Exception that caused the given $request to fail
*
* @param RequestInterface $request Failed command
*
* @return \Exception|null
*/
public function getExceptionForFailedRequest(RequestInterface $request)
{
$oid = spl_object_hash($request);
return isset($this->exceptionForRequest[$oid]) ? $this->exceptionForRequest[$oid] : null;
}
/**
* Set all of the successful requests
*

View file

@ -43,7 +43,7 @@ class IoEmittingEntityBody extends AbstractEntityBodyDecorator implements HasDis
public function dispatch($eventName, array $context = array())
{
$this->getEventDispatcher()->dispatch($eventName, new Event($context));
return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
}
/**

View file

@ -7,7 +7,6 @@ use Guzzle\Http\EntityBodyInterface;
use Guzzle\Http\QueryString;
use Guzzle\Http\RedirectPlugin;
use Guzzle\Http\Exception\RequestException;
use Guzzle\Http\Mimetypes;
/**
* HTTP request that sends an entity-body in the request message (POST, PUT, PATCH, DELETE)
@ -61,7 +60,7 @@ class EntityEnclosingRequest extends Request implements EntityEnclosingRequestIn
// Auto detect the Content-Type from the path of the request if possible
if ($contentType === null && !$this->hasHeader('Content-Type')) {
$contentType = $this->body->getContentType() ?: Mimetypes::getInstance()->fromFilename($this->getPath());
$contentType = $this->body->getContentType();
}
if ($contentType) {
@ -181,7 +180,7 @@ class EntityEnclosingRequest extends Request implements EntityEnclosingRequestIn
return $this;
}
public function addPostFile($field, $filename = null, $contentType = null)
public function addPostFile($field, $filename = null, $contentType = null, $postname = null)
{
$data = null;
@ -197,7 +196,7 @@ class EntityEnclosingRequest extends Request implements EntityEnclosingRequestIn
throw new RequestException('The path to a file must be a string');
} elseif (!empty($filename)) {
// Adding an empty file will cause cURL to error out
$data = new PostFile($field, $filename, $contentType);
$data = new PostFile($field, $filename, $contentType, $postname);
}
if ($data) {

View file

@ -108,9 +108,10 @@ interface EntityEnclosingRequestInterface extends RequestInterface
* @param string $filename Full path to the file. Do not include the @ symbol.
* @param string $contentType Optional Content-Type to add to the Content-Disposition.
* Default behavior is to guess. Set to false to not specify.
* @param string $postname The name of the file, when posted. (e.g. rename the file)
* @return self
*/
public function addPostFile($field, $filename = null, $contentType = null);
public function addPostFile($field, $filename = null, $contentType = null, $postname = null);
/**
* Add POST files to use in the upload

View file

@ -81,7 +81,8 @@ class Header implements HeaderInterface
for ($i = 0, $total = count($values); $i < $total; $i++) {
if (strpos($values[$i], $this->glue) !== false) {
foreach (explode($this->glue, $values[$i]) as $v) {
// Explode on glue when the glue is not inside of a comma
foreach (preg_split('/' . preg_quote($this->glue) . '(?=([^"]*"[^"]*")*[^"]*$)/', $values[$i]) as $v) {
$values[] = trim($v);
}
unset($values[$i]);
@ -122,23 +123,24 @@ class Header implements HeaderInterface
return new \ArrayIterator($this->toArray());
}
/**
* {@inheritdoc}
* @todo Do not split semicolons when enclosed in quotes (e.g. foo="baz;bar")
*/
public function parseParams()
{
$params = array();
$params = $matches = array();
$callback = array($this, 'trimHeader');
// Normalize the header into a single array and iterate over all values
foreach ($this->normalize()->toArray() as $val) {
$part = array();
foreach (explode(';', $val) as $kvp) {
$pieces = array_map($callback, explode('=', $kvp, 2));
foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
if (!preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
continue;
}
$pieces = array_map($callback, $matches[0]);
$part[$pieces[0]] = isset($pieces[1]) ? $pieces[1] : '';
}
$params[] = $part;
if ($part) {
$params[] = $part;
}
}
return $params;

View file

@ -2,7 +2,6 @@
namespace Guzzle\Http\Message\Header;
use Guzzle\Common\Collection;
use Guzzle\Common\ToArrayInterface;
/**

View file

@ -14,16 +14,19 @@ class PostFile implements PostFileInterface
protected $fieldName;
protected $contentType;
protected $filename;
protected $postname;
/**
* @param string $fieldName Name of the field
* @param string $filename Path to the file
* @param string $filename Local path to the file
* @param string $postname Remote post file name
* @param string $contentType Content-Type of the upload
*/
public function __construct($fieldName, $filename, $contentType = null)
public function __construct($fieldName, $filename, $contentType = null, $postname = null)
{
$this->fieldName = $fieldName;
$this->setFilename($filename);
$this->postname = $postname ? $postname : basename($filename);
$this->contentType = $contentType ?: $this->guessContentType();
}
@ -55,11 +58,23 @@ class PostFile implements PostFileInterface
return $this;
}
public function setPostname($postname)
{
$this->postname = $postname;
return $this;
}
public function getFilename()
{
return $this->filename;
}
public function getPostname()
{
return $this->postname;
}
public function setContentType($type)
{
$this->contentType = $type;
@ -77,11 +92,11 @@ class PostFile implements PostFileInterface
// PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax
// See: https://wiki.php.net/rfc/curl-file-upload
if (function_exists('curl_file_create')) {
return curl_file_create($this->filename, $this->contentType, basename($this->filename));
return curl_file_create($this->filename, $this->contentType, $this->postname);
}
// Use the old style if using an older version of PHP
$value = "@{$this->filename};filename=" . basename($this->filename);
$value = "@{$this->filename};filename=" . $this->postname;
if ($this->contentType) {
$value .= ';type=' . $this->contentType;
}

View file

@ -35,6 +35,15 @@ interface PostFileInterface
*/
public function setFilename($path);
/**
* Set the post name of the file
*
* @param string $name The new name of the file
*
* @return self
*/
public function setPostname($name);
/**
* Get the full path to the file
*
@ -42,6 +51,13 @@ interface PostFileInterface
*/
public function getFilename();
/**
* Get the post name of the file
*
* @return string
*/
public function getPostname();
/**
* Set the Content-Type of the file
*

View file

@ -275,7 +275,7 @@ class Request extends AbstractMessage implements RequestInterface
// Include the port in the Host header if it is not the default port for the scheme of the URL
$scheme = $this->url->getScheme();
if (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443)) {
if ($port && (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443))) {
$this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost() . ':' . $port);
} else {
$this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost());
@ -532,7 +532,8 @@ class Request extends AbstractMessage implements RequestInterface
public function dispatch($eventName, array $context = array())
{
$context['request'] = $this;
$this->getEventDispatcher()->dispatch($eventName, new Event($context));
return $this->getEventDispatcher()->dispatch($eventName, new Event($context));
}
public function addSubscriber(EventSubscriberInterface $subscriber)
@ -542,21 +543,6 @@ class Request extends AbstractMessage implements RequestInterface
return $this;
}
/**
* {@inheritdoc}
* Adds a check for Host header changes
*/
public function addHeader($header, $value)
{
parent::addHeader($header, $value);
if ($header == 'host' || $header == 'Host') {
$this->setHost((string) $this->getHeader('Host'));
}
return $this;
}
/**
* Get an array containing the request and response for event notifications
*

View file

@ -7,7 +7,6 @@ use Guzzle\Common\Exception\InvalidArgumentException;
use Guzzle\Http\RedirectPlugin;
use Guzzle\Http\Url;
use Guzzle\Parser\ParserRegistry;
use Guzzle\Plugin\Log\LogPlugin;
/**
* Default HTTP request factory used to create the default {@see Request} and {@see EntityEnclosingRequest} objects.
@ -85,7 +84,7 @@ class RequestFactory implements RequestFactoryInterface
{
$method = strtoupper($method);
if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE' || $method == 'OPTIONS') {
if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE') {
// Handle non-entity-enclosing request methods
$request = new $this->requestClass($method, $url, $headers);
if ($body) {
@ -98,7 +97,7 @@ class RequestFactory implements RequestFactoryInterface
} else {
// Create an entity enclosing request by default
$request = new $this->entityEnclosingRequestClass($method, $url, $headers);
if ($body) {
if ($body || $body === '0') {
// Add POST fields and files to an entity enclosing request if an array is used
if (is_array($body) || $body instanceof Collection) {
// Normalize PHP style cURL uploads with a leading '@' symbol
@ -139,7 +138,7 @@ class RequestFactory implements RequestFactoryInterface
public function cloneRequestWithMethod(RequestInterface $request, $method)
{
// Create the request with the same client if possible
if ($client = $request->getClient()) {
if ($request->getClient()) {
$cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders());
} else {
$cloned = $this->create($method, $request->getUrl(), $request->getHeaders());
@ -270,7 +269,7 @@ class RequestFactory implements RequestFactoryInterface
if ($value === false || $value === 0) {
$dispatcher = $request->getEventDispatcher();
foreach ($dispatcher->getListeners('request.error') as $listener) {
if ($listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') {
if (is_array($listener) && $listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') {
$dispatcher->removeListener('request.error', $listener);
break;
}
@ -294,22 +293,26 @@ class RequestFactory implements RequestFactoryInterface
protected function visit_timeout(RequestInterface $request, $value, $flags)
{
$request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000);
if (defined('CURLOPT_TIMEOUT_MS')) {
$request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000);
} else {
$request->getCurlOptions()->set(CURLOPT_TIMEOUT, $value);
}
}
protected function visit_connect_timeout(RequestInterface $request, $value, $flags)
{
$request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000);
if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
$request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000);
} else {
$request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, $value);
}
}
protected function visit_debug(RequestInterface $request, $value, $flags)
{
if (class_exists('Guzzle\Plugin\Log\LogPlugin')) {
$request->addSubscriber(LogPlugin::getDebugPlugin());
} else {
// @codeCoverageIgnoreStart
if ($value) {
$request->getCurlOptions()->set(CURLOPT_VERBOSE, true);
// @codeCoverageIgnoreEnd
}
}
@ -333,4 +336,24 @@ class RequestFactory implements RequestFactoryInterface
{
$request->getCurlOptions()->set(CURLOPT_PROXY, $value, $flags);
}
protected function visit_cert(RequestInterface $request, $value, $flags)
{
if (is_array($value)) {
$request->getCurlOptions()->set(CURLOPT_SSLCERT, $value[0]);
$request->getCurlOptions()->set(CURLOPT_SSLCERTPASSWD, $value[1]);
} else {
$request->getCurlOptions()->set(CURLOPT_SSLCERT, $value);
}
}
protected function visit_ssl_key(RequestInterface $request, $value, $flags)
{
if (is_array($value)) {
$request->getCurlOptions()->set(CURLOPT_SSLKEY, $value[0]);
$request->getCurlOptions()->set(CURLOPT_SSLKEYPASSWD, $value[1]);
} else {
$request->getCurlOptions()->set(CURLOPT_SSLKEY, $value);
}
}
}

View file

@ -88,6 +88,12 @@ interface RequestFactoryInterface
* indefinitely.
* "verify": Set to true to enable SSL cert validation (the default), false to disable, or supply the path
* to a CA bundle to enable verification using a custom certificate.
* "cert": Set to a string to specify the path to a file containing a PEM formatted certificate. If a
* password is required, then set an array containing the path to the PEM file followed by the the
* password required for the certificate.
* "ssl_key": Specify the path to a file containing a private SSL key in PEM format. If a password is
* required, then set an array containing the path to the SSL key followed by the password required for
* the certificate.
* "proxy": Specify an HTTP proxy (e.g. "http://username:password@192.168.16.1:10")
* "debug": Set to true to display all data sent over the wire
* @param int $flags Bitwise flags to apply when applying the options to the request. Defaults to no special

View file

@ -865,18 +865,38 @@ class Response extends AbstractMessage implements \Serializable
}
/**
* Parse the XML response body and return a SimpleXMLElement
* Parse the XML response body and return a \SimpleXMLElement.
*
* In order to prevent XXE attacks, this method disables loading external
* entities. If you rely on external entities, then you must parse the
* XML response manually by accessing the response body directly.
*
* @return \SimpleXMLElement
* @throws RuntimeException if the response body is not in XML format
* @link http://websec.io/2012/08/27/Preventing-XXE-in-PHP.html
*/
public function xml()
{
$errorMessage = null;
$internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
libxml_clear_errors();
try {
// Allow XML to be retrieved even if there is no response body
$xml = new \SimpleXMLElement((string) $this->body ?: '<root />');
$xml = new \SimpleXMLElement((string) $this->body ?: '<root />', LIBXML_NONET);
if ($error = libxml_get_last_error()) {
$errorMessage = $error->message;
}
} catch (\Exception $e) {
throw new RuntimeException('Unable to parse response body into XML: ' . $e->getMessage());
$errorMessage = $e->getMessage();
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities);
if ($errorMessage) {
throw new RuntimeException('Unable to parse response body into XML: ' . $errorMessage);
}
return $xml;

View file

@ -943,6 +943,8 @@ class Mimetypes
*/
public function fromExtension($extension)
{
$extension = strtolower($extension);
return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null;
}

View file

@ -3,6 +3,8 @@
namespace Guzzle\Http;
use Guzzle\Common\Collection;
use Guzzle\Common\Exception\RuntimeException;
use Guzzle\Http\QueryAggregator\DuplicateAggregator;
use Guzzle\Http\QueryAggregator\QueryAggregatorInterface;
use Guzzle\Http\QueryAggregator\PhpAggregator;
@ -33,7 +35,7 @@ class QueryString extends Collection
protected $aggregator;
/** @var array Cached PHP aggregator */
protected static $defaultAggregator = null;
private static $defaultAggregator = null;
/**
* Parse a query string into a QueryString object
@ -45,31 +47,40 @@ class QueryString extends Collection
public static function fromString($query)
{
$q = new static();
if ($query === '') {
return $q;
}
if ($query || $query === '0') {
if ($query[0] == '?') {
$query = substr($query, 1);
$foundDuplicates = $foundPhpStyle = false;
foreach (explode('&', $query) as $kvp) {
$parts = explode('=', $kvp, 2);
$key = rawurldecode($parts[0]);
if ($paramIsPhpStyleArray = substr($key, -2) == '[]') {
$foundPhpStyle = true;
$key = substr($key, 0, -2);
}
foreach (explode('&', $query) as $kvp) {
$parts = explode('=', $kvp, 2);
$key = rawurldecode($parts[0]);
if ($paramIsPhpStyleArray = substr($key, -2) == '[]') {
$key = substr($key, 0, -2);
}
if (isset($parts[1])) {
$value = rawurldecode(str_replace('+', '%20', $parts[1]));
if ($paramIsPhpStyleArray && !$q->hasKey($key)) {
$value = array($value);
}
if (isset($parts[1])) {
$value = rawurldecode(str_replace('+', '%20', $parts[1]));
if (isset($q[$key])) {
$q->add($key, $value);
$foundDuplicates = true;
} elseif ($paramIsPhpStyleArray) {
$q[$key] = array($value);
} else {
$q->add($key, null);
$q[$key] = $value;
}
} else {
// Uses false by default to represent keys with no trailing "=" sign.
$q->add($key, false);
}
}
// Use the duplicate aggregator if duplicates were found and not using PHP style arrays
if ($foundDuplicates && !$foundPhpStyle) {
$q->setAggregator(new DuplicateAggregator());
}
return $q;
}
@ -77,6 +88,7 @@ class QueryString extends Collection
* Convert the query string parameters to a query string string
*
* @return string
* @throws RuntimeException
*/
public function __toString()
{
@ -84,21 +96,12 @@ class QueryString extends Collection
return '';
}
$queryString = '';
$queryList = array();
foreach ($this->prepareData($this->data) as $name => $value) {
foreach ((array) $value as $v) {
if ($queryString) {
$queryString .= $this->fieldSeparator;
}
$queryString .= $name;
if ($v !== self::BLANK) {
$queryString .= $this->valueSeparator . $v;
}
}
$queryList[] = $this->convertKvp($name, $value);
}
return $queryString;
return implode($this->fieldSeparator, $queryList);
}
/**
@ -255,7 +258,10 @@ class QueryString extends Collection
$temp = array();
foreach ($data as $key => $value) {
if (is_array($value)) {
if ($value === false || $value === null) {
// False and null will not include the "=". Use an empty string to include the "=".
$temp[$this->encodeValue($key)] = $value;
} elseif (is_array($value)) {
$temp = array_merge($temp, $this->aggregator->aggregate($key, $value, $this));
} else {
$temp[$this->encodeValue($key)] = $this->encodeValue($value);
@ -264,4 +270,28 @@ class QueryString extends Collection
return $temp;
}
/**
* Converts a key value pair that can contain strings, nulls, false, or arrays
* into a single string.
*
* @param string $name Name of the field
* @param mixed $value Value of the field
* @return string
*/
private function convertKvp($name, $value)
{
if ($value === self::BLANK || $value === null || $value === false) {
return $name;
} elseif (!is_array($value)) {
return $name . $this->valueSeparator . $value;
}
$result = '';
foreach ($value as $v) {
$result .= $this->convertKvp($name, $v) . $this->fieldSeparator;
}
return rtrim($result, $this->fieldSeparator);
}
}

View file

@ -22,7 +22,6 @@ class ReadLimitEntityBody extends AbstractEntityBodyDecorator
{
parent::__construct($body);
$this->setLimit($limit)->setOffset($offset);
$this->body->seek($offset);
}
/**
@ -36,7 +35,8 @@ class ReadLimitEntityBody extends AbstractEntityBodyDecorator
public function isConsumed()
{
return (($this->offset + $this->limit) - $this->body->ftell()) <= 0;
return $this->body->isConsumed() ||
($this->body->ftell() >= $this->offset + $this->limit);
}
/**

View file

@ -68,7 +68,7 @@ class RedirectPlugin implements EventSubscriberInterface
// Trace the original request based on parameter history
$original = $this->getOriginalRequest($request);
// Terminating condition to set the effective repsonse on the original request
// Terminating condition to set the effective response on the original request
if (!$response->isRedirect() || !$response->hasHeader('Location')) {
if ($request !== $original) {
// This is a terminating redirect response, so set it on the original request
@ -123,9 +123,9 @@ class RedirectPlugin implements EventSubscriberInterface
$redirectRequest = null;
$strict = $original->getParams()->get(self::STRICT_REDIRECTS);
// 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
if ($request instanceof EntityEnclosingRequestInterface && !$strict && $statusCode <= 302) {
// Switch method to GET for 303 redirects. 301 and 302 redirects also switch to GET unless we are forcing RFC
// compliance to emulate what most browsers do. NOTE: IE only switches methods on 301/302 when coming from a POST.
if ($request instanceof EntityEnclosingRequestInterface && ($statusCode == 303 || (!$strict && $statusCode <= 302))) {
$redirectRequest = RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET');
} else {
$redirectRequest = clone $request;
@ -141,7 +141,7 @@ class RedirectPlugin implements EventSubscriberInterface
$originalUrl = $redirectRequest->getUrl(true);
// Remove query string parameters and just take what is present on the redirect Location header
$originalUrl->getQuery()->clear();
$location = $originalUrl->combine((string) $location);
$location = $originalUrl->combine((string) $location, true);
}
$redirectRequest->setUrl($location);
@ -174,7 +174,7 @@ class RedirectPlugin implements EventSubscriberInterface
/**
* Prepare the request for redirection and enforce the maximum number of allowed redirects per client
*
* @param RequestInterface $original Origina request
* @param RequestInterface $original Original request
* @param RequestInterface $request Request to prepare and validate
* @param Response $response The current response
*

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more