Fix for S3 hashing (new API implementation)

This commit is contained in:
ruben- 2015-02-18 21:43:28 +01:00
parent b01c3039c8
commit c72b38c3d1
496 changed files with 54071 additions and 2488 deletions

View file

@ -381,7 +381,7 @@ class MainWPRemoteBackupSystem
$website = ($pSiteId != null ? $pSiteId : null);

$array = get_option('mainwp_upload_progress');
if ($remoteDestination->upload($pFile, $pType, $pSubfolder, $pRegexFile, $website, $pUnique, (is_array($array) && isset($array[$pUnique]) && ($array[$pUnique]['offset'] > 0))))
if ($remoteDestination->upload($pFile, $pType, $pSubfolder, $pRegexFile, $website, null, $pUnique, (is_array($array) && isset($array[$pUnique]) && ($array[$pUnique]['offset'] > 0))))
{
$result['result'] = 'success';
}
@ -466,7 +466,7 @@ class MainWPRemoteBackupSystem
$backupTaskProgress = MainWPRemoteBackupDB::Instance()->updateBackupTaskProgress($taskId, $website->id, array('remoteDestinations' => json_encode($remoteDestinations)));

session_write_close();
if ($remoteDestination->upload($localBackupFile, $what, $subfolder, $regexBackupFile, $website->id, null, ($remoteDestinations[$remote_destination_from_db->id] != 1)))
if ($remoteDestination->upload($localBackupFile, $what, $subfolder, $regexBackupFile, $website->id, $taskId, null, ($remoteDestinations[$remote_destination_from_db->id] != 1)))
{
$backup_result[$remoteDestination->getType()] = 'success';
}

View file

@ -29,7 +29,7 @@ abstract class MainWPRemoteDestination
* @param $pType: full or not
* @return mixed
*/
public abstract function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pUnique = null, $pTryResume = false);
public abstract function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pTaskId = null, $pUnique = null, $pTryResume = false);
public abstract function getIdentifier();
public abstract function limitFiles($ftp, $pLocalbackupfile, $pRegexFile, &$backupFiles, $dir = null);
public abstract function test($fields = array());

View file

@ -1,4 +1,6 @@
<?php
use Aws\S3\S3Client;

class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
{
public function __construct($pObject = array('type' => 'amazon'))
@ -39,7 +41,7 @@ class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
* @param null $dir
* @return mixed
*/
public function limitFiles($amazon, $pLocalbackupfile, $pRegexFile, &$backupFiles, $dir = null)
public function limitFiles($s3Client, $pLocalbackupfile, $pRegexFile, &$backupFiles, $dir = null)
{
$maxBackups = get_option('mainwp_backupOnExternalSources');
if ($maxBackups === false) $maxBackups = 1;
@ -47,14 +49,33 @@ class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
if ($maxBackups == 0) return $backupFiles;
$maxBackups--;

$filesToRemove = $amazon->listObjects($this->getBucket(), $pRegexFile, basename($pLocalbackupfile), $backupFiles);
$filesToRemove = array();
try
{
$result = $s3Client->listObjects(array('Bucket' => $this->getBucket()));
foreach ($result->get('Contents') as $content)
{
$file = $content['Key'];
if ((basename($pLocalbackupfile) != basename($file)) &&
(preg_match('/' . $pRegexFile . '/', basename($file)) || in_array(basename($file), $backupFiles))
)
{
$filesToRemove[] = array('file' => $file, 'dts' => $content['LastModified']);
}
}
}
catch (Exception $e)
{

}

if (count($filesToRemove) <= $maxBackups) return $backupFiles;

$filesToRemove = MainWPRemoteDestinationUtility::sortmulti($filesToRemove, 'dts', 'desc');

for ($i = $maxBackups; $i < count($filesToRemove); $i++)
{
$amazon->deleteObject($this->getBucket(), $filesToRemove[$i]['file']);
$s3Client->deleteObject(array('Bucket' => $this->getBucket(), 'Key' => $filesToRemove[$i]['file']));

if (($key = array_search(basename($filesToRemove[$i]['file']), $backupFiles)) !== false)
{
@ -65,7 +86,7 @@ class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
return $backupFiles;
}

public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pUnique = null, $pTryResume = false)
public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pTaskId = null, $pUnique = null, $pTryResume = false)
{
$dir = $this->getDir();
if ($pSubfolder != '')
@ -82,32 +103,47 @@ class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
}

$amazon_uri = (($dir != '') ? $dir . '/' : '') . basename($pLocalbackupfile);
$uploader = new S3($this->getAccess(), $this->getSecret(), false);
$uploader->setExceptions(true);

$s3Client = S3Client::factory(array(
'key' => $this->getAccess(),
'secret' => $this->getSecret()
));

// $uploader = new S3($this->getAccess(), $this->getSecret(), false);
// $uploader->setExceptions(true);

if ($pUnique != null)
{
$uploadTracker = new MainWPRemoteDestinationUploadTracker($pUnique);
$uploader->setUploadTracker($uploadTracker);
//todo: uploadTracker?
// $uploadTracker = new MainWPRemoteDestinationUploadTracker($pUnique);
// $uploader->setUploadTracker($uploadTracker);
}
try
{
$bucketLocation = null;
try
{
$bucketLocation = $uploader->getBucketLocation(urlencode($this->getBucket()));
$bucketLocation = $s3Client->getBucketLocation(array('Bucket' => urlencode($this->getBucket())));
}
catch (Exception $e)
{

}

if ($bucketLocation == null) $uploader->putBucket(urlencode($this->getBucket()));

if ($bucketLocation == null) $s3Client->createBucket(array('Bucket' => urlencode($this->getBucket())));

if ($pLocalbackupfile != null)
{
$uploaded = $uploader->putObjectFile($pLocalbackupfile, urlencode($this->getBucket()), $amazon_uri);
$metadata = array('creator' => 'MainWP');
if ($pSiteId != null) $metadata['mainwp-siteid'] = $pSiteId;
if ($pTaskId != null) $metadata['mainwp-taskid'] = $pTaskId;

$uploaded = $s3Client->putObject(array(
'Bucket' => urlencode($this->getBucket()),
'Key' => $amazon_uri,
'SourceFile' => $pLocalbackupfile,
'Metadata' => $metadata
));
if (!$uploaded)
{
throw new Exception('Upload failed');
@ -128,7 +164,7 @@ class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
$backupsTaken = $backups[$pType];
}

$backupsTaken = $this->limitFiles($uploader, $pLocalbackupfile, $pRegexFile, $backupsTaken);
$backupsTaken = $this->limitFiles($s3Client, $pLocalbackupfile, $pRegexFile, $backupsTaken);

array_push($backupsTaken, basename($pLocalbackupfile));
$backups[$pType] = $backupsTaken;
@ -211,22 +247,25 @@ class MainWPRemoteDestinationAmazon extends MainWPRemoteDestination
$key_secret = $fields == null ? $this->getSecret() : (!isset($fields['secret']) ? null : $fields['secret']);
$bucket_name = $fields == null ? $this->getBucket() : (!isset($fields['bucket']) ? null : $fields['bucket']);
if (($key_id == null) || ($key_id == '') || ($key_secret == null) || ($key_secret == '') || ($bucket_name == null) || ($bucket_name == '')) throw new Exception('Please fill in all the fields');
$tester = new S3($key_id, $key_secret, false);
$tester->setExceptions(true);

$s3Client = S3Client::factory(array(
'key' => $key_id,
'secret' => $key_secret
));

try
{
$bucketLocation = null;
try
{
$bucketLocation = $tester->getBucketLocation(urlencode($bucket_name));
$bucketLocation = $s3Client->getBucketLocation(array('Bucket' => urlencode($bucket_name)));
}
catch (Exception $e)
{

}

if ($bucketLocation == null) $tester->putBucket(urlencode($bucket_name));
if ($bucketLocation == null) $s3Client->createBucket(array('Bucket' => urlencode($bucket_name)));
}
catch (S3Exception $e)
{

View file

@ -128,7 +128,7 @@ class MainWPRemoteDestinationCopy extends MainWPRemoteDestination
return $backupFiles;
}

public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pUnique = null, $pTryResume = false)
public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pTaskId = null, $pUnique = null, $pTryResume = false)
{
$dir = $this->getDir();
if ($pSubfolder != '')

View file

@ -31,7 +31,7 @@ class MainWPRemoteDestinationDropbox extends MainWPRemoteDestination
return true;
}

public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pUnique = null, $pTryResume = false)
public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pTaskId = null, $pUnique = null, $pTryResume = false)
{
$uploader = new DropboxUploader($this->getUsername(), $this->getPassword());
$connected = $uploader->testConnection();

View file

@ -179,7 +179,7 @@ class MainWPRemoteDestinationDropbox2 extends MainWPRemoteDestination
return $backupFiles;
}

public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pUnique = null, $pTryResume = false)
public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pTaskId = null, $pUnique = null, $pTryResume = false)
{
include_once mainwp_remote_backup_extension_dir() . 'Dropbox/OAuth/Consumer/ConsumerAbstract.php';
include_once mainwp_remote_backup_extension_dir() . 'Dropbox/OAuth/Consumer/Curl.php';

View file

@ -135,7 +135,7 @@ class MainWPRemoteDestinationFTP extends MainWPRemoteDestination
return $backupFiles;
}

public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pUnique = null, $pTryResume = false)
public function upload($pLocalbackupfile, $pType, $pSubfolder, $pRegexFile, $pSiteId = null, $pTaskId = null, $pUnique = null, $pTryResume = false)
{
$ftp = new Ftp();
if ($this->getSSL() == '1') {

File diff suppressed because it is too large Load diff

105
libs/Aws/Common/Aws.php Normal file
View file

@ -0,0 +1,105 @@
<?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;

use Aws\Common\Facade\Facade;
use Guzzle\Service\Builder\ServiceBuilder;
use Guzzle\Service\Builder\ServiceBuilderLoader;

/**
* Base class for interacting with web service clients
*/
class Aws extends ServiceBuilder
{
/**
* @var string Current version of the SDK
*/
const VERSION = '2.7.18';

/**
* Create a new service locator for the AWS SDK
*
* You can configure the service locator is four different ways:
*
* 1. Use the default configuration file shipped with the SDK that wires class names with service short names and
* specify global parameters to add to every definition (e.g. key, secret, credentials, etc)
*
* 2. Use a custom configuration file that extends the default config and supplies credentials for each service.
*
* 3. Use a custom config file that wires services to custom short names for services.
*
* 4. If you are on Amazon EC2, you can use the default configuration file and not provide any credentials so that
* you are using InstanceProfile credentials.
*
* @param array|string $config The full path to a .php or .js|.json file, or an associative array of data
* to use as global parameters to pass to each service.
* @param array $globalParameters Global parameters to pass to every service as it is instantiated.
*
* @return Aws
*/
public static function factory($config = null, array $globalParameters = array())
{
if (!$config) {
// If nothing is passed in, then use the default configuration file with credentials from the environment
$config = self::getDefaultServiceDefinition();
} elseif (is_array($config)) {
// If an array was passed, then use the default configuration file with parameter overrides
$globalParameters = $config;
$config = self::getDefaultServiceDefinition();
}

$loader = new ServiceBuilderLoader();
$loader->addAlias('_aws', self::getDefaultServiceDefinition())
->addAlias('_sdk1', __DIR__ . '/Resources/sdk1-config.php');

return $loader->load($config, $globalParameters);
}

/**
* Get the full path to the default service builder definition file
*
* @return string
*/
public static function getDefaultServiceDefinition()
{
return __DIR__ . '/Resources/aws-config.php';
}

/**
* Returns the configuration for the service builder
*
* @return array
*/
public function getConfig()
{
return $this->builderConfig;
}

/**
* Enables the facades for the clients defined in the service builder
*
* @param string|null $namespace The namespace that the facades should be mounted to. Defaults to global namespace
*
* @return Aws
*/
public function enableFacades($namespace = null)
{
Facade::mountFacades($this, $namespace);

return $this;
}
}

View file

@ -0,0 +1,283 @@
<?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\Client;

use Aws\Common\Aws;
use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\TransferException;
use Aws\Common\RulesEndpointProvider;
use Aws\Common\Signature\EndpointSignatureInterface;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Signature\SignatureListener;
use Aws\Common\Waiter\WaiterClassFactory;
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\Http\QueryAggregator\DuplicateAggregator;
use Guzzle\Service\Client;
use Guzzle\Service\Description\ServiceDescriptionInterface;

/**
* Abstract AWS client
*/
abstract class AbstractClient extends Client implements AwsClientInterface
{
/** @var CredentialsInterface AWS credentials */
protected $credentials;

/** @var SignatureInterface Signature implementation of the service */
protected $signature;

/** @var WaiterFactoryInterface Factory used to create waiter classes */
protected $waiterFactory;

/** @var DuplicateAggregator Cached query aggregator*/
protected $aggregator;

/**
* {@inheritdoc}
*/
public static function getAllEvents()
{
return array_merge(Client::getAllEvents(), array(
'client.region_changed',
'client.credentials_changed',
));
}

/**
* @param CredentialsInterface $credentials AWS credentials
* @param SignatureInterface $signature Signature implementation
* @param Collection $config Configuration options
*
* @throws InvalidArgumentException if an endpoint provider isn't provided
*/
public function __construct(CredentialsInterface $credentials, SignatureInterface $signature, Collection $config)
{
// Bootstrap with Guzzle
parent::__construct($config->get(Options::BASE_URL), $config);
$this->credentials = $credentials;
$this->signature = $signature;
$this->aggregator = new DuplicateAggregator();

// Make sure the user agent is prefixed by the SDK version
$this->setUserAgent('aws-sdk-php2/' . Aws::VERSION, true);

// Add the event listener so that requests are signed before they are sent
$dispatcher = $this->getEventDispatcher();
$dispatcher->addSubscriber(new SignatureListener($credentials, $signature));

if ($backoff = $config->get(Options::BACKOFF)) {
$dispatcher->addSubscriber($backoff, -255);
}
}

public function __call($method, $args)
{
if (substr($method, 0, 3) === 'get' && substr($method, -8) === 'Iterator') {
// Allow magic method calls for iterators (e.g. $client->get<CommandName>Iterator($params))
$commandOptions = isset($args[0]) ? $args[0] : null;
$iteratorOptions = isset($args[1]) ? $args[1] : array();
return $this->getIterator(substr($method, 3, -8), $commandOptions, $iteratorOptions);
} elseif (substr($method, 0, 9) == 'waitUntil') {
// Allow magic method calls for waiters (e.g. $client->waitUntil<WaiterName>($params))
return $this->waitUntil(substr($method, 9), isset($args[0]) ? $args[0]: array());
} else {
return parent::__call(ucfirst($method), $args);
}
}

/**
* Get an endpoint for a specific region from a service description
* @deprecated This function will no longer be updated to work with new regions.
*/
public static function getEndpoint(ServiceDescriptionInterface $description, $region, $scheme)
{
try {
$service = $description->getData('endpointPrefix');
$provider = RulesEndpointProvider::fromDefaults();
$result = $provider(array(
'service' => $service,
'region' => $region,
'scheme' => $scheme
));
return $result['endpoint'];
} catch (\InvalidArgumentException $e) {
throw new InvalidArgumentException($e->getMessage(), 0, $e);
}
}

public function getCredentials()
{
return $this->credentials;
}

public function setCredentials(CredentialsInterface $credentials)
{
$formerCredentials = $this->credentials;
$this->credentials = $credentials;

// Dispatch an event that the credentials have been changed
$this->dispatch('client.credentials_changed', array(
'credentials' => $credentials,
'former_credentials' => $formerCredentials,
));

return $this;
}

public function getSignature()
{
return $this->signature;
}

public function getRegions()
{
return $this->serviceDescription->getData('regions');
}

public function getRegion()
{
return $this->getConfig(Options::REGION);
}

public function setRegion($region)
{
$config = $this->getConfig();
$formerRegion = $config->get(Options::REGION);
$global = $this->serviceDescription->getData('globalEndpoint');
$provider = $config->get('endpoint_provider');

if (!$provider) {
throw new \RuntimeException('No endpoint provider configured');
}

// Only change the region if the service does not have a global endpoint
if (!$global || $this->serviceDescription->getData('namespace') === 'S3') {

$endpoint = call_user_func(
$provider,
array(
'scheme' => $config->get(Options::SCHEME),
'region' => $region,
'service' => $config->get(Options::SERVICE)
)
);

$this->setBaseUrl($endpoint['endpoint']);
$config->set(Options::BASE_URL, $endpoint['endpoint']);
$config->set(Options::REGION, $region);

// Update the signature if necessary
$signature = $this->getSignature();
if ($signature instanceof EndpointSignatureInterface) {
/** @var $signature EndpointSignatureInterface */
$signature->setRegionName($region);
}

// Dispatch an event that the region has been changed
$this->dispatch('client.region_changed', array(
'region' => $region,
'former_region' => $formerRegion,
));
}

return $this;
}

public function waitUntil($waiter, array $input = array())
{
$this->getWaiter($waiter, $input)->wait();

return $this;
}

public function getWaiter($waiter, array $input = array())
{
return $this->getWaiterFactory()->build($waiter)
->setClient($this)
->setConfig($input);
}

public function setWaiterFactory(WaiterFactoryInterface $waiterFactory)
{
$this->waiterFactory = $waiterFactory;

return $this;
}

public function getWaiterFactory()
{
if (!$this->waiterFactory) {
$clientClass = get_class($this);
// Use a composite factory that checks for classes first, then config waiters
$this->waiterFactory = new CompositeWaiterFactory(array(
new WaiterClassFactory(substr($clientClass, 0, strrpos($clientClass, '\\')) . '\\Waiter')
));
if ($this->getDescription()) {
$waiterConfig = $this->getDescription()->getData('waiters') ?: array();
$this->waiterFactory->addFactory(new WaiterConfigFactory($waiterConfig));
}
}

return $this->waiterFactory;
}

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;
}
}

/**
* Ensures that the duplicate query string aggregator is used so that
* query string values are sent over the wire as foo=bar&foo=baz.
* {@inheritdoc}
*/
public function createRequest(
$method = 'GET',
$uri = null,
$headers = null,
$body = null,
array $options = array()
) {
$request = parent::createRequest($method, $uri, $headers, $body, $options);
$request->getQuery()->setAggregator($this->aggregator);
return $request;
}
}

View file

@ -0,0 +1,118 @@
<?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\Client;

use Aws\Common\Credentials\CredentialsInterface;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Waiter\WaiterFactoryInterface;
use Aws\Common\Waiter\WaiterInterface;
use Guzzle\Service\ClientInterface;

/**
* Interface that all AWS clients implement
*/
interface AwsClientInterface extends ClientInterface
{
/**
* Returns the AWS credentials associated with the client
*
* @return CredentialsInterface
*/
public function getCredentials();

/**
* Sets the credentials object associated with the client
*
* @param CredentialsInterface $credentials Credentials object to use
*
* @return self
*/
public function setCredentials(CredentialsInterface $credentials);

/**
* Returns the signature implementation used with the client
*
* @return SignatureInterface
*/
public function getSignature();

/**
* Get a list of available regions and region data
*
* @return array
*/
public function getRegions();

/**
* Get the name of the region to which the client is configured to send requests
*
* @return string
*/
public function getRegion();

/**
* Change the region to which the client is configured to send requests
*
* @param string $region Name of the region
*
* @return self
*/
public function setRegion($region);

/**
* Get the waiter factory being used by the client
*
* @return WaiterFactoryInterface
*/
public function getWaiterFactory();

/**
* Set the waiter factory to use with the client
*
* @param WaiterFactoryInterface $waiterFactory Factory used to create waiters
*
* @return self
*/
public function setWaiterFactory(WaiterFactoryInterface $waiterFactory);

/**
* Wait until a resource is available or an associated waiter returns true
*
* @param string $waiter Name of the waiter
* @param array $input Values used as input for the underlying operation and to control the waiter
*
* @return self
*/
public function waitUntil($waiter, array $input = array());

/**
* Get a named waiter object
*
* @param string $waiter Name of the waiter
* @param array $input Values used as input for the underlying operation and to control the waiter
*
* @return WaiterInterface
*/
public function getWaiter($waiter, array $input = array());

/**
* Get the API version of the client (e.g. 2006-03-01)
*
* @return string
*/
public function getApiVersion();
}

View file

@ -0,0 +1,518 @@
<?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\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\Exception\ExceptionListener;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\NamespaceExceptionFactory;
use Aws\Common\Exception\Parser\DefaultXmlExceptionParser;
use Aws\Common\Exception\Parser\ExceptionParserInterface;
use Aws\Common\Iterator\AwsResourceIteratorFactory;
use Aws\Common\RulesEndpointProvider;
use Aws\Common\Signature\EndpointSignatureInterface;
use Aws\Common\Signature\SignatureInterface;
use Aws\Common\Signature\SignatureV2;
use Aws\Common\Signature\SignatureV3Https;
use Aws\Common\Signature\SignatureV4;
use Guzzle\Common\Collection;
use Guzzle\Plugin\Backoff\BackoffPlugin;
use Guzzle\Plugin\Backoff\CurlBackoffStrategy;
use Guzzle\Plugin\Backoff\ExponentialBackoffStrategy;
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
use Guzzle\Plugin\Backoff\TruncatedBackoffStrategy;
use Guzzle\Service\Description\ServiceDescription;
use Guzzle\Service\Resource\ResourceIteratorClassFactory;
use Guzzle\Log\LogAdapterInterface;
use Guzzle\Log\ClosureLogAdapter;
use Guzzle\Plugin\Backoff\BackoffLogger;

/**
* Builder for creating AWS service clients
*/
class ClientBuilder
{
/**
* @var array Default client config
*/
protected static $commonConfigDefaults = array('scheme' => 'https');

/**
* @var array Default client requirements
*/
protected static $commonConfigRequirements = array(Options::SERVICE_DESCRIPTION);

/**
* @var string The namespace of the client
*/
protected $clientNamespace;

/**
* @var array The config options
*/
protected $config = array();

/**
* @var array The config defaults
*/
protected $configDefaults = array();

/**
* @var array The config requirements
*/
protected $configRequirements = array();

/**
* @var ExceptionParserInterface The Parser interface for the client
*/
protected $exceptionParser;

/**
* @var array Array of configuration data for iterators available for the client
*/
protected $iteratorsConfig = array();

/** @var string */
private $clientClass;

/** @var string */
private $serviceName;

/**
* Factory method for creating the client builder
*
* @param string $namespace The namespace of the client
*
* @return ClientBuilder
*/
public static function factory($namespace = null)
{
return new static($namespace);
}

/**
* Constructs a client builder
*
* @param string $namespace The namespace of the client
*/
public function __construct($namespace = null)
{
$this->clientNamespace = $namespace;

// Determine service and class name
$this->clientClass = 'Aws\Common\Client\DefaultClient';

if ($this->clientNamespace) {
$this->serviceName = substr($this->clientNamespace, strrpos($this->clientNamespace, '\\') + 1);
$this->clientClass = $this->clientNamespace . '\\' . $this->serviceName . 'Client';
}
}

/**
* Sets the config options
*
* @param array|Collection $config The config options
*
* @return ClientBuilder
*/
public function setConfig($config)
{
$this->config = $this->processArray($config);

return $this;
}

/**
* Sets the config options' defaults
*
* @param array|Collection $defaults The default values
*
* @return ClientBuilder
*/
public function setConfigDefaults($defaults)
{
$this->configDefaults = $this->processArray($defaults);

return $this;
}

/**
* Sets the required config options
*
* @param array|Collection $required The required config options
*
* @return ClientBuilder
*/
public function setConfigRequirements($required)
{
$this->configRequirements = $this->processArray($required);

return $this;
}

/**
* Sets the exception parser. If one is not provided the builder will use
* the default XML exception parser.
*
* @param ExceptionParserInterface $parser The exception parser
*
* @return ClientBuilder
*/
public function setExceptionParser(ExceptionParserInterface $parser)
{
$this->exceptionParser = $parser;

return $this;
}

/**
* Set the configuration for the client's iterators
*
* @param array $config Configuration data for client's iterators
*
* @return ClientBuilder
*/
public function setIteratorsConfig(array $config)
{
$this->iteratorsConfig = $config;

return $this;
}

/**
* Performs the building logic using all of the parameters that have been
* set and falling back to default values. Returns an instantiate service
* client with credentials prepared and plugins attached.
*
* @return AwsClientInterface
* @throws InvalidArgumentException
*/
public function build()
{
// Resolve configuration
$config = Collection::fromConfig(
$this->config,
array_merge(self::$commonConfigDefaults, $this->configDefaults),
(self::$commonConfigRequirements + $this->configRequirements)
);

if ($config[Options::VERSION] === 'latest') {
$config[Options::VERSION] = constant("{$this->clientClass}::LATEST_API_VERSION");
}

if (!isset($config['endpoint_provider'])) {
$config['endpoint_provider'] = RulesEndpointProvider::fromDefaults();
}

// Resolve the endpoint, signature, and credentials
$description = $this->updateConfigFromDescription($config);
$signature = $this->getSignature($description, $config);
$credentials = $this->getCredentials($config);
$this->extractHttpConfig($config);

// Resolve exception parser
if (!$this->exceptionParser) {
$this->exceptionParser = new DefaultXmlExceptionParser();
}

// Resolve backoff strategy
$backoff = $config->get(Options::BACKOFF);
if ($backoff === null) {
$backoff = $this->createDefaultBackoff();
$config->set(Options::BACKOFF, $backoff);
}

if ($backoff) {
$this->addBackoffLogger($backoff, $config);
}

/** @var $client AwsClientInterface */
$client = new $this->clientClass($credentials, $signature, $config);
$client->setDescription($description);

// Add exception marshaling so that more descriptive exception are thrown
if ($this->clientNamespace) {
$exceptionFactory = new NamespaceExceptionFactory(
$this->exceptionParser,
"{$this->clientNamespace}\\Exception",
"{$this->clientNamespace}\\Exception\\{$this->serviceName}Exception"
);
$client->addSubscriber(new ExceptionListener($exceptionFactory));
}

// Add the UserAgentPlugin to append to the User-Agent header of requests
$client->addSubscriber(new UserAgentListener());

// Filters used for the cache plugin
$client->getConfig()->set(
'params.cache.key_filter',
'header=date,x-amz-date,x-amz-security-token,x-amzn-authorization'
);

// Set the iterator resource factory based on the provided iterators config
$client->setResourceIteratorFactory(new AwsResourceIteratorFactory(
$this->iteratorsConfig,
new ResourceIteratorClassFactory($this->clientNamespace . '\\Iterator')
));

// Disable parameter validation if needed
if ($config->get(Options::VALIDATION) === false) {
$params = $config->get('command.params') ?: array();
$params['command.disable_validation'] = true;
$config->set('command.params', $params);
}

return $client;
}

/**
* Add backoff logging to the backoff plugin if needed
*
* @param BackoffPlugin $plugin Backoff plugin
* @param Collection $config Configuration settings
*
* @throws InvalidArgumentException
*/
protected function addBackoffLogger(BackoffPlugin $plugin, Collection $config)
{
// The log option can be set to `debug` or an instance of a LogAdapterInterface
if ($logger = $config->get(Options::BACKOFF_LOGGER)) {
$format = $config->get(Options::BACKOFF_LOGGER_TEMPLATE);
if ($logger === 'debug') {
$logger = new ClosureLogAdapter(function ($message) {
trigger_error($message . "\n");
});
} elseif (!($logger instanceof LogAdapterInterface)) {
throw new InvalidArgumentException(
Options::BACKOFF_LOGGER . ' must be set to `debug` or an instance of '
. 'Guzzle\\Common\\Log\\LogAdapterInterface'
);
}
// Create the plugin responsible for logging exponential backoff retries
$logPlugin = new BackoffLogger($logger);
// You can specify a custom format or use the default
if ($format) {
$logPlugin->setTemplate($format);
}
$plugin->addSubscriber($logPlugin);
}
}

/**
* Ensures that an array (e.g. for config data) is actually in array form
*
* @param array|Collection $array The array data
*
* @return array
* @throws InvalidArgumentException if the arg is not an array or Collection
*/
protected function processArray($array)
{
if ($array instanceof Collection) {
$array = $array->getAll();
}

if (!is_array($array)) {
throw new InvalidArgumentException('The config must be provided as an array or Collection.');
}

return $array;
}

/**
* Update a configuration object from a service description
*
* @param Collection $config Config to update
*
* @return ServiceDescription
* @throws InvalidArgumentException
*/
protected function updateConfigFromDescription(Collection $config)
{
$description = $config->get(Options::SERVICE_DESCRIPTION);
if (!($description instanceof ServiceDescription)) {
// Inject the version into the sprintf template if it is a string
if (is_string($description)) {
$description = sprintf($description, $config->get(Options::VERSION));
}
$description = ServiceDescription::factory($description);
$config->set(Options::SERVICE_DESCRIPTION, $description);
}

if (!$config->get(Options::SERVICE)) {
$config->set(Options::SERVICE, $description->getData('endpointPrefix'));
}

if ($iterators = $description->getData('iterators')) {
$this->setIteratorsConfig($iterators);
}

$this->handleRegion($config);
$this->handleEndpoint($config);

return $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
*
* @return SignatureInterface
* @throws InvalidArgumentException
*/
protected function getSignature(ServiceDescription $description, Collection $config)
{
// 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
$signature->setServiceName($config->get(Options::SIGNATURE_SERVICE)
?: $description->getData('signingName')
?: $description->getData('endpointPrefix'));

// Determine the region to use when signing requests
$signature->setRegionName($config->get(Options::SIGNATURE_REGION) ?: $config->get(Options::REGION));
}

return $signature;
}

protected function getCredentials(Collection $config)
{
$credentials = $config->get(Options::CREDENTIALS);

if (is_array($credentials)) {
$credentials = Credentials::factory($credentials);
} elseif ($credentials === false) {
$credentials = new NullCredentials();
} elseif (!$credentials instanceof CredentialsInterface) {
$credentials = Credentials::factory($config);
}

return $credentials;
}

private function handleRegion(Collection $config)
{
// Make sure a valid region is set
$region = $config[Options::REGION];
$description = $config[Options::SERVICE_DESCRIPTION];
$global = $description->getData('globalEndpoint');

if (!$global && !$region) {
throw new InvalidArgumentException(
'A region is required when using ' . $description->getData('serviceFullName')
);
} elseif ($global && !$region) {
$config[Options::REGION] = 'us-east-1';
}
}

private function handleEndpoint(Collection $config)
{
// Alias "endpoint" with "base_url" for forwards compatibility.
if ($config['endpoint']) {
$config[Options::BASE_URL] = $config['endpoint'];
return;
}

if ($config[Options::BASE_URL]) {
return;
}

$endpoint = call_user_func(
$config['endpoint_provider'],
array(
'scheme' => $config[Options::SCHEME],
'region' => $config[Options::REGION],
'service' => $config[Options::SERVICE]
)
);

$config[Options::BASE_URL] = $endpoint['endpoint'];

// Set a signature if one was not explicitly provided.
if (!$config->hasKey(Options::SIGNATURE)
&& isset($endpoint['signatureVersion'])
) {
$config->set(Options::SIGNATURE, $endpoint['signatureVersion']);
}
}

private function createDefaultBackoff()
{
return new BackoffPlugin(
// Retry failed requests up to 3 times if it is determined that the request can be retried
new TruncatedBackoffStrategy(3,
// Retry failed requests with 400-level responses due to throttling
new ThrottlingErrorChecker($this->exceptionParser,
// 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()
)
)
)
)
)
);
}

private function extractHttpConfig(Collection $config)
{
$http = $config['http'];

if (!is_array($http)) {
return;
}

if (isset($http['verify'])) {
$config[Options::SSL_CERT] = $http['verify'];
}
}
}

View file

@ -0,0 +1,67 @@
<?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\Client;

use Aws\Common\Enum\ClientOptions as Options;
use Guzzle\Common\Collection;

/**
* Generic client for interacting with an AWS service
*/
class DefaultClient extends AbstractClient
{
/**
* Factory method to create a default 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. 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 endpoint is not supplied
* - endpoint: 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
*
* @return self
*/
public static function factory($config = array())
{
return ClientBuilder::factory()
->setConfig($config)
->setConfigDefaults(array(Options::SCHEME => 'https'))
->build();
}
}

View file

@ -0,0 +1,80 @@
<?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\Client;

use Aws\Common\Credentials\AbstractRefreshableCredentials;
use Aws\Common\Client\AwsClientInterface;
use Aws\Common\Exception\Parser\ExceptionParserInterface;
use Guzzle\Http\Exception\HttpException;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Plugin\Backoff\BackoffStrategyInterface;
use Guzzle\Plugin\Backoff\AbstractBackoffStrategy;

/**
* Backoff logic that handles retrying requests when credentials expire
*/
class ExpiredCredentialsChecker extends AbstractBackoffStrategy
{
/**
* @var array Array of known retrying exception codes
*/
protected $retryable = array(
'RequestExpired' => true,
'ExpiredTokenException' => true,
'ExpiredToken' => true
);

/**
* @var ExceptionParserInterface Exception parser used to parse exception responses
*/
protected $exceptionParser;

public function __construct(ExceptionParserInterface $exceptionParser, BackoffStrategyInterface $next = null) {
$this->exceptionParser = $exceptionParser;
$this->next = $next;
}

public function makesDecision()
{
return true;
}

protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null)
{
if ($response && $response->isClientError()) {

$parts = $this->exceptionParser->parse($request, $response);
if (!isset($this->retryable[$parts['code']]) || !$request->getClient()) {
return null;
}

/** @var $client AwsClientInterface */
$client = $request->getClient();
// Only retry if the credentials can be refreshed
if (!($client->getCredentials() instanceof AbstractRefreshableCredentials)) {
return null;
}

// Resign the request using new credentials
$client->getSignature()->signRequest($request, $client->getCredentials()->setExpiration(-1));

// Retry immediately with no delay
return 0;
}
}
}

View file

@ -0,0 +1,75 @@
<?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\Client;

use Aws\Common\Exception\Parser\ExceptionParserInterface;
use Guzzle\Http\Exception\HttpException;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Plugin\Backoff\BackoffStrategyInterface;
use Guzzle\Plugin\Backoff\AbstractBackoffStrategy;

/**
* Backoff logic that handles throttling exceptions from services
*/
class ThrottlingErrorChecker extends AbstractBackoffStrategy
{
/** @var array Whitelist of exception codes (as indexes) that indicate throttling */
protected static $throttlingExceptions = array(
'RequestLimitExceeded' => true,
'Throttling' => true,
'ThrottlingException' => true,
'ProvisionedThroughputExceededException' => true,
'RequestThrottled' => true,
);

/**
* @var ExceptionParserInterface Exception parser used to parse exception responses
*/
protected $exceptionParser;

public function __construct(ExceptionParserInterface $exceptionParser, BackoffStrategyInterface $next = null)
{
$this->exceptionParser = $exceptionParser;
if ($next) {
$this->setNext($next);
}
}

/**
* {@inheritdoc}
*/
public function makesDecision()
{
return true;
}

/**
* {@inheritdoc}
*/
protected function getDelay(
$retries,
RequestInterface $request,
Response $response = null,
HttpException $e = null
) {
if ($response && $response->isClientError()) {
$parts = $this->exceptionParser->parse($request, $response);
return isset(self::$throttlingExceptions[$parts['code']]) ? true : null;
}
}
}

View file

@ -0,0 +1,95 @@
<?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\Client;

use Aws\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Event;
use Guzzle\Http\EntityBody;
use Guzzle\Service\Command\AbstractCommand as Command;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Prepares the body parameter of a command such that the parameter is more flexible (e.g. accepts file handles) with
* the value it accepts but converts it to the correct format for the command. Also looks for a "Filename" parameter.
*/
class UploadBodyListener implements EventSubscriberInterface
{
/**
* @var array The names of the commands of which to modify the body parameter
*/
protected $commands;

/**
* @var string The key for the upload body parameter
*/
protected $bodyParameter;

/**
* @var string The key for the source file parameter
*/
protected $sourceParameter;

/**
* @param array $commands The commands to modify
* @param string $bodyParameter The key for the body parameter
* @param string $sourceParameter The key for the source file parameter
*/
public function __construct(array $commands, $bodyParameter = 'Body', $sourceParameter = 'SourceFile')
{
$this->commands = $commands;
$this->bodyParameter = (string) $bodyParameter;
$this->sourceParameter = (string) $sourceParameter;
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array('command.before_prepare' => array('onCommandBeforePrepare'));
}

/**
* 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)
{
/** @var $command Command */
$command = $event['command'];
if (in_array($command->getName(), $this->commands)) {
// Get the interesting parameters
$source = $command->get($this->sourceParameter);
$body = $command->get($this->bodyParameter);

// If a file path is passed in then get the file handle
if (is_string($source) && file_exists($source)) {
$body = fopen($source, 'r');
}

// Prepare the body parameter and remove the source file parameter
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

@ -0,0 +1,61 @@
<?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\Client;

use Guzzle\Common\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Listener used to append strings to the User-Agent header of a request based
* on the `ua.append` option. `ua.append` can contain a string or array of values.
*/
class UserAgentListener implements EventSubscriberInterface
{
/**
* @var string Option used to store User-Agent modifiers
*/
const OPTION = 'ua.append';

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array('command.before_send' => 'onBeforeSend');
}

/**
* Adds strings to the User-Agent header using the `ua.append` parameter of a command
*
* @param Event $event Event emitted
*/
public function onBeforeSend(Event $event)
{
$command = $event['command'];
if ($userAgentAppends = $command->get(self::OPTION)) {
$request = $command->getRequest();
$userAgent = (string) $request->getHeader('User-Agent');
foreach ((array) $userAgentAppends as $append) {
$append = ' ' . $append;
if (strpos($userAgent, $append) === false) {
$userAgent .= $append;
}
}
$request->setHeader('User-Agent', $userAgent);
}
}
}

View file

@ -0,0 +1,117 @@
<?php

namespace Aws\Common\Command;

use Guzzle\Http\Message\RequestInterface;
use Guzzle\Service\Description\Parameter;
use Guzzle\Service\Command\CommandInterface;
use Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor;

/**
* Location visitor used to serialize AWS query parameters (e.g. EC2, SES, SNS, SQS, etc) as POST fields
*/
class AwsQueryVisitor extends AbstractRequestVisitor
{
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);
}

/**
* Map nested parameters into the location_key based parameters
*
* @param array $value Value to map
* @param Parameter $param Parameter that holds information about the current key
* @param array $query Built up query string values
* @param string $prefix String to prepend to sub query values
*/
protected function customResolver($value, Parameter $param, array &$query, $prefix = '')
{
switch ($param->getType()) {
case 'object':
$this->resolveObject($param, $value, $prefix, $query);
break;
case 'array':
$this->resolveArray($param, $value, $prefix, $query);
break;
default:
$query[$prefix] = $param->filter($value);
}
}

/**
* Custom handling for objects
*
* @param Parameter $param Parameter for the object
* @param array $value Value that is set for this parameter
* @param string $prefix Prefix for the resulting key
* @param array $query Query string array passed by reference
*/
protected function resolveObject(Parameter $param, array $value, $prefix, array &$query)
{
// Maps are implemented using additional properties
$hasAdditionalProperties = ($param->getAdditionalProperties() instanceof Parameter);
$additionalPropertyCount = 0;

foreach ($value as $name => $v) {
if ($subParam = $param->getProperty($name)) {
// if the parameter was found by name as a regular property
$key = $prefix . '.' . $subParam->getWireName();
$this->customResolver($v, $subParam, $query, $key);
} elseif ($hasAdditionalProperties) {
// Handle map cases like &Attribute.1.Name=<name>&Attribute.1.Value=<value>
$additionalPropertyCount++;
$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 {
$query[$newPrefix] = $param->filter($v);
}
}
}
}

/**
* Custom handling for arrays
*
* @param Parameter $param Parameter for the object
* @param array $value Value that is set for this parameter
* @param string $prefix Prefix for the resulting key
* @param array $query Query string array passed by reference
*/
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;
if (is_array($v) && $items = $param->getItems()) {
$this->customResolver($v, $items, $query, $prefix . '.' . $index);
} else {
$query[$prefix . '.' . $index] = $param->filter($v);
}
}
}
}

View file

@ -0,0 +1,47 @@
<?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\Command;

use Guzzle\Service\Command\OperationCommand;
use Guzzle\Http\Curl\CurlHandle;

/**
* Adds AWS JSON body functionality to dynamically generated HTTP requests
*/
class JsonCommand extends OperationCommand
{
/**
* {@inheritdoc}
*/
protected function build()
{
parent::build();

// Ensure that the body of the request ALWAYS includes some JSON. By default, this is an empty object.
if (!$this->request->getBody()) {
$this->request->setBody('{}');
}

// Never send the Expect header when interacting with a JSON query service
$this->request->removeHeader('Expect');

// Always send JSON requests as a raw string rather than using streams to avoid issues with
// cURL error code 65: "necessary data rewind wasn't possible".
// This could be removed after PHP addresses https://bugs.php.net/bug.php?id=47204
$this->request->getCurlOptions()->set(CurlHandle::BODY_AS_STRING, true);
}
}

View file

@ -0,0 +1,53 @@
<?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\Command;

use Guzzle\Service\Command\OperationCommand;

/**
* Adds AWS Query service serialization
*/
class QueryCommand extends OperationCommand
{
/**
* @var AwsQueryVisitor
*/
protected static $queryVisitor;

/**
* @var XmlResponseLocationVisitor
*/
protected static $xmlVisitor;

/**
* Register the aws.query visitor
*/
protected function init()
{
// @codeCoverageIgnoreStart
if (!self::$queryVisitor) {
self::$queryVisitor = new AwsQueryVisitor();
}
if (!self::$xmlVisitor) {
self::$xmlVisitor = new XmlResponseLocationVisitor();
}
// @codeCoverageIgnoreEnd

$this->getRequestSerializer()->addVisitor('aws.query', self::$queryVisitor);
$this->getResponseParser()->addVisitor('xml', self::$xmlVisitor);
}
}

View file

@ -0,0 +1,74 @@
<?php

namespace Aws\Common\Command;

use Guzzle\Service\Description\Operation;
use Guzzle\Service\Command\CommandInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Service\Description\Parameter;
use Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor;

/**
* Class used for custom AWS XML response parsing of query services
*/
class XmlResponseLocationVisitor extends XmlVisitor
{
/**
* {@inheritdoc}
*/
public function before(CommandInterface $command, array &$result)
{
parent::before($command, $result);

// Unwrapped wrapped responses
$operation = $command->getOperation();
if ($operation->getServiceDescription()->getData('resultWrapped')) {
$wrappingNode = $operation->getName() . 'Result';
if (isset($result[$wrappingNode])) {
$result = $result[$wrappingNode] + $result;
unset($result[$wrappingNode]);
}
}
}

/**
* Accounts for wrapper nodes
* {@inheritdoc}
*/
public function visit(
CommandInterface $command,
Response $response,
Parameter $param,
&$value,
$context = null
) {
parent::visit($command, $response, $param, $value, $context);

// Account for wrapper nodes (e.g. RDS, ElastiCache, etc)
if ($param->getData('wrapper')) {
$wireName = $param->getWireName();
$value += $value[$wireName];
unset($value[$wireName]);
}
}

/**
* Filter used when converting XML maps into associative arrays in service descriptions
*
* @param array $value Value to filter
* @param string $entryName Name of each entry
* @param string $keyName Name of each key
* @param string $valueName Name of each value
*
* @return array Returns the map of the XML data
*/
public static function xmlMap($value, $entryName, $keyName, $valueName)
{
$result = array();
foreach ($value as $entry) {
$result[$entry[$keyName]] = $entry[$valueName];
}

return $result;
}
}

View file

@ -0,0 +1,136 @@
<?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\Credentials;

/**
* Abstract credentials decorator
*/
class AbstractCredentialsDecorator implements CredentialsInterface
{
/**
* @var CredentialsInterface Wrapped credentials object
*/
protected $credentials;

/**
* Constructs a new BasicAWSCredentials object, with the specified AWS
* access key and AWS secret key
*
* @param CredentialsInterface $credentials
*/
public function __construct(CredentialsInterface $credentials)
{
$this->credentials = $credentials;
}

/**
* {@inheritdoc}
*/
public function serialize()
{
return $this->credentials->serialize();
}

/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
$this->credentials = new Credentials('', '');
$this->credentials->unserialize($serialized);
}

/**
* {@inheritdoc}
*/
public function getAccessKeyId()
{
return $this->credentials->getAccessKeyId();
}

/**
* {@inheritdoc}
*/
public function getSecretKey()
{
return $this->credentials->getSecretKey();
}

/**
* {@inheritdoc}
*/
public function getSecurityToken()
{
return $this->credentials->getSecurityToken();
}

/**
* {@inheritdoc}
*/
public function getExpiration()
{
return $this->credentials->getExpiration();
}

/**
* {@inheritdoc}
*/
public function isExpired()
{
return $this->credentials->isExpired();
}

/**
* {@inheritdoc}
*/
public function setAccessKeyId($key)
{
$this->credentials->setAccessKeyId($key);

return $this;
}

/**
* {@inheritdoc}
*/
public function setSecretKey($secret)
{
$this->credentials->setSecretKey($secret);

return $this;
}

/**
* {@inheritdoc}
*/
public function setSecurityToken($token)
{
$this->credentials->setSecurityToken($token);

return $this;
}

/**
* {@inheritdoc}
*/
public function setExpiration($timestamp)
{
$this->credentials->setExpiration($timestamp);

return $this;
}
}

View file

@ -0,0 +1,76 @@
<?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\Credentials;

/**
* Abstract decorator to provide a foundation for refreshable credentials
*/
abstract class AbstractRefreshableCredentials extends AbstractCredentialsDecorator
{
/**
* {@inheritdoc}
*/
public function getAccessKeyId()
{
if ($this->credentials->isExpired()) {
$this->refresh();
}

return $this->credentials->getAccessKeyId();
}

/**
* {@inheritdoc}
*/
public function getSecretKey()
{
if ($this->credentials->isExpired()) {
$this->refresh();
}

return $this->credentials->getSecretKey();
}

/**
* {@inheritdoc}
*/
public function getSecurityToken()
{
if ($this->credentials->isExpired()) {
$this->refresh();
}

return $this->credentials->getSecurityToken();
}

/**
* {@inheritdoc}
*/
public function serialize()
{
if ($this->credentials->isExpired()) {
$this->refresh();
}

return $this->credentials->serialize();
}

/**
* Attempt to get new credentials
*/
abstract protected function refresh();
}

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\Common\Credentials;

use Guzzle\Cache\CacheAdapterInterface;

/**
* Credentials decorator used to implement caching credentials
*/
class CacheableCredentials extends AbstractRefreshableCredentials
{
/**
* @var CacheAdapterInterface Cache adapter used to store credentials
*/
protected $cache;

/**
* @var string Cache key used to store the credentials
*/
protected $cacheKey;

/**
* CacheableCredentials is a decorator that decorates other credentials
*
* @param CredentialsInterface $credentials Credentials to adapt
* @param CacheAdapterInterface $cache Cache to use to store credentials
* @param string $cacheKey Cache key of the credentials
*/
public function __construct(CredentialsInterface $credentials, CacheAdapterInterface $cache, $cacheKey)
{
$this->credentials = $credentials;
$this->cache = $cache;
$this->cacheKey = $cacheKey;
}

/**
* Attempt to get new credentials from cache or from the adapted object
*/
protected function refresh()
{
if (!$cache = $this->cache->fetch($this->cacheKey)) {
// The credentials were not found, so try again and cache if new
$this->credentials->getAccessKeyId();
if (!$this->credentials->isExpired()) {
// The credentials were updated, so cache them
$this->cache->save($this->cacheKey, $this->credentials, $this->credentials->getExpiration() - time());
}
} else {
// The credentials were found in cache, so update the adapter object
// if the cached credentials are not expired
if (!$cache->isExpired()) {
$this->credentials->setAccessKeyId($cache->getAccessKeyId());
$this->credentials->setSecretKey($cache->getSecretKey());
$this->credentials->setSecurityToken($cache->getSecurityToken());
$this->credentials->setExpiration($cache->getExpiration());
}
}
}
}

View file

@ -0,0 +1,337 @@
<?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\Credentials;

use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\RequiredExtensionNotLoadedException;
use Aws\Common\Exception\RuntimeException;
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
* pass in the AWS access key and secret access in the constructor.
*/
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 */
protected $key;

/** @var string AWS Secret Access Key */
protected $secret;

/** @var string AWS Security Token */
protected $token;

/** @var int Time to die of token */
protected $ttd;

/**
* Get the available keys for the factory method
*
* @return array
*/
public static function getConfigDefaults()
{
return array(
Options::KEY => null,
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
);
}

/**
* Factory method for creating new credentials. This factory method will
* create the appropriate credentials object with appropriate decorators
* based on the passed configuration options.
*
* @param array $config Options to use when instantiating the credentials
*
* @return CredentialsInterface
* @throws InvalidArgumentException If the caching options are invalid
* @throws RuntimeException If using the default cache and APC is disabled
*/
public static function factory($config = array())
{
// Add default key values
foreach (self::getConfigDefaults() as $key => $value) {
if (!isset($config[$key])) {
$config[$key] = $value;
}
}

// Start tracking the cache key
$cacheKey = $config[Options::CREDENTIALS_CACHE_KEY];

// Create the credentials object
if (!$config[Options::KEY] || !$config[Options::SECRET]) {
$credentials = self::createFromEnvironment($config);
// If no cache key was set, use the crc32 hostname of the server
$cacheKey = $cacheKey ?: 'credentials_' . crc32(gethostname());
} else {
// Instantiate using short or long term credentials
$credentials = new static(
$config[Options::KEY],
$config[Options::SECRET],
$config[Options::TOKEN],
$config[Options::TOKEN_TTD]
);
// If no cache key was set, use the access key ID
$cacheKey = $cacheKey ?: 'credentials_' . $config[Options::KEY];
}

// Check if the credentials are refreshable, and if so, configure caching
$cache = $config[Options::CREDENTIALS_CACHE];
if ($cacheKey && $cache) {
$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
*
* @param string $accessKeyId AWS access key ID
* @param string $secretAccessKey AWS secret access key
* @param string $token Security token to use
* @param int $expiration UNIX timestamp for when credentials expire
*/
public function __construct($accessKeyId, $secretAccessKey, $token = null, $expiration = null)
{
$this->key = trim($accessKeyId);
$this->secret = trim($secretAccessKey);
$this->token = $token;
$this->ttd = $expiration;
}

public function serialize()
{
return json_encode(array(
Options::KEY => $this->key,
Options::SECRET => $this->secret,
Options::TOKEN => $this->token,
Options::TOKEN_TTD => $this->ttd
));
}

public function unserialize($serialized)
{
$data = json_decode($serialized, true);
$this->key = $data[Options::KEY];
$this->secret = $data[Options::SECRET];
$this->token = $data[Options::TOKEN];
$this->ttd = $data[Options::TOKEN_TTD];
}

public function getAccessKeyId()
{
return $this->key;
}

public function getSecretKey()
{
return $this->secret;
}

public function getSecurityToken()
{
return $this->token;
}

public function getExpiration()
{
return $this->ttd;
}

public function isExpired()
{
return $this->ttd !== null && time() >= $this->ttd;
}

public function setAccessKeyId($key)
{
$this->key = $key;

return $this;
}

public function setSecretKey($secret)
{
$this->secret = $secret;

return $this;
}

public function setSecurityToken($token)
{
$this->token = $token;

return $this;
}

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,96 @@
<?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\Credentials;

/**
* Provides access to the AWS credentials used for accessing AWS services: AWS
* access key ID, secret access key, and security token. These credentials are
* used to securely sign requests to AWS services.
*/
interface CredentialsInterface extends \Serializable
{
/**
* Returns the AWS access key ID for this credentials object.
*
* @return string
*/
public function getAccessKeyId();

/**
* Returns the AWS secret access key for this credentials object.
*
* @return string
*/
public function getSecretKey();

/**
* Get the associated security token if available
*
* @return string|null
*/
public function getSecurityToken();

/**
* Get the UNIX timestamp in which the credentials will expire
*
* @return int|null
*/
public function getExpiration();

/**
* Set the AWS access key ID for this credentials object.
*
* @param string $key AWS access key ID
*
* @return self
*/
public function setAccessKeyId($key);

/**
* Set the AWS secret access key for this credentials object.
*
* @param string $secret AWS secret access key
*
* @return CredentialsInterface
*/
public function setSecretKey($secret);

/**
* Set the security token to use with this credentials object
*
* @param string $token Security token
*
* @return self
*/
public function setSecurityToken($token);

/**
* Set the UNIX timestamp in which the credentials will expire
*
* @param int $timestamp UNIX timestamp expiration
*
* @return self
*/
public function setExpiration($timestamp);

/**
* Check if the credentials are expired
*
* @return bool
*/
public function isExpired();
}

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

@ -0,0 +1,59 @@
<?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\Credentials;

use Aws\Common\InstanceMetadata\InstanceMetadataClient;
use Aws\Common\Exception\InstanceProfileCredentialsException;

/**
* Credentials decorator used to implement retrieving credentials from the
* EC2 metadata server
*/
class RefreshableInstanceProfileCredentials extends AbstractRefreshableCredentials
{
/**
* @var InstanceMetadataClient
*/
protected $client;

/**
* Constructs a new instance profile credentials decorator
*
* @param CredentialsInterface $credentials Credentials to adapt
* @param InstanceMetadataClient $client Client used to get new credentials
*/
public function __construct(CredentialsInterface $credentials, InstanceMetadataClient $client = null)
{
$this->credentials = $credentials;
$this->client = $client ?: InstanceMetadataClient::factory();
}

/**
* Attempt to get new credentials from the instance profile
*
* @throws InstanceProfileCredentialsException On error
*/
protected function refresh()
{
$credentials = $this->client->getInstanceProfileCredentials();
// Expire the token 1 minute before it actually expires to pre-fetch before expiring
$this->credentials->setAccessKeyId($credentials->getAccessKeyId())
->setSecretKey($credentials->getSecretKey())
->setSecurityToken($credentials->getSecurityToken())
->setExpiration($credentials->getExpiration());
}
}

55
libs/Aws/Common/Enum.php Normal file
View file

@ -0,0 +1,55 @@
<?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;

/**
* Represents an enumerable set of values
*/
abstract class Enum
{
/**
* @var array A cache of all enum values to increase performance
*/
protected static $cache = array();

/**
* Returns the names (or keys) of all of constants in the enum
*
* @return array
*/
public static function keys()
{
return array_keys(static::values());
}

/**
* Return the names and values of all the constants in the enum
*
* @return array
*/
public static function values()
{
$class = get_called_class();

if (!isset(self::$cache[$class])) {
$reflected = new \ReflectionClass($class);
self::$cache[$class] = $reflected->getConstants();
}

return self::$cache[$class];
}
}

View file

@ -0,0 +1,162 @@
<?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\Enum;

use Aws\Common\Enum;

/**
* Contains enumerable default factory options that can be passed to a client's factory method
*/
class ClientOptions extends Enum
{
/**
* AWS Access Key ID
*
* @deprecated Use "credentials" instead.
*/
const KEY = 'key';

/**
* AWS secret access key
*
* @deprecated Use "credentials" instead.
*/
const SECRET = 'secret';

/**
* Custom AWS security token to use with request authentication.
*
* @deprecated Use "credentials" instead.
*/
const TOKEN = 'token';

/**
* Provide an array of "key", "secret", and "token" or an instance of
* `Aws\Common\Credentials\CredentialsInterface`.
*/
const CREDENTIALS = 'credentials';

/**
* @var string Name of a credential profile to read from your ~/.aws/credentials file
*/
const PROFILE = 'profile';

/**
* @var string UNIX timestamp for when the custom credentials expire
*/
const TOKEN_TTD = 'token.ttd';

/**
* @var string Used to cache credentials when using providers that require HTTP requests. Set the trueto use the
* default APC cache or provide a `Guzzle\Cache\CacheAdapterInterface` object.
*/
const CREDENTIALS_CACHE = 'credentials.cache';

/**
* @var string Optional custom cache key to use with the credentials
*/
const CREDENTIALS_CACHE_KEY = 'credentials.cache.key';

/**
* @var string Pass this option to specify a custom `Guzzle\Http\ClientInterface` to use if your credentials require
* a HTTP request (e.g. RefreshableInstanceProfileCredentials)
*/
const CREDENTIALS_CLIENT = 'credentials.client';

/**
* @var string Region name (e.g. 'us-east-1', 'us-west-1', 'us-west-2', 'eu-west-1', etc...)
*/
const REGION = 'region';

/**
* @var string URI Scheme of the base URL (e.g. 'https', 'http').
*/
const SCHEME = 'scheme';

/**
* @var string Specify the name of the service
*/
const SERVICE = 'service';

/**
* Instead of using a `region` and `scheme`, you can specify a custom base
* URL for the client.
*
* @deprecated Use the "endpoint" option instead.
*/
const BASE_URL = 'base_url';

/**
* @var string You can optionally provide a custom signature implementation used to sign requests
*/
const SIGNATURE = 'signature';

/**
* @var string Set to explicitly override the service name used in signatures
*/
const SIGNATURE_SERVICE = 'signature.service';

/**
* @var string Set to explicitly override the region name used in signatures
*/
const SIGNATURE_REGION = 'signature.region';

/**
* @var string Option key holding an exponential backoff plugin
*/
const BACKOFF = 'client.backoff';

/**
* @var string `Guzzle\Log\LogAdapterInterface` object used to log backoff retries. Use 'debug' to emit PHP
* warnings when a retry is issued.
*/
const BACKOFF_LOGGER = 'client.backoff.logger';

/**
* @var string Optional template to use for exponential backoff log messages. See
* `Guzzle\Plugin\Backoff\BackoffLogger` for formatting information.
*/
const BACKOFF_LOGGER_TEMPLATE = 'client.backoff.logger.template';

/**
* Set to true to use the bundled CA cert or pass the full path to an SSL
* certificate bundle. This option should be modified when you encounter
* curl error code 60. Set to "system" to use the cacert bundle on your
* system.
*/
const SSL_CERT = 'ssl.certificate_authority';

/**
* @var string Service description to use with the client
*/
const SERVICE_DESCRIPTION = 'service.description';

/**
* @var string Whether or not modeled responses have transformations applied to them
*/
const MODEL_PROCESSING = 'command.model_processing';

/**
* @var bool Set to false to disable validation
*/
const VALIDATION = 'validation';

/**
* @var string API version used by the client
*/
const VERSION = 'version';
}

View file

@ -0,0 +1,31 @@
<?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\Enum;

use Aws\Common\Enum;

/**
* Contains enumerable date format values used in the SDK
*/
class DateFormat extends Enum
{
const ISO8601 = 'Ymd\THis\Z';
const ISO8601_S3 = 'Y-m-d\TH:i:s\Z';
const RFC1123 = 'D, d M Y H:i:s \G\M\T';
const RFC2822 = \DateTime::RFC2822;
const SHORT = 'Ymd';
}

View file

@ -0,0 +1,63 @@
<?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\Enum;

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.aws.amazon.com/general/latest/gr/rande.html AWS Regions and Endpoints
*/
class Region extends Enum
{
const US_EAST_1 = 'us-east-1';
const VIRGINIA = 'us-east-1';
const NORTHERN_VIRGINIA = 'us-east-1';

const US_WEST_1 = 'us-west-1';
const CALIFORNIA = 'us-west-1';
const NORTHERN_CALIFORNIA = 'us-west-1';

const US_WEST_2 = 'us-west-2';
const OREGON = 'us-west-2';

const EU_WEST_1 = 'eu-west-1';
const IRELAND = 'eu-west-1';
const EU_CENTRAL_1 = 'eu-central-1';
const FRANKFURT = 'eu-central-1';

const AP_SOUTHEAST_1 = 'ap-southeast-1';
const SINGAPORE = 'ap-southeast-1';

const AP_SOUTHEAST_2 = 'ap-southeast-2';
const SYDNEY = 'ap-southeast-2';

const AP_NORTHEAST_1 = 'ap-northeast-1';
const TOKYO = 'ap-northeast-1';

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,53 @@
<?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\Enum;

use Aws\Common\Enum;

/**
* Contains enumerable byte-size values
*/
class Size extends Enum
{
const B = 1;
const BYTE = 1;
const BYTES = 1;

const KB = 1024;
const KILOBYTE = 1024;
const KILOBYTES = 1024;

const MB = 1048576;
const MEGABYTE = 1048576;
const MEGABYTES = 1048576;

const GB = 1073741824;
const GIGABYTE = 1073741824;
const GIGABYTES = 1073741824;

const TB = 1099511627776;
const TERABYTE = 1099511627776;
const TERABYTES = 1099511627776;

const PB = 1125899906842624;
const PETABYTE = 1125899906842624;
const PETABYTES = 1125899906842624;

const EB = 1152921504606846976;
const EXABYTE = 1152921504606846976;
const EXABYTES = 1152921504606846976;
}

View file

@ -0,0 +1,46 @@
<?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\Enum;

use Aws\Common\Enum;

/**
* Contains enumerable time values
*/
class Time extends Enum
{
const SECOND = 1;
const SECONDS = 1;

const MINUTE = 60;
const MINUTES = 60;

const HOUR = 3600;
const HOURS = 3600;

const DAY = 86400;
const DAYS = 86400;

const WEEK = 604800;
const WEEKS = 604800;

const MONTH = 2592000;
const MONTHS = 2592000;

const YEAR = 31557600;
const YEARS = 31557600;
}

View file

@ -0,0 +1,55 @@
<?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\Enum;

use Aws\Common\Enum;

/**
* User-Agent header strings for various high level operations
*/
class UaString extends Enum
{
/**
* @var string Name of the option used to add to the UA string
*/
const OPTION = 'ua.append';

/**
* @var string Resource iterator
*/
const ITERATOR = 'ITR';

/**
* @var string Resource waiter
*/
const WAITER = 'WTR';

/**
* @var string Session handlers (e.g. Amazon DynamoDB session handler)
*/
const SESSION = 'SES';

/**
* @var string Multipart upload helper for Amazon S3
*/
const MULTIPART_UPLOAD = 'MUP';

/**
* @var string Command executed during a batch transfer
*/
const BATCH = 'BAT';
}

View file

@ -0,0 +1,30 @@
<?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;

/**
* "Marker Interface" implemented by every exception in the AWS SDK
*/
interface AwsExceptionInterface
{
public function getCode();
public function getLine();
public function getFile();
public function getMessage();
public function getPrevious();
public function getTrace();
}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL BadMethodCallException.
*/
class BadMethodCallException extends \BadMethodCallException implements AwsExceptionInterface {}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL DomainException.
*/
class DomainException extends \DomainException implements AwsExceptionInterface {}

View file

@ -0,0 +1,36 @@
<?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\Message\RequestInterface;
use Guzzle\Http\Message\Response;

/**
* Interface used to create AWS exception
*/
interface ExceptionFactoryInterface
{
/**
* Returns an AWS service specific exception
*
* @param RequestInterface $request Unsuccessful request
* @param Response $response Unsuccessful response that was encountered
*
* @return \Exception|AwsExceptionInterface
*/
public function fromResponse(RequestInterface $request, Response $response);
}

View file

@ -0,0 +1,59 @@
<?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\Common\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Converts generic Guzzle response exceptions into AWS specific exceptions
*/
class ExceptionListener implements EventSubscriberInterface
{
/**
* @var ExceptionFactoryInterface Factory used to create new exceptions
*/
protected $factory;

/**
* @param ExceptionFactoryInterface $factory Factory used to create exceptions
*/
public function __construct(ExceptionFactoryInterface $factory)
{
$this->factory = $factory;
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array('request.error' => array('onRequestError', -1));
}

/**
* Throws a more meaningful request exception if available
*
* @param Event $event Event emitted
*/
public function onRequestError(Event $event)
{
$e = $this->factory->fromResponse($event['request'], $event['response']);
$event->stopPropagation();
throw $e;
}
}

View file

@ -0,0 +1,50 @@
<?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 Aws\Common\Exception\RuntimeException;

/**
* Exception thrown when an error occurs with instance profile credentials
*/
class InstanceProfileCredentialsException extends RuntimeException
{
/**
* @var string
*/
protected $statusCode;

/**
* Set the error response code received from the instance metadata
*
* @param string $code Response code
*/
public function setStatusCode($code)
{
$this->statusCode = $code;
}

/**
* Get the error response code from the service
*
* @return string|null
*/
public function getStatusCode()
{
return $this->statusCode;
}
}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL InvalidArgumentException.
*/
class InvalidArgumentException extends \InvalidArgumentException implements AwsExceptionInterface {}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL LogicException.
*/
class LogicException extends \LogicException implements AwsExceptionInterface {}

View file

@ -0,0 +1,55 @@
<?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 Aws\Common\Model\MultipartUpload\TransferStateInterface;

/**
* Thrown when a {@see Aws\Common\MultipartUpload\TransferInterface} object encounters an error during transfer
*/
class MultipartUploadException extends RuntimeException
{
/**
* @var TransferStateInterface State of the transfer when the error was encountered
*/
protected $state;

/**
* @param TransferStateInterface $state Transfer state
* @param \Exception $exception Last encountered exception
*/
public function __construct(TransferStateInterface $state, \Exception $exception = null)
{
parent::__construct(
'An error was encountered while performing a multipart upload: ' . $exception->getMessage(),
0,
$exception
);

$this->state = $state;
}

/**
* Get the state of the transfer
*
* @return TransferStateInterface
*/
public function getState()
{
return $this->state;
}
}

View file

@ -0,0 +1,103 @@
<?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 Aws\Common\Exception\Parser\ExceptionParserInterface;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;

/**
* Attempts to create exceptions by inferring the name from the code and a base
* namespace that contains exceptions. Exception classes are expected to be in
* upper camelCase and always end in 'Exception'. 'Exception' will be appended
* if it is not present in the exception code.
*/
class NamespaceExceptionFactory implements ExceptionFactoryInterface
{
/**
* @var ExceptionParserInterface $parser Parser used to parse responses
*/
protected $parser;

/**
* @var string Base namespace containing exception classes
*/
protected $baseNamespace;

/**
* @var string Default class to instantiate if a match is not found
*/
protected $defaultException;

/**
* @param ExceptionParserInterface $parser Parser used to parse exceptions
* @param string $baseNamespace Namespace containing exceptions
* @param string $defaultException Default class to use if one is not mapped
*/
public function __construct(
ExceptionParserInterface $parser,
$baseNamespace,
$defaultException = 'Aws\Common\Exception\ServiceResponseException'
) {
$this->parser = $parser;
$this->baseNamespace = $baseNamespace;
$this->defaultException = $defaultException;
}

/**
* {@inheritdoc}
*/
public function fromResponse(RequestInterface $request, Response $response)
{
$parts = $this->parser->parse($request, $response);

// Removing leading 'AWS.' and embedded periods
$className = $this->baseNamespace . '\\' . str_replace(array('AWS.', '.'), '', $parts['code']);
if (substr($className, -9) !== 'Exception') {
$className .= 'Exception';
}

$className = class_exists($className) ? $className : $this->defaultException;

return $this->createException($className, $request, $response, $parts);
}

/**
* Create an prepare an exception object
*
* @param string $className Name of the class to create
* @param RequestInterface $request Request
* @param Response $response Response received
* @param array $parts Parsed exception data
*
* @return \Exception
*/
protected function createException($className, RequestInterface $request, Response $response, array $parts)
{
$class = new $className($parts['message']);

if ($class instanceof ServiceResponseException) {
$class->setExceptionCode($parts['code']);
$class->setExceptionType($parts['type']);
$class->setResponse($response);
$class->setRequest($request);
$class->setRequestId($parts['request_id']);
}

return $class;
}
}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL OverflowException.
*/
class OutOfBoundsException extends \OutOfBoundsException implements AwsExceptionInterface {}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL OverflowException.
*/
class OverflowException extends \OverflowException implements AwsExceptionInterface {}

View file

@ -0,0 +1,66 @@
<?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\Parser;

use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;

/**
* Parses JSON encoded exception responses
*/
abstract class AbstractJsonExceptionParser implements ExceptionParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(RequestInterface $request, Response $response)
{
// Build array of default error data
$data = array(
'code' => null,
'message' => null,
'type' => $response->isClientError() ? 'client' : 'server',
'request_id' => (string) $response->getHeader('x-amzn-RequestId'),
'parsed' => null
);

// Parse the json and normalize key casings
if (null !== $json = json_decode($response->getBody(true), true)) {
$data['parsed'] = array_change_key_case($json);
}

// Do additional, protocol-specific parsing and return the result
$data = $this->doParse($data, $response);

// Remove "Fault" suffix from exception names
if (isset($data['code']) && strpos($data['code'], 'Fault')) {
$data['code'] = preg_replace('/^([a-zA-Z]+)Fault$/', '$1', $data['code']);
}

return $data;
}

/**
* Pull relevant exception data out of the parsed json
*
* @param array $data The exception data
* @param Response $response The response from the service containing the error
*
* @return array
*/
abstract protected function doParse(array $data, Response $response);
}

View file

@ -0,0 +1,100 @@
<?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\Parser;

use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;

/**
* Parses default XML exception responses
*/
class DefaultXmlExceptionParser implements ExceptionParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(RequestInterface $request, Response $response)
{
$data = array(
'code' => null,
'message' => null,
'type' => $response->isClientError() ? 'client' : 'server',
'request_id' => null,
'parsed' => null
);

if ($body = $response->getBody(true)) {
$this->parseBody(new \SimpleXMLElement($body), $data);
} else {
$this->parseHeaders($request, $response, $data);
}

return $data;
}

/**
* Parses additional exception information from the response headers
*
* @param RequestInterface $request Request that was issued
* @param Response $response The response from the request
* @param array $data The current set of exception data
*/
protected function parseHeaders(RequestInterface $request, Response $response, array &$data)
{
$data['message'] = $response->getStatusCode() . ' ' . $response->getReasonPhrase();
if ($requestId = $response->getHeader('x-amz-request-id')) {
$data['request_id'] = $requestId;
$data['message'] .= " (Request-ID: $requestId)";
}
}

/**
* Parses additional exception information from the response body
*
* @param \SimpleXMLElement $body The response body as XML
* @param array $data The current set of exception data
*/
protected function parseBody(\SimpleXMLElement $body, array &$data)
{
$data['parsed'] = $body;

$namespaces = $body->getDocNamespaces();
if (isset($namespaces[''])) {
// Account for the default namespace being defined and PHP not being able to handle it :(
$body->registerXPathNamespace('ns', $namespaces['']);
$prefix = 'ns:';
} else {
$prefix = '';
}

if ($tempXml = $body->xpath("//{$prefix}Code[1]")) {
$data['code'] = (string) $tempXml[0];
}

if ($tempXml = $body->xpath("//{$prefix}Message[1]")) {
$data['message'] = (string) $tempXml[0];
}

$tempXml = $body->xpath("//{$prefix}RequestId[1]");
if (empty($tempXml)) {
$tempXml = $body->xpath("//{$prefix}RequestID[1]");
}
if (isset($tempXml[0])) {
$data['request_id'] = (string) $tempXml[0];
}
}
}

View file

@ -0,0 +1,42 @@
<?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\Parser;

use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;

/**
* Interface used to parse exceptions into an associative array of data
*/
interface ExceptionParserInterface
{
/**
* Parses an exception into an array of data containing at minimum the
* following array keys:
* - type: Exception type
* - code: Exception code
* - message: Exception message
* - request_id: Request ID
* - parsed: The parsed representation of the data (array, SimpleXMLElement, etc)
*
* @param RequestInterface $request
* @param Response $response Unsuccessful response
*
* @return array
*/
public function parse(RequestInterface $request, Response $response);
}

View file

@ -0,0 +1,41 @@
<?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\Parser;

use Guzzle\Http\Message\Response;

/**
* Parses JSON encoded exception responses from query services
*/
class JsonQueryExceptionParser extends AbstractJsonExceptionParser
{
/**
* {@inheritdoc}
*/
protected function doParse(array $data, Response $response)
{
if ($json = $data['parsed']) {
if (isset($json['__type'])) {
$parts = explode('#', $json['__type']);
$data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
}
$data['message'] = isset($json['message']) ? $json['message'] : null;
}

return $data;
}
}

View file

@ -0,0 +1,48 @@
<?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\Parser;

use Guzzle\Http\Message\Response;

/**
* Parses JSON encoded exception responses from REST services
*/
class JsonRestExceptionParser extends AbstractJsonExceptionParser
{
/**
* {@inheritdoc}
*/
protected function doParse(array $data, Response $response)
{
// Merge in error data from the JSON body
if ($json = $data['parsed']) {
$data = array_replace($data, $json);
}

// Correct error type from services like Amazon Glacier
if (!empty($data['type'])) {
$data['type'] = strtolower($data['type']);
}

// Retrieve the error code from services like Amazon Elastic Transcoder
if ($code = (string) $response->getHeader('x-amzn-ErrorType')) {
$data['code'] = substr($code, 0, strpos($code, ':'));
}

return $data;
}
}

View file

@ -0,0 +1,22 @@
<?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;

/**
* Thrown when a particular PHP extension is required to execute the guarded logic, but the extension is not loaded
*/
class RequiredExtensionNotLoadedException extends RuntimeException {}

View file

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL RuntimeException.
*/
class RuntimeException extends \RuntimeException implements AwsExceptionInterface {}

View file

@ -0,0 +1,183 @@
<?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\Message\RequestInterface;
use Guzzle\Http\Message\Response;

/**
* Default AWS exception
*/
class ServiceResponseException extends RuntimeException
{
/**
* @var Response Response
*/
protected $response;

/**
* @var RequestInterface Request
*/
protected $request;

/**
* @var string Request ID
*/
protected $requestId;

/**
* @var string Exception type (client / server)
*/
protected $exceptionType;

/**
* @var string Exception code
*/
protected $exceptionCode;

/**
* Set the exception code
*
* @param string $code Exception code
*/
public function setExceptionCode($code)
{
$this->exceptionCode = $code;
}

/**
* Get the exception code
*
* @return string|null
*/
public function getExceptionCode()
{
return $this->exceptionCode;
}

/**
* Set the exception type
*
* @param string $type Exception type
*/
public function setExceptionType($type)
{
$this->exceptionType = $type;
}

/**
* Get the exception type (one of client or server)
*
* @return string|null
*/
public function getExceptionType()
{
return $this->exceptionType;
}

/**
* Set the request ID
*
* @param string $id Request ID
*/
public function setRequestId($id)
{
$this->requestId = $id;
}

/**
* Get the Request ID
*
* @return string|null
*/
public function getRequestId()
{
return $this->requestId;
}

/**
* Set the associated response
*
* @param Response $response Response
*/
public function setResponse(Response $response)
{
$this->response = $response;
}

/**
* Get the associated response object
*
* @return Response|null
*/
public function getResponse()
{
return $this->response;
}

/**
* Set the associated request
*
* @param RequestInterface $request
*/
public function setRequest(RequestInterface $request)
{
$this->request = $request;
}

/**
* Get the associated request object
*
* @return RequestInterface|null
*/
public function getRequest()
{
return $this->request;
}

/**
* Get the status code of the response
*
* @return int|null
*/
public function getStatusCode()
{
return $this->response ? $this->response->getStatusCode() : null;
}

/**
* Cast to a string
*
* @return string
*/
public function __toString()
{
$message = get_class($this) . ': '
. 'AWS Error Code: ' . $this->getExceptionCode() . ', '
. 'Status Code: ' . $this->getStatusCode() . ', '
. 'AWS Request ID: ' . $this->getRequestId() . ', '
. 'AWS Error Type: ' . $this->getExceptionType() . ', '
. 'AWS Error Message: ' . $this->getMessage();

// Add the User-Agent if available
if ($this->request) {
$message .= ', ' . 'User-Agent: ' . $this->request->getHeader('User-Agent');
}

return $message;
}
}

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

@ -0,0 +1,22 @@
<?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;

/**
* AWS SDK namespaced version of the SPL UnexpectedValueException.
*/
class UnexpectedValueException extends \UnexpectedValueException implements AwsExceptionInterface {}

View file

@ -0,0 +1,67 @@
<?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\Facade;

use Aws\Common\Aws;

/**
* Base facade class that handles the delegation logic
*/
abstract class Facade implements FacadeInterface
{
/** @var Aws */
protected static $serviceBuilder;

/**
* Mounts the facades by extracting information from the service builder config and using creating class aliases
*
* @param string|null $targetNamespace Namespace that the facades should be mounted to. Defaults to global namespace
*
* @param Aws $serviceBuilder
*/
public static function mountFacades(Aws $serviceBuilder, $targetNamespace = null)
{
self::$serviceBuilder = $serviceBuilder;
require_once __DIR__ . '/facade-classes.php';
foreach ($serviceBuilder->getConfig() as $service) {
if (isset($service['alias'], $service['class'])) {
$facadeClass = __NAMESPACE__ . '\\' . $service['alias'];
$facadeAlias = ltrim($targetNamespace . '\\' . $service['alias'], '\\');
if (!class_exists($facadeAlias) && class_exists($facadeClass)) {
// @codeCoverageIgnoreStart
class_alias($facadeClass, $facadeAlias);
// @codeCoverageIgnoreEnd
}
}
}
}

/**
* Returns the instance of the client that the facade operates on
*
* @return \Aws\Common\Client\AwsClientInterface
*/
public static function getClient()
{
return self::$serviceBuilder->get(static::getServiceBuilderKey());
}

public static function __callStatic($method, $args)
{
return call_user_func_array(array(self::getClient(), $method), $args);
}
}

View file

@ -0,0 +1,32 @@
<?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\Facade;

/**
* Interface that defines a client facade. Facades are convenient static classes that allow you to run client methods
* statically on a default instance from the service builder. The facades themselves are aliased into the global
* namespace for ease of use.
*/
interface FacadeInterface
{
/**
* Returns the key used to access the client instance from the Service Builder
*
* @return string
*/
public static function getServiceBuilderKey();
}

View file

@ -0,0 +1,283 @@
<?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\Facade;

/**
* The following classes are used to implement the static client facades and are aliased into the global namespaced. We
* discourage the use of these classes directly by their full namespace since they are not autoloaded and are considered
* an implementation detail that could possibly be changed in the future.
*/

// @codeCoverageIgnoreStart

class AutoScaling extends Facade
{
public static function getServiceBuilderKey()
{
return 'autoscaling';
}
}

class CloudFormation extends Facade
{
public static function getServiceBuilderKey()
{
return 'cloudformation';
}
}

class CloudFront extends Facade
{
public static function getServiceBuilderKey()
{
return 'cloudfront';
}
}

class CloudSearch extends Facade
{
public static function getServiceBuilderKey()
{
return 'cloudsearch';
}
}

class CloudTrail extends Facade
{
public static function getServiceBuilderKey()
{
return 'cloudtrail';
}
}

class CloudWatch extends Facade
{
public static function getServiceBuilderKey()
{
return 'cloudwatch';
}
}

class DataPipeline extends Facade
{
public static function getServiceBuilderKey()
{
return 'datapipeline';
}
}

class DirectConnect extends Facade
{
public static function getServiceBuilderKey()
{
return 'directconnect';
}
}

class DynamoDb extends Facade
{
public static function getServiceBuilderKey()
{
return 'dynamodb';
}
}

class Ec2 extends Facade
{
public static function getServiceBuilderKey()
{
return 'ec2';
}
}

class ElastiCache extends Facade
{
public static function getServiceBuilderKey()
{
return 'elasticache';
}
}

class ElasticBeanstalk extends Facade
{
public static function getServiceBuilderKey()
{
return 'elasticbeanstalk';
}
}

class ElasticLoadBalancing extends Facade
{
public static function getServiceBuilderKey()
{
return 'elasticloadbalancing';
}
}

class ElasticTranscoder extends Facade
{
public static function getServiceBuilderKey()
{
return 'elastictranscoder';
}
}

class Emr extends Facade
{
public static function getServiceBuilderKey()
{
return 'emr';
}
}

class Glacier extends Facade
{
public static function getServiceBuilderKey()
{
return 'glacier';
}
}

class Iam extends Facade
{
public static function getServiceBuilderKey()
{
return 'iam';
}
}

class ImportExport extends Facade
{
public static function getServiceBuilderKey()
{
return 'importexport';
}
}

class Kinesis extends Facade
{
public static function getServiceBuilderKey()
{
return 'kinesis';
}
}

class OpsWorks extends Facade
{
public static function getServiceBuilderKey()
{
return 'opsworks';
}
}

class Rds extends Facade
{
public static function getServiceBuilderKey()
{
return 'rds';
}
}

class Redshift extends Facade
{
public static function getServiceBuilderKey()
{
return 'redshift';
}
}

class Route53 extends Facade
{
public static function getServiceBuilderKey()
{
return 'route53';
}
}

class S3 extends Facade
{
public static function getServiceBuilderKey()
{
return 's3';
}
}

class SimpleDb extends Facade
{
public static function getServiceBuilderKey()
{
return 'sdb';
}
}

class Ses extends Facade
{
public static function getServiceBuilderKey()
{
return 'ses';
}
}

class Sns extends Facade
{
public static function getServiceBuilderKey()
{
return 'sns';
}
}

class Sqs extends Facade
{
public static function getServiceBuilderKey()
{
return 'sqs';
}
}

class StorageGateway extends Facade
{
public static function getServiceBuilderKey()
{
return 'storagegateway';
}
}

class Sts extends Facade
{
public static function getServiceBuilderKey()
{
return 'sts';
}
}

class Support extends Facade
{
public static function getServiceBuilderKey()
{
return 'support';
}
}

class Swf extends Facade
{
public static function getServiceBuilderKey()
{
return 'swf';
}
}

// @codeCoverageIgnoreEnd

View file

@ -0,0 +1,87 @@
<?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\Hash;

use Aws\Common\Exception\LogicException;

/**
* Encapsulates the creation of a hash from streamed chunks of data
*/
class ChunkHash implements ChunkHashInterface
{
/**
* @var resource The hash context as created by `hash_init()`
*/
protected $context;

/**
* @var string The resulting hash in hex form
*/
protected $hash;

/**
* @var string The resulting hash in binary form
*/
protected $hashRaw;

/**
* {@inheritdoc}
*/
public function __construct($algorithm = self::DEFAULT_ALGORITHM)
{
HashUtils::validateAlgorithm($algorithm);
$this->context = hash_init($algorithm);
}

/**
* {@inheritdoc}
*/
public function addData($data)
{
if (!$this->context) {
throw new LogicException('You may not add more data to a finalized chunk hash.');
}

hash_update($this->context, $data);

return $this;
}

/**
* {@inheritdoc}
*/
public function getHash($returnBinaryForm = false)
{
if (!$this->hash) {
$this->hashRaw = hash_final($this->context, true);
$this->hash = HashUtils::binToHex($this->hashRaw);
$this->context = null;
}

return $returnBinaryForm ? $this->hashRaw : $this->hash;
}

/**
* {@inheritdoc}
*/
public function __clone()
{
if ($this->context) {
$this->context = hash_copy($this->context);
}
}
}

View file

@ -0,0 +1,52 @@
<?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\Hash;

/**
* Interface for objects that encapsulate the creation of a hash from streamed chunks of data
*/
interface ChunkHashInterface
{
const DEFAULT_ALGORITHM = 'sha256';

/**
* Constructs the chunk hash and sets the algorithm to use for hashing
*
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return self
*/
public function __construct($algorithm = 'sha256');

/**
* Add a chunk of data to be hashed
*
* @param string $data Data to be hashed
*
* @return self
*/
public function addData($data);

/**
* Return the results of the hash
*
* @param bool $returnBinaryForm If true, returns the hash in binary form instead of hex form
*
* @return string
*/
public function getHash($returnBinaryForm = false);
}

View file

@ -0,0 +1,76 @@
<?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\Hash;

use Aws\Common\Exception\InvalidArgumentException;

/**
* Contains hashing utilities
*/
class HashUtils
{
/**
* Converts a hash in hex form to binary form
*
* @param string $hash Hash in hex form
*
* @return string Hash in binary form
*/
public static function hexToBin($hash)
{
// If using PHP 5.4, there is a native function to convert from hex to binary
static $useNative;
if ($useNative === null) {
$useNative = function_exists('hex2bin');
}

if (!$useNative && strlen($hash) % 2 !== 0) {
$hash = '0' . $hash;
}

return $useNative ? hex2bin($hash) : pack("H*", $hash);
}

/**
* Converts a hash in binary form to hex form
*
* @param string $hash Hash in binary form
*
* @return string Hash in hex form
*/
public static function binToHex($hash)
{
return bin2hex($hash);
}

/**
* Checks if the algorithm specified exists and throws an exception if it does not
*
* @param string $algorithm Name of the algorithm to validate
*
* @return bool
* @throws InvalidArgumentException if the algorithm doesn't exist
*/
public static function validateAlgorithm($algorithm)
{
if (!in_array($algorithm, hash_algos(), true)) {
throw new InvalidArgumentException("The hashing algorithm specified ({$algorithm}) does not exist.");
}

return true;
}
}

View file

@ -0,0 +1,195 @@
<?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\Hash;

use Aws\Common\Enum\Size;
use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\LogicException;
use Guzzle\Http\EntityBody;

/**
* Encapsulates the creation of a tree hash from streamed chunks of data
*/
class TreeHash implements ChunkHashInterface
{
/**
* @var string The algorithm used for hashing
*/
protected $algorithm;

/**
* @var array Set of binary checksums from which the tree hash is derived
*/
protected $checksums = array();

/**
* @var string The resulting hash in hex form
*/
protected $hash;

/**
* @var string The resulting hash in binary form
*/
protected $hashRaw;

/**
* Create a tree hash from an array of existing tree hash checksums
*
* @param array $checksums Set of checksums
* @param bool $inBinaryForm Whether or not the checksums are already in binary form
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return TreeHash
*/
public static function fromChecksums(array $checksums, $inBinaryForm = false, $algorithm = self::DEFAULT_ALGORITHM)
{
$treeHash = new self($algorithm);

// Convert checksums to binary form if provided in hex form and add them to the tree hash
$treeHash->checksums = $inBinaryForm ? $checksums : array_map('Aws\Common\Hash\HashUtils::hexToBin', $checksums);

// Pre-calculate hash
$treeHash->getHash();

return $treeHash;
}

/**
* Create a tree hash from a content body
*
* @param string|resource|EntityBody $content Content to create a tree hash for
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return TreeHash
*/
public static function fromContent($content, $algorithm = self::DEFAULT_ALGORITHM)
{
$treeHash = new self($algorithm);

// Read the data in 1MB chunks and add to tree hash
$content = EntityBody::factory($content);
while ($data = $content->read(Size::MB)) {
$treeHash->addData($data);
}

// Pre-calculate hash
$treeHash->getHash();

return $treeHash;
}

/**
* Validates an entity body with a tree hash checksum
*
* @param string|resource|EntityBody $content Content to create a tree hash for
* @param string $checksum The checksum to use for validation
* @param string $algorithm A valid hash algorithm name as returned by `hash_algos()`
*
* @return bool
*/
public static function validateChecksum($content, $checksum, $algorithm = self::DEFAULT_ALGORITHM)
{
$treeHash = self::fromContent($content, $algorithm);

return ($checksum === $treeHash->getHash());
}

/**
* {@inheritdoc}
*/
public function __construct($algorithm = self::DEFAULT_ALGORITHM)
{
HashUtils::validateAlgorithm($algorithm);
$this->algorithm = $algorithm;
}

/**
* {@inheritdoc}
* @throws LogicException if the root tree hash is already calculated
* @throws InvalidArgumentException if the data is larger than 1MB
*/
public function addData($data)
{
// Error if hash is already calculated
if ($this->hash) {
throw new LogicException('You may not add more data to a finalized tree hash.');
}

// Make sure that only 1MB chunks or smaller get passed in
if (strlen($data) > Size::MB) {
throw new InvalidArgumentException('The chunk of data added is too large for tree hashing.');
}

// Store the raw hash of this data segment
$this->checksums[] = hash($this->algorithm, $data, true);

return $this;
}

/**
* Add a checksum to the tree hash directly
*
* @param string $checksum The checksum to add
* @param bool $inBinaryForm Whether or not the checksum is already in binary form
*
* @return self
* @throws LogicException if the root tree hash is already calculated
*/
public function addChecksum($checksum, $inBinaryForm = false)
{
// Error if hash is already calculated
if ($this->hash) {
throw new LogicException('You may not add more checksums to a finalized tree hash.');
}

// Convert the checksum to binary form if necessary
$this->checksums[] = $inBinaryForm ? $checksum : HashUtils::hexToBin($checksum);

return $this;
}

/**
* {@inheritdoc}
*/
public function getHash($returnBinaryForm = false)
{
if (!$this->hash) {
// Perform hashes up the tree to arrive at the root checksum of the tree hash
$hashes = $this->checksums;
while (count($hashes) > 1) {
$sets = array_chunk($hashes, 2);
$hashes = array();
foreach ($sets as $set) {
$hashes[] = (count($set) === 1) ? $set[0] : hash($this->algorithm, $set[0] . $set[1], true);
}
}

$this->hashRaw = $hashes[0];
$this->hash = HashUtils::binToHex($this->hashRaw);
}

return $returnBinaryForm ? $this->hashRaw : $this->hash;
}

/**
* @return array Array of raw checksums composing the tree hash
*/
public function getChecksums()
{
return $this->checksums;
}
}

View file

@ -0,0 +1,85 @@
<?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;

use Guzzle\Http\Url;

/**
* Utility class for parsing regions and services from URLs
*/
class HostNameUtils
{
const DEFAULT_REGION = 'us-east-1';
const DEFAULT_GOV_REGION = 'us-gov-west-1';

/**
* Parse the AWS region name from a URL
*
*
* @param Url $url HTTP URL
*
* @return string
* @link http://docs.aws.amazon.com/general/latest/gr/rande.html
*/
public static function parseRegionName(Url $url)
{
// If we don't recognize the domain, just return the default
if (substr($url->getHost(), -14) != '.amazonaws.com') {
return self::DEFAULT_REGION;
}

$serviceAndRegion = substr($url->getHost(), 0, -14);
// Special handling for S3 regions
$separator = strpos($serviceAndRegion, 's3') === 0 ? '-' : '.';
$separatorPos = strpos($serviceAndRegion, $separator);

// If don't detect a separator, then return the default region
if ($separatorPos === false) {
return self::DEFAULT_REGION;
}

$region = substr($serviceAndRegion, $separatorPos + 1);

// All GOV regions currently use the default GOV region
if ($region == 'us-gov') {
return self::DEFAULT_GOV_REGION;
}

return $region;
}

/**
* Parse the AWS service name from a URL
*
* @param Url $url HTTP URL
*
* @return string Returns a service name (or empty string)
* @link http://docs.aws.amazon.com/general/latest/gr/rande.html
*/
public static function parseServiceName(Url $url)
{
// The service name is the first part of the host
$parts = explode('.', $url->getHost(), 2);

// Special handling for S3
if (stripos($parts[0], 's3') === 0) {
return 's3';
}

return $parts[0];
}
}

View file

@ -0,0 +1,102 @@
<?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\InstanceMetadata;

use Aws\Common\Enum\ClientOptions as Options;
use Aws\Common\Exception\InstanceProfileCredentialsException;
use Aws\Common\Credentials\Credentials;
use Aws\Common\Client\AbstractClient;
use Guzzle\Common\Collection;
use Guzzle\Http\Message\RequestFactory;

/**
* Client used for interacting with the Amazon EC2 instance metadata server
*/
class InstanceMetadataClient extends AbstractClient
{
/**
* Factory method to create a new InstanceMetadataClient using an array
* of configuration options.
*
* The configuration options accepts the following array keys and values:
* - base_url: Override the base URL of the instance metadata server
* - version: Version of the metadata server to interact with
*
* @param array|Collection $config Configuration options
*
* @return InstanceMetadataClient
*/
public static function factory($config = array())
{
$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);
}

/**
* Constructor override
*/
public function __construct(Collection $config)
{
$this->setConfig($config);
$this->setBaseUrl($config->get(Options::BASE_URL));
$this->defaultHeaders = new Collection();
$this->setRequestFactory(RequestFactory::getInstance());
}

/**
* Get instance profile credentials
*
* @return Credentials
* @throws InstanceProfileCredentialsException
*/
public function getInstanceProfileCredentials()
{
try {
$request = $this->get('meta-data/iam/security-credentials/');
$credentials = trim($request->send()->getBody(true));
$result = $this->get("meta-data/iam/security-credentials/{$credentials}")->send()->json();
} catch (\Exception $e) {
$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. (%s)', $e->getMessage());
throw new InstanceProfileCredentialsException($message, $e->getCode());
}

// Ensure that the status code was successful
if ($result['Code'] !== 'Success') {
$e = new InstanceProfileCredentialsException('Unexpected response code: ' . $result['Code']);
$e->setStatusCode($result['Code']);
throw $e;
}

return new Credentials(
$result['AccessKeyId'],
$result['SecretAccessKey'],
$result['Token'],
strtotime($result['Expiration'])
);
}
}

View file

@ -0,0 +1,50 @@
<?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\InstanceMetadata\Waiter;

use Aws\Common\Waiter\AbstractResourceWaiter;
use Guzzle\Http\Exception\CurlException;

/**
* Waits until the instance metadata service is responding. Will send up to
* 4 requests with a 5 second delay between each try. Each try can last up to
* 11 seconds to complete if the service is not responding.
*
* @codeCoverageIgnore
*/
class ServiceAvailable extends AbstractResourceWaiter
{
protected $interval = 5;
protected $maxAttempts = 4;

/**
* {@inheritdoc}
*/
public function doWait()
{
$request = $this->client->get();
try {
$request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, 10)
->set(CURLOPT_TIMEOUT, 10);
$request->send();

return true;
} catch (CurlException $e) {
return false;
}
}
}

View file

@ -0,0 +1,169 @@
<?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\Iterator;

use Aws\Common\Enum\UaString as Ua;
use Aws\Common\Exception\RuntimeException;
Use Guzzle\Service\Resource\Model;
use Guzzle\Service\Resource\ResourceIterator;

/**
* Iterate over a client command
*/
class AwsResourceIterator extends ResourceIterator
{
/**
* @var Model Result of a command
*/
protected $lastResult = null;

/**
* 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
*/
public function getLastResult()
{
return $this->lastResult;
}

/**
* {@inheritdoc}
* This AWS specific version of the resource iterator provides a default implementation of the typical AWS iterator
* process. It relies on configuration and extension to implement the operation-specific logic of handling results
* and nextTokens. This method will loop until resources are acquired or there are no more iterations available.
*/
protected function sendRequest()
{
do {
// Prepare the request including setting the next token
$this->prepareRequest();
if ($this->nextToken) {
$this->applyNextToken();
}

// Execute the request and handle the results
$this->command->add(Ua::OPTION, Ua::ITERATOR);
$this->lastResult = $this->command->getResult();
$resources = $this->handleResults($this->lastResult);
$this->determineNextToken($this->lastResult);

// If no resources collected, prepare to reiterate before yielding
if ($reiterate = empty($resources) && $this->nextToken) {
$this->command = clone $this->originalCommand;
}
} while ($reiterate);

return $resources;
}

protected function prepareRequest()
{
// Get the limit parameter key to set
$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) {
$realLimit = min($limit, $pageSize);
$this->command->set($limitKey, $realLimit);
}
}
}

protected function handleResults(Model $result)
{
$results = array();

// Get the result key that contains the results
if ($resultKey = $this->get('result_key')) {
$results = $this->getValueFromResult($result, $resultKey) ?: array();
}

return $results;
}

protected function applyNextToken()
{
// Get the token parameter key to set
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)) {
foreach (array_combine($tokenParam, $this->nextToken) as $param => $token) {
$this->command->set($param, $token);
}
} else {
throw new RuntimeException('The definition of the iterator\'s token parameter and the actual token '
. 'value are not compatible.');
}
} else {
$this->command->set($tokenParam, $this->nextToken);
}
}
}

protected function determineNextToken(Model $result)
{
$this->nextToken = null;

// 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('output_token')) {
// Get the next token's value. Works with multi-value tokens
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

@ -0,0 +1,106 @@
<?php

namespace Aws\Common\Iterator;

use Aws\Common\Exception\InvalidArgumentException;
use Guzzle\Common\Collection;
use Guzzle\Service\Command\CommandInterface;
use Guzzle\Service\Resource\ResourceIteratorFactoryInterface;

/**
* Resource iterator factory used to instantiate the default AWS resource iterator with the correct configuration or
* use a concrete iterator class if one exists
*/
class AwsResourceIteratorFactory implements ResourceIteratorFactoryInterface
{
/**
* @var array Default configuration values for iterators
*/
protected static $defaultIteratorConfig = array(
'input_token' => null,
'output_token' => null,
'limit_key' => null,
'result_key' => null,
'more_results' => null,
);

/**
* @var array Legacy configuration options mapped to their new names
*/
private static $legacyConfigOptions = array(
'token_param' => 'input_token',
'token_key' => 'output_token',
'limit_param' => 'limit_key',
'more_key' => 'more_results',
);

/**
* @var array Iterator configuration for each iterable operation
*/
protected $config;

/**
* @var ResourceIteratorFactoryInterface Another factory that will be used first to instantiate the iterator
*/
protected $primaryIteratorFactory;

/**
* @param array $config An array of configuration values for the factory
* @param ResourceIteratorFactoryInterface $primaryIteratorFactory Another factory to use for chain of command
*/
public function __construct(array $config, ResourceIteratorFactoryInterface $primaryIteratorFactory = null)
{
$this->primaryIteratorFactory = $primaryIteratorFactory;
$this->config = array();
foreach ($config as $name => $operation) {
$this->config[$name] = $operation + self::$defaultIteratorConfig;
}
}

public function build(CommandInterface $command, array $options = array())
{
// Get the configuration data for the command
$commandName = $command->getName();
$commandSupported = isset($this->config[$commandName]);
$options = $this->translateLegacyConfigOptions($options);
$options += $commandSupported ? $this->config[$commandName] : array();

// 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 (!$commandSupported) {
throw new InvalidArgumentException("Iterator was not found for {$commandName}.");
} else {
// Instantiate a generic AWS resource iterator
$iterator = new AwsResourceIterator($command, $options);
}

return $iterator;
}

public function canBuild(CommandInterface $command)
{
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

@ -0,0 +1,270 @@
<?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\Model\MultipartUpload;

use Aws\Common\Client\AwsClientInterface;
use Aws\Common\Exception\MultipartUploadException;
use Aws\Common\Exception\RuntimeException;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Http\EntityBody;
use Guzzle\Http\EntityBodyInterface;
use Guzzle\Service\Command\OperationCommand;
use Guzzle\Service\Resource\Model;

/**
* Abstract class for transfer commonalities
*/
abstract class AbstractTransfer extends AbstractHasDispatcher implements TransferInterface
{
const BEFORE_UPLOAD = 'multipart_upload.before_upload';
const AFTER_UPLOAD = 'multipart_upload.after_upload';
const BEFORE_PART_UPLOAD = 'multipart_upload.before_part_upload';
const AFTER_PART_UPLOAD = 'multipart_upload.after_part_upload';
const AFTER_ABORT = 'multipart_upload.after_abort';
const AFTER_COMPLETE = 'multipart_upload.after_complete';

/**
* @var AwsClientInterface Client used for the transfers
*/
protected $client;

/**
* @var TransferStateInterface State of the transfer
*/
protected $state;

/**
* @var EntityBody Data source of the transfer
*/
protected $source;

/**
* @var array Associative array of options
*/
protected $options;

/**
* @var int Size of each part to upload
*/
protected $partSize;

/**
* @var bool Whether or not the transfer has been stopped
*/
protected $stopped = false;

/**
* Construct a new transfer object
*
* @param AwsClientInterface $client Client used for the transfers
* @param TransferStateInterface $state State used to track transfer
* @param EntityBody $source Data source of the transfer
* @param array $options Array of options to apply
*/
public function __construct(
AwsClientInterface $client,
TransferStateInterface $state,
EntityBody $source,
array $options = array()
) {
$this->client = $client;
$this->state = $state;
$this->source = $source;
$this->options = $options;

$this->init();

$this->partSize = $this->calculatePartSize();
}

public function __invoke()
{
return $this->upload();
}

/**
* {@inheritdoc}
*/
public static function getAllEvents()
{
return array(
self::BEFORE_PART_UPLOAD,
self::AFTER_UPLOAD,
self::BEFORE_PART_UPLOAD,
self::AFTER_PART_UPLOAD,
self::AFTER_ABORT,
self::AFTER_COMPLETE
);
}

/**
* {@inheritdoc}
*/
public function abort()
{
$command = $this->getAbortCommand();
$result = $command->getResult();

$this->state->setAborted(true);
$this->stop();
$this->dispatch(self::AFTER_ABORT, $this->getEventData($command));

return $result;
}

/**
* {@inheritdoc}
*/
public function stop()
{
$this->stopped = true;

return $this->state;
}

/**
* {@inheritdoc}
*/
public function getState()
{
return $this->state;
}

/**
* Get the array of options associated with the transfer
*
* @return array
*/
public function getOptions()
{
return $this->options;
}

/**
* Set an option on the transfer
*
* @param string $option Name of the option
* @param mixed $value Value to set
*
* @return self
*/
public function setOption($option, $value)
{
$this->options[$option] = $value;

return $this;
}

/**
* Get the source body of the upload
*
* @return EntityBodyInterface
*/
public function getSource()
{
return $this->source;
}

/**
* {@inheritdoc}
* @throws MultipartUploadException when an error is encountered. Use getLastException() to get more information.
* @throws RuntimeException when attempting to upload an aborted transfer
*/
public function upload()
{
if ($this->state->isAborted()) {
throw new RuntimeException('The transfer has been aborted and cannot be uploaded');
}

$this->stopped = false;
$eventData = $this->getEventData();
$this->dispatch(self::BEFORE_UPLOAD, $eventData);

try {
$this->transfer();
$this->dispatch(self::AFTER_UPLOAD, $eventData);

if ($this->stopped) {
return null;
} else {
$result = $this->complete();
$this->dispatch(self::AFTER_COMPLETE, $eventData);
}
} catch (\Exception $e) {
throw new MultipartUploadException($this->state, $e);
}

return $result;
}

/**
* Get an array used for event notifications
*
* @param OperationCommand $command Command to include in event data
*
* @return array
*/
protected function getEventData(OperationCommand $command = null)
{
$data = array(
'transfer' => $this,
'source' => $this->source,
'options' => $this->options,
'client' => $this->client,
'part_size' => $this->partSize,
'state' => $this->state
);

if ($command) {
$data['command'] = $command;
}

return $data;
}

/**
* Hook to initialize the transfer
*/
protected function init() {}

/**
* Determine the upload part size based on the size of the source data and
* taking into account the acceptable minimum and maximum part sizes.
*
* @return int The part size
*/
abstract protected function calculatePartSize();

/**
* Complete the multipart upload
*
* @return Model Returns the result of the complete multipart upload command
*/
abstract protected function complete();

/**
* Hook to implement in subclasses to perform the actual transfer
*/
abstract protected function transfer();

/**
* Fetches the abort command fom the concrete implementation
*
* @return OperationCommand
*/
abstract protected function getAbortCommand();
}

View file

@ -0,0 +1,164 @@
<?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\Model\MultipartUpload;

use Aws\Common\Exception\RuntimeException;

/**
* State of a multipart upload
*/
abstract class AbstractTransferState implements TransferStateInterface
{
/**
* @var UploadIdInterface Object holding params used to identity the upload part
*/
protected $uploadId;

/**
* @var array Array of parts where the part number is the index
*/
protected $parts = array();

/**
* @var bool Whether or not the transfer was aborted
*/
protected $aborted = false;

/**
* Construct a new transfer state object
*
* @param UploadIdInterface $uploadId Upload identifier object
*/
public function __construct(UploadIdInterface $uploadId)
{
$this->uploadId = $uploadId;
}

/**
* {@inheritdoc}
*/
public function getUploadId()
{
return $this->uploadId;
}

/**
* Get a data value from the transfer state's uploadId
*
* @param string $key Key to retrieve (e.g. Bucket, Key, UploadId, etc)
*
* @return string|null
*/
public function getFromId($key)
{
$params = $this->uploadId->toParams();

return isset($params[$key]) ? $params[$key] : null;
}

/**
* {@inheritdoc}
*/
public function getPart($partNumber)
{
return isset($this->parts[$partNumber]) ? $this->parts[$partNumber] : null;
}

/**
* {@inheritdoc}
*/
public function addPart(UploadPartInterface $part)
{
$partNumber = $part->getPartNumber();
$this->parts[$partNumber] = $part;

return $this;
}

/**
* {@inheritdoc}
*/
public function hasPart($partNumber)
{
return isset($this->parts[$partNumber]);
}

/**
* {@inheritdoc}
*/
public function getPartNumbers()
{
return array_keys($this->parts);
}

/**
* {@inheritdoc}
*/
public function setAborted($aborted)
{
$this->aborted = (bool) $aborted;

return $this;
}

/**
* {@inheritdoc}
*/
public function isAborted()
{
return $this->aborted;
}

/**
* {@inheritdoc}
*/
public function count()
{
return count($this->parts);
}

/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->parts);
}

/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize(get_object_vars($this));
}

/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
$data = unserialize($serialized);
foreach (get_object_vars($this) as $property => $oldValue) {
if (array_key_exists($property, $data)) {
$this->{$property} = $data[$property];
} else {
throw new RuntimeException("The {$property} property could be restored during unserialization.");
}
}
}
}

View file

@ -0,0 +1,148 @@
<?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\Model\MultipartUpload;

use Aws\Common\Client\AwsClientInterface;
use Aws\Common\Exception\InvalidArgumentException;
use Guzzle\Http\EntityBody;

/**
* Easily create a multipart uploader used to quickly and reliably upload a
* large file or data stream to Amazon S3 using multipart uploads
*/
abstract class AbstractUploadBuilder
{
/**
* @var AwsClientInterface Client used to transfer requests
*/
protected $client;

/**
* @var TransferStateInterface State of the transfer
*/
protected $state;

/**
* @var EntityBody Source of the data
*/
protected $source;

/**
* @var array Array of headers to set on the object
*/
protected $headers = array();

/**
* Return a new instance of the UploadBuilder
*
* @return static
*/
public static function newInstance()
{
return new static;
}

/**
* Set the client used to connect to the AWS service
*
* @param AwsClientInterface $client Client to use
*
* @return $this
*/
public function setClient(AwsClientInterface $client)
{
$this->client = $client;

return $this;
}

/**
* Set the state of the upload. This is useful for resuming from a previously started multipart upload.
* You must use a local file stream as the data source if you wish to resume from a previous upload.
*
* @param TransferStateInterface|string $state Pass a TransferStateInterface object or the ID of the initiated
* multipart upload. When an ID is passed, the builder will create a
* state object using the data from a ListParts API response.
*
* @return $this
*/
public function resumeFrom($state)
{
$this->state = $state;

return $this;
}

/**
* Set the data source of the transfer
*
* @param resource|string|EntityBody $source Source of the transfer. Pass a string to transfer from a file on disk.
* You can also stream from a resource returned from fopen or a Guzzle
* {@see EntityBody} object.
*
* @return $this
* @throws InvalidArgumentException when the source cannot be found or opened
*/
public function setSource($source)
{
// Use the contents of a file as the data source
if (is_string($source)) {
if (!file_exists($source)) {
throw new InvalidArgumentException("File does not exist: {$source}");
}
// Clear the cache so that we send accurate file sizes
clearstatcache(true, $source);
$source = fopen($source, 'r');
}

$this->source = EntityBody::factory($source);

if ($this->source->isSeekable() && $this->source->getSize() == 0) {
throw new InvalidArgumentException('Empty body provided to upload builder');
}

return $this;
}

/**
* Specify the headers to set on the upload
*
* @param array $headers Headers to add to the uploaded object
*
* @return $this
*/
public function setHeaders(array $headers)
{
$this->headers = $headers;

return $this;
}

/**
* Build the appropriate uploader based on the builder options
*
* @return TransferInterface
*/
abstract public function build();

/**
* Initiate the multipart upload
*
* @return TransferStateInterface
*/
abstract protected function initiateMultipartUpload();
}

View file

@ -0,0 +1,89 @@
<?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\Model\MultipartUpload;

use Aws\Common\Exception\InvalidArgumentException;

/**
* An object that encapsulates the data identifying an upload
*/
abstract class AbstractUploadId implements UploadIdInterface
{
/**
* @var array Expected values (with defaults)
*/
protected static $expectedValues = array();

/**
* @var array Params representing the identifying information
*/
protected $data = array();

/**
* {@inheritdoc}
*/
public static function fromParams($data)
{
$uploadId = new static();
$uploadId->loadData($data);

return $uploadId;
}

/**
* {@inheritdoc}
*/
public function toParams()
{
return $this->data;
}

/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize($this->data);
}

/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
$this->loadData(unserialize($serialized));
}

/**
* Loads an array of data into the UploadId by extracting only the needed keys
*
* @param array $data Data to load
*
* @throws InvalidArgumentException if a required key is missing
*/
protected function loadData($data)
{
$data = array_replace(static::$expectedValues, array_intersect_key($data, static::$expectedValues));
foreach ($data as $key => $value) {
if (isset($data[$key])) {
$this->data[$key] = $data[$key];
} else {
throw new InvalidArgumentException("A required key [$key] was missing from the UploadId.");
}
}
}
}

View file

@ -0,0 +1,101 @@
<?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\Model\MultipartUpload;

use Aws\Common\Exception\InvalidArgumentException;

/**
* An object that encapsulates the data for an upload part
*/
abstract class AbstractUploadPart implements UploadPartInterface
{
/**
* @var array A map of external array keys to internal property names
*/
protected static $keyMap = array();

/**
* @var int The number of the upload part representing its order in the overall upload
*/
protected $partNumber;

/**
* {@inheritdoc}
*/
public static function fromArray($data)
{
$part = new static();
$part->loadData($data);

return $part;
}

/**
* {@inheritdoc}
*/
public function getPartNumber()
{
return $this->partNumber;
}

/**
* {@inheritdoc}
*/
public function toArray()
{
$array = array();
foreach (static::$keyMap as $key => $property) {
$array[$key] = $this->{$property};
}

return $array;
}

/**
* {@inheritdoc}
*/
public function serialize()
{
return serialize($this->toArray());
}

/**
* {@inheritdoc}
*/
public function unserialize($serialized)
{
$this->loadData(unserialize($serialized));
}

/**
* Loads an array of data into the upload part by extracting only the needed keys
*
* @param array|\Traversable $data Data to load into the upload part value object
*
* @throws InvalidArgumentException if a required key is missing
*/
protected function loadData($data)
{
foreach (static::$keyMap as $key => $property) {
if (isset($data[$key])) {
$this->{$property} = $data[$key];
} else {
throw new InvalidArgumentException("A required key [$key] was missing from the upload part.");
}
}
}
}

View file

@ -0,0 +1,66 @@
<?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\Model\MultipartUpload;

use Guzzle\Common\HasDispatcherInterface;
use Guzzle\Service\Resource\Model;

/**
* Interface for transferring the contents of a data source to an AWS service via a multipart upload interface
*/
interface TransferInterface extends HasDispatcherInterface
{
/**
* Upload the source to using a multipart upload
*
* @return Model|null Result of the complete multipart upload command or null if uploading was stopped
*/
public function upload();

/**
* Abort the upload
*
* @return Model Returns the result of the abort multipart upload command
*/
public function abort();

/**
* Get the current state of the upload
*
* @return TransferStateInterface
*/
public function getState();

/**
* Stop the transfer and retrieve the current state.
*
* This allows you to stop and later resume a long running transfer if needed.
*
* @return TransferStateInterface
*/
public function stop();

/**
* Set an option on the transfer object
*
* @param string $option Option to set
* @param mixed $value The value to set
*
* @return self
*/
public function setOption($option, $value);
}

View file

@ -0,0 +1,92 @@
<?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\Model\MultipartUpload;

use Aws\Common\Client\AwsClientInterface;

/**
* State of a multipart upload
*/
interface TransferStateInterface extends \Countable, \IteratorAggregate, \Serializable
{
/**
* Create the transfer state from the results of list parts request
*
* @param AwsClientInterface $client Client used to send the request
* @param UploadIdInterface $uploadId Params needed to identify the upload and form the request
*
* @return self
*/
public static function fromUploadId(AwsClientInterface $client, UploadIdInterface $uploadId);

/**
* Get the params used to identify an upload part
*
* @return UploadIdInterface
*/
public function getUploadId();

/**
* Get the part information of a specific part
*
* @param int $partNumber Part to retrieve
*
* @return UploadPartInterface
*/
public function getPart($partNumber);

/**
* Add a part to the transfer state
*
* @param UploadPartInterface $part The part to add
*
* @return self
*/
public function addPart(UploadPartInterface $part);

/**
* Check if a specific part has been uploaded
*
* @param int $partNumber Part to check
*
* @return bool
*/
public function hasPart($partNumber);

/**
* Get a list of all of the uploaded part numbers
*
* @return array
*/
public function getPartNumbers();

/**
* Set whether or not the transfer has been aborted
*
* @param bool $aborted Set to true to mark the transfer as aborted
*
* @return self
*/
public function setAborted($aborted);

/**
* Check if the transfer has been marked as aborted
*
* @return bool
*/
public function isAborted();
}

View file

@ -0,0 +1,39 @@
<?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\Model\MultipartUpload;

/**
* An object that encapsulates the data identifying an upload
*/
interface UploadIdInterface extends \Serializable
{
/**
* Create an UploadId from an array
*
* @param array $data Data representing the upload identification
*
* @return self
*/
public static function fromParams($data);

/**
* Returns the array form of the upload identification for use as command params
*
* @return array
*/
public function toParams();
}

View file

@ -0,0 +1,46 @@
<?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\Model\MultipartUpload;

/**
* An object that encapsulates the data for an upload part
*/
interface UploadPartInterface extends \Serializable
{
/**
* Create an upload part from an array
*
* @param array|\Traversable $data Data representing the upload part
*
* @return self
*/
public static function fromArray($data);

/**
* Returns the part number of the upload part which is used as an identifier
*
* @return int
*/
public function getPartNumber();

/**
* Returns the array form of the upload part
*
* @return array
*/
public function toArray();
}

View file

@ -0,0 +1,308 @@
<?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.
*/

return array(
'class' => 'Aws\Common\Aws',
'services' => array(

'default_settings' => array(
'params' => array()
),

'autoscaling' => array(
'alias' => 'AutoScaling',
'extends' => 'default_settings',
'class' => 'Aws\AutoScaling\AutoScalingClient'
),

'cloudformation' => array(
'alias' => 'CloudFormation',
'extends' => 'default_settings',
'class' => 'Aws\CloudFormation\CloudFormationClient'
),

'cloudfront' => array(
'alias' => 'CloudFront',
'extends' => 'default_settings',
'class' => 'Aws\CloudFront\CloudFrontClient'
),

'cloudfront_20120505' => array(
'extends' => 'cloudfront',
'params' => array(
'version' => '2012-05-05'
)
),

'cloudhsm' => array(
'alias' => 'CloudHsm',
'extends' => 'default_settings',
'class' => 'Aws\CloudHsm\CloudHsmClient'
),

'cloudsearch' => array(
'alias' => 'CloudSearch',
'extends' => 'default_settings',
'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'
),

'cloudwatchlogs' => array(
'alias' => 'CloudWatchLogs',
'extends' => 'default_settings',
'class' => 'Aws\CloudWatchLogs\CloudWatchLogsClient'
),

'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'),

'codedeploy' => array(
'alias' => 'CodeDeploy',
'extends' => 'default_settings',
'class' => 'Aws\CodeDeploy\CodeDeployClient'
),

'config' => array(
'alias' => 'ConfigService',
'extends' => 'default_settings',
'class' => 'Aws\ConfigService\ConfigServiceClient'
),

'datapipeline' => array(
'alias' => 'DataPipeline',
'extends' => 'default_settings',
'class' => 'Aws\DataPipeline\DataPipelineClient'
),

'directconnect' => array(
'alias' => 'DirectConnect',
'extends' => 'default_settings',
'class' => 'Aws\DirectConnect\DirectConnectClient'
),

'dynamodb' => array(
'alias' => 'DynamoDb',
'extends' => 'default_settings',
'class' => 'Aws\DynamoDb\DynamoDbClient'
),

'dynamodb_20111205' => array(
'extends' => 'dynamodb',
'params' => array(
'version' => '2011-12-05'
)
),

'ec2' => array(
'alias' => 'Ec2',
'extends' => 'default_settings',
'class' => 'Aws\Ec2\Ec2Client'
),

'ecs' => array(
'alias' => 'Ecs',
'extends' => 'default_settings',
'class' => 'Aws\Ecs\EcsClient'
),

'elasticache' => array(
'alias' => 'ElastiCache',
'extends' => 'default_settings',
'class' => 'Aws\ElastiCache\ElastiCacheClient'
),

'elasticbeanstalk' => array(
'alias' => 'ElasticBeanstalk',
'extends' => 'default_settings',
'class' => 'Aws\ElasticBeanstalk\ElasticBeanstalkClient'
),

'elasticloadbalancing' => array(
'alias' => 'ElasticLoadBalancing',
'extends' => 'default_settings',
'class' => 'Aws\ElasticLoadBalancing\ElasticLoadBalancingClient'
),

'elastictranscoder' => array(
'alias' => 'ElasticTranscoder',
'extends' => 'default_settings',
'class' => 'Aws\ElasticTranscoder\ElasticTranscoderClient'
),

'emr' => array(
'alias' => 'Emr',
'extends' => 'default_settings',
'class' => 'Aws\Emr\EmrClient'
),

'glacier' => array(
'alias' => 'Glacier',
'extends' => 'default_settings',
'class' => 'Aws\Glacier\GlacierClient'
),

'kinesis' => array(
'alias' => 'Kinesis',
'extends' => 'default_settings',
'class' => 'Aws\Kinesis\KinesisClient'
),

'kms' => array(
'alias' => 'Kms',
'extends' => 'default_settings',
'class' => 'Aws\Kms\KmsClient'
),

'lambda' => array(
'alias' => 'Lambda',
'extends' => 'default_settings',
'class' => 'Aws\Lambda\LambdaClient'
),

'iam' => array(
'alias' => 'Iam',
'extends' => 'default_settings',
'class' => 'Aws\Iam\IamClient'
),

'importexport' => array(
'alias' => 'ImportExport',
'extends' => 'default_settings',
'class' => 'Aws\ImportExport\ImportExportClient'
),

'opsworks' => array(
'alias' => 'OpsWorks',
'extends' => 'default_settings',
'class' => 'Aws\OpsWorks\OpsWorksClient'
),

'rds' => array(
'alias' => 'Rds',
'extends' => 'default_settings',
'class' => 'Aws\Rds\RdsClient'
),

'redshift' => array(
'alias' => 'Redshift',
'extends' => 'default_settings',
'class' => 'Aws\Redshift\RedshiftClient'
),

'route53' => array(
'alias' => 'Route53',
'extends' => 'default_settings',
'class' => 'Aws\Route53\Route53Client'
),

'route53domains' => array(
'alias' => 'Route53Domains',
'extends' => 'default_settings',
'class' => 'Aws\Route53Domains\Route53DomainsClient'
),

's3' => array(
'alias' => 'S3',
'extends' => 'default_settings',
'class' => 'Aws\S3\S3Client'
),

'sdb' => array(
'alias' => 'SimpleDb',
'extends' => 'default_settings',
'class' => 'Aws\SimpleDb\SimpleDbClient'
),

'ses' => array(
'alias' => 'Ses',
'extends' => 'default_settings',
'class' => 'Aws\Ses\SesClient'
),

'sns' => array(
'alias' => 'Sns',
'extends' => 'default_settings',
'class' => 'Aws\Sns\SnsClient'
),

'sqs' => array(
'alias' => 'Sqs',
'extends' => 'default_settings',
'class' => 'Aws\Sqs\SqsClient'
),

'storagegateway' => array(
'alias' => 'StorageGateway',
'extends' => 'default_settings',
'class' => 'Aws\StorageGateway\StorageGatewayClient'
),

'sts' => array(
'alias' => 'Sts',
'extends' => 'default_settings',
'class' => 'Aws\Sts\StsClient'
),

'support' => array(
'alias' => 'Support',
'extends' => 'default_settings',
'class' => 'Aws\Support\SupportClient'
),

'swf' => array(
'alias' => 'Swf',
'extends' => 'default_settings',
'class' => 'Aws\Swf\SwfClient'
),
)
);

View file

@ -0,0 +1,64 @@
<?php
return array(
'version' => 2,
'endpoints' => array(
'*/*' => array(
'endpoint' => '{service}.{region}.amazonaws.com'
),
'cn-north-1/*' => array(
'endpoint' => '{service}.{region}.amazonaws.com.cn',
'signatureVersion' => 'v4'
),
'us-gov-west-1/iam' => array(
'endpoint' => 'iam.us-gov.amazonaws.com'
),
'us-gov-west-1/sts' => array(
'endpoint' => 'sts.us-gov-west-1.amazonaws.com'
),
'us-gov-west-1/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'*/cloudfront' => array(
'endpoint' => 'cloudfront.amazonaws.com'
),
'*/iam' => array(
'endpoint' => 'iam.amazonaws.com'
),
'*/importexport' => array(
'endpoint' => 'importexport.amazonaws.com'
),
'*/route53' => array(
'endpoint' => 'route53.amazonaws.com'
),
'*/sts' => array(
'endpoint' => 'sts.amazonaws.com'
),
'us-east-1/sdb' => array(
'endpoint' => 'sdb.amazonaws.com'
),
'us-east-1/s3' => array(
'endpoint' => 's3.amazonaws.com'
),
'us-west-1/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'us-west-2/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'eu-west-1/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'ap-southeast-1/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'ap-southeast-2/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'ap-northeast-1/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
),
'sa-east-1/s3' => array(
'endpoint' => 's3-{region}.amazonaws.com'
)
)
);

View file

@ -0,0 +1,138 @@
<?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.
*/

return array(
'includes' => array('_aws'),
'services' => array(

'sdk1_settings' => array(
'extends' => 'default_settings',
'params' => array(
'certificate_authority' => false
)
),

'v1.autoscaling' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonAS'
),

'v1.cloudformation' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonCloudFormation'
),

'v1.cloudfront' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonCloudFront'
),

'v1.cloudsearch' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonCloudSearch'
),

'v1.cloudwatch' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonCloudWatch'
),

'v1.dynamodb' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonDynamoDB'
),

'v1.ec2' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonEC2'
),

'v1.elasticache' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonElastiCache'
),

'v1.elasticbeanstalk' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonElasticBeanstalk'
),

'v1.elb' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonELB'
),

'v1.emr' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonEMR'
),

'v1.iam' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonIAM'
),

'v1.importexport' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonImportExport'
),

'v1.rds' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonRDS'
),

'v1.s3' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonS3'
),

'v1.sdb' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonSDB'
),

'v1.ses' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonSES'
),

'v1.sns' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonSNS'
),

'v1.sqs' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonSQS'
),

'v1.storagegateway' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonStorageGateway'
),

'v1.sts' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonSTS'
),

'v1.swf' => array(
'extends' => 'sdk1_settings',
'class' => 'AmazonSWF'
)
)
);

View file

@ -0,0 +1,67 @@
<?php
namespace Aws\Common;

/**
* Provides endpoints based on a rules configuration file.
*/
class RulesEndpointProvider
{
/** @var array */
private $patterns;

/**
* @param array $patterns Hash of endpoint patterns mapping to endpoint
* configurations.
*/
public function __construct(array $patterns)
{
$this->patterns = $patterns;
}

/**
* Creates and returns the default RulesEndpointProvider based on the
* public rule sets.
*
* @return self
*/
public static function fromDefaults()
{
return new self(require __DIR__ . '/Resources/public-endpoints.php');
}

public function __invoke(array $args = array())
{
if (!isset($args['service'])) {
throw new \InvalidArgumentException('Requires a "service" value');
}

if (!isset($args['region'])) {
throw new \InvalidArgumentException('Requires a "region" value');
}

foreach ($this->getKeys($args['region'], $args['service']) as $key) {
if (isset($this->patterns['endpoints'][$key])) {
return $this->expand($this->patterns['endpoints'][$key], $args);
}
}

throw new \RuntimeException('Could not resolve endpoint');
}

private function expand(array $config, array $args)
{
$scheme = isset($args['scheme']) ? $args['scheme'] : 'https';
$config['endpoint'] = $scheme . '://' . str_replace(
array('{service}', '{region}'),
array($args['service'], $args['region']),
$config['endpoint']
);

return $config;
}

private function getKeys($region, $service)
{
return array("$region/$service", "$region/*", "*/$service", "*/*");
}
}

View file

@ -0,0 +1,44 @@
<?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 Guzzle\Http\Message\RequestInterface;

abstract class AbstractSignature implements SignatureInterface
{
/**
* Provides the timestamp used for the class (used for mocking PHP's time() function)
*
* @return int
*/
protected function getTimestamp()
{
return time();
}

/**
* @codeCoverageIgnore
*/
public function createPresignedUrl(
RequestInterface $request,
CredentialsInterface $credentials,
$expires
) {
throw new \BadMethodCallException(__METHOD__ . ' not implemented');
}
}

View file

@ -0,0 +1,42 @@
<?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;

/**
* Interface for signatures that use specific region and service names when
* signing requests.
*/
interface EndpointSignatureInterface extends SignatureInterface
{
/**
* Set the service name instead of inferring it from a request URL
*
* @param string $service Name of the service used when signing
*
* @return self
*/
public function setServiceName($service);

/**
* Set the region name instead of inferring it from a request URL
*
* @param string $region Name of the region used when signing
*
* @return self
*/
public function setRegionName($region);
}

View file

@ -0,0 +1,52 @@
<?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 Guzzle\Http\Message\RequestInterface;

/**
* Interface used to provide interchangeable strategies for signing requests
* using the various AWS signature protocols.
*/
interface SignatureInterface
{
/**
* Signs the specified request with an AWS signing protocol by using the
* provided AWS account credentials and adding the required headers to the
* request.
*
* @param RequestInterface $request Request to add a signature to
* @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

@ -0,0 +1,83 @@
<?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\Credentials\NullCredentials;
use Guzzle\Common\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Listener used to sign requests before they are sent over the wire
*/
class SignatureListener implements EventSubscriberInterface
{
/**
* @var CredentialsInterface
*/
protected $credentials;

/**
* @var SignatureInterface
*/
protected $signature;

/**
* Construct a new request signing plugin
*
* @param CredentialsInterface $credentials Credentials used to sign requests
* @param SignatureInterface $signature Signature implementation
*/
public function __construct(CredentialsInterface $credentials, SignatureInterface $signature)
{
$this->credentials = $credentials;
$this->signature = $signature;
}

/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
'request.before_send' => array('onRequestBeforeSend', -255),
'client.credentials_changed' => array('onCredentialsChanged')
);
}

/**
* Updates the listener with new credentials if the client is updated
*
* @param Event $event Event emitted
*/
public function onCredentialsChanged(Event $event)
{
$this->credentials = $event['credentials'];
}

/**
* Signs requests before they are sent
*
* @param Event $event Event emitted
*/
public function onRequestBeforeSend(Event $event)
{
if(!$this->credentials instanceof NullCredentials) {
$this->signature->signRequest($event['request'], $this->credentials);
}
}
}

View file

@ -0,0 +1,109 @@
<?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 Guzzle\Http\Message\RequestInterface;

/**
* Implementation of Signature Version 2
* @link http://aws.amazon.com/articles/1928
*/
class SignatureV2 extends AbstractSignature
{
public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
// refresh the cached timestamp
$timestamp = $this->getTimestamp(true);

// set values we need in CanonicalizedParameterString
$this->addParameter($request, 'Timestamp', gmdate('c', $timestamp));
$this->addParameter($request, 'SignatureVersion', '2');
$this->addParameter($request, 'SignatureMethod', 'HmacSHA256');
$this->addParameter($request, 'AWSAccessKeyId', $credentials->getAccessKeyId());

if ($token = $credentials->getSecurityToken()) {
$this->addParameter($request, 'SecurityToken', $token);
}

// Get the path and ensure it's absolute
$path = '/' . ltrim($request->getUrl(true)->normalizePath()->getPath(), '/');

// build string to sign
$sign = $request->getMethod() . "\n"
. $request->getHost() . "\n"
. $path . "\n"
. $this->getCanonicalizedParameterString($request);

// 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',
$sign,
$credentials->getSecretKey(),
true
)
);

$this->addParameter($request, 'Signature', $signature);
}

/**
* Add a parameter key and value to the request according to type
*
* @param RequestInterface $request The request
* @param string $key The name of the parameter
* @param string $value The value of the parameter
*/
public function addParameter(RequestInterface $request, $key, $value)
{
if ($request->getMethod() == 'POST') {
$request->setPostField($key, $value);
} else {
$request->getQuery()->set($key, $value);
}
}

/**
* Get the canonicalized query/parameter string for a request
*
* @param RequestInterface $request Request used to build canonicalized string
*
* @return string
*/
private function getCanonicalizedParameterString(RequestInterface $request)
{
if ($request->getMethod() == 'POST') {
$params = $request->getPostFields()->toArray();
} else {
$params = $request->getQuery()->toArray();
}

// Don't resign a previous signature value
unset($params['Signature']);
uksort($params, 'strcmp');

$str = '';
foreach ($params as $key => $val) {
$str .= rawurlencode($key) . '=' . rawurlencode($val) . '&';
}

return substr($str, 0, -1);
}
}

View file

@ -0,0 +1,52 @@
<?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;

/**
* Implementation of Signature Version 3 HTTPS
* @link http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/RESTAuthentication.html
*/
class SignatureV3Https extends AbstractSignature
{
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', gmdate(DateFormat::RFC1123, $this->getTimestamp()));
}

// Add the security token if one is present
if ($credentials->getSecurityToken()) {
$request->setHeader('x-amz-security-token', $credentials->getSecurityToken());
}

// Determine the string to sign
$stringToSign = (string) ($request->getHeader('Date') ?: $request->getHeader('x-amz-date'));
$request->getParams()->set('aws.string_to_sign', $stringToSign);

// Calculate the signature
$signature = base64_encode(hash_hmac('sha256', $stringToSign, $credentials->getSecretKey(), true));

// Add the authorization header to the request
$headerFormat = 'AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s';
$request->setHeader('X-Amzn-Authorization', sprintf($headerFormat, $credentials->getAccessKeyId(), $signature));
}
}

View file

@ -0,0 +1,470 @@
<?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 Aws\Common\HostNameUtils;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
use Guzzle\Http\Message\RequestFactory;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\QueryString;
use Guzzle\Http\Url;
use Guzzle\Stream\Stream;

/**
* Signature Version 4
* @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 */
const DEFAULT_PAYLOAD = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

/** @var string Explicitly set service name */
protected $serviceName;

/** @var string Explicitly set region name */
protected $regionName;

/** @var int Maximum number of hashes to cache */
protected $maxCacheSize = 50;

/** @var array Cache of previously signed values */
protected $hashCache = array();

/** @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
*
* @param string $service Name of the service used when signing
*
* @return self
*/
public function setServiceName($service)
{
$this->serviceName = $service;

return $this;
}

/**
* Set the region name instead of inferring it from a request URL
*
* @param string $region Name of the region used when signing
*
* @return self
*/
public function setRegionName($region)
{
$this->regionName = $region;

return $this;
}

/**
* Set the maximum number of computed hashes to cache
*
* @param int $maxCacheSize Maximum number of hashes to cache
*
* @return self
*/
public function setMaxCacheSize($maxCacheSize)
{
$this->maxCacheSize = $maxCacheSize;

return $this;
}

public function signRequest(RequestInterface $request, CredentialsInterface $credentials)
{
$timestamp = $this->getTimestamp();
$longDate = gmdate(DateFormat::ISO8601, $timestamp);
$shortDate = substr($longDate, 0, 8);

// 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', gmdate(DateFormat::RFC1123, $timestamp));
}

// Add the security token if one is present
if ($credentials->getSecurityToken()) {
$request->setHeader('x-amz-security-token', $credentials->getSecurityToken());
}

// Parse the service and region or use one that is explicitly set
$region = $this->regionName;
$service = $this->serviceName;
if (!$region || !$service) {
$url = Url::factory($request->getUrl());
$region = $region ?: HostNameUtils::parseRegionName($url);
$service = $service ?: HostNameUtils::parseServiceName($url);
}

$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());
$signature = hash_hmac('sha256', $signingContext['string_to_sign'], $signingKey);

$request->setHeader('Authorization', "AWS4-HMAC-SHA256 "
. "Credential={$credentials->getAccessKeyId()}/{$credentialScope}, "
. "SignedHeaders={$signingContext['signed_headers']}, Signature={$signature}");

// Add debug information to the request
$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) {
if ($request->getMethod() == 'POST' && count($request->getPostFields())) {
return hash('sha256', (string) $request->getPostFields());
} elseif ($body = $request->getBody()) {
return Stream::getHash($request->getBody(), 'sha256');
}
}

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 including:
* - canonical_request
* - signed_headers
*/
private function createSigningContext(RequestInterface $request, $payload)
{
$signable = array(
'host' => true,
'date' => true,
'content-md5' => true
);

// Normalize the path as required by SigV4 and ensure it's absolute
$canon = $request->getMethod() . "\n"
. $this->createCanonicalizedPath($request) . "\n"
. $this->getCanonicalizedQueryString($request) . "\n";

$canonHeaders = array();

foreach ($request->getHeaders()->getAll() as $key => $values) {
$key = strtolower($key);
if (isset($signable[$key]) || substr($key, 0, 6) === 'x-amz-') {
$values = $values->toArray();
if (count($values) == 1) {
$values = $values[0];
} else {
sort($values);
$values = implode(',', $values);
}
$canonHeaders[$key] = $key . ':' . preg_replace('/\s+/', ' ', $values);
}
}

ksort($canonHeaders);
$signedHeadersString = implode(';', array_keys($canonHeaders));
$canon .= implode("\n", $canonHeaders) . "\n\n"
. $signedHeadersString . "\n"
. $payload;

return array(
'canonical_request' => $canon,
'signed_headers' => $signedHeadersString
);
}

/**
* Get a hash for a specific key and value. If the hash was previously
* cached, return it
*
* @param string $shortDate Short date
* @param string $region Region name
* @param string $service Service name
* @param string $secretKey Secret Access Key
*
* @return string
*/
private function getSigningKey($shortDate, $region, $service, $secretKey)
{
$cacheKey = $shortDate . '_' . $region . '_' . $service . '_' . $secretKey;

// Retrieve the hash form the cache or create it and add it to the cache
if (!isset($this->hashCache[$cacheKey])) {
// When the cache size reaches the max, then just clear the cache
if (++$this->cacheSize > $this->maxCacheSize) {
$this->hashCache = array();
$this->cacheSize = 0;
}
$dateKey = hash_hmac('sha256', $shortDate, 'AWS4' . $secretKey, true);
$regionKey = hash_hmac('sha256', $region, $dateKey, true);
$serviceKey = hash_hmac('sha256', $service, $regionKey, true);
$this->hashCache[$cacheKey] = hash_hmac('sha256', 'aws4_request', $serviceKey, true);
}

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 === 0) {
$values = array('0');
} 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

@ -0,0 +1,53 @@
<?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\Waiter;

use Aws\Common\Client\AwsClientInterface;
use Aws\Common\Exception\RuntimeException;

/**
* Abstract waiter implementation used to wait on resources
*/
abstract class AbstractResourceWaiter extends AbstractWaiter implements ResourceWaiterInterface
{
/**
* @var AwsClientInterface
*/
protected $client;

/**
* {@inheritdoc}
*/
public function setClient(AwsClientInterface $client)
{
$this->client = $client;

return $this;
}

/**
* {@inheritdoc}
*/
public function wait()
{
if (!$this->client) {
throw new RuntimeException('No client has been specified on the waiter');
}

parent::wait();
}
}

View file

@ -0,0 +1,146 @@
<?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\Waiter;

use Aws\Common\Exception\RuntimeException;
use Guzzle\Common\AbstractHasDispatcher;

/**
* Abstract wait implementation
*/
abstract class AbstractWaiter extends AbstractHasDispatcher implements WaiterInterface
{
protected $attempts = 0;
protected $config = array();

/**
* {@inheritdoc}
*/
public static function getAllEvents()
{
return array(
// About to check if the waiter needs to wait
'waiter.before_attempt',
// About to sleep
'waiter.before_wait',
);
}

/**
* The max attempts allowed by the waiter
*
* @return int
*/
public function getMaxAttempts()
{
return isset($this->config[self::MAX_ATTEMPTS]) ? $this->config[self::MAX_ATTEMPTS] : 10;
}

/**
* Get the amount of time in seconds to delay between attempts
*
* @return int
*/
public function getInterval()
{
return isset($this->config[self::INTERVAL]) ? $this->config[self::INTERVAL] : 0;
}

/**
* {@inheritdoc}
*/
public function setMaxAttempts($maxAttempts)
{
$this->config[self::MAX_ATTEMPTS] = $maxAttempts;

return $this;
}

/**
* {@inheritdoc}
*/
public function setInterval($interval)
{
$this->config[self::INTERVAL] = $interval;

return $this;
}

/**
* Set config options associated with the waiter
*
* @param array $config Options to set
*
* @return self
*/
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;
}

/**
* {@inheritdoc}
*/
public function wait()
{
$this->attempts = 0;

do {
$this->dispatch('waiter.before_attempt', array(
'waiter' => $this,
'config' => $this->config,
));

if ($this->doWait()) {
break;
}

if (++$this->attempts >= $this->getMaxAttempts()) {
throw new RuntimeException('Wait method never resolved to true after ' . $this->attempts . ' attempts');
}

$this->dispatch('waiter.before_wait', array(
'waiter' => $this,
'config' => $this->config,
));

if ($this->getInterval()) {
usleep($this->getInterval() * 1000000);
}

} while (1);
}

/**
* Method to implement in subclasses
*
* @return bool Return true when successful, false on failure
*/
abstract protected function doWait();
}

View file

@ -0,0 +1,82 @@
<?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\Waiter;

use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\RuntimeException;

/**
* Callable wait implementation
*/
class CallableWaiter extends AbstractWaiter
{
/**
* @var callable Callable function
*/
protected $callable;

/**
* @var array Additional context for the callable function
*/
protected $context = array();

/**
* Set the callable function to call in each wait attempt
*
* @param callable $callable Callable function
*
* @return self
* @throws InvalidArgumentException when the method is not callable
*/
public function setCallable($callable)
{
if (!is_callable($callable)) {
throw new InvalidArgumentException('Value is not callable');
}

$this->callable = $callable;

return $this;
}

/**
* Set additional context for the callable function. This data will be passed into the callable function as the
* second argument
*
* @param array $context Additional context
*
* @return self
*/
public function setContext(array $context)
{
$this->context = $context;

return $this;
}

/**
* {@inheritdoc}
*/
public function doWait()
{
if (!$this->callable) {
throw new RuntimeException('No callable was specified for the wait method');
}

return call_user_func($this->callable, $this->attempts, $this->context);
}
}

View file

@ -0,0 +1,90 @@
<?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\Waiter;

use Aws\Common\Exception\InvalidArgumentException;

/**
* Factory that utilizes multiple factories for creating waiters
*/
class CompositeWaiterFactory implements WaiterFactoryInterface
{
/**
* @var array Array of factories
*/
protected $factories;

/**
* @param array $factories Array of factories used to instantiate waiters
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}

/**
* {@inheritdoc}
*/
public function build($waiter)
{
if (!($factory = $this->getFactory($waiter))) {
throw new InvalidArgumentException("Waiter was not found matching {$waiter}.");
}

return $factory->build($waiter);
}

/**
* {@inheritdoc}
*/
public function canBuild($waiter)
{
return (bool) $this->getFactory($waiter);
}

/**
* Add a factory to the composite factory
*
* @param WaiterFactoryInterface $factory Factory to add
*
* @return self
*/
public function addFactory(WaiterFactoryInterface $factory)
{
$this->factories[] = $factory;

return $this;
}

/**
* Get the factory that matches the waiter name
*
* @param string $waiter Name of the waiter
*
* @return WaiterFactoryInterface|bool
*/
protected function getFactory($waiter)
{
foreach ($this->factories as $factory) {
if ($factory->canBuild($waiter)) {
return $factory;
}
}

return false;
}
}

View file

@ -0,0 +1,225 @@
<?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\Waiter;

use Aws\Common\Exception\InvalidArgumentException;
use Aws\Common\Exception\RuntimeException;
use Aws\Common\Exception\ServiceResponseException;
use Guzzle\Service\Resource\Model;
use Guzzle\Service\Exception\ValidationException;

/**
* Resource waiter driven by configuration options
*/
class ConfigResourceWaiter extends AbstractResourceWaiter
{
/**
* @var WaiterConfig Waiter configuration
*/
protected $waiterConfig;

/**
* @param WaiterConfig $waiterConfig Waiter configuration
*/
public function __construct(WaiterConfig $waiterConfig)
{
$this->waiterConfig = $waiterConfig;
$this->setInterval($waiterConfig->get(WaiterConfig::INTERVAL));
$this->setMaxAttempts($waiterConfig->get(WaiterConfig::MAX_ATTEMPTS));
}

/**
* {@inheritdoc}
*/
public function setConfig(array $config)
{
foreach ($config as $key => $value) {
if (substr($key, 0, 7) == 'waiter.') {
$this->waiterConfig->set(substr($key, 7), $value);
}
}

if (!isset($config[self::INTERVAL])) {
$config[self::INTERVAL] = $this->waiterConfig->get(WaiterConfig::INTERVAL);
}

if (!isset($config[self::MAX_ATTEMPTS])) {
$config[self::MAX_ATTEMPTS] = $this->waiterConfig->get(WaiterConfig::MAX_ATTEMPTS);
}

return parent::setConfig($config);
}

/**
* Get the waiter's configuration data
*
* @return WaiterConfig
*/
public function getWaiterConfig()
{
return $this->waiterConfig;
}

/**
* {@inheritdoc}
*/
protected function doWait()
{
$params = $this->config;
// remove waiter settings from the operation's input
foreach (array_keys($params) as $key) {
if (substr($key, 0, 7) == 'waiter.') {
unset($params[$key]);
}
}

$operation = $this->client->getCommand($this->waiterConfig->get(WaiterConfig::OPERATION), $params);

try {
return $this->checkResult($this->client->execute($operation));
} catch (ValidationException $e) {
throw new InvalidArgumentException(
$this->waiterConfig->get(WaiterConfig::WAITER_NAME) . ' waiter validation failed: ' . $e->getMessage(),
$e->getCode(),
$e
);
} catch (ServiceResponseException $e) {

// Check if this exception satisfies a success or failure acceptor
$transition = $this->checkErrorAcceptor($e);
if (null !== $transition) {
return $transition;
}

// Check if this exception should be ignored
foreach ((array) $this->waiterConfig->get(WaiterConfig::IGNORE_ERRORS) as $ignore) {
if ($e->getExceptionCode() == $ignore) {
// This exception is ignored, so it counts as a failed attempt rather than a fast-fail
return false;
}
}

// Allow non-ignore exceptions to bubble through
throw $e;
}
}

/**
* Check if an exception satisfies a success or failure acceptor
*
* @param ServiceResponseException $e
*
* @return bool|null Returns true for success, false for failure, and null for no transition
*/
protected function checkErrorAcceptor(ServiceResponseException $e)
{
if ($this->waiterConfig->get(WaiterConfig::SUCCESS_TYPE) == 'error') {
if ($e->getExceptionCode() == $this->waiterConfig->get(WaiterConfig::SUCCESS_VALUE)) {
// Mark as a success
return true;
}
}

// Mark as an attempt
return null;
}

/**
* Check to see if the response model satisfies a success or failure state
*
* @param Model $result Result model
*
* @return bool
* @throws RuntimeException
*/
protected function checkResult(Model $result)
{
// Check if the result evaluates to true based on the path and output model
if ($this->waiterConfig->get(WaiterConfig::SUCCESS_TYPE) == 'output' &&
$this->checkPath(
$result,
$this->waiterConfig->get(WaiterConfig::SUCCESS_PATH),
$this->waiterConfig->get(WaiterConfig::SUCCESS_VALUE)
)
) {
return true;
}

// It did not finish waiting yet. Determine if we need to fail-fast based on the failure acceptor.
if ($this->waiterConfig->get(WaiterConfig::FAILURE_TYPE) == 'output') {
$failureValue = $this->waiterConfig->get(WaiterConfig::FAILURE_VALUE);
if ($failureValue) {
$key = $this->waiterConfig->get(WaiterConfig::FAILURE_PATH);
if ($this->checkPath($result, $key, $failureValue, false)) {
// Determine which of the results triggered the failure
$triggered = array_intersect(
(array) $this->waiterConfig->get(WaiterConfig::FAILURE_VALUE),
array_unique((array) $result->getPath($key))
);
// fast fail because the failure case was satisfied
throw new RuntimeException(
'A resource entered into an invalid state of "'
. implode(', ', $triggered) . '" while waiting with the "'
. $this->waiterConfig->get(WaiterConfig::WAITER_NAME) . '" waiter.'
);
}
}
}

return false;
}

/**
* Check to see if the path of the output key is satisfied by the value
*
* @param Model $model Result model
* @param string $key Key to check
* @param string $checkValue Compare the key to the value
* @param bool $all Set to true to ensure all value match or false to only match one
*
* @return bool
*/
protected function checkPath(Model $model, $key = null, $checkValue = array(), $all = true)
{
// If no key is set, then just assume true because the request succeeded
if (!$key) {
return true;
}

if (!($result = $model->getPath($key))) {
return false;
}

$total = $matches = 0;
foreach ((array) $result as $value) {
$total++;
foreach ((array) $checkValue as $check) {
if ($value == $check) {
$matches++;
break;
}
}
}

// When matching all values, ensure that the match count matches the total count
if ($all && $total != $matches) {
return false;
}

return $matches > 0;
}
}

View file

@ -0,0 +1,34 @@
<?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\Waiter;

use Aws\Common\Client\AwsClientInterface;

/**
* Interface used in conjunction with clients to wait on a resource
*/
interface ResourceWaiterInterface extends WaiterInterface
{
/**
* Set the client associated with the waiter
*
* @param AwsClientInterface $client Client to use with the waiter
*
* @return self
*/
public function setClient(AwsClientInterface $client);
}

View file

@ -0,0 +1,106 @@
<?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\Waiter;

use Aws\Common\Exception\InvalidArgumentException;
use Guzzle\Inflection\Inflector;
use Guzzle\Inflection\InflectorInterface;

/**
* Factory for creating {@see WaiterInterface} objects using a convention of
* storing waiter classes in the Waiter folder of a client class namespace using
* a snake_case to CamelCase conversion (e.g. camel_case => CamelCase).
*/
class WaiterClassFactory implements WaiterFactoryInterface
{
/**
* @var array List of namespaces used to look for classes
*/
protected $namespaces;

/**
* @var InflectorInterface Inflector used to inflect class names
*/
protected $inflector;

/**
* @param array|string $namespaces Namespaces of waiter objects
* @param InflectorInterface $inflector Inflector used to resolve class names
*/
public function __construct($namespaces = array(), InflectorInterface $inflector = null)
{
$this->namespaces = (array) $namespaces;
$this->inflector = $inflector ?: Inflector::getDefault();
}

/**
* Registers a namespace to check for Waiters
*
* @param string $namespace Namespace which contains Waiter classes
*
* @return self
*/
public function registerNamespace($namespace)
{
array_unshift($this->namespaces, $namespace);

return $this;
}

/**
* {@inheritdoc}
*/
public function build($waiter)
{
if (!($className = $this->getClassName($waiter))) {
throw new InvalidArgumentException("Waiter was not found matching {$waiter}.");
}

return new $className();
}

/**
* {@inheritdoc}
*/
public function canBuild($waiter)
{
return $this->getClassName($waiter) !== null;
}

/**
* Get the name of a waiter class
*
* @param string $waiter Waiter name
*
* @return string|null
*/
protected function getClassName($waiter)
{
$waiterName = $this->inflector->camel($waiter);

// Determine the name of the class to load
$className = null;
foreach ($this->namespaces as $namespace) {
$potentialClassName = $namespace . '\\' . $waiterName;
if (class_exists($potentialClassName)) {
return $potentialClassName;
}
}

return null;
}
}

View file

@ -0,0 +1,67 @@
<?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\Waiter;

use Guzzle\Common\Collection;

/**
* Configuration info of a waiter object
*/
class WaiterConfig extends Collection
{
const WAITER_NAME = 'name';
const MAX_ATTEMPTS = 'max_attempts';
const INTERVAL = 'interval';
const OPERATION = 'operation';
const IGNORE_ERRORS = 'ignore_errors';
const DESCRIPTION = 'description';
const SUCCESS_TYPE = 'success.type';
const SUCCESS_PATH = 'success.path';
const SUCCESS_VALUE = 'success.value';
const FAILURE_TYPE = 'failure.type';
const FAILURE_PATH = 'failure.path';
const FAILURE_VALUE = 'failure.value';

/**
* @param array $data Array of configuration directives
*/
public function __construct(array $data = array())
{
$this->data = $data;
$this->extractConfig();
}

/**
* Create the command configuration variables
*/
protected function extractConfig()
{
// Populate success.* and failure.* if specified in acceptor.*
foreach ($this->data as $key => $value) {
if (substr($key, 0, 9) == 'acceptor.') {
$name = substr($key, 9);
if (!isset($this->data["success.{$name}"])) {
$this->data["success.{$name}"] = $value;
}
if (!isset($this->data["failure.{$name}"])) {
$this->data["failure.{$name}"] = $value;
}
unset($this->data[$key]);
}
}
}
}

View file

@ -0,0 +1,98 @@
<?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\Waiter;

use Aws\Common\Exception\InvalidArgumentException;
use Guzzle\Inflection\Inflector;
use Guzzle\Inflection\InflectorInterface;

/**
* Factory for creating {@see WaiterInterface} objects using a configuration DSL.
*/
class WaiterConfigFactory implements WaiterFactoryInterface
{
/**
* @var array Configuration directives
*/
protected $config;

/**
* @var InflectorInterface Inflector used to inflect class names
*/
protected $inflector;

/**
* @param array $config Array of configuration directives
* @param InflectorInterface $inflector Inflector used to resolve class names
*/
public function __construct(
array $config,
InflectorInterface $inflector = null
) {
$this->config = $config;
$this->inflector = $inflector ?: Inflector::getDefault();
}

/**
* {@inheritdoc}
*/
public function build($waiter)
{
return new ConfigResourceWaiter($this->getWaiterConfig($waiter));
}

/**
* {@inheritdoc}
*/
public function canBuild($waiter)
{
return isset($this->config[$waiter]) || isset($this->config[$this->inflector->camel($waiter)]);
}

/**
* Get waiter configuration data, taking __default__ and extensions into account
*
* @param string $name Waiter name
*
* @return WaiterConfig
* @throws InvalidArgumentException
*/
protected function getWaiterConfig($name)
{
if (!$this->canBuild($name)) {
throw new InvalidArgumentException('No waiter found matching "' . $name . '"');
}

// inflect the name if needed
$name = isset($this->config[$name]) ? $name : $this->inflector->camel($name);
$waiter = new WaiterConfig($this->config[$name]);
$waiter['name'] = $name;

// Always use __default__ as the basis if it's set
if (isset($this->config['__default__'])) {
$parentWaiter = new WaiterConfig($this->config['__default__']);
$waiter = $parentWaiter->overwriteWith($waiter);
}

// Allow for configuration extensions
if (isset($this->config[$name]['extends'])) {
$waiter = $this->getWaiterConfig($this->config[$name]['extends'])->overwriteWith($waiter);
}

return $waiter;
}
}

View file

@ -0,0 +1,41 @@
<?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\Waiter;

/**
* Waiter factory used to create waiter objects by short names
*/
interface WaiterFactoryInterface
{
/**
* Create a waiter by name
*
* @param string $waiter Name of the waiter to create
*
* @return WaiterInterface
*/
public function build($waiter);

/**
* Check if the factory can create a waiter by a specific name
*
* @param string $waiter Name of the waiter to check
*
* @return bool
*/
public function canBuild($waiter);
}

View file

@ -0,0 +1,60 @@
<?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\Waiter;

/**
* WaiterInterface used to wait on something to be in a particular state
*/
interface WaiterInterface
{
const INTERVAL = 'waiter.interval';
const MAX_ATTEMPTS = 'waiter.max_attempts';

/**
* Set the maximum number of attempts to make when waiting
*
* @param int $maxAttempts Max number of attempts
*
* @return self
*/
public function setMaxAttempts($maxAttempts);

/**
* Set the amount of time to interval between attempts
*
* @param int $interval Interval in seconds
*
* @return self
*/
public function setInterval($interval);

/**
* Set configuration options associated with the waiter
*
* @param array $config Configuration options to set
*
* @return self
*/
public function setConfig(array $config);

/**
* Begin the waiting loop
*
* @throw RuntimeException if the method never resolves to true
*/
public function wait();
}

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