null,
'UserAgent' => 'FBAInventoryServiceMWS PHP5 Library',
'SignatureVersion' => 2,
'SignatureMethod' => 'HmacSHA256',
'ProxyHost' => null,
'ProxyPort' => -1,
'ProxyUsername' => null,
'ProxyPassword' => null,
'MaxErrorRetry' => 3,
'Headers' => array()
);
/**
* Get Service Status
* Gets the status of the service.
* Status is one of GREEN, RED representing:
* GREEN: The service section is operating normally.
* RED: The service section disruption.
*
* @param mixed $request array of parameters for FBAInventoryServiceMWS_Model_GetServiceStatus request or FBAInventoryServiceMWS_Model_GetServiceStatus object itself
* @see FBAInventoryServiceMWS_Model_GetServiceStatusRequest
* @return FBAInventoryServiceMWS_Model_GetServiceStatusResponse
*
* @throws FBAInventoryServiceMWS_Exception
*/
public function getServiceStatus($request)
{
if (!($request instanceof FBAInventoryServiceMWS_Model_GetServiceStatusRequest)) {
require_once (dirname(__FILE__) . '/Model/GetServiceStatusRequest.php');
$request = new FBAInventoryServiceMWS_Model_GetServiceStatusRequest($request);
}
$parameters = $request->toQueryParameterArray();
$parameters['Action'] = 'GetServiceStatus';
$httpResponse = $this->_invoke($parameters);
require_once (dirname(__FILE__) . '/Model/GetServiceStatusResponse.php');
$response = FBAInventoryServiceMWS_Model_GetServiceStatusResponse::fromXML($httpResponse['ResponseBody']);
$response->setResponseHeaderMetadata($httpResponse['ResponseHeaderMetadata']);
return $response;
}
/**
* Convert GetServiceStatusRequest to name value pairs
*/
private function _convertGetServiceStatus($request) {
$parameters = array();
$parameters['Action'] = 'GetServiceStatus';
if ($request->isSetSellerId()) {
$parameters['SellerId'] = $request->getSellerId();
}
if ($request->isSetMWSAuthToken()) {
$parameters['MWSAuthToken'] = $request->getMWSAuthToken();
}
if ($request->isSetMarketplace()) {
$parameters['Marketplace'] = $request->getMarketplace();
}
return $parameters;
}
/**
* List Inventory Supply
* Get information about the supply of seller-owned inventory in
* Amazon's fulfillment network. "Supply" is inventory that is available
* for fulfilling (a.k.a. Multi-Channel Fulfillment) orders. In general
* this includes all sellable inventory that has been received by Amazon,
* that is not reserved for existing orders or for internal FC processes,
* and also inventory expected to be received from inbound shipments.
*
* This operation provides 2 typical usages by setting different
* ListInventorySupplyRequest value:
*
* 1. Set value to SellerSkus and not set value to QueryStartDateTime,
* this operation will return all sellable inventory that has been received
* by Amazon's fulfillment network for these SellerSkus.
*
* 2. Not set value to SellerSkus and set value to QueryStartDateTime,
* This operation will return information about the supply of all seller-owned
* inventory in Amazon's fulfillment network, for inventory items that may have had
* recent changes in inventory levels. It provides the most efficient mechanism
* for clients to maintain local copies of inventory supply data.
*
* Only 1 of these 2 parameters (SellerSkus and QueryStartDateTime) can be set value for 1 request.
* If both with values or neither with values, an exception will be thrown.
*
* This operation is used with ListInventorySupplyByNextToken
* to paginate over the resultset. Begin pagination by invoking the
* ListInventorySupply operation, and retrieve the first set of
* results. If more results are available,continuing iteratively requesting further
* pages results by invoking the ListInventorySupplyByNextToken operation (each time
* passing in the NextToken value from the previous result), until the returned NextToken
* is null, indicating no further results are available.
*
* @param mixed $request array of parameters for FBAInventoryServiceMWS_Model_ListInventorySupply request or FBAInventoryServiceMWS_Model_ListInventorySupply object itself
* @see FBAInventoryServiceMWS_Model_ListInventorySupplyRequest
* @return FBAInventoryServiceMWS_Model_ListInventorySupplyResponse
*
* @throws FBAInventoryServiceMWS_Exception
*/
public function listInventorySupply($request)
{
if (!($request instanceof FBAInventoryServiceMWS_Model_ListInventorySupplyRequest)) {
require_once (dirname(__FILE__) . '/Model/ListInventorySupplyRequest.php');
$request = new FBAInventoryServiceMWS_Model_ListInventorySupplyRequest($request);
}
$parameters = $request->toQueryParameterArray();
$parameters['Action'] = 'ListInventorySupply';
$httpResponse = $this->_invoke($parameters);
require_once (dirname(__FILE__) . '/Model/ListInventorySupplyResponse.php');
$response = FBAInventoryServiceMWS_Model_ListInventorySupplyResponse::fromXML($httpResponse['ResponseBody']);
$response->setResponseHeaderMetadata($httpResponse['ResponseHeaderMetadata']);
return $response;
}
/**
* Convert ListInventorySupplyRequest to name value pairs
*/
private function _convertListInventorySupply($request) {
$parameters = array();
$parameters['Action'] = 'ListInventorySupply';
if ($request->isSetSellerId()) {
$parameters['SellerId'] = $request->getSellerId();
}
if ($request->isSetMWSAuthToken()) {
$parameters['MWSAuthToken'] = $request->getMWSAuthToken();
}
if ($request->isSetMarketplace()) {
$parameters['Marketplace'] = $request->getMarketplace();
}
if ($request->isSetSupplyRegion()) {
$parameters['SupplyRegion'] = $request->getSupplyRegion();
}
if ($request->isSetSellerSkus()) {
$SellerSkusListInventorySupplyRequest = $request->getSellerSkus();
foreach ($SellerSkusListInventorySupplyRequest->getmember() as $memberSellerSkusIndex => $memberSellerSkus) {
$parameters['SellerSkus' . '.' . 'member' . '.' . ($memberSellerSkusIndex + 1)] = $memberSellerSkus;
}
}
if ($request->isSetQueryStartDateTime()) {
$parameters['QueryStartDateTime'] = $request->getQueryStartDateTime();
}
if ($request->isSetResponseGroup()) {
$parameters['ResponseGroup'] = $request->getResponseGroup();
}
return $parameters;
}
/**
* List Inventory Supply By Next Token
* Continues pagination over a resultset of inventory data for inventory
* items.
*
* This operation is used in conjunction with ListUpdatedInventorySupply.
* Please refer to documentation for that operation for further details.
*
* @param mixed $request array of parameters for FBAInventoryServiceMWS_Model_ListInventorySupplyByNextToken request or FBAInventoryServiceMWS_Model_ListInventorySupplyByNextToken object itself
* @see FBAInventoryServiceMWS_Model_ListInventorySupplyByNextTokenRequest
* @return FBAInventoryServiceMWS_Model_ListInventorySupplyByNextTokenResponse
*
* @throws FBAInventoryServiceMWS_Exception
*/
public function listInventorySupplyByNextToken($request)
{
if (!($request instanceof FBAInventoryServiceMWS_Model_ListInventorySupplyByNextTokenRequest)) {
require_once (dirname(__FILE__) . '/Model/ListInventorySupplyByNextTokenRequest.php');
$request = new FBAInventoryServiceMWS_Model_ListInventorySupplyByNextTokenRequest($request);
}
$parameters = $request->toQueryParameterArray();
$parameters['Action'] = 'ListInventorySupplyByNextToken';
$httpResponse = $this->_invoke($parameters);
require_once (dirname(__FILE__) . '/Model/ListInventorySupplyByNextTokenResponse.php');
$response = FBAInventoryServiceMWS_Model_ListInventorySupplyByNextTokenResponse::fromXML($httpResponse['ResponseBody']);
$response->setResponseHeaderMetadata($httpResponse['ResponseHeaderMetadata']);
return $response;
}
/**
* Convert ListInventorySupplyByNextTokenRequest to name value pairs
*/
private function _convertListInventorySupplyByNextToken($request) {
$parameters = array();
$parameters['Action'] = 'ListInventorySupplyByNextToken';
if ($request->isSetSellerId()) {
$parameters['SellerId'] = $request->getSellerId();
}
if ($request->isSetMWSAuthToken()) {
$parameters['MWSAuthToken'] = $request->getMWSAuthToken();
}
if ($request->isSetMarketplace()) {
$parameters['Marketplace'] = $request->getMarketplace();
}
if ($request->isSetSupplyRegion()) {
$parameters['SupplyRegion'] = $request->getSupplyRegion();
}
if ($request->isSetNextToken()) {
$parameters['NextToken'] = $request->getNextToken();
}
return $parameters;
}
/**
* Construct new Client
*
* @param string $awsAccessKeyId AWS Access Key ID
* @param string $awsSecretAccessKey AWS Secret Access Key
* @param array $config configuration options.
* Valid configuration options are:
*
* - ServiceURL
* - UserAgent
* - SignatureVersion
* - TimesRetryOnError
* - ProxyHost
* - ProxyPort
* - ProxyUsername
-
*
- ProxyPassword
-
*
- MaxErrorRetry
*
*/
public function __construct(
$awsAccessKeyId, $awsSecretAccessKey, $applicationName, $applicationVersion,$config, $attributes = null)
{
if (PHP_VERSION_ID < 50600) {
iconv_set_encoding('input_encoding', 'UTF-8');
iconv_set_encoding('output_encoding', 'UTF-8');
iconv_set_encoding('internal_encoding', 'UTF-8');
} else {
ini_set('default_charset', 'UTF-8');
}
$this->_awsAccessKeyId = $awsAccessKeyId;
$this->_awsSecretAccessKey = $awsSecretAccessKey;
$this->_serviceVersion = $applicationVersion;
if (!is_null($config)) $this->_config = array_merge($this->_config, $config);
$this->setUserAgentHeader($applicationName, $applicationVersion, $attributes);
}
public function setUserAgentHeader(
$applicationName,
$applicationVersion,
$attributes = null) {
if (is_null($attributes)) {
$attributes = array ();
}
$this->_config['UserAgent'] =
$this->constructUserAgentHeader($applicationName, $applicationVersion, $attributes);
}
private function constructUserAgentHeader($applicationName, $applicationVersion, $attributes = null) {
if (is_null($applicationName) || $applicationName === "") {
throw new InvalidArgumentException('$applicationName cannot be null');
}
if (is_null($applicationVersion) || $applicationVersion === "") {
throw new InvalidArgumentException('$applicationVersion cannot be null');
}
$userAgent =
$this->quoteApplicationName($applicationName)
. '/'
. $this->quoteApplicationVersion($applicationVersion);
$userAgent .= ' (';
$userAgent .= 'Language=PHP/' . phpversion();
$userAgent .= '; ';
$userAgent .= 'Platform=' . php_uname('s') . '/' . php_uname('m') . '/' . php_uname('r');
$userAgent .= '; ';
$userAgent .= 'MWSClientVersion=' . self::MWS_CLIENT_VERSION;
foreach ($attributes as $key => $value) {
if (empty($value)) {
throw new InvalidArgumentException("Value for $key cannot be null or empty.");
}
$userAgent .= '; '
. $this->quoteAttributeName($key)
. '='
. $this->quoteAttributeValue($value);
}
$userAgent .= ')';
return $userAgent;
}
/**
* Collapse multiple whitespace characters into a single ' ' character.
* @param $s
* @return string
*/
private function collapseWhitespace($s) {
return preg_replace('/ {2,}|\s/', ' ', $s);
}
/**
* Collapse multiple whitespace characters into a single ' ' and backslash escape '\',
* and '/' characters from a string.
* @param $s
* @return string
*/
private function quoteApplicationName($s) {
$quotedString = $this->collapseWhitespace($s);
$quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString);
$quotedString = preg_replace('/\//', '\\/', $quotedString);
return $quotedString;
}
/**
* Collapse multiple whitespace characters into a single ' ' and backslash escape '\',
* and '(' characters from a string.
*
* @param $s
* @return string
*/
private function quoteApplicationVersion($s) {
$quotedString = $this->collapseWhitespace($s);
$quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString);
$quotedString = preg_replace('/\\(/', '\\(', $quotedString);
return $quotedString;
}
/**
* Collapse multiple whitespace characters into a single ' ' and backslash escape '\',
* and '=' characters from a string.
*
* @param $s
* @return unknown_type
*/
private function quoteAttributeName($s) {
$quotedString = $this->collapseWhitespace($s);
$quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString);
$quotedString = preg_replace('/\\=/', '\\=', $quotedString);
return $quotedString;
}
/**
* Collapse multiple whitespace characters into a single ' ' and backslash escape ';', '\',
* and ')' characters from a string.
*
* @param $s
* @return unknown_type
*/
private function quoteAttributeValue($s) {
$quotedString = $this->collapseWhitespace($s);
$quotedString = preg_replace('/\\\\/', '\\\\\\\\', $quotedString);
$quotedString = preg_replace('/\\;/', '\\;', $quotedString);
$quotedString = preg_replace('/\\)/', '\\)', $quotedString);
return $quotedString;
}
// Private API ------------------------------------------------------------//
/**
* Invoke request and return response
*/
private function _invoke(array $parameters)
{
try {
if (empty($this->_config['ServiceURL'])) {
require_once (dirname(__FILE__) . '/Exception.php');
throw new FBAInventoryServiceMWS_Exception(
array ('ErrorCode' => 'InvalidServiceURL',
'Message' => "Missing serviceUrl configuration value. You may obtain a list of valid MWS URLs by consulting the MWS Developer's Guide, or reviewing the sample code published along side this library."));
}
$parameters = $this->_addRequiredParameters($parameters);
$retries = 0;
for (;;) {
$response = $this->_httpPost($parameters);
$status = $response['Status'];
if ($status == 200) {
return array('ResponseBody' => $response['ResponseBody'],
'ResponseHeaderMetadata' => $response['ResponseHeaderMetadata']);
}
if ($status == 500 && $this->_pauseOnRetry(++$retries)) {
continue;
}
throw $this->_reportAnyErrors($response['ResponseBody'],
$status, $response['ResponseHeaderMetadata']);
}
} catch (FBAInventoryServiceMWS_Exception $se) {
throw $se;
} catch (Exception $t) {
require_once (dirname(__FILE__) . '/Exception.php');
throw new FBAInventoryServiceMWS_Exception(array('Exception' => $t, 'Message' => $t->getMessage()));
}
}
/**
* Look for additional error strings in the response and return formatted exception
*/
private function _reportAnyErrors($responseBody, $status, $responseHeaderMetadata, Exception $e = null)
{
$exProps = array();
$exProps["StatusCode"] = $status;
$exProps["ResponseHeaderMetadata"] = $responseHeaderMetadata;
libxml_use_internal_errors(true); // Silence XML parsing errors
$xmlBody = simplexml_load_string($responseBody);
if ($xmlBody !== false) { // Check XML loaded without errors
$exProps["XML"] = $responseBody;
$exProps["ErrorCode"] = $xmlBody->Error->Code;
$exProps["Message"] = $xmlBody->Error->Message;
$exProps["ErrorType"] = !empty($xmlBody->Error->Type) ? $xmlBody->Error->Type : "Unknown";
$exProps["RequestId"] = !empty($xmlBody->RequestID) ? $xmlBody->RequestID : $xmlBody->RequestId; // 'd' in RequestId is sometimes capitalized
} else { // We got bad XML in response, just throw a generic exception
$exProps["Message"] = "Internal Error";
}
require_once (dirname(__FILE__) . '/Exception.php');
return new FBAInventoryServiceMWS_Exception($exProps);
}
/**
* Perform HTTP post with exponential retries on error 500 and 503
*
*/
private function _httpPost(array $parameters)
{
$config = $this->_config;
$query = $this->_getParametersAsString($parameters);
$url = parse_url ($config['ServiceURL']);
$uri = array_key_exists('path', $url) ? $url['path'] : null;
if (!isset ($uri)) {
$uri = "/";
}
switch ($url['scheme']) {
case 'https':
$scheme = 'https://';
$port = isset($url['port']) ? $url['port'] : 443;
break;
default:
$scheme = 'http://';
$port = isset($url['port']) ? $url['port'] : 80;
}
$allHeaders = $config['Headers'];
$allHeaders['Content-Type'] = "application/x-www-form-urlencoded; charset=utf-8"; // We need to make sure to set utf-8 encoding here
$allHeaders['Expect'] = null; // Don't expect 100 Continue
$allHeadersStr = array();
foreach($allHeaders as $name => $val) {
$str = $name . ": ";
if(isset($val)) {
$str = $str . $val;
}
$allHeadersStr[] = $str;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $scheme . $url['host'] . $uri);
curl_setopt($ch, CURLOPT_PORT, $port);
$this->setSSLCurlOptions($ch);
curl_setopt($ch, CURLOPT_USERAGENT, $this->_config['UserAgent']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_setopt($ch, CURLOPT_HTTPHEADER, $allHeadersStr);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($config['ProxyHost'] != null && $config['ProxyPort'] != -1)
{
curl_setopt($ch, CURLOPT_PROXY, $config['ProxyHost'] . ':' . $config['ProxyPort']);
}
if ($config['ProxyUsername'] != null && $config['ProxyPassword'] != null)
{
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $config['ProxyUsername'] . ':' . $config['ProxyPassword']);
}
$response = "";
$response = curl_exec($ch);
if($response === false) {
require_once (dirname(__FILE__) . '/Exception.php');
$exProps["Message"] = curl_error($ch);
$exProps["ErrorType"] = "HTTP";
curl_close($ch);
throw new FBAInventoryServiceMWS_Exception($exProps);
}
curl_close($ch);
return $this->_extractHeadersAndBody($response);
}
/**
* This method will attempt to extract the headers and body of our response.
* We need to split the raw response string by 2 'CRLF's. 2 'CRLF's should indicate the separation of the response header
* from the response body. However in our case we have some circumstances (certain client proxies) that result in
* multiple responses concatenated. We could encounter a response like
*
* HTTP/1.1 100 Continue
*
* HTTP/1.1 200 OK
* Date: Tue, 01 Apr 2014 13:02:51 GMT
* Content-Type: text/html; charset=iso-8859-1
* Content-Length: 12605
*
* ... body ..
*
* This method will throw away extra response status lines and attempt to find the first full response headers and body
*
* return [status, body, ResponseHeaderMetadata]
*/
private function _extractHeadersAndBody($response){
//First split by 2 'CRLF'
$responseComponents = preg_split("/(?:\r?\n){2}/", $response, 2);
$body = null;
for ($count = 0;
$count < count($responseComponents) && $body == null;
$count++) {
$headers = $responseComponents[$count];
$responseStatus = $this->_extractHttpStatusCode($headers);
if($responseStatus != null &&
$this->_httpHeadersHaveContent($headers)){
$responseHeaderMetadata = $this->_extractResponseHeaderMetadata($headers);
//The body will be the next item in the responseComponents array
$body = $responseComponents[++$count];
}
}
//If the body is null here then we were unable to parse the response and will throw an exception
if($body == null){
require_once (dirname(__FILE__) . '/Exception.php');
$exProps["Message"] = "Failed to parse valid HTTP response (" . $response . ")";
$exProps["ErrorType"] = "HTTP";
throw new FBAInventoryServiceMWS_Exception($exProps);
}
return array(
'Status' => $responseStatus,
'ResponseBody' => $body,
'ResponseHeaderMetadata' => $responseHeaderMetadata);
}
/**
* parse the status line of a header string for the proper format and
* return the status code
*
* Example: HTTP/1.1 200 OK
* ...
* returns String statusCode or null if the status line can't be parsed
*/
private function _extractHttpStatusCode($headers){
$statusCode = null;
if (1 === preg_match("/(\\S+) +(\\d+) +([^\n\r]+)(?:\r?\n|\r)/", $headers, $matches)) {
//The matches array [entireMatchString, protocol, statusCode, the rest]
$statusCode = $matches[2];
}
return $statusCode;
}
/**
* Tries to determine some valid headers indicating this response
* has content. In this case
* return true if there is a valid "Content-Length" or "Transfer-Encoding" header
*/
private function _httpHeadersHaveContent($headers){
return (1 === preg_match("/[cC]ontent-[lL]ength: +(?:\\d+)(?:\\r?\\n|\\r|$)/", $headers) ||
1 === preg_match("/Transfer-Encoding: +(?!identity[\r\n;= ])(?:[^\r\n]+)(?:\r?\n|\r|$)/i", $headers));
}
/**
* extract a ResponseHeaderMetadata object from the raw headers
*/
private function _extractResponseHeaderMetadata($rawHeaders){
$inputHeaders = preg_split("/\r\n|\n|\r/", $rawHeaders);
$headers = array();
$headers['x-mws-request-id'] = null;
$headers['x-mws-response-context'] = null;
$headers['x-mws-timestamp'] = null;
$headers['x-mws-quota-max'] = null;
$headers['x-mws-quota-remaining'] = null;
$headers['x-mws-quota-resetsOn'] = null;
foreach ($inputHeaders as $currentHeader) {
$keyValue = explode (': ', $currentHeader);
if (isset($keyValue[1])) {
list ($key, $value) = $keyValue;
if (isset($headers[$key]) && $headers[$key]!==null) {
$headers[$key] = $headers[$key] . "," . $value;
} else {
$headers[$key] = $value;
}
}
}
require_once(dirname(__FILE__) . '/Model/ResponseHeaderMetadata.php');
return new FBAInventoryServiceMWS_Model_ResponseHeaderMetadata(
$headers['x-mws-request-id'],
$headers['x-mws-response-context'],
$headers['x-mws-timestamp'],
$headers['x-mws-quota-max'],
$headers['x-mws-quota-remaining'],
$headers['x-mws-quota-resetsOn']);
}
/**
* Set curl options relating to SSL. Protected to allow overriding.
* @param $ch curl handle
*/
protected function setSSLCurlOptions($ch) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
}
/**
* Exponential sleep on failed request
*
* @param retries current retry
*/
private function _pauseOnRetry($retries)
{
if ($retries <= $this->_config['MaxErrorRetry']) {
$delay = (int) (pow(4, $retries) * 100000);
usleep($delay);
return true;
}
return false;
}
/**
* Add authentication related and version parameters
*/
private function _addRequiredParameters(array $parameters)
{
$parameters['AWSAccessKeyId'] = $this->_awsAccessKeyId;
$parameters['Timestamp'] = $this->_getFormattedTimestamp();
$parameters['Version'] = self::SERVICE_VERSION;
$parameters['SignatureVersion'] = $this->_config['SignatureVersion'];
if ($parameters['SignatureVersion'] > 1) {
$parameters['SignatureMethod'] = $this->_config['SignatureMethod'];
}
$parameters['Signature'] = $this->_signParameters($parameters, $this->_awsSecretAccessKey);
return $parameters;
}
/**
* Convert paremeters to Url encoded query string
*/
private function _getParametersAsString(array $parameters)
{
$queryParameters = array();
foreach ($parameters as $key => $value) {
$queryParameters[] = $key . '=' . $this->_urlencode($value);
}
return implode('&', $queryParameters);
}
/**
* Computes RFC 2104-compliant HMAC signature for request parameters
* Implements AWS Signature, as per following spec:
*
* If Signature Version is 0, it signs concatenated Action and Timestamp
*
* If Signature Version is 1, it performs the following:
*
* Sorts all parameters (including SignatureVersion and excluding Signature,
* the value of which is being created), ignoring case.
*
* Iterate over the sorted list and append the parameter name (in original case)
* and then its value. It will not URL-encode the parameter values before
* constructing this string. There are no separators.
*
* If Signature Version is 2, string to sign is based on following:
*
* 1. The HTTP Request Method followed by an ASCII newline (%0A)
* 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline.
* 3. The URL encoded HTTP absolute path component of the URI
* (up to but not including the query string parameters);
* if this is empty use a forward '/'. This parameter is followed by an ASCII newline.
* 4. The concatenation of all query string components (names and values)
* as UTF-8 characters which are URL encoded as per RFC 3986
* (hex characters MUST be uppercase), sorted using lexicographic byte ordering.
* Parameter names are separated from their values by the '=' character
* (ASCII character 61), even if the value is empty.
* Pairs of parameter and values are separated by the '&' character (ASCII code 38).
*
*/
private function _signParameters(array $parameters, $key) {
$signatureVersion = $parameters['SignatureVersion'];
$algorithm = "HmacSHA1";
$stringToSign = null;
if (2 == $signatureVersion) {
$algorithm = $this->_config['SignatureMethod'];
$parameters['SignatureMethod'] = $algorithm;
$stringToSign = $this->_calculateStringToSignV2($parameters);
} else {
throw new Exception("Invalid Signature Version specified");
}
return $this->_sign($stringToSign, $key, $algorithm);
}
/**
* Calculate String to Sign for SignatureVersion 2
* @param array $parameters request parameters
* @return String to Sign
*/
private function _calculateStringToSignV2(array $parameters) {
$data = 'POST';
$data .= "\n";
$endpoint = parse_url ($this->_config['ServiceURL']);
$data .= $endpoint['host'];
$data .= "\n";
$uri = array_key_exists('path', $endpoint) ? $endpoint['path'] : null;
if (!isset ($uri)) {
$uri = "/";
}
$uriencoded = implode("/", array_map(array($this, "_urlencode"), explode("/", $uri)));
$data .= $uriencoded;
$data .= "\n";
uksort($parameters, 'strcmp');
$data .= $this->_getParametersAsString($parameters);
return $data;
}
private function _urlencode($value) {
return str_replace('%7E', '~', rawurlencode($value));
}
/**
* Computes RFC 2104-compliant HMAC signature.
*/
private function _sign($data, $key, $algorithm)
{
if ($algorithm === 'HmacSHA1') {
$hash = 'sha1';
} else if ($algorithm === 'HmacSHA256') {
$hash = 'sha256';
} else {
throw new Exception ("Non-supported signing method specified");
}
return base64_encode(
hash_hmac($hash, $data, $key, true)
);
}
/**
* Formats date as ISO 8601 timestamp
*/
private function _getFormattedTimestamp()
{
return gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time());
}
/**
* Formats date as ISO 8601 timestamp
*/
private function getFormattedTimestamp($dateTime)
{
return $dateTime->format(DATE_ISO8601);
}
}