W3 Total Cache - Version 0.9.7.5

Version Description

  • Updated AWS library
  • Added support of set_sql_mode by dbcluster
  • Improved support for webserver running on non-default port with disk-enhanced
  • Improved menu icons
  • Fixed php warning when remote service cannot be loaded
  • Fixed php warnings on support page
Download this release

Release Info

Developer fredericktownes
Plugin Icon 128x128 W3 Total Cache
Version 0.9.7.5
Comparing to
See all releases

Code changes from version 0.9.7.4 to 0.9.7.5

Files changed (206) hide show
  1. CdnEngine.php +2 -2
  2. CdnEngine_Azure.php +5 -11
  3. CdnEngine_Base.php +2 -8
  4. CdnEngine_CloudFront.php +345 -0
  5. CdnEngine_Mirror_CloudFront.php +324 -0
  6. CdnEngine_S3.php +188 -235
  7. CdnEngine_S3_Cf.php +0 -453
  8. CdnEngine_S3_Cf_Custom.php +0 -19
  9. CdnEngine_S3_Cf_S3.php +0 -10
  10. CdnEngine_S3_Compatible.php +6 -6
  11. Cdn_AdminActions.php +22 -17
  12. Cdn_Core_Admin.php +45 -57
  13. Cdn_Plugin_Admin.php +1 -1
  14. Cdn_Util.php +1 -0
  15. Cdnfsd_CloudFront_Api.php +0 -344
  16. Cdnfsd_CloudFront_Engine.php +46 -11
  17. Cdnfsd_CloudFront_Popup.php +114 -83
  18. Cdnfsd_CloudFront_Popup_View_Distribution.php +45 -47
  19. Config.php +13 -13
  20. ConfigKeys.php +0 -4
  21. DbCache_Wpdb.php +14 -0
  22. DbCache_WpdbInjection.php +8 -0
  23. Enterprise_CacheFlush_MakeSnsEvent.php +6 -3
  24. Enterprise_Dbcache_WpdbInjection_Cluster.php +1 -0
  25. Enterprise_SnsBase.php +13 -9
  26. Enterprise_SnsServer.php +14 -19
  27. Generic_Plugin.php +57 -34
  28. Generic_Plugin_Admin.php +4 -30
  29. Generic_WidgetServices_View.php +17 -17
  30. PgCache_ContentGrabber.php +3 -1
  31. PgCache_Flush.php +38 -18
  32. Root_AdminMenu.php +4 -4
  33. Support_Page.php +19 -22
  34. Support_Page_View_PageContent.php +10 -10
  35. Util_Admin.php +7 -21
  36. inc/mime/html.php +0 -1
  37. inc/options/browsercache.php +1 -1
  38. inc/options/general.php +285 -285
  39. lib/Aws/Aws/Api/AbstractModel.php +67 -0
  40. lib/Aws/Aws/Api/ApiProvider.php +244 -0
  41. lib/Aws/Aws/Api/DateTimeResult.php +41 -0
  42. lib/Aws/Aws/Api/DocModel.php +128 -0
  43. lib/Aws/Aws/Api/ErrorParser/JsonParserTrait.php +26 -0
  44. lib/Aws/Aws/Api/ErrorParser/JsonRpcErrorParser.php +31 -0
  45. lib/Aws/Aws/Api/ErrorParser/RestJsonErrorParser.php +35 -0
  46. lib/Aws/Aws/Api/ErrorParser/XmlErrorParser.php +82 -0
  47. lib/Aws/Aws/Api/ListShape.php +35 -0
  48. lib/Aws/Aws/Api/MapShape.php +54 -0
  49. lib/Aws/Aws/Api/Operation.php +97 -0
  50. lib/Aws/Aws/Api/Parser/AbstractParser.php +46 -0
  51. lib/Aws/Aws/Api/Parser/AbstractRestParser.php +173 -0
  52. lib/Aws/Aws/Api/Parser/Crc32ValidatingParser.php +54 -0
  53. lib/Aws/Aws/Api/Parser/DecodingEventStreamIterator.php +335 -0
  54. lib/Aws/Aws/Api/Parser/EventParsingIterator.php +107 -0
  55. lib/Aws/Aws/Api/Parser/Exception/ParserException.php +31 -0
  56. lib/Aws/Aws/Api/Parser/JsonParser.php +62 -0
  57. lib/Aws/Aws/Api/Parser/JsonRpcParser.php +51 -0
  58. lib/Aws/Aws/Api/Parser/PayloadParserTrait.php +61 -0
  59. lib/Aws/Aws/Api/Parser/QueryParser.php +60 -0
  60. lib/Aws/Aws/Api/Parser/RestJsonParser.php +49 -0
  61. lib/Aws/Aws/Api/Parser/RestXmlParser.php +42 -0
  62. lib/Aws/Aws/Api/Parser/XmlParser.php +138 -0
  63. lib/Aws/Aws/Api/Serializer/Ec2ParamBuilder.php +40 -0
  64. lib/Aws/Aws/Api/Serializer/JsonBody.php +96 -0
  65. lib/Aws/Aws/Api/Serializer/JsonRpcSerializer.php +69 -0
  66. lib/Aws/Aws/Api/Serializer/QueryParamBuilder.php +157 -0
  67. lib/Aws/Aws/Api/Serializer/QuerySerializer.php +69 -0
  68. lib/Aws/Aws/Api/Serializer/RestJsonSerializer.php +39 -0
  69. lib/Aws/Aws/Api/Serializer/RestSerializer.php +219 -0
  70. lib/Aws/Aws/Api/Serializer/RestXmlSerializer.php +34 -0
  71. lib/Aws/Aws/Api/Serializer/XmlBody.php +220 -0
  72. lib/Aws/Aws/Api/Service.php +448 -0
  73. lib/Aws/Aws/Api/Shape.php +69 -0
  74. lib/Aws/Aws/Api/ShapeMap.php +66 -0
  75. lib/Aws/Aws/Api/StructureShape.php +79 -0
  76. lib/Aws/Aws/Api/TimestampShape.php +48 -0
  77. lib/Aws/Aws/Api/Validator.php +286 -0
  78. lib/Aws/Aws/AwsClient.php +402 -0
  79. lib/Aws/Aws/AwsClientInterface.php +169 -0
  80. lib/Aws/Aws/AwsClientTrait.php +92 -0
  81. lib/Aws/Aws/CacheInterface.php +34 -0
  82. lib/Aws/Aws/ClientResolver.php +768 -0
  83. lib/Aws/Aws/ClientSideMonitoring/AbstractMonitoringMiddleware.php +275 -0
  84. lib/Aws/Aws/ClientSideMonitoring/ApiCallAttemptMonitoringMiddleware.php +262 -0
  85. lib/Aws/Aws/ClientSideMonitoring/ApiCallMonitoringMiddleware.php +176 -0
  86. lib/Aws/Aws/ClientSideMonitoring/Configuration.php +65 -0
  87. lib/Aws/Aws/ClientSideMonitoring/ConfigurationInterface.php +37 -0
  88. lib/Aws/Aws/ClientSideMonitoring/ConfigurationProvider.php +342 -0
  89. lib/Aws/Aws/ClientSideMonitoring/Exception/ConfigurationException.php +15 -0
  90. lib/Aws/Aws/ClientSideMonitoring/MonitoringMiddlewareInterface.php +35 -0
  91. lib/Aws/Aws/CloudFront/CloudFrontClient.php +190 -0
  92. lib/Aws/Aws/CloudFront/CookieSigner.php +65 -0
  93. lib/Aws/Aws/CloudFront/Exception/CloudFrontException.php +9 -0
  94. lib/Aws/Aws/CloudFront/Signer.php +117 -0
  95. lib/Aws/Aws/CloudFront/UrlSigner.php +119 -0
  96. lib/Aws/Aws/Command.php +62 -0
  97. lib/Aws/Aws/CommandInterface.php +42 -0
  98. lib/Aws/Aws/CommandPool.php +150 -0
  99. lib/Aws/Aws/Credentials/AssumeRoleCredentialProvider.php +64 -0
  100. lib/Aws/Aws/Credentials/CredentialProvider.php +488 -0
  101. lib/Aws/Aws/Credentials/Credentials.php +91 -0
  102. lib/Aws/Aws/Credentials/CredentialsInterface.php +52 -0
  103. lib/Aws/Aws/Credentials/EcsCredentialProvider.php +88 -0
  104. lib/Aws/Aws/Credentials/InstanceProfileProvider.php +118 -0
  105. lib/Aws/Aws/DoctrineCacheAdapter.php +55 -0
  106. lib/Aws/Aws/Endpoint/EndpointProvider.php +96 -0
  107. lib/Aws/Aws/Endpoint/Partition.php +183 -0
  108. lib/Aws/Aws/Endpoint/PartitionEndpointProvider.php +108 -0
  109. lib/Aws/Aws/Endpoint/PartitionInterface.php +56 -0
  110. lib/Aws/Aws/Endpoint/PatternEndpointProvider.php +51 -0
  111. lib/Aws/Aws/EndpointDiscovery/Configuration.php +48 -0
  112. lib/Aws/Aws/EndpointDiscovery/ConfigurationInterface.php +30 -0
  113. lib/Aws/Aws/EndpointDiscovery/ConfigurationProvider.php +333 -0
  114. lib/Aws/Aws/EndpointDiscovery/EndpointDiscoveryMiddleware.php +414 -0
  115. lib/Aws/Aws/EndpointDiscovery/EndpointList.php +85 -0
  116. lib/Aws/Aws/EndpointDiscovery/Exception/ConfigurationException.php +14 -0
  117. lib/Aws/Aws/EndpointParameterMiddleware.php +84 -0
  118. lib/Aws/Aws/Exception/AwsException.php +237 -0
  119. lib/Aws/Aws/Exception/CouldNotCreateChecksumException.php +25 -0
  120. lib/Aws/Aws/Exception/CredentialsException.php +11 -0
  121. lib/Aws/Aws/Exception/EventStreamDataException.php +38 -0
  122. lib/Aws/Aws/Exception/MultipartUploadException.php +63 -0
  123. lib/Aws/Aws/Exception/UnresolvedApiException.php +11 -0
  124. lib/Aws/Aws/Exception/UnresolvedEndpointException.php +11 -0
  125. lib/Aws/Aws/Exception/UnresolvedSignatureException.php +11 -0
  126. lib/Aws/Aws/Handler/GuzzleV5/GuzzleHandler.php +210 -0
  127. lib/Aws/Aws/Handler/GuzzleV5/GuzzleStream.php +24 -0
  128. lib/Aws/Aws/Handler/GuzzleV5/PsrStream.php +34 -0
  129. lib/Aws/Aws/Handler/GuzzleV6/GuzzleHandler.php +85 -0
  130. lib/Aws/Aws/HandlerList.php +451 -0
  131. lib/Aws/Aws/HasDataTrait.php +60 -0
  132. lib/Aws/Aws/HasMonitoringEventsTrait.php +39 -0
  133. lib/Aws/Aws/HashInterface.php +27 -0
  134. lib/Aws/Aws/HashingStream.php +60 -0
  135. lib/Aws/Aws/History.php +156 -0
  136. lib/Aws/Aws/IdempotencyTokenMiddleware.php +118 -0
  137. lib/Aws/Aws/JsonCompiler.php +25 -0
  138. lib/Aws/Aws/LruArrayCache.php +79 -0
  139. lib/Aws/Aws/Middleware.php +372 -0
  140. lib/Aws/Aws/MockHandler.php +145 -0
  141. lib/Aws/Aws/MonitoringEventsInterface.php +32 -0
  142. lib/Aws/Aws/MultiRegionClient.php +236 -0
  143. lib/Aws/Aws/PhpHash.php +81 -0
  144. lib/Aws/Aws/PresignUrlMiddleware.php +99 -0
  145. lib/Aws/Aws/Psr16CacheAdapter.php +30 -0
  146. lib/Aws/Aws/PsrCacheAdapter.php +38 -0
  147. lib/Aws/Aws/ResponseContainerInterface.php +13 -0
  148. lib/Aws/Aws/Result.php +57 -0
  149. lib/Aws/Aws/ResultInterface.php +54 -0
  150. lib/Aws/Aws/ResultPaginator.php +169 -0
  151. lib/Aws/Aws/RetryMiddleware.php +315 -0
  152. lib/Aws/Aws/S3/AmbiguousSuccessParser.php +68 -0
  153. lib/Aws/Aws/S3/ApplyChecksumMiddleware.php +78 -0
  154. lib/Aws/Aws/S3/BatchDelete.php +237 -0
  155. lib/Aws/Aws/S3/BucketEndpointMiddleware.php +75 -0
  156. lib/Aws/Aws/S3/Crypto/CryptoParamsTrait.php +75 -0
  157. lib/Aws/Aws/S3/Crypto/HeadersMetadataStrategy.php +52 -0
  158. lib/Aws/Aws/S3/Crypto/InstructionFileMetadataStrategy.php +90 -0
  159. lib/Aws/Aws/S3/Crypto/S3EncryptionClient.php +317 -0
  160. lib/Aws/Aws/S3/Crypto/S3EncryptionMultipartUploader.php +157 -0
  161. lib/Aws/Aws/S3/Exception/DeleteMultipleObjectsException.php +68 -0
  162. lib/Aws/Aws/S3/Exception/PermanentRedirectException.php +4 -0
  163. lib/Aws/Aws/S3/Exception/S3Exception.php +9 -0
  164. lib/Aws/Aws/S3/Exception/S3MultipartUploadException.php +84 -0
  165. lib/Aws/Aws/S3/GetBucketLocationParser.php +49 -0
  166. lib/Aws/Aws/S3/MultipartCopy.php +183 -0
  167. lib/Aws/Aws/S3/MultipartUploader.php +168 -0
  168. lib/Aws/Aws/S3/MultipartUploadingTrait.php +132 -0
  169. lib/Aws/Aws/S3/ObjectCopier.php +150 -0
  170. lib/Aws/Aws/S3/ObjectUploader.php +140 -0
  171. lib/Aws/Aws/S3/PermanentRedirectMiddleware.php +62 -0
  172. lib/Aws/Aws/S3/PostObject.php +160 -0
  173. lib/Aws/Aws/S3/PostObjectV4.php +195 -0
  174. lib/Aws/Aws/S3/PutObjectUrlMiddleware.php +57 -0
  175. lib/Aws/Aws/S3/RetryableMalformedResponseParser.php +56 -0
  176. lib/Aws/Aws/S3/S3Client.php +633 -0
  177. lib/Aws/Aws/S3/S3ClientInterface.php +322 -0
  178. lib/Aws/Aws/S3/S3ClientTrait.php +323 -0
  179. lib/Aws/Aws/S3/S3EndpointMiddleware.php +234 -0
  180. lib/Aws/Aws/S3/S3MultiRegionClient.php +339 -0
  181. lib/Aws/Aws/S3/S3UriParser.php +133 -0
  182. lib/Aws/Aws/S3/SSECMiddleware.php +75 -0
  183. lib/Aws/Aws/S3/StreamWrapper.php +958 -0
  184. lib/Aws/Aws/S3/Transfer.php +428 -0
  185. lib/Aws/Aws/Sdk.php +466 -0
  186. lib/Aws/Aws/Signature/AnonymousSignature.php +26 -0
  187. lib/Aws/Aws/Signature/S3SignatureV4.php +68 -0
  188. lib/Aws/Aws/Signature/SignatureInterface.php +44 -0
  189. lib/Aws/Aws/Signature/SignatureProvider.php +131 -0
  190. lib/Aws/Aws/Signature/SignatureTrait.php +49 -0
  191. lib/Aws/Aws/Signature/SignatureV4.php +412 -0
  192. lib/Aws/Aws/Sns/Exception/InvalidSnsMessageException.php +9 -0
  193. lib/Aws/Aws/Sns/Exception/SnsException.php +9 -0
  194. lib/Aws/Aws/Sns/Message.php +156 -0
  195. lib/Aws/Aws/Sns/MessageValidator.php +190 -0
  196. lib/Aws/Aws/Sns/SnsClient.php +76 -0
  197. lib/Aws/Aws/TraceMiddleware.php +314 -0
  198. lib/Aws/Aws/Waiter.php +262 -0
  199. lib/Aws/Aws/WrappedHttpHandler.php +203 -0
  200. lib/Aws/Aws/data/cloudfront/2015-07-27/api-2.json.php +3 -0
  201. lib/Aws/Aws/data/cloudfront/2015-07-27/paginators-1.json.php +3 -0
  202. lib/Aws/Aws/data/cloudfront/2015-07-27/waiters-2.json.php +3 -0
  203. lib/Aws/Aws/data/cloudfront/2016-01-28/api-2.json.php +3 -0
  204. lib/Aws/Aws/data/cloudfront/2016-01-28/paginators-1.json.php +3 -0
  205. lib/Aws/Aws/data/cloudfront/2016-01-28/waiters-2.json.php +3 -0
  206. lib/Aws/Aws/data/cloudfront/2016-08-01/api-2.json.php +3 -0
CdnEngine.php CHANGED
@@ -32,11 +32,11 @@ class CdnEngine {
32
  break;
33
 
34
  case 'cf':
35
- $instances[$instance_key] = new CdnEngine_S3_Cf_S3( $config );
36
  break;
37
 
38
  case 'cf2':
39
- $instances[$instance_key] = new CdnEngine_S3_Cf_Custom( $config );
40
  break;
41
 
42
  case 'cotendo':
32
  break;
33
 
34
  case 'cf':
35
+ $instances[$instance_key] = new CdnEngine_CloudFront( $config );
36
  break;
37
 
38
  case 'cf2':
39
+ $instances[$instance_key] = new CdnEngine_Mirror_CloudFront( $config );
40
  break;
41
 
42
  case 'cotendo':
CdnEngine_Azure.php CHANGED
@@ -336,27 +336,24 @@ class CdnEngine_Azure extends CdnEngine_Base {
336
  /**
337
  * Creates bucket
338
  *
339
- * @param string $container_id
340
  * @param string $error
341
  * @return boolean
342
  */
343
- function create_container( &$container_id, &$error ) {
344
  if ( !$this->_init( $error ) ) {
345
- return false;
346
  }
347
 
348
  try {
349
  $containers = $this->_client->listContainers();
350
  } catch ( \Exception $exception ) {
351
  $error = sprintf( 'Unable to list containers (%s).', $exception->getMessage() );
352
-
353
- return false;
354
  }
355
 
356
  if ( in_array( $this->_config['container'], (array) $containers ) ) {
357
  $error = sprintf( 'Container already exists: %s.', $this->_config['container'] );
358
-
359
- return false;
360
  }
361
 
362
  try {
@@ -367,11 +364,8 @@ class CdnEngine_Azure extends CdnEngine_Base {
367
  $this->_client->createContainer( $this->_config['container'], $createContainerOptions );
368
  } catch ( \Exception $exception ) {
369
  $error = sprintf( 'Unable to create container: %s (%s)', $this->_config['container'], $exception->getMessage() );
370
-
371
- return false;
372
  }
373
-
374
- return true;
375
  }
376
 
377
  /**
336
  /**
337
  * Creates bucket
338
  *
 
339
  * @param string $error
340
  * @return boolean
341
  */
342
+ function create_container() {
343
  if ( !$this->_init( $error ) ) {
344
+ throw new \Exception( $error );
345
  }
346
 
347
  try {
348
  $containers = $this->_client->listContainers();
349
  } catch ( \Exception $exception ) {
350
  $error = sprintf( 'Unable to list containers (%s).', $exception->getMessage() );
351
+ throw new \Exception( $error );
 
352
  }
353
 
354
  if ( in_array( $this->_config['container'], (array) $containers ) ) {
355
  $error = sprintf( 'Container already exists: %s.', $this->_config['container'] );
356
+ throw new \Exception( $error );
 
357
  }
358
 
359
  try {
364
  $this->_client->createContainer( $this->_config['container'], $createContainerOptions );
365
  } catch ( \Exception $exception ) {
366
  $error = sprintf( 'Unable to create container: %s (%s)', $this->_config['container'], $exception->getMessage() );
367
+ throw new \Exception( $error );
 
368
  }
 
 
369
  }
370
 
371
  /**
CdnEngine_Base.php CHANGED
@@ -126,15 +126,9 @@ class CdnEngine_Base {
126
 
127
  /**
128
  * Create bucket / container for some CDN engines
129
- *
130
- * @param string $container_id
131
- * @param string $error
132
- * @return boolean
133
  */
134
- function create_container( &$container_id, &$error ) {
135
- $error = 'Not implemented.';
136
-
137
- return false;
138
  }
139
 
140
  /**
126
 
127
  /**
128
  * Create bucket / container for some CDN engines
 
 
 
 
129
  */
130
+ function create_container() {
131
+ throw new \Exception( 'Not implemented.' );
 
 
132
  }
133
 
134
  /**
CdnEngine_CloudFront.php ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace W3TC;
3
+
4
+ if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
5
+ require_once W3TC_LIB_DIR . '/Aws/aws-autoloader.php';
6
+ }
7
+
8
+ /**
9
+ * Amazon CloudFront (S3 origin) CDN engine
10
+ */
11
+ class CdnEngine_CloudFront extends CdnEngine_Base {
12
+ private $s3;
13
+ private $api;
14
+
15
+ function __construct( $config = array() ) {
16
+ $config = array_merge( array(
17
+ 'id' => ''
18
+ ), $config );
19
+
20
+ parent::__construct( $config );
21
+
22
+ $this->s3 = new CdnEngine_S3( $config );
23
+ }
24
+
25
+ /**
26
+ * Initialize
27
+ */
28
+ function _init() {
29
+ if ( !is_null( $this->api ) ) {
30
+ return;
31
+ }
32
+
33
+ $credentials = new \Aws\Credentials\Credentials(
34
+ $this->_config['key'],
35
+ $this->_config['secret'] );
36
+
37
+ $this->api = new \Aws\CloudFront\CloudFrontClient( array(
38
+ 'credentials' => $credentials,
39
+ 'region' => $this->_config['bucket_location'],
40
+ 'version' => '2018-11-05'
41
+ )
42
+ );
43
+
44
+ return true;
45
+ }
46
+
47
+ /**
48
+ * Formats URL
49
+ */
50
+ function _format_url( $path ) {
51
+ // the same limitation as S3 has
52
+ return $this->s3->_format_url( $path );
53
+ }
54
+
55
+ /**
56
+ * Upload files
57
+ *
58
+ * @param array $files
59
+ * @param array $results
60
+ * @param boolean $force_rewrite
61
+ * @return boolean
62
+ */
63
+ function upload( $files, &$results, $force_rewrite = false,
64
+ $timeout_time = NULL ) {
65
+ return $this->s3->upload( $files, $results, $force_rewrite,
66
+ $timeout_time );
67
+ }
68
+
69
+ /**
70
+ * Delete files from CDN
71
+ *
72
+ * @param array $files
73
+ * @param array $results
74
+ * @return boolean
75
+ */
76
+ function delete( $files, &$results ) {
77
+ return $this->s3->delete( $files, $results );
78
+ }
79
+
80
+ /**
81
+ * Purge files from CDN
82
+ *
83
+ * @param array $files
84
+ * @param array $results
85
+ * @return boolean
86
+ */
87
+ function purge( $files, &$results ) {
88
+ if ( !$this->s3->upload( $files, $results, true ) ) {
89
+ return false;
90
+ }
91
+
92
+ try {
93
+ $this->_init();
94
+ $dist = $this->_get_distribution();
95
+ } catch ( \Exception $ex ) {
96
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
97
+ return false;
98
+ }
99
+
100
+ $paths = array();
101
+
102
+ foreach ( $files as $file ) {
103
+ $remote_file = $file['remote_path'];
104
+ $paths[] = '/' . $remote_file;
105
+ }
106
+
107
+ try {
108
+ $invalidation = $this->api->createInvalidation( array(
109
+ 'DistributionId' => $dist['Id'],
110
+ 'InvalidationBatch' => array(
111
+ 'CallerReference' => 'w3tc-' . microtime(),
112
+ 'Paths' => array(
113
+ 'Items' => $paths,
114
+ 'Quantity' => count( $paths ),
115
+ ),
116
+ )
117
+ )
118
+ );
119
+ } catch ( \Exception $ex ) {
120
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT,
121
+ sprintf( 'Unable to create invalidation batch (%s).',
122
+ $ex->getMessage() ) );
123
+
124
+ return false;
125
+ }
126
+
127
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
128
+ return true;
129
+ }
130
+
131
+ /**
132
+ * Returns origin
133
+ */
134
+ function _get_origin() {
135
+ return sprintf( '%s.s3.amazonaws.com', $this->_config['bucket'] );
136
+ }
137
+
138
+ /**
139
+ * Returns array of CDN domains
140
+ */
141
+ public function get_domains() {
142
+ if ( !empty( $this->_config['cname'] ) ) {
143
+ return (array) $this->_config['cname'];
144
+ } elseif ( !empty( $this->_config['id'] ) ) {
145
+ $domain = sprintf( '%s.cloudfront.net', $this->_config['id'] );
146
+
147
+ return array(
148
+ $domain
149
+ );
150
+ }
151
+
152
+ return array();
153
+ }
154
+
155
+ /**
156
+ * Test CDN connectivity
157
+ */
158
+ function test( &$error ) {
159
+ $this->_init();
160
+ if ( !$this->s3->test( $error ) ) {
161
+ return false;
162
+ }
163
+
164
+ /**
165
+ * Search active CF distribution
166
+ */
167
+ $dists = $this->api->listDistributions();
168
+
169
+ if ( !isset( $dists['DistributionList']['Items'] ) ) {
170
+ $error = 'Unable to list distributions.';
171
+ return false;
172
+ }
173
+
174
+ if ( !count( $dists['DistributionList']['Items'] ) ) {
175
+ $error = 'No distributions found.';
176
+
177
+ return false;
178
+ }
179
+
180
+ $dist = $this->_get_distribution( $dists );
181
+ if ( $dist["Status"] != 'Deployed' ) {
182
+ $error = sprintf( 'Distribution status is not Deployed, but "%s".', $dist["Status"] );
183
+ return false;
184
+ }
185
+
186
+ if ( !$dist['Enabled'] ) {
187
+ $error = sprintf( 'Distribution for origin "%s" is disabled.', $origin );
188
+ return false;
189
+ }
190
+
191
+ if ( !empty( $this->_config['cname'] ) ) {
192
+ $domains = (array) $this->_config['cname'];
193
+ $cnames = ( isset( $dist['Aliases']['Items'] ) ? (array) $dist['Aliases']['Items'] : array() );
194
+
195
+ foreach ( $domains as $domain ) {
196
+ $_domains = array_map( 'trim', explode( ',', $domain ) );
197
+
198
+ foreach ( $_domains as $_domain ) {
199
+ if ( !in_array( $_domain, $cnames ) ) {
200
+ $error = sprintf( 'Domain name %s is not in distribution <acronym title="Canonical Name">CNAME</acronym> list.', $_domain );
201
+
202
+ return false;
203
+ }
204
+ }
205
+ }
206
+ } elseif ( !empty( $this->_config['id'] ) ) {
207
+ $domain = $this->get_domain();
208
+
209
+ if ( $domain != $dist['DomainName'] ) {
210
+ $error = sprintf( 'Distribution domain name mismatch (%s != %s).', $domain, $dist['DomainName'] );
211
+
212
+ return false;
213
+ }
214
+ }
215
+
216
+ return true;
217
+ }
218
+
219
+ /**
220
+ * Create bucket
221
+ */
222
+ function create_container() {
223
+ $this->_init();
224
+ $this->s3->create_container();
225
+
226
+ // plugin cant set CNAMEs list since it CloudFront requires
227
+ // certificate to be specified associated with it
228
+ $cnames = array();
229
+
230
+ // make distibution
231
+ $originDomain = $this->_get_origin();
232
+
233
+ try {
234
+ $result = $this->api->createDistribution(array(
235
+ 'DistributionConfig' => array(
236
+ 'CallerReference' => $originDomain,
237
+ 'Comment' => 'Created by W3-Total-Cache',
238
+ 'DefaultCacheBehavior' => array(
239
+ 'AllowedMethods' => array(
240
+ 'CachedMethods' => array(
241
+ 'Items' => array( 'HEAD', 'GET' ),
242
+ 'Quantity' => 2,
243
+ ),
244
+ 'Items' => array( 'HEAD', 'GET' ),
245
+ 'Quantity' => 2,
246
+ ),
247
+ 'Compress' => true,
248
+ 'DefaultTTL' => 86400,
249
+ 'FieldLevelEncryptionId' => '',
250
+ 'ForwardedValues' => array(
251
+ 'Cookies' => array(
252
+ 'Forward' => 'none',
253
+ ),
254
+ 'Headers' => array(
255
+ 'Quantity' => 0,
256
+ ),
257
+ 'QueryString' => false,
258
+ 'QueryStringCacheKeys' => array(
259
+ 'Quantity' => 0,
260
+ ),
261
+ ),
262
+ 'LambdaFunctionAssociations' => array( 'Quantity' => 0),
263
+ 'MinTTL' => 0,
264
+ 'SmoothStreaming' => false,
265
+ 'TargetOriginId' => $originDomain,
266
+ 'TrustedSigners' => array(
267
+ 'Enabled' => false,
268
+ 'Quantity' => 0,
269
+ ),
270
+ 'ViewerProtocolPolicy' => 'allow-all',
271
+ ),
272
+ 'Enabled' => true,
273
+ 'Origins' => array(
274
+ 'Items' => array(
275
+ array(
276
+ 'DomainName' => $originDomain,
277
+ 'Id' => $originDomain,
278
+ 'OriginPath' => '',
279
+ 'CustomHeaders' => array( 'Quantity' => 0 ),
280
+ 'S3OriginConfig' => array(
281
+ 'OriginAccessIdentity' => ''
282
+ ),
283
+ ),
284
+ ),
285
+ 'Quantity' => 1,
286
+ ),
287
+ 'Aliases' => array(
288
+ 'Items' => $cnames,
289
+ 'Quantity' => count( $cnames )
290
+ )
291
+ )
292
+ ));
293
+
294
+ // extract domain dynamic part stored later in a config
295
+ $domain = $result['Distribution']['DomainName'];
296
+ $container_id = '';
297
+ if ( preg_match( '~^(.+)\.cloudfront\.net$~', $domain, $matches ) ) {
298
+ $container_id = $matches[1];
299
+ }
300
+
301
+ return $container_id;
302
+
303
+ } catch ( \Exception $ex ) {
304
+ throw new \Exception( sprintf(
305
+ 'Unable to create distribution for origin %s: %s', $originDomain,
306
+ $ex->getMessage() ) );
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Returns via string
312
+ *
313
+ * @return string
314
+ */
315
+ function get_via() {
316
+ $domain = $this->get_domain();
317
+ $via = ( $domain ? $domain : 'N/A' );
318
+
319
+ return sprintf( 'Amazon Web Services: CloudFront: %s', $via );
320
+ }
321
+
322
+ private function _get_distribution( $dists = null ) {
323
+ if ( is_null( $dists ) ) {
324
+ $dists = $this->api->listDistributions();
325
+ }
326
+
327
+ if ( !isset( $dists['DistributionList']['Items'] ) ||
328
+ !count( $dists['DistributionList']['Items'] ) ) {
329
+ throw new \Exception( 'No distributions found.' );
330
+ }
331
+
332
+ $dist = false;
333
+ $origin = $this->_get_origin();
334
+
335
+ $items = $dists['DistributionList']['Items'];
336
+ foreach ( $items as $dist ) {
337
+ if ( isset( $dist['Origins']['Items'][0]['DomainName'] ) &&
338
+ $dist['Origins']['Items'][0]['DomainName'] == $origin ) {
339
+ return $dist;
340
+ }
341
+ }
342
+
343
+ throw new \Exception( sprintf( 'Distribution for origin "%s" not found.', $origin ) );
344
+ }
345
+ }
CdnEngine_Mirror_CloudFront.php ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace W3TC;
3
+
4
+ if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
5
+ require_once W3TC_LIB_DIR . '/Aws/aws-autoloader.php';
6
+ }
7
+
8
+ /**
9
+ * Amazon CloudFront (mirror) CDN engine
10
+ */
11
+ class CdnEngine_Mirror_CloudFront extends CdnEngine_Mirror {
12
+ private $api;
13
+
14
+ /**
15
+ * Constructor
16
+ */
17
+ function __construct( $config = array() ) {
18
+ parent::__construct( $config );
19
+ }
20
+
21
+ /**
22
+ * Initializes S3 object
23
+ *
24
+ * @param string $error
25
+ * @return bool
26
+ */
27
+ function _init() {
28
+ if ( !is_null( $this->api ) ) {
29
+ return;
30
+ }
31
+
32
+ $credentials = new \Aws\Credentials\Credentials(
33
+ $this->_config['key'],
34
+ $this->_config['secret'] );
35
+
36
+ $this->api = new \Aws\CloudFront\CloudFrontClient( array(
37
+ 'credentials' => $credentials,
38
+ 'region' => 'us-east-1',
39
+ 'version' => '2018-11-05'
40
+ )
41
+ );
42
+
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * Returns origin
48
+ *
49
+ * @return string
50
+ */
51
+ function _get_origin() {
52
+ return Util_Environment::host_port();
53
+ }
54
+
55
+ /**
56
+ * Purge files from CDN
57
+ *
58
+ * @param array $files
59
+ * @param array $results
60
+ * @return boolean
61
+ */
62
+ function purge( $files, &$results ) {
63
+ try {
64
+ $this->_init();
65
+ $dist = $this->_get_distribution();
66
+ } catch ( \Exception $ex ) {
67
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
68
+ return false;
69
+ }
70
+
71
+ $paths = array();
72
+
73
+ foreach ( $files as $file ) {
74
+ $remote_file = $file['remote_path'];
75
+ $paths[] = '/' . $remote_file;
76
+ }
77
+
78
+ try {
79
+ $invalidation = $this->api->createInvalidation( array(
80
+ 'DistributionId' => $dist['Id'],
81
+ 'InvalidationBatch' => array(
82
+ 'CallerReference' => 'w3tc-' . microtime(),
83
+ 'Paths' => array(
84
+ 'Items' => $paths,
85
+ 'Quantity' => count( $paths ),
86
+ ),
87
+ )
88
+ )
89
+ );
90
+ } catch ( \Exception $ex ) {
91
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT,
92
+ sprintf( 'Unable to create invalidation batch (%s).',
93
+ $ex->getMessage() ) );
94
+
95
+ return false;
96
+ }
97
+
98
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
99
+ return true;
100
+ }
101
+
102
+ /**
103
+ * Purge CDN completely
104
+ *
105
+ * @param unknown $results
106
+ * @return bool
107
+ */
108
+ function purge_all( &$results ) {
109
+ return $this->purge( array( 'remote_path' => '*' ), $results );
110
+ }
111
+
112
+ /**
113
+ * Returns array of CDN domains
114
+ *
115
+ * @return array
116
+ */
117
+ function get_domains() {
118
+ if ( !empty( $this->_config['cname'] ) ) {
119
+ return (array) $this->_config['cname'];
120
+ } elseif ( !empty( $this->_config['id'] ) ) {
121
+ $domain = sprintf( '%s.cloudfront.net', $this->_config['id'] );
122
+
123
+ return array(
124
+ $domain
125
+ );
126
+ }
127
+
128
+ return array();
129
+ }
130
+
131
+ /**
132
+ * Tests CF
133
+ *
134
+ * @param string $error
135
+ * @return boolean
136
+ */
137
+ function test( &$error ) {
138
+ $this->_init();
139
+
140
+ /**
141
+ * Search active CF distribution
142
+ */
143
+ $dists = $this->api->listDistributions();
144
+
145
+ if ( !isset( $dists['DistributionList']['Items'] ) ) {
146
+ $error = 'Unable to list distributions.';
147
+ return false;
148
+ }
149
+
150
+ if ( !count( $dists['DistributionList']['Items'] ) ) {
151
+ $error = 'No distributions found.';
152
+
153
+ return false;
154
+ }
155
+
156
+ $dist = $this->_get_distribution( $dists );
157
+ if ( $dist["Status"] != 'Deployed' ) {
158
+ $error = sprintf( 'Distribution status is not Deployed, but "%s".', $dist["Status"] );
159
+ return false;
160
+ }
161
+
162
+ if ( !$dist['Enabled'] ) {
163
+ $error = sprintf( 'Distribution for origin "%s" is disabled.', $origin );
164
+ return false;
165
+ }
166
+
167
+ if ( !empty( $this->_config['cname'] ) ) {
168
+ $domains = (array) $this->_config['cname'];
169
+ $cnames = ( isset( $dist['Aliases']['Items'] ) ? (array) $dist['Aliases']['Items'] : array() );
170
+
171
+ foreach ( $domains as $domain ) {
172
+ $_domains = array_map( 'trim', explode( ',', $domain ) );
173
+
174
+ foreach ( $_domains as $_domain ) {
175
+ if ( !in_array( $_domain, $cnames ) ) {
176
+ $error = sprintf( 'Domain name %s is not in distribution <acronym title="Canonical Name">CNAME</acronym> list.', $_domain );
177
+
178
+ return false;
179
+ }
180
+ }
181
+ }
182
+ } elseif ( !empty( $this->_config['id'] ) ) {
183
+ $domain = $this->get_domain();
184
+
185
+ if ( $domain != $dist['DomainName'] ) {
186
+ $error = sprintf( 'Distribution domain name mismatch (%s != %s).', $domain, $dist['DomainName'] );
187
+
188
+ return false;
189
+ }
190
+ }
191
+
192
+ return true;
193
+ }
194
+
195
+ /**
196
+ * Create distribution
197
+ */
198
+ function create_container() {
199
+ $this->_init();
200
+
201
+ // plugin cant set CNAMEs list since it CloudFront requires
202
+ // certificate to be specified associated with it
203
+ $cnames = array();
204
+
205
+ // make distibution
206
+ $originDomain = $this->_get_origin();
207
+
208
+ try {
209
+ $result = $this->api->createDistribution( array(
210
+ 'DistributionConfig' => array(
211
+ 'CallerReference' => $originDomain,
212
+ 'Comment' => 'Created by W3-Total-Cache',
213
+ 'DefaultCacheBehavior' => array(
214
+ 'AllowedMethods' => array(
215
+ 'CachedMethods' => array(
216
+ 'Items' => array( 'HEAD', 'GET' ),
217
+ 'Quantity' => 2,
218
+ ),
219
+ 'Items' => array( 'HEAD', 'GET' ),
220
+ 'Quantity' => 2,
221
+ ),
222
+ 'Compress' => true,
223
+ 'DefaultTTL' => 86400,
224
+ 'FieldLevelEncryptionId' => '',
225
+ 'ForwardedValues' => array(
226
+ 'Cookies' => array(
227
+ 'Forward' => 'none',
228
+ ),
229
+ 'Headers' => array(
230
+ 'Quantity' => 0,
231
+ ),
232
+ 'QueryString' => false,
233
+ 'QueryStringCacheKeys' => array(
234
+ 'Quantity' => 0,
235
+ ),
236
+ ),
237
+ 'LambdaFunctionAssociations' => array( 'Quantity' => 0),
238
+ 'MinTTL' => 0,
239
+ 'SmoothStreaming' => false,
240
+ 'TargetOriginId' => $originDomain,
241
+ 'TrustedSigners' => array(
242
+ 'Enabled' => false,
243
+ 'Quantity' => 0,
244
+ ),
245
+ 'ViewerProtocolPolicy' => 'allow-all',
246
+ ),
247
+ 'Enabled' => true,
248
+ 'Origins' => array(
249
+ 'Items' => array(
250
+ array(
251
+ 'DomainName' => $originDomain,
252
+ 'Id' => $originDomain,
253
+ 'OriginPath' => '',
254
+ 'CustomHeaders' => array( 'Quantity' => 0 ),
255
+ 'CustomOriginConfig' => array(
256
+ 'HTTPPort' => 80,
257
+ 'HTTPSPort' => 443,
258
+ 'OriginProtocolPolicy' => 'match-viewer'
259
+ ),
260
+ ),
261
+ ),
262
+ 'Quantity' => 1,
263
+ ),
264
+ 'Aliases' => array(
265
+ 'Items' => $cnames,
266
+ 'Quantity' => count( $cnames )
267
+ )
268
+ )
269
+ ));
270
+
271
+ // extract domain dynamic part stored later in a config
272
+ $domain = $result['Distribution']['DomainName'];
273
+ $container_id = '';
274
+ if ( preg_match( '~^(.+)\.cloudfront\.net$~', $domain, $matches ) ) {
275
+ $container_id = $matches[1];
276
+ }
277
+
278
+ return $container_id;
279
+
280
+ } catch ( \Aws\Exception\AwsException $ex ) {
281
+ throw new \Exception( sprintf(
282
+ 'Unable to create distribution for origin %s: %s', $originDomain,
283
+ $ex->getAwsErrorMessage() ) );
284
+ } catch ( \Exception $ex ) {
285
+ throw new \Exception( sprintf(
286
+ 'Unable to create distribution for origin %s: %s', $originDomain,
287
+ $ex->getMessage() ) );
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Returns via string
293
+ */
294
+ function get_via() {
295
+ $domain = $this->get_domain();
296
+ $via = ( $domain ? $domain : 'N/A' );
297
+
298
+ return sprintf( 'Amazon Web Services: CloudFront: %s', $via );
299
+ }
300
+
301
+ private function _get_distribution( $dists = null ) {
302
+ if ( is_null( $dists ) ) {
303
+ $dists = $this->api->listDistributions();
304
+ }
305
+
306
+ if ( !isset( $dists['DistributionList']['Items'] ) ||
307
+ !count( $dists['DistributionList']['Items'] ) ) {
308
+ throw new \Exception( 'No distributions found.' );
309
+ }
310
+
311
+ $dist = false;
312
+ $origin = $this->_get_origin();
313
+
314
+ $items = $dists['DistributionList']['Items'];
315
+ foreach ( $items as $dist ) {
316
+ if ( isset( $dist['Origins']['Items'][0]['DomainName'] ) &&
317
+ $dist['Origins']['Items'][0]['DomainName'] == $origin ) {
318
+ return $dist;
319
+ }
320
+ }
321
+
322
+ throw new \Exception( sprintf( 'Distribution for origin "%s" not found.', $origin ) );
323
+ }
324
+ }
CdnEngine_S3.php CHANGED
@@ -1,31 +1,17 @@
1
  <?php
2
  namespace W3TC;
3
 
4
- /**
5
- * Amazon S3 CDN engine
6
- */
7
-
8
- if ( !class_exists( 'S3' ) ) {
9
- require_once W3TC_LIB_DIR . '/S3.php';
10
  }
11
 
12
  /**
13
- * class CdnEngine_S3
14
  */
15
  class CdnEngine_S3 extends CdnEngine_Base {
16
- /**
17
- * S3 object
18
- *
19
- * @var S3
20
- */
21
- var $_s3 = null;
22
 
23
- /**
24
- * PHP5 Constructor
25
- *
26
- * @param array $config
27
- */
28
- function __construct( $config = array() ) {
29
  $config = array_merge( array(
30
  'key' => '',
31
  'secret' => '',
@@ -39,9 +25,6 @@ class CdnEngine_S3 extends CdnEngine_Base {
39
 
40
  /**
41
  * Formats URL
42
- *
43
- * @param string $path
44
- * @return string
45
  */
46
  function _format_url( $path ) {
47
  $domain = $this->get_domain( $path );
@@ -65,40 +48,33 @@ class CdnEngine_S3 extends CdnEngine_Base {
65
  * @param string $error
66
  * @return boolean
67
  */
68
- function _init( &$error ) {
69
- if ( empty( $this->_config['key'] ) ) {
70
- $error = 'Empty access key.';
 
71
 
72
- return false;
 
73
  }
74
 
75
  if ( empty( $this->_config['secret'] ) ) {
76
- $error = 'Empty secret key.';
77
-
78
- return false;
79
  }
80
 
81
  if ( empty( $this->_config['bucket'] ) ) {
82
- $error = 'Empty bucket.';
83
-
84
- return false;
85
  }
86
 
87
- if ( empty( $this->_config['bucket_location'] ) ) {
88
- $region = '';
89
- $endpoint = 's3.amazonaws.com';
90
- } else {
91
- $region = $this->_config['bucket_location'];
92
- $endpoint = 's3.dualstack.' . $region . '.amazonaws.com';
93
- }
94
 
95
- $this->_s3 = new \S3( $this->_config['key'], $this->_config['secret'],
96
- false, $endpoint, $region );
97
- if ( empty( $region ) ) {
98
- $this->_s3->setSignatureVersion( 'v2' );
99
- }
100
-
101
- return true;
102
  }
103
 
104
  /**
@@ -109,13 +85,14 @@ class CdnEngine_S3 extends CdnEngine_Base {
109
  * @param boolean $force_rewrite
110
  * @return boolean
111
  */
112
- function upload( $files, &$results, $force_rewrite = false,
113
  $timeout_time = NULL ) {
114
  $error = null;
115
 
116
- if ( !$this->_init( $error ) ) {
117
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
118
-
 
119
  return false;
120
  }
121
 
@@ -148,7 +125,7 @@ class CdnEngine_S3 extends CdnEngine_Base {
148
  * @param boolean $force_rewrite
149
  * @return array
150
  */
151
- function _upload( $file, $force_rewrite = false ) {
152
  $local_path = $file['local_path'];
153
  $remote_path = $file['remote_path'];
154
 
@@ -157,52 +134,51 @@ class CdnEngine_S3 extends CdnEngine_Base {
157
  W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
158
  }
159
 
160
- if ( !$force_rewrite ) {
161
- $this->_set_error_handler();
162
- $info = @$this->_s3->getObjectInfo( $this->_config['bucket'], $remote_path );
163
- $this->_restore_error_handler();
164
-
165
- if ( $info ) {
166
- $hash = @md5_file( $local_path );
167
- $s3_hash = ( isset( $info['hash'] ) ? $info['hash'] : '' );
168
-
169
- if ( $hash === $s3_hash ) {
170
- return $this->_get_result( $local_path, $remote_path,
171
- W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
 
 
 
 
 
 
 
 
172
  }
173
  }
174
- }
175
 
176
- $headers = $this->_get_headers( $file );
177
-
178
- $this->_set_error_handler();
179
- $result = @$this->_s3->putObjectFile( $local_path, $this->_config['bucket'], $remote_path, \S3::ACL_PUBLIC_READ, array(), $headers );
180
- $this->_restore_error_handler();
 
 
181
 
182
- if ( $result ) {
183
  return $this->_get_result( $local_path, $remote_path,
184
  W3TC_CDN_RESULT_OK, 'OK', $file );
185
- }
 
186
 
187
- if ( strpos( $this->_get_last_error(), 'AWS4-HMAC-SHA256' ) !== false ) {
188
- $error = "Bucket location region is incorrect. Please select the right one.";
189
- } else {
190
- $error = sprintf( 'Unable to put object (%s).', $this->_get_last_error() );
191
  }
192
-
193
- return $this->_get_result( $local_path, $remote_path,
194
- W3TC_CDN_RESULT_ERROR, $error, $file );
195
  }
196
 
197
  /**
198
  * Uploads gzip version of file
199
- *
200
- * @param string $local_path
201
- * @param string $remote_path
202
- * @param boolean $force_rewrite
203
- * @return array
204
  */
205
- function _upload_gzip( $file, $force_rewrite = false ) {
206
  $local_path = $file['local_path'];
207
  $remote_path = $file['remote_path_gzip'];
208
 
@@ -225,45 +201,70 @@ class CdnEngine_S3 extends CdnEngine_Base {
225
 
226
  $data = gzencode( $contents );
227
 
228
- if ( !$force_rewrite ) {
229
- $this->_set_error_handler();
230
- $info = @$this->_s3->getObjectInfo( $this->_config['bucket'], $remote_path );
231
- $this->_restore_error_handler();
232
-
233
- if ( $info ) {
234
- $hash = md5( $data );
235
- $s3_hash = ( isset( $info['hash'] ) ? $info['hash'] : '' );
236
-
237
- if ( $hash === $s3_hash ) {
238
- return $this->_get_result( $local_path, $remote_path,
239
- W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
 
 
 
 
 
 
 
 
240
  }
241
  }
242
- }
243
 
244
- $headers = $this->_get_headers( $file );
245
- $headers = array_merge( $headers, array(
246
- 'Vary' => 'Accept-Encoding',
247
- 'Content-Encoding' => 'gzip'
248
- ) );
249
-
250
- $this->_set_error_handler();
251
- $result = @$this->_s3->putObjectString( $data, $this->_config['bucket'], $remote_path, \S3::ACL_PUBLIC_READ, array(), $headers );
252
- $this->_restore_error_handler();
 
 
 
253
 
254
- if ( $result ) {
255
  return $this->_get_result( $local_path, $remote_path,
256
  W3TC_CDN_RESULT_OK, 'OK', $file );
 
 
 
 
 
257
  }
 
258
 
259
- if ( strpos( $this->_get_last_error(), 'AWS4-HMAC-SHA256' ) !== false ) {
260
- $error = "Bucket location region is incorrect. Please select the right one.";
261
- } else {
262
- $error = sprintf( 'Unable to put object (%s).', $this->_get_last_error() );
 
 
 
 
 
 
 
 
 
 
 
263
  }
264
 
265
- return $this->_get_result( $local_path, $remote_path,
266
- W3TC_CDN_RESULT_ERROR, $error, $file );
267
  }
268
 
269
  /**
@@ -273,12 +274,13 @@ class CdnEngine_S3 extends CdnEngine_Base {
273
  * @param array $results
274
  * @return boolean
275
  */
276
- function delete( $files, &$results ) {
277
  $error = null;
278
 
279
- if ( !$this->_init( $error ) ) {
280
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
281
-
 
282
  return false;
283
  }
284
 
@@ -286,36 +288,36 @@ class CdnEngine_S3 extends CdnEngine_Base {
286
  $local_path = $file['local_path'];
287
  $remote_path = $file['remote_path'];
288
 
289
- $this->_set_error_handler();
290
- $result = @$this->_s3->deleteObject( $this->_config['bucket'], $remote_path );
291
- $this->_restore_error_handler();
292
-
293
- if ( $result ) {
294
  $results[] = $this->_get_result( $local_path, $remote_path,
295
  W3TC_CDN_RESULT_OK, 'OK', $file );
296
- } else {
297
  $results[] = $this->_get_result( $local_path, $remote_path,
298
  W3TC_CDN_RESULT_ERROR,
299
  sprintf( 'Unable to delete object (%s).',
300
- $this->_get_last_error() ),
301
  $file );
302
  }
303
 
304
  if ( $this->_config['compression'] ) {
305
  $remote_path_gzip = $remote_path . $this->_gzip_extension;
306
 
307
- $this->_set_error_handler();
308
- $result = @$this->_s3->deleteObject( $this->_config['bucket'], $remote_path_gzip );
309
- $this->_restore_error_handler();
310
-
311
- if ( $result ) {
312
- $results[] = $this->_get_result( $local_path,
313
- $remote_path_gzip, W3TC_CDN_RESULT_OK, 'OK', $file );
314
- } else {
315
- $results[] = $this->_get_result( $local_path,
316
- $remote_path_gzip, W3TC_CDN_RESULT_ERROR,
317
  sprintf( 'Unable to delete object (%s).',
318
- $this->_get_last_error() ),
319
  $file );
320
  }
321
  }
@@ -325,80 +327,57 @@ class CdnEngine_S3 extends CdnEngine_Base {
325
  }
326
 
327
  /**
328
- * Tests S3
329
- *
330
- * @param string $error
331
- * @return boolean
332
  */
333
- function test( &$error ) {
334
  if ( !parent::test( $error ) ) {
335
  return false;
336
  }
337
 
338
- $string = 'test_s3_' . md5( time() );
339
-
340
- if ( !$this->_init( $error ) ) {
341
- return false;
342
- }
343
-
344
- $this->_set_error_handler();
345
-
346
- $buckets = @$this->_s3->listBuckets();
347
-
348
- if ( $buckets === false ) {
349
- $error = sprintf( 'Unable to list buckets (%s).', $this->_get_last_error() );
350
-
351
- $this->_restore_error_handler();
352
-
353
- return false;
354
- }
355
-
356
- if ( !in_array( $this->_config['bucket'], (array) $buckets ) ) {
357
- $error = sprintf( 'Bucket doesn\'t exist: %s.', $this->_config['bucket'] );
358
 
359
- $this->_restore_error_handler();
 
360
 
361
- return false;
362
- }
363
-
364
- if ( !@$this->_s3->putObjectString( $string, $this->_config['bucket'], $string, \S3::ACL_PUBLIC_READ ) ) {
365
- if ( strpos( $this->_get_last_error(), 'AWS4-HMAC-SHA256' ) !== false ) {
366
- $error = "Bucket location region is incorrect. Please select the right one.";
367
- } else {
368
- $error = sprintf( 'Unable to put object (%s).', $this->_get_last_error() );
369
  }
370
-
371
- $this->_restore_error_handler();
372
-
373
- return false;
374
  }
375
 
376
- if ( !( $object = @$this->_s3->getObject( $this->_config['bucket'], $string ) ) ) {
377
- $error = sprintf( 'Unable to get object (%s).', $this->_get_last_error() );
378
-
379
- $this->_restore_error_handler();
380
-
381
- return false;
382
  }
383
 
384
- if ( $object->body != $string ) {
385
- $error = 'Objects are not equal.';
386
-
387
- @$this->_s3->deleteObject( $this->_config['bucket'], $string );
388
- $this->_restore_error_handler();
 
 
389
 
390
- return false;
391
- }
 
 
392
 
393
- if ( !@$this->_s3->deleteObject( $this->_config['bucket'], $string ) ) {
394
- $error = sprintf( 'Unable to delete object (%s).', $this->_get_last_error() );
395
 
396
- $this->_restore_error_handler();
 
 
 
397
 
398
  return false;
399
  }
400
 
401
- $this->_restore_error_handler();
 
 
 
402
 
403
  return true;
404
  }
@@ -408,7 +387,7 @@ class CdnEngine_S3 extends CdnEngine_Base {
408
  *
409
  * @return array
410
  */
411
- function get_domains() {
412
  if ( !empty( $this->_config['cname'] ) ) {
413
  return (array) $this->_config['cname'];
414
  } elseif ( !empty( $this->_config['bucket'] ) ) {
@@ -427,61 +406,35 @@ class CdnEngine_S3 extends CdnEngine_Base {
427
  *
428
  * @return string
429
  */
430
- function get_via() {
431
  return sprintf( 'Amazon Web Services: S3: %s', parent::get_via() );
432
  }
433
 
434
  /**
435
  * Creates bucket
436
- *
437
- * @param string $container_id
438
- * @param string $error
439
- * @return boolean
440
  */
441
- function create_container( &$container_id, &$error ) {
442
- if ( !$this->_init( $error ) ) {
443
- return false;
444
- }
445
-
446
- $this->_set_error_handler();
447
-
448
- $buckets = @$this->_s3->listBuckets();
449
-
450
- if ( $buckets === false ) {
451
- $error = sprintf( 'Unable to list buckets (%s).', $this->_get_last_error() );
452
-
453
- $this->_restore_error_handler();
454
-
455
- return false;
456
- }
457
 
458
- if ( in_array( $this->_config['bucket'], (array) $buckets ) ) {
459
- $error = sprintf( 'Bucket already exists: %s.', $this->_config['bucket'] );
460
-
461
- $this->_restore_error_handler();
462
-
463
- return false;
464
  }
465
 
466
- if ( empty( $this->_config['bucket_acl'] ) ) {
467
- $this->_config['bucket_acl'] = \S3::ACL_PRIVATE;
468
- }
469
-
470
- if ( !isset( $this->_config['bucket_location'] ) ) {
471
- $this->_config['bucket_location'] = \S3::LOCATION_US;
472
  }
473
 
474
- if ( !@$this->_s3->putBucket( $this->_config['bucket'], $this->_config['bucket_acl'], $this->_config['bucket_location'] ) ) {
475
- $error = sprintf( 'Unable to create bucket: %s (%s).', $this->_config['bucket'], $this->_get_last_error() );
476
-
477
- $this->_restore_error_handler();
478
-
479
- return false;
480
  }
481
-
482
- $this->_restore_error_handler();
483
-
484
- return true;
485
  }
486
 
487
  /**
@@ -489,7 +442,7 @@ class CdnEngine_S3 extends CdnEngine_Base {
489
  *
490
  * @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
491
  */
492
- function headers_support() {
493
  return W3TC_CDN_HEADER_UPLOADABLE;
494
  }
495
  }
1
  <?php
2
  namespace W3TC;
3
 
4
+ if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
5
+ require_once W3TC_LIB_DIR . '/Aws/aws-autoloader.php';
 
 
 
 
6
  }
7
 
8
  /**
9
+ * CDN engine for S3 push type
10
  */
11
  class CdnEngine_S3 extends CdnEngine_Base {
12
+ private $api;
 
 
 
 
 
13
 
14
+ public function __construct( $config = array() ) {
 
 
 
 
 
15
  $config = array_merge( array(
16
  'key' => '',
17
  'secret' => '',
25
 
26
  /**
27
  * Formats URL
 
 
 
28
  */
29
  function _format_url( $path ) {
30
  $domain = $this->get_domain( $path );
48
  * @param string $error
49
  * @return boolean
50
  */
51
+ public function _init() {
52
+ if ( !is_null( $this->api ) ) {
53
+ return;
54
+ }
55
 
56
+ if ( empty( $this->_config['key'] ) ) {
57
+ throw new \Exception( 'Empty access key.' );
58
  }
59
 
60
  if ( empty( $this->_config['secret'] ) ) {
61
+ throw new \Exception( 'Empty secret key.' );
 
 
62
  }
63
 
64
  if ( empty( $this->_config['bucket'] ) ) {
65
+ throw new \Exception( 'Empty bucket.' );
 
 
66
  }
67
 
68
+ $credentials = new \Aws\Credentials\Credentials(
69
+ $this->_config['key'],
70
+ $this->_config['secret'] );
 
 
 
 
71
 
72
+ $this->api = new \Aws\S3\S3Client( array(
73
+ 'credentials' => $credentials,
74
+ 'region' => $this->_config['bucket_location'],
75
+ 'version' => '2006-03-01'
76
+ )
77
+ );
 
78
  }
79
 
80
  /**
85
  * @param boolean $force_rewrite
86
  * @return boolean
87
  */
88
+ public function upload( $files, &$results, $force_rewrite = false,
89
  $timeout_time = NULL ) {
90
  $error = null;
91
 
92
+ try {
93
+ $this->_init();
94
+ } catch ( \Exception $ex ) {
95
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
96
  return false;
97
  }
98
 
125
  * @param boolean $force_rewrite
126
  * @return array
127
  */
128
+ private function _upload( $file, $force_rewrite = false ) {
129
  $local_path = $file['local_path'];
130
  $remote_path = $file['remote_path'];
131
 
134
  W3TC_CDN_RESULT_ERROR, 'Source file not found.', $file );
135
  }
136
 
137
+ try {
138
+ if ( !$force_rewrite ) {
139
+ try {
140
+ $info = $this->api->headObject( array(
141
+ 'Bucket' => $this->_config['bucket'],
142
+ 'Key' => $remote_path )
143
+ );
144
+
145
+ $hash = '"' . @md5_file( $local_path ) . '"';
146
+ $s3_hash = ( isset( $info['ETag'] ) ? $info['ETag'] : '' );
147
+
148
+ if ( $hash === $s3_hash ) {
149
+ return $this->_get_result( $local_path, $remote_path,
150
+ W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
151
+ }
152
+ } catch ( \Aws\Exception\AwsException $ex ) {
153
+ if ( $ex->getAwsErrorCode() == 'NotFound' ) {
154
+ } else {
155
+ throw $ex;
156
+ }
157
  }
158
  }
 
159
 
160
+ $headers = $this->_get_headers( $file );
161
+ $result = $this->_put_object( array(
162
+ 'Key' => $remote_path,
163
+ 'SourceFile' => $local_path,
164
+ 'Metadata' => $headers
165
+ )
166
+ );
167
 
 
168
  return $this->_get_result( $local_path, $remote_path,
169
  W3TC_CDN_RESULT_OK, 'OK', $file );
170
+ } catch ( \Exception $ex ) {
171
+ $error = sprintf( 'Unable to put object (%s).', $ex->getMessage() );
172
 
173
+ return $this->_get_result( $local_path, $remote_path,
174
+ W3TC_CDN_RESULT_ERROR, $error, $file );
 
 
175
  }
 
 
 
176
  }
177
 
178
  /**
179
  * Uploads gzip version of file
 
 
 
 
 
180
  */
181
+ private function _upload_gzip( $file, $force_rewrite = false ) {
182
  $local_path = $file['local_path'];
183
  $remote_path = $file['remote_path_gzip'];
184
 
201
 
202
  $data = gzencode( $contents );
203
 
204
+ try {
205
+ if ( !$force_rewrite ) {
206
+ try {
207
+ $info = $this->api->headObject( array(
208
+ 'Bucket' => $this->_config['bucket'],
209
+ 'Key' => $remote_path )
210
+ );
211
+
212
+ $hash = '"' . md5( $data ) . '"';
213
+ $s3_hash = ( isset( $info['ETag'] ) ? $info['ETag'] : '' );
214
+
215
+ if ( $hash === $s3_hash ) {
216
+ return $this->_get_result( $local_path, $remote_path,
217
+ W3TC_CDN_RESULT_OK, 'Object up-to-date.', $file );
218
+ }
219
+ } catch ( \Aws\Exception\AwsException $ex ) {
220
+ if ( $ex->getAwsErrorCode() == 'NotFound' ) {
221
+ } else {
222
+ throw $ex;
223
+ }
224
  }
225
  }
 
226
 
227
+ $headers = $this->_get_headers( $file );
228
+ $headers = array_merge( $headers, array(
229
+ 'Vary' => 'Accept-Encoding',
230
+ 'Content-Encoding' => 'gzip'
231
+ ) );
232
+
233
+ $result = $this->_put_object( array(
234
+ 'Key' => $remote_path,
235
+ 'Body' => $data,
236
+ 'Metadata' => $headers
237
+ )
238
+ );
239
 
 
240
  return $this->_get_result( $local_path, $remote_path,
241
  W3TC_CDN_RESULT_OK, 'OK', $file );
242
+ } catch ( \Exception $ex ) {
243
+ $error = sprintf( 'Unable to put object (%s).', $ex->getMessage() );
244
+
245
+ return $this->_get_result( $local_path, $remote_path,
246
+ W3TC_CDN_RESULT_ERROR, $error, $file );
247
  }
248
+ }
249
 
250
+ /**
251
+ * Wrapper to set headers well
252
+ */
253
+ private function _put_object( $data ) {
254
+ $data['ACL'] = 'public-read';
255
+ $data['Bucket'] = $this->_config['bucket'];
256
+
257
+ if ( isset( $data['Metadata']['Content-Type'] ) ) {
258
+ $data['ContentType'] = $data['Metadata']['Content-Type'];
259
+ }
260
+ if ( isset( $data['Metadata']['Content-Encoding'] ) ) {
261
+ $data['ContentEncoding'] = $data['Metadata']['Content-Encoding'];
262
+ }
263
+ if ( isset( $data['Metadata']['Cache-Control'] ) ) {
264
+ $data['CacheControl'] = $data['Metadata']['Cache-Control'];
265
  }
266
 
267
+ return $this->api->putObject( $data );
 
268
  }
269
 
270
  /**
274
  * @param array $results
275
  * @return boolean
276
  */
277
+ public function delete( $files, &$results ) {
278
  $error = null;
279
 
280
+ try {
281
+ $this->_init();
282
+ } catch ( \Exception $ex ) {
283
+ $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $ex->getMessage() );
284
  return false;
285
  }
286
 
288
  $local_path = $file['local_path'];
289
  $remote_path = $file['remote_path'];
290
 
291
+ try {
292
+ $this->api->deleteObject( array(
293
+ 'Bucket' => $this->_config['bucket'],
294
+ 'Key' => $remote_path
295
+ ) );
296
  $results[] = $this->_get_result( $local_path, $remote_path,
297
  W3TC_CDN_RESULT_OK, 'OK', $file );
298
+ } catch ( \Exception $ex ) {
299
  $results[] = $this->_get_result( $local_path, $remote_path,
300
  W3TC_CDN_RESULT_ERROR,
301
  sprintf( 'Unable to delete object (%s).',
302
+ $ex->getMessage() ),
303
  $file );
304
  }
305
 
306
  if ( $this->_config['compression'] ) {
307
  $remote_path_gzip = $remote_path . $this->_gzip_extension;
308
 
309
+ try {
310
+ $this->api->deleteObject( array(
311
+ 'Bucket' => $this->_config['bucket'],
312
+ 'Key' => $remote_path_gzip
313
+ ) );
314
+ $results[] = $this->_get_result( $local_path, $remote_path_gzip,
315
+ W3TC_CDN_RESULT_OK, 'OK', $file );
316
+ } catch ( \Exception $ex ) {
317
+ $results[] = $this->_get_result( $local_path, $remote_path_gzip,
318
+ W3TC_CDN_RESULT_ERROR,
319
  sprintf( 'Unable to delete object (%s).',
320
+ $ex->getMessage() ),
321
  $file );
322
  }
323
  }
327
  }
328
 
329
  /**
330
+ * Test CDN connectivity works
 
 
 
331
  */
332
+ public function test( &$error ) {
333
  if ( !parent::test( $error ) ) {
334
  return false;
335
  }
336
 
337
+ $key = 'test_s3_' . md5( time() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
+ $this->_init();
340
+ $buckets = $this->api->listBuckets();
341
 
342
+ $bucket_found = false;
343
+ foreach ( $buckets['Buckets'] as $bucket ) {
344
+ if ( $bucket['Name'] == $this->_config['bucket'] ) {
345
+ $bucket_found = true;
 
 
 
 
346
  }
 
 
 
 
347
  }
348
 
349
+ if ( !$bucket_found ) {
350
+ throw new \Exception( 'Bucket doesn\'t exist: %s.', $this->_config['bucket'] );
 
 
 
 
351
  }
352
 
353
+ $result = $this->api->putObject( array(
354
+ 'ACL' => 'public-read',
355
+ 'Bucket' => $this->_config['bucket'],
356
+ 'Key' => $key,
357
+ 'Body' => $key
358
+ )
359
+ );
360
 
361
+ $object = $this->api->getObject( array(
362
+ 'Bucket' => $this->_config['bucket'],
363
+ 'Key' => $key
364
+ ) );
365
 
366
+ if ( $object['Body'] != $key ) {
367
+ $error = 'Objects are not equal.';
368
 
369
+ $this->api->deleteObject( array(
370
+ 'Bucket' => $this->_config['bucket'],
371
+ 'Key' => $key
372
+ ) );
373
 
374
  return false;
375
  }
376
 
377
+ $this->api->deleteObject( array(
378
+ 'Bucket' => $this->_config['bucket'],
379
+ 'Key' => $key
380
+ ) );
381
 
382
  return true;
383
  }
387
  *
388
  * @return array
389
  */
390
+ public function get_domains() {
391
  if ( !empty( $this->_config['cname'] ) ) {
392
  return (array) $this->_config['cname'];
393
  } elseif ( !empty( $this->_config['bucket'] ) ) {
406
  *
407
  * @return string
408
  */
409
+ public function get_via() {
410
  return sprintf( 'Amazon Web Services: S3: %s', parent::get_via() );
411
  }
412
 
413
  /**
414
  * Creates bucket
 
 
 
 
415
  */
416
+ public function create_container() {
417
+ $this->_init();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
 
419
+ try {
420
+ $buckets = $this->api->listBuckets();
421
+ } catch ( \Exception $ex ) {
422
+ throw new \Exception( 'Unable to list buckets: ' . $ex->getMessage() );
 
 
423
  }
424
 
425
+ foreach ( $buckets['Buckets'] as $bucket ) {
426
+ if ( $bucket['Name'] == $this->_config['bucket'] ) {
427
+ throw new \Exception( 'Bucket already exists: ' . $this->_config['bucket'] );
428
+ }
 
 
429
  }
430
 
431
+ try {
432
+ $result = $this->api->createBucket( array(
433
+ 'Bucket' => $this->_config['bucket'],
434
+ ) );
435
+ } catch ( \Exception $e) {
436
+ throw new \Exception( 'Failed to create bucket: ' . $ex->getMessage() );
437
  }
 
 
 
 
438
  }
439
 
440
  /**
442
  *
443
  * @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
444
  */
445
+ public function headers_support() {
446
  return W3TC_CDN_HEADER_UPLOADABLE;
447
  }
448
  }
CdnEngine_S3_Cf.php DELETED
@@ -1,453 +0,0 @@
1
- <?php
2
- namespace W3TC;
3
-
4
- /**
5
- * Amazon CloudFront CDN engine
6
- */
7
-
8
- define( 'W3TC_CDN_CF_TYPE_S3', 's3' );
9
- define( 'W3TC_CDN_CF_TYPE_CUSTOM', 'custom' );
10
-
11
- /**
12
- * class CdnEngine_S3_Cf
13
- */
14
- class CdnEngine_S3_Cf extends CdnEngine_S3 {
15
- /**
16
- * Type
17
- *
18
- * @var string
19
- */
20
- var $type = '';
21
-
22
- /**
23
- * PHP5 Constructor
24
- *
25
- * @param array $config
26
- */
27
- function __construct( $config = array() ) {
28
- $config = array_merge( array(
29
- 'id' => ''
30
- ), $config );
31
-
32
- parent::__construct( $config );
33
- }
34
-
35
- /**
36
- * Initializes S3 object
37
- *
38
- * @param string $error
39
- * @return bool
40
- */
41
- function _init( &$error ) {
42
- if ( empty( $this->type ) ) {
43
- $error = 'Empty type.';
44
-
45
- return false;
46
- } elseif ( !in_array( $this->type, array( W3TC_CDN_CF_TYPE_S3, W3TC_CDN_CF_TYPE_CUSTOM ) ) ) {
47
- $error = 'Invalid type.';
48
-
49
- return false;
50
- }
51
-
52
- if ( empty( $this->_config['key'] ) ) {
53
- $error = 'Empty access key.';
54
-
55
- return false;
56
- }
57
-
58
- if ( empty( $this->_config['secret'] ) ) {
59
- $error = 'Empty secret key.';
60
-
61
- return false;
62
- }
63
-
64
- if ( $this->type == W3TC_CDN_CF_TYPE_S3 && empty( $this->_config['bucket'] ) ) {
65
- $error = 'Empty bucket.';
66
-
67
- return false;
68
- }
69
-
70
- if ( empty( $this->_config['bucket_location'] ) ) {
71
- $region = '';
72
- $endpoint = 's3.amazonaws.com';
73
- } else {
74
- $region = $this->_config['bucket_location'];
75
- $endpoint = 's3.dualstack.'.$region.'.amazonaws.com';
76
- }
77
-
78
- $this->_s3 = new \S3( $this->_config['key'], $this->_config['secret'],
79
- false, $endpoint, $region );
80
-
81
- if ( empty( $region ) ) {
82
- $this->_s3->setSignatureVersion( 'v2' );
83
- }
84
-
85
- return true;
86
- }
87
-
88
- /**
89
- * Returns origin
90
- *
91
- * @return string
92
- */
93
- function _get_origin() {
94
- if ( $this->type == W3TC_CDN_CF_TYPE_S3 ) {
95
- $origin = sprintf( '%s.s3.amazonaws.com', $this->_config['bucket'] );
96
- } else {
97
- $origin = Util_Environment::host_port();
98
- }
99
-
100
- return $origin;
101
- }
102
-
103
- /**
104
- * Upload files
105
- *
106
- * @param array $files
107
- * @param array $results
108
- * @param boolean $force_rewrite
109
- * @return boolean
110
- */
111
- function upload( $files, &$results, $force_rewrite = false,
112
- $timeout_time = NULL ) {
113
- if ( $this->type == W3TC_CDN_CF_TYPE_S3 ) {
114
- return parent::upload( $files, $results, $force_rewrite,
115
- $timeout_time );
116
- } else {
117
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
118
-
119
- return true;
120
- }
121
- }
122
-
123
- /**
124
- * Delete files from CDN
125
- *
126
- * @param array $files
127
- * @param array $results
128
- * @return boolean
129
- */
130
- function delete( $files, &$results ) {
131
- if ( $this->type == W3TC_CDN_CF_TYPE_S3 ) {
132
- return parent::delete( $files, $results );
133
- } else {
134
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
135
-
136
- return true;
137
- }
138
- }
139
-
140
- /**
141
- * Purge files from CDN
142
- *
143
- * @param array $files
144
- * @param array $results
145
- * @return boolean
146
- */
147
- function purge( $files, &$results ) {
148
- if ( parent::purge( $files, $results ) ) {
149
- return $this->invalidate( $files, $results );
150
- }
151
-
152
- return false;
153
- }
154
-
155
- /**
156
- * Invalidates files
157
- *
158
- * @param array $files
159
- * @param array $results
160
- * @return boolean
161
- */
162
- function invalidate( $files, &$results ) {
163
- if ( !$this->_init( $error ) ) {
164
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, $error );
165
-
166
- return false;
167
- }
168
-
169
- $this->_set_error_handler();
170
- $dists = @$this->_s3->listDistributions();
171
- $this->_restore_error_handler();
172
-
173
- if ( $dists === false ) {
174
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( 'Unable to list distributions (%s).', $this->_get_last_error() ) );
175
-
176
- return false;
177
- }
178
-
179
- if ( !count( $dists ) ) {
180
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, 'No distributions found.' );
181
-
182
- return false;
183
- }
184
-
185
- $dist = false;
186
- $origin = $this->_get_origin();
187
-
188
- foreach ( (array) $dists as $_dist ) {
189
- if ( isset( $_dist['origin'] ) && $_dist['origin'] == $origin ) {
190
- $dist = $_dist;
191
- break;
192
- }
193
- }
194
-
195
- if ( !$dist ) {
196
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( 'Distribution for origin "%s" not found.', $origin ) );
197
-
198
- return false;
199
- }
200
-
201
- $paths = array();
202
-
203
- foreach ( $files as $file ) {
204
- $remote_file = $file['remote_path'];
205
- $paths[] = '/' . $remote_file;
206
- }
207
-
208
- $this->_set_error_handler();
209
- $invalidation = @$this->_s3->invalidateDistribution( $dist['id'], $paths );
210
- $this->_restore_error_handler();
211
-
212
- if ( !$invalidation ) {
213
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_HALT, sprintf( 'Unable to create invalidation bath (%s).', $this->_get_last_error() ) );
214
-
215
- return false;
216
- }
217
-
218
- $results = $this->_get_results( $files, W3TC_CDN_RESULT_OK, 'OK' );
219
-
220
- return true;
221
- }
222
-
223
- /**
224
- * Returns array of CDN domains
225
- *
226
- * @return array
227
- */
228
- function get_domains() {
229
- if ( !empty( $this->_config['cname'] ) ) {
230
- return (array) $this->_config['cname'];
231
- } elseif ( !empty( $this->_config['id'] ) ) {
232
- $domain = sprintf( '%s.cloudfront.net', $this->_config['id'] );
233
-
234
- return array(
235
- $domain
236
- );
237
- }
238
-
239
- return array();
240
- }
241
-
242
- /**
243
- * Tests CF
244
- *
245
- * @param string $error
246
- * @return boolean
247
- */
248
- function test( &$error ) {
249
- if ( $this->type == W3TC_CDN_CF_TYPE_S3 ) {
250
- if ( !parent::test( $error ) ) {
251
- return false;
252
- }
253
- } elseif ( $this->type == W3TC_CDN_CF_TYPE_CUSTOM ) {
254
- if ( !$this->_init( $error ) ) {
255
- return false;
256
- }
257
- }
258
-
259
- /**
260
- * Search active CF distribution
261
- */
262
- $this->_set_error_handler();
263
- $dists = @$this->_s3->listDistributions();
264
- $this->_restore_error_handler();
265
-
266
- if ( $dists === false ) {
267
- $error = sprintf( 'Unable to list distributions (%s).', $this->_get_last_error() );
268
-
269
- return false;
270
- }
271
-
272
- if ( !count( $dists ) ) {
273
- $error = 'No distributions found.';
274
-
275
- return false;
276
- }
277
-
278
- $dist = false;
279
- $origin = $this->_get_origin();
280
-
281
- foreach ( (array) $dists as $_dist ) {
282
- if ( isset( $_dist['origin'] ) && $_dist['origin'] == $origin ) {
283
- $dist = $_dist;
284
- break;
285
- }
286
- }
287
-
288
- if ( !$dist ) {
289
- $error = sprintf( 'Distribution for origin "%s" not found.', $origin );
290
-
291
- return false;
292
- }
293
-
294
- if ( !$dist['enabled'] ) {
295
- $error = sprintf( 'Distribution for origin "%s" is disabled.', $origin );
296
-
297
- return false;
298
- }
299
-
300
- if ( !empty( $this->_config['cname'] ) ) {
301
- $domains = (array) $this->_config['cname'];
302
- $cnames = ( isset( $dist['cnames'] ) ? (array) $dist['cnames'] : array() );
303
-
304
- foreach ( $domains as $domain ) {
305
- $_domains = array_map( 'trim', explode( ',', $domain ) );
306
-
307
- foreach ( $_domains as $_domain ) {
308
- if ( !in_array( $_domain, $cnames ) ) {
309
- $error = sprintf( 'Domain name %s is not in distribution <acronym title="Canonical Name">CNAME</acronym> list.', $_domain );
310
-
311
- return false;
312
- }
313
- }
314
- }
315
- } elseif ( !empty( $this->_config['id'] ) ) {
316
- $domain = $this->get_domain();
317
-
318
- if ( $domain != $dist['domain'] ) {
319
- $error = sprintf( 'Distribution domain name mismatch (%s != %s).', $domain, $dist['domain'] );
320
-
321
- return false;
322
- }
323
- }
324
-
325
- return true;
326
- }
327
-
328
- /**
329
- * Create bucket
330
- *
331
- * @param string $container_id
332
- * @param string $error
333
- * @return boolean
334
- */
335
- function create_container( &$container_id, &$error ) {
336
- if ( $this->type == W3TC_CDN_CF_TYPE_S3 ) {
337
- if ( !parent::create_container( $container_id, $error ) ) {
338
- return false;
339
- }
340
- } elseif ( $this->type == W3TC_CDN_CF_TYPE_CUSTOM ) {
341
- if ( !$this->_init( $error ) ) {
342
- return false;
343
- }
344
- }
345
-
346
- $cnames = array();
347
-
348
- if ( !empty( $this->_config['cname'] ) ) {
349
- $domains = (array) $this->_config['cname'];
350
-
351
- foreach ( $domains as $domain ) {
352
- $_domains = array_map( 'trim', explode( ',', $domain ) );
353
-
354
- foreach ( $_domains as $_domain ) {
355
- $cnames[] = $_domain;
356
- }
357
- }
358
- }
359
-
360
- $origin = $this->_get_origin();
361
-
362
- $this->_set_error_handler();
363
- $dist = @$this->_s3->createDistribution( $origin, $this->type, true, $cnames );
364
- $this->_restore_error_handler();
365
-
366
- if ( !$dist ) {
367
- $error = sprintf( 'Unable to create distribution for origin %s (%s).', $origin, $this->_get_last_error() );
368
-
369
- return false;
370
- }
371
-
372
- $matches = null;
373
-
374
- if ( !empty( $dist['domain'] ) && preg_match( '~^(.+)\.cloudfront\.net$~', $dist['domain'], $matches ) ) {
375
- $container_id = $matches[1];
376
- }
377
-
378
- return true;
379
- }
380
-
381
- /**
382
- * Returns via string
383
- *
384
- * @return string
385
- */
386
- function get_via() {
387
- $domain = $this->get_domain();
388
-
389
- $via = ( $domain ? $domain : 'N/A' );
390
-
391
- return sprintf( 'Amazon Web Services: CloudFront: %s', $via );
392
- }
393
-
394
- /**
395
- * Update distribution CNAMEs
396
- *
397
- * @param string $error
398
- * @return boolean
399
- */
400
- function update_cnames( &$error ) {
401
- if ( !$this->_init( $error ) ) {
402
- return false;
403
- }
404
-
405
- $this->_set_error_handler();
406
- $dists = @$this->_s3->listDistributions();
407
- $this->_restore_error_handler();
408
-
409
- if ( $dists === false ) {
410
- $error = sprintf( 'Unable to list distributions (%s).', $this->_get_last_error() );
411
-
412
- return false;
413
- }
414
-
415
- $dist_id = false;
416
- $origin = $this->_get_origin();
417
-
418
- foreach ( (array) $dists as $dist ) {
419
- if ( isset( $dist['origin'] ) && $dist['origin'] == $origin ) {
420
- $dist_id = $dist['id'];
421
- break;
422
- }
423
- }
424
-
425
- if ( !$dist_id ) {
426
- $error = sprintf( 'Distribution ID for origin "%s" not found.', $origin );
427
-
428
- return false;
429
- }
430
-
431
- $this->_set_error_handler();
432
- $dist = @$this->_s3->getDistribution( $dist_id );
433
- $this->_restore_error_handler();
434
-
435
- if ( !$dist ) {
436
- $error = sprintf( 'Unable to get distribution by ID: %s (%s).', $dist_id, $this->_get_last_error() );
437
- }
438
-
439
- $dist['cnames'] = ( isset( $this->_config['cname'] ) ? (array) $this->_config['cname'] : array() );
440
-
441
- $this->_set_error_handler();
442
- $dist = @$this->_s3->updateDistribution( $dist );
443
- $this->_restore_error_handler();
444
-
445
- if ( !$dist ) {
446
- $error = sprintf( 'Unable to update distribution: %s (%s).', json_encode( $dist ), $this->_get_last_error() );
447
-
448
- return false;
449
- }
450
-
451
- return true;
452
- }
453
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CdnEngine_S3_Cf_Custom.php DELETED
@@ -1,19 +0,0 @@
1
- <?php
2
- namespace W3TC;
3
-
4
- /**
5
- * Amazon CloudFront (Custom origin) CDN engine
6
- */
7
-
8
- class CdnEngine_S3_Cf_Custom extends CdnEngine_S3_Cf {
9
- var $type = W3TC_CDN_CF_TYPE_CUSTOM;
10
-
11
- /**
12
- * How and if headers should be set
13
- *
14
- * @return string W3TC_CDN_HEADER_NONE, W3TC_CDN_HEADER_UPLOADABLE, W3TC_CDN_HEADER_MIRRORING
15
- */
16
- function headers_support() {
17
- return W3TC_CDN_HEADER_MIRRORING;
18
- }
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CdnEngine_S3_Cf_S3.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
- namespace W3TC;
3
-
4
- /**
5
- * Amazon CloudFront (S3 origin) CDN engine
6
- */
7
-
8
- class CdnEngine_S3_Cf_S3 extends CdnEngine_S3_Cf {
9
- var $type = W3TC_CDN_CF_TYPE_S3;
10
- }
 
 
 
 
 
 
 
 
 
 
CdnEngine_S3_Compatible.php CHANGED
@@ -5,8 +5,8 @@ namespace W3TC;
5
  * Amazon S3 CDN engine
6
  */
7
 
8
- if ( !class_exists( 'S3' ) ) {
9
- require_once W3TC_LIB_DIR . '/S3.php';
10
  }
11
 
12
  /**
@@ -31,7 +31,7 @@ class CdnEngine_S3_Compatible extends CdnEngine_Base {
31
  'cname' => array(),
32
  ), $config );
33
 
34
- $this->_s3 = new \S3( $config['key'], $config['secret'], false,
35
  $config['api_host'] );
36
  $this->_s3->setSignatureVersion( 'v2' );
37
 
@@ -133,7 +133,7 @@ class CdnEngine_S3_Compatible extends CdnEngine_Base {
133
  $this->_set_error_handler();
134
  $result = @$this->_s3->putObjectFile( $local_path,
135
  $this->_config['bucket'], $remote_path,
136
- \S3::ACL_PUBLIC_READ, array(), $headers );
137
  $this->_restore_error_handler();
138
 
139
  if ( $result ) {
@@ -199,7 +199,7 @@ class CdnEngine_S3_Compatible extends CdnEngine_Base {
199
 
200
  $this->_set_error_handler();
201
  $result = @$this->_s3->putObjectString( $data, $this->_config['bucket'],
202
- $remote_path, \S3::ACL_PUBLIC_READ, array(), $headers );
203
  $this->_restore_error_handler();
204
 
205
  if ( $result )
@@ -280,7 +280,7 @@ class CdnEngine_S3_Compatible extends CdnEngine_Base {
280
  $this->_set_error_handler();
281
 
282
  if ( !@$this->_s3->putObjectString( $string, $this->_config['bucket'],
283
- $string, \S3::ACL_PUBLIC_READ ) ) {
284
  $error = sprintf( 'Unable to put object (%s).',
285
  $this->_get_last_error() );
286
 
5
  * Amazon S3 CDN engine
6
  */
7
 
8
+ if ( !class_exists( 'S3Compatible' ) ) {
9
+ require_once W3TC_LIB_DIR . '/S3Compatible.php';
10
  }
11
 
12
  /**
31
  'cname' => array(),
32
  ), $config );
33
 
34
+ $this->_s3 = new \S3Compatible( $config['key'], $config['secret'], false,
35
  $config['api_host'] );
36
  $this->_s3->setSignatureVersion( 'v2' );
37
 
133
  $this->_set_error_handler();
134
  $result = @$this->_s3->putObjectFile( $local_path,
135
  $this->_config['bucket'], $remote_path,
136
+ \S3Compatible::ACL_PUBLIC_READ, array(), $headers );
137
  $this->_restore_error_handler();
138
 
139
  if ( $result ) {
199
 
200
  $this->_set_error_handler();
201
  $result = @$this->_s3->putObjectString( $data, $this->_config['bucket'],
202
+ $remote_path, \S3Compatible::ACL_PUBLIC_READ, array(), $headers );
203
  $this->_restore_error_handler();
204
 
205
  if ( $result )
280
  $this->_set_error_handler();
281
 
282
  if ( !@$this->_s3->putObjectString( $string, $this->_config['bucket'],
283
+ $string, \S3Compatible::ACL_PUBLIC_READ ) ) {
284
  $error = sprintf( 'Unable to put object (%s).',
285
  $this->_get_last_error() );
286
 
Cdn_AdminActions.php CHANGED
@@ -423,12 +423,17 @@ class Cdn_AdminActions {
423
 
424
  @set_time_limit( $this->_config->get_integer( 'timelimit.cdn_test' ) );
425
 
426
- if ( $w3_cdn->test( $error ) ) {
427
- $result = true;
428
- $error = __( 'Test passed', 'w3-total-cache' );
429
- } else {
 
 
 
 
 
430
  $result = false;
431
- $error = sprintf( __( 'Error: %s', 'w3-total-cache' ), $error );
432
  }
433
  }
434
 
@@ -454,8 +459,6 @@ class Cdn_AdminActions {
454
  'debug' => false
455
  ) );
456
 
457
- $result = false;
458
- $error = __( 'Incorrect type.', 'w3-total-cache' );
459
  $container_id = '';
460
 
461
  switch ( $engine ) {
@@ -463,22 +466,24 @@ class Cdn_AdminActions {
463
  case 'cf':
464
  case 'cf2':
465
  case 'azure':
466
- $result = true;
467
- break;
468
- }
469
-
470
- if ( $result ) {
471
  $w3_cdn = CdnEngine::instance( $engine, $config );
472
 
473
- @set_time_limit( $this->_config->get_integer( 'timelimit.cdn_container_create' ) );
474
 
475
- if ( $w3_cdn->create_container( $container_id, $error ) ) {
 
 
476
  $result = true;
477
  $error = __( 'Created successfully.', 'w3-total-cache' );
478
- } else {
479
- $result = false;
480
- $error = sprintf( __( 'Error: %s', 'w3-total-cache' ), $error );
481
  }
 
 
 
 
 
482
  }
483
 
484
  $response = array(
423
 
424
  @set_time_limit( $this->_config->get_integer( 'timelimit.cdn_test' ) );
425
 
426
+ try {
427
+ if ( $w3_cdn->test( $error ) ) {
428
+ $result = true;
429
+ $error = __( 'Test passed', 'w3-total-cache' );
430
+ } else {
431
+ $result = false;
432
+ $error = sprintf( __( 'Error: %s', 'w3-total-cache' ), $error );
433
+ }
434
+ } catch ( \Exception $ex ) {
435
  $result = false;
436
+ $error = sprintf( __( 'Error: %s', 'w3-total-cache' ), $ex->getMessage() );
437
  }
438
  }
439
 
459
  'debug' => false
460
  ) );
461
 
 
 
462
  $container_id = '';
463
 
464
  switch ( $engine ) {
466
  case 'cf':
467
  case 'cf2':
468
  case 'azure':
 
 
 
 
 
469
  $w3_cdn = CdnEngine::instance( $engine, $config );
470
 
471
+ @set_time_limit( $this->_config->get_integer( 'timelimit.cdn_upload' ) );
472
 
473
+ $result = false;
474
+ try {
475
+ $container_id = $w3_cdn->create_container();
476
  $result = true;
477
  $error = __( 'Created successfully.', 'w3-total-cache' );
478
+ } catch ( \Exception $ex ) {
479
+ $error = sprintf( __( 'Error: %s', 'w3-total-cache' ),
480
+ $ex->getMessage() );
481
  }
482
+
483
+ break;
484
+ default:
485
+ $result = false;
486
+ $error = __( 'Incorrect type.', 'w3-total-cache' );
487
  }
488
 
489
  $response = array(
Cdn_Core_Admin.php CHANGED
@@ -198,20 +198,20 @@ class Cdn_Core_Admin {
198
 
199
  if ( $upload_info ) {
200
  $sql = sprintf( 'SELECT
201
- pm.meta_value AS file,
202
- pm2.meta_value AS metadata
203
- FROM
204
- %sposts AS p
205
- LEFT JOIN
206
- %spostmeta AS pm ON p.ID = pm.post_ID AND pm.meta_key = "_wp_attached_file"
207
- LEFT JOIN
208
- %spostmeta AS pm2 ON p.ID = pm2.post_ID AND pm2.meta_key = "_wp_attachment_metadata"
209
- WHERE
210
- p.post_type = "attachment" AND (pm.meta_value IS NOT NULL OR pm2.meta_value IS NOT NULL)
211
- GROUP BY
212
- p.ID
213
- ORDER BY
214
- p.ID', $wpdb->prefix, $wpdb->prefix, $wpdb->prefix );
215
 
216
  if ( $limit ) {
217
  $sql .= sprintf( ' LIMIT %d', $limit );
@@ -287,17 +287,17 @@ class Cdn_Core_Admin {
287
  * Search for posts with links or images
288
  */
289
  $sql = sprintf( 'SELECT
290
- ID,
291
- post_content,
292
- post_date
293
- FROM
294
- %sposts
295
- WHERE
296
- post_status = "publish"
297
- AND (post_type = "post" OR post_type = "page")
298
- AND (post_content LIKE "%%src=%%"
299
- OR post_content LIKE "%%href=%%")
300
- ', $wpdb->prefix );
301
 
302
  if ( $limit ) {
303
  $sql .= sprintf( ' LIMIT %d', $limit );
@@ -550,17 +550,17 @@ class Cdn_Core_Admin {
550
 
551
  if ( $upload_info ) {
552
  $sql = sprintf( 'SELECT
553
- ID,
554
- post_content,
555
- post_date
556
- FROM
557
- %sposts
558
- WHERE
559
- post_status = "publish"
560
- AND (post_type = "post" OR post_type = "page")
561
- AND (post_content LIKE "%%src=%%"
562
- OR post_content LIKE "%%href=%%")
563
- ', $wpdb->prefix );
564
 
565
  if ( $limit ) {
566
  $sql .= sprintf( ' LIMIT %d', $limit );
@@ -636,15 +636,15 @@ WHERE p.post_type = "attachment" AND (pm.meta_value IS NOT NULL OR pm2.meta_valu
636
  global $wpdb;
637
 
638
  $sql = sprintf( 'SELECT
639
- COUNT(*)
640
- FROM
641
- %sposts
642
- WHERE
643
- post_status = "publish"
644
- AND (post_type = "post" OR post_type = "page")
645
- AND (post_content LIKE "%%src=%%"
646
- OR post_content LIKE "%%href=%%")
647
- ', $wpdb->prefix );
648
 
649
  return $wpdb->get_var( $sql );
650
  }
@@ -689,18 +689,6 @@ WHERE p.post_type = "attachment" AND (pm.meta_value IS NOT NULL OR pm2.meta_valu
689
  return $regexp;
690
  }
691
 
692
- /**
693
- *
694
- *
695
- * @param unknown $error
696
- */
697
- function update_cnames( &$error ) {
698
- $common = Dispatcher::component( 'Cdn_Core' );
699
- $cdn = $common->get_cdn();
700
- $cdn->update_cnames( $error );
701
- }
702
-
703
-
704
  /**
705
  * media_row_actions filter
706
  *
198
 
199
  if ( $upload_info ) {
200
  $sql = sprintf( 'SELECT
201
+ pm.meta_value AS file,
202
+ pm2.meta_value AS metadata
203
+ FROM
204
+ %sposts AS p
205
+ LEFT JOIN
206
+ %spostmeta AS pm ON p.ID = pm.post_ID AND pm.meta_key = "_wp_attached_file"
207
+ LEFT JOIN
208
+ %spostmeta AS pm2 ON p.ID = pm2.post_ID AND pm2.meta_key = "_wp_attachment_metadata"
209
+ WHERE
210
+ p.post_type = "attachment" AND (pm.meta_value IS NOT NULL OR pm2.meta_value IS NOT NULL)
211
+ GROUP BY
212
+ p.ID
213
+ ORDER BY
214
+ p.ID', $wpdb->prefix, $wpdb->prefix, $wpdb->prefix );
215
 
216
  if ( $limit ) {
217
  $sql .= sprintf( ' LIMIT %d', $limit );
287
  * Search for posts with links or images
288
  */
289
  $sql = sprintf( 'SELECT
290
+ ID,
291
+ post_content,
292
+ post_date
293
+ FROM
294
+ %sposts
295
+ WHERE
296
+ post_status = "publish"
297
+ AND (post_type = "post" OR post_type = "page")
298
+ AND (post_content LIKE "%%src=%%"
299
+ OR post_content LIKE "%%href=%%")
300
+ ', $wpdb->prefix );
301
 
302
  if ( $limit ) {
303
  $sql .= sprintf( ' LIMIT %d', $limit );
550
 
551
  if ( $upload_info ) {
552
  $sql = sprintf( 'SELECT
553
+ ID,
554
+ post_content,
555
+ post_date
556
+ FROM
557
+ %sposts
558
+ WHERE
559
+ post_status = "publish"
560
+ AND (post_type = "post" OR post_type = "page")
561
+ AND (post_content LIKE "%%src=%%"
562
+ OR post_content LIKE "%%href=%%")
563
+ ', $wpdb->prefix );
564
 
565
  if ( $limit ) {
566
  $sql .= sprintf( ' LIMIT %d', $limit );
636
  global $wpdb;
637
 
638
  $sql = sprintf( 'SELECT
639
+ COUNT(*)
640
+ FROM
641
+ %sposts
642
+ WHERE
643
+ post_status = "publish"
644
+ AND (post_type = "post" OR post_type = "page")
645
+ AND (post_content LIKE "%%src=%%"
646
+ OR post_content LIKE "%%href=%%")
647
+ ', $wpdb->prefix );
648
 
649
  return $wpdb->get_var( $sql );
650
  }
689
  return $regexp;
690
  }
691
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  /**
693
  * media_row_actions filter
694
  *
Cdn_Plugin_Admin.php CHANGED
@@ -212,7 +212,7 @@ class Cdn_Plugin_Admin {
212
  );
213
  $engine_values['cf'] = array(
214
  'disabled' => ( !Util_Installed::curl() ? true : null ),
215
- 'label' => __( 'Amazon CloudFront', 'w3-total-cache' ),
216
  'optgroup' => $optgroup_push
217
  );
218
  $engine_values['s3'] = array(
212
  );
213
  $engine_values['cf'] = array(
214
  'disabled' => ( !Util_Installed::curl() ? true : null ),
215
+ 'label' => __( 'Amazon CloudFront Over S3', 'w3-total-cache' ),
216
  'optgroup' => $optgroup_push
217
  );
218
  $engine_values['s3'] = array(
Cdn_Util.php CHANGED
@@ -58,6 +58,7 @@ class Cdn_Util {
58
  static public function can_purge_all( $engine ) {
59
  return in_array( $engine, array(
60
  'att',
 
61
  'cotendo',
62
  'edgecast',
63
  'highwinds',
58
  static public function can_purge_all( $engine ) {
59
  return in_array( $engine, array(
60
  'att',
61
+ 'cf2',
62
  'cotendo',
63
  'edgecast',
64
  'highwinds',
Cdnfsd_CloudFront_Api.php DELETED
@@ -1,344 +0,0 @@
1
- <?php
2
- namespace W3TC;
3
-
4
- class Cdnfsd_CloudFront_Api {
5
- private $access_key; // AWS Access key
6
- private $secret_Key; // AWS Secret key
7
- private $api_host; // AWS host where API is located
8
-
9
-
10
- public function __construct( $access_key = null, $secret_key = null,
11
- $api_host = 'cloudfront.amazonaws.com' ) {
12
- $this->access_key = $access_key;
13
- $this->secret_key = $secret_key;
14
- $this->api_host = $api_host;
15
- }
16
-
17
-
18
-
19
- /**
20
- * Get a list of CloudFront distributions
21
- */
22
- public function distributions_list() {
23
- $response = $this->request( 'GET', '/2014-11-06/distribution' );
24
- $data = $response['body_array'];
25
-
26
- $data = $this->fix_array( $data, array(
27
- 'Items', 'DistributionSummary' ) );
28
-
29
- if ( isset( $data['Items']['DistributionSummary'] ) ) {
30
- foreach ( $data['Items']['DistributionSummary'] as $key => $value ) {
31
- $data['Items']['DistributionSummary'][$key] =
32
- $this->fix_distribution(
33
- $data['Items']['DistributionSummary'][$key] );
34
- }
35
- }
36
-
37
- return $data;
38
- }
39
-
40
-
41
-
42
- /**
43
- * Get a list of CloudFront distributions
44
- */
45
- public function distribution_get( $id ) {
46
- $response = $this->request( 'GET', '/2014-11-06/distribution/' . $id );
47
- $data = $response['body_array'];
48
-
49
- if ( isset( $data['DistributionConfig'] ) )
50
- $data['DistributionConfig'] = $this->fix_distribution(
51
- $data['DistributionConfig'] );
52
-
53
- return $data;
54
- }
55
-
56
-
57
-
58
- /**
59
- * $distribution
60
- * origin
61
- */
62
- public function distribution_create( $distribution ) {
63
- if ( !isset( $distribution['CallerReference'] ) )
64
- $distribution['CallerReference'] = rand();
65
- if ( !isset( $distribution['Enabled'] ) )
66
- $distribution['Enabled'] = 'true';
67
-
68
- $data =
69
- '<?xml version="1.0" encoding="UTF-8"?>' .
70
- '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2014-11-06/">' .
71
- $this->array_to_xml( $distribution ) .
72
- '</DistributionConfig>';
73
-
74
- $response = $this->request( 'POST', '/2014-11-06/distribution', $data );
75
- $response_data = $response['body_array'];
76
-
77
- return $response_data;
78
- }
79
-
80
-
81
-
82
- /**
83
- * $distribution
84
- * origin
85
- */
86
- public function distribution_update( $distribution_id, $distribution ) {
87
- $get_response = $this->request( 'GET', '/2014-11-06/distribution/' .
88
- $distribution_id );
89
- $get_distribution = $get_response['body_array'];
90
-
91
- $c = $get_distribution['DistributionConfig'];
92
-
93
- // update values
94
- foreach ( $distribution as $key => $value ) {
95
- if ( $key == 'DefaultCacheBehavior' ) {
96
- // inherit inner values of that setting too
97
- foreach ( $distribution[$key] as $key2 => $value2 ) {
98
- $c[$key][$key2] = $value2;
99
- }
100
- } else {
101
- $c[$key] = $value;
102
- }
103
- }
104
-
105
- $data =
106
- '<?xml version="1.0" encoding="UTF-8"?>' .
107
- '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2014-11-06/">' .
108
- $this->array_to_xml( $c ) .
109
- '</DistributionConfig>';
110
-
111
- $response = $this->request( 'PUT',
112
- '/2014-11-06/distribution/' . $distribution_id . '/config', $data,
113
- array( 'If-Match' => $get_response['headers']['etag'] ) );
114
- $data = $response['body_array'];
115
-
116
- if ( isset( $data['DistributionConfig'] ) )
117
- $data['DistributionConfig'] = $this->fix_distribution(
118
- $data['DistributionConfig'] );
119
-
120
- return $data;
121
- }
122
-
123
-
124
-
125
- /**
126
- * Invalidate cache
127
- */
128
- public function invalidation_create( $distribution_id, $uris ) {
129
- $data =
130
- '<?xml version="1.0" encoding="UTF-8"?>' .
131
- '<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2014-11-06/">' .
132
- '<Paths>' .
133
- '<Quantity>' . count( $uris ) . '</Quantity>' .
134
- $this->array_to_xml( array(
135
- 'Items' => array(
136
- 'Path' => $uris
137
- )
138
- ) ) .
139
- '</Paths>' .
140
- '<CallerReference>' . rand() . '</CallerReference>' .
141
- '</InvalidationBatch>';
142
-
143
- $response = $this->request( 'POST',
144
- '/2014-11-06/distribution/' . $distribution_id . '/invalidation', $data );
145
- $response_data = $response['body_array'];
146
-
147
- return $response_data;
148
- }
149
-
150
- /**
151
- * Constructor
152
- *
153
- * @param string $verb Verb
154
- * @param string $bucket Bucket name
155
- * @param string $uri Object URI
156
- * @return mixed
157
- */
158
- private function request( $method, $uri = '', $data = '', $headers = array() ) {
159
- $url = 'https://' . $this->api_host . $uri;
160
-
161
- $headers['Host'] = $this->api_host;
162
- $headers['x-amz-date'] = gmdate( 'Ymd\THis\Z', time() );
163
-
164
- if ( $method == "POST" || $method == "PUT" ) {
165
- $headers['Content-Type'] = 'application/xml; charset=utf-8';
166
- $headers['Content-Length'] = strlen( $data );
167
- } else {
168
- $data = '';
169
- }
170
-
171
- $headers['Authorization'] = $this->calculateSignature( $method, $uri,
172
- $headers, $data );
173
-
174
-
175
- // do the request
176
- $request = array(
177
- 'sslverify' => false,
178
- 'headers' => $headers,
179
- 'method' => $method,
180
- 'body' => $data
181
- );
182
-
183
- $response = wp_remote_request( $url, $request );
184
-
185
- // handle response
186
- if ( is_wp_error( $response ) ) {
187
- throw new \Exception( 'Failed to reach CloudFront: ' .
188
- implode( '; ', $response->get_error_messages() ) );
189
- }
190
-
191
- if ( substr( $response['body'], 0, 5 ) != '<?xml' )
192
- throw new \Exception( 'Unexpected non-xml response from service received' );
193
-
194
- $xml = simplexml_load_string( $response['body'] );
195
- $json = json_encode( $xml );
196
- $body_array = json_decode( $json, TRUE );
197
- $response['body_array'] = $body_array;
198
-
199
- if ( isset( $body_array['Error'] ) && isset( $body_array['Error']['Message'] ) )
200
- throw new \Exception( $body_array['Error']['Message'] );
201
-
202
- return $response;
203
- }
204
-
205
-
206
-
207
- public function calculateSignature( $method, $uri, $headers, $data ) {
208
- $short_date = substr( $headers['x-amz-date'], 0, 8 );
209
-
210
- // Parse the service and region or use one that is explicitly set
211
- $region = 'us-east-1';
212
- $service = 'cloudfront';
213
-
214
- $credential_scope = $short_date . '/' . $region . '/' . $service .
215
- '/aws4_request';
216
-
217
-
218
- // calc payload
219
- if ( empty( $data ) )
220
- $payload = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
221
- else
222
- $payload = hash( 'sha256', $data );
223
-
224
-
225
- // create canonical request
226
- $canonical_headers = array(
227
- 'host:' . $headers['Host'],
228
- 'x-amz-date:' . $headers['x-amz-date']
229
- );
230
-
231
- $signed_headers = 'host;x-amz-date';
232
- $canonical_request = $method . "\n" .
233
- $this->canonicalize_uri( $uri ) . "\n" .
234
- '' /* query string */ . "\n" .
235
- implode( "\n", $canonical_headers ) . "\n\n" .
236
- $signed_headers . "\n" .
237
- $payload;
238
-
239
- $string_to_sign =
240
- "AWS4-HMAC-SHA256\n" .
241
- $headers['x-amz-date'] . "\n" .
242
- $credential_scope . "\n" .
243
- hash( 'sha256', $canonical_request );
244
-
245
- // Calculate the signing key using a series of derived keys
246
- $signingKey = $this->get_signing_key( $short_date, $region, $service,
247
- $this->secret_key );
248
- $signature = hash_hmac( 'sha256', $string_to_sign, $signingKey );
249
-
250
- return "AWS4-HMAC-SHA256 " .
251
- "Credential={$this->access_key}/{$credential_scope}, " .
252
- "SignedHeaders={$signed_headers}, Signature={$signature}";
253
- }
254
-
255
-
256
-
257
-
258
- private function canonicalize_uri( $uri ) {
259
- $doubleEncoded = rawurlencode( ltrim( $uri, '/' ) );
260
- return '/' . str_replace( '%2F', '/', $doubleEncoded );
261
- }
262
-
263
-
264
-
265
- private function get_signing_key( $short_date, $region, $service, $secretKey ) {
266
- $dateKey = hash_hmac( 'sha256', $short_date, 'AWS4' . $secretKey, true );
267
- $regionKey = hash_hmac( 'sha256', $region, $dateKey, true );
268
- $serviceKey = hash_hmac( 'sha256', $service, $regionKey, true );
269
-
270
- return hash_hmac( 'sha256', 'aws4_request', $serviceKey, true );
271
- }
272
-
273
-
274
-
275
- private function array_to_xml( $a ) {
276
- if ( !is_array( $a ) )
277
- return $a;
278
-
279
- $s = '';
280
- foreach ( $a as $key => $value ) {
281
- if ( is_array( $value ) && isset( $value[0] ) ) {
282
- // number-indexed array, serialized as list
283
- foreach ( $value as $array_item ) {
284
- $s .=
285
- '<' . $key . '>' .
286
- $this->array_to_xml( $array_item ) .
287
- '</' . $key . '>';
288
- }
289
- } else {
290
- $s .=
291
- '<' . $key . '>' .
292
- $this->array_to_xml( $value ) .
293
- '</' . $key . '>';
294
- }
295
- }
296
-
297
- return $s;
298
- }
299
-
300
-
301
-
302
- /**
303
- * XML parsing suffers common problem of array recognition
304
- * <items><item>a</></> is accessible via $data['items']['item']
305
- * but
306
- * <items><item>a</><item>b</></> is accessible
307
- * via $data['items'][$n]['item']
308
- *
309
- * by knowing tag we can try to guess that it contains only 1 element
310
- * and turn it to array so that it will be always accessible via 2nd way
311
- */
312
- private function fix_array( $response, $keys ) {
313
- $a = &$response;
314
-
315
- for ( $n = 0; $n < count( $keys ) - 1; $n++ ) {
316
- $key = $keys[$n];
317
- if ( !isset( $a[$key] ) )
318
- break;
319
- $a = &$a[$key];
320
- }
321
-
322
- $last_key = $keys[count( $keys ) - 1];
323
- if ( isset( $a[$last_key] ) ) {
324
- if ( !isset( $a[$last_key][0] ) || is_string( $a[$last_key] ) ) {
325
- $a[$last_key] = array(
326
- $a[$last_key]
327
- );
328
- }
329
- }
330
-
331
- return $response;
332
- }
333
-
334
-
335
-
336
- private function fix_distribution( $data ) {
337
- $data = $this->fix_array( $data, array( 'Aliases', 'Items', 'CNAME' ) );
338
- $data = $this->fix_array( $data, array( 'Origins', 'Items', 'Origin' ) );
339
- $data = $this->fix_array( $data, array( 'DefaultCacheBehavior',
340
- 'ForwardedValues', 'Headers', 'Items', 'Name' ) );
341
-
342
- return $data;
343
- }
344
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Cdnfsd_CloudFront_Engine.php CHANGED
@@ -1,6 +1,10 @@
1
  <?php
2
  namespace W3TC;
3
 
 
 
 
 
4
 
5
 
6
  class Cdnfsd_CloudFront_Engine {
@@ -10,7 +14,7 @@ class Cdnfsd_CloudFront_Engine {
10
 
11
 
12
 
13
- function __construct( $config = array() ) {
14
  $this->access_key = $config['access_key'];
15
  $this->secret_key = $config['secret_key'];
16
  $this->distribution_id = $config['distribution_id'];
@@ -18,12 +22,9 @@ class Cdnfsd_CloudFront_Engine {
18
 
19
 
20
 
21
- function flush_urls( $urls ) {
22
- if ( empty( $this->access_key ) || empty( $this->secret_key ) ||
23
- empty( $this->distribution_id ) )
24
- throw new \Exception( __( 'Access key not specified.', 'w3-total-cache' ) );
25
 
26
- $api = new Cdnfsd_CloudFront_Api( $this->access_key, $this->secret_key );
27
  $uris = array();
28
  foreach ( $urls as $url ) {
29
  $parsed = parse_url( $url );
@@ -33,7 +34,17 @@ class Cdnfsd_CloudFront_Engine {
33
  $uris[] = $relative_url;
34
  }
35
 
36
- $api->invalidation_create( $this->distribution_id, $uris );
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
 
@@ -41,14 +52,38 @@ class Cdnfsd_CloudFront_Engine {
41
  /**
42
  * Flushes CDN completely
43
  */
44
- function flush_all() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  if ( empty( $this->access_key ) || empty( $this->secret_key ) ||
46
  empty( $this->distribution_id ) )
47
  throw new \Exception( __( 'Access key not specified.', 'w3-total-cache' ) );
48
 
49
- $api = new Cdnfsd_CloudFront_Api( $this->access_key, $this->secret_key );
50
- $uris = array( '/*' );
51
 
52
- $api->invalidation_create( $this->distribution_id, $uris );
 
 
 
 
 
53
  }
54
  }
1
  <?php
2
  namespace W3TC;
3
 
4
+ if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
5
+ require_once W3TC_LIB_DIR . '/Aws/aws-autoloader.php';
6
+ }
7
+
8
 
9
 
10
  class Cdnfsd_CloudFront_Engine {
14
 
15
 
16
 
17
+ public function __construct( $config = array() ) {
18
  $this->access_key = $config['access_key'];
19
  $this->secret_key = $config['secret_key'];
20
  $this->distribution_id = $config['distribution_id'];
22
 
23
 
24
 
25
+ public function flush_urls( $urls ) {
26
+ $api = $this->_api();
 
 
27
 
 
28
  $uris = array();
29
  foreach ( $urls as $url ) {
30
  $parsed = parse_url( $url );
34
  $uris[] = $relative_url;
35
  }
36
 
37
+ $api->createInvalidation( array(
38
+ 'DistributionId' => $this->distribution_id,
39
+ 'InvalidationBatch' => array(
40
+ 'CallerReference' => 'w3tc-' . microtime(),
41
+ 'Paths' => array(
42
+ 'Items' => $uris,
43
+ 'Quantity' => count( $uris ),
44
+ ),
45
+ )
46
+ )
47
+ );
48
  }
49
 
50
 
52
  /**
53
  * Flushes CDN completely
54
  */
55
+ public function flush_all() {
56
+ $api = $this->_api();
57
+ $uris = array( '/*' );
58
+
59
+ $api->createInvalidation( array(
60
+ 'DistributionId' => $this->distribution_id,
61
+ 'InvalidationBatch' => array(
62
+ 'CallerReference' => 'w3tc-' . microtime(),
63
+ 'Paths' => array(
64
+ 'Items' => $uris,
65
+ 'Quantity' => count( $uris ),
66
+ ),
67
+ )
68
+ )
69
+ );
70
+ }
71
+
72
+
73
+
74
+ private function _api() {
75
  if ( empty( $this->access_key ) || empty( $this->secret_key ) ||
76
  empty( $this->distribution_id ) )
77
  throw new \Exception( __( 'Access key not specified.', 'w3-total-cache' ) );
78
 
79
+ $credentials = new \Aws\Credentials\Credentials(
80
+ $this->access_key, $this->secret_key );
81
 
82
+ return new \Aws\CloudFront\CloudFrontClient( array(
83
+ 'credentials' => $credentials,
84
+ 'region' => 'us-east-1',
85
+ 'version' => '2018-11-05'
86
+ )
87
+ );
88
  }
89
  }
Cdnfsd_CloudFront_Popup.php CHANGED
@@ -1,6 +1,10 @@
1
  <?php
2
  namespace W3TC;
3
 
 
 
 
 
4
 
5
 
6
  class Cdnfsd_CloudFront_Popup {
@@ -44,7 +48,6 @@ class Cdnfsd_CloudFront_Popup {
44
  $access_key = $_REQUEST['access_key'];
45
  $secret_key = $_REQUEST['secret_key'];
46
 
47
- $api = new Cdnfsd_CloudFront_Api( $access_key, $secret_key );
48
  if ( empty( $access_key ) || empty( $secret_key ) ) {
49
  $this->render_intro( array(
50
  'error_message' => 'Can\'t authenticate: Access Key or Secret not valid'
@@ -53,7 +56,13 @@ class Cdnfsd_CloudFront_Popup {
53
  }
54
 
55
  try {
56
- $distributions = $api->distributions_list();
 
 
 
 
 
 
57
  } catch ( \Exception $ex ) {
58
  $error_message = 'Can\'t authenticate: ' . $ex->getMessage();
59
 
@@ -65,12 +74,14 @@ class Cdnfsd_CloudFront_Popup {
65
 
66
  $items = array();
67
 
68
- if ( isset( $distributions['Items']['DistributionSummary'] ) ) {
69
- foreach ( $distributions['Items']['DistributionSummary'] as $i ) {
70
- if ( empty( $i['Comment'] ) )
71
  $i['Comment'] = $i['DomainName'];
72
- if ( isset( $i['Origins']['Items']['Origin'] ) )
73
- $i['Origin_DomainName'] = $i['Origins']['Items']['Origin'][0]['DomainName'];
 
 
74
 
75
  $items[] = $i;
76
  }
@@ -109,9 +120,6 @@ class Cdnfsd_CloudFront_Popup {
109
  ),
110
  'forward_host' => array(
111
  'new' => true
112
- ),
113
- 'alias' => array(
114
- 'new' => Util_Environment::home_url_host()
115
  )
116
  );
117
 
@@ -119,10 +127,13 @@ class Cdnfsd_CloudFront_Popup {
119
  // create new zone mode
120
  $details['distribution_comment'] = Util_Request::get( 'comment_new' );
121
  } else {
122
- $api = new Cdnfsd_CloudFront_Api( $access_key, $secret_key );
123
 
124
  try {
125
- $distribution = $api->distribution_get( $distribution_id );
 
 
 
126
  } catch ( \Exception $ex ) {
127
  $this->render_intro( array(
128
  'error_message' => 'Can\'t obtain zone: ' . $ex->getMessage()
@@ -130,8 +141,8 @@ class Cdnfsd_CloudFront_Popup {
130
  exit();
131
  }
132
 
133
- if ( isset( $distribution['DistributionConfig'] ) )
134
- $c = $distribution['DistributionConfig'];
135
  else
136
  $c = array();
137
 
@@ -158,9 +169,6 @@ class Cdnfsd_CloudFront_Popup {
158
  ( isset( $b['Cookies'] ) && isset( $b['Cookies']['Forward'] ) &&
159
  $b['Cookies']['Forward'] == 'all' );
160
 
161
- if ( isset( $c['Aliases']['Items']['CNAME'][0] ) )
162
- $details['alias']['current'] = $c['Aliases']['Items']['CNAME'][0];
163
-
164
  $details['forward_host']['current'] = false;
165
  if ( isset( $b['Headers']['Items']['Name'] ) ) {
166
  foreach ( $b['Headers']['Items']['Name'] as $name )
@@ -247,78 +255,84 @@ class Cdnfsd_CloudFront_Popup {
247
  $origin_id = rand();
248
 
249
  $distribution = array(
250
- 'Comment' => Util_Request::get( 'distribution_comment' ),
251
- 'Origins' => array(
252
- 'Quantity' => 1,
253
- 'Items' => array(
254
- 'Origin' => array(
255
- 'Id' => $origin_id,
256
- 'DomainName' => Util_Request::get( 'origin' ),
257
- 'OriginPath' => '',
258
- 'CustomOriginConfig' => array(
259
- 'HTTPPort' => 80,
260
- 'HTTPSPort' => 443,
261
- 'OriginProtocolPolicy' => 'match-viewer'
262
- )
263
- )
264
- )
265
- ),
266
- 'Aliases' => array(
267
- 'Quantity' => 1,
268
- 'Items' => array(
269
- 'CNAME' => Util_Request::get( 'alias' )
270
- )
271
- ),
272
- 'DefaultCacheBehavior' => array(
273
- 'TargetOriginId' => $origin_id,
274
- 'ForwardedValues' => array(
275
- 'QueryString' => 'true',
276
- 'Cookies' => array(
277
- 'Forward' => 'all'
 
 
 
 
 
 
 
 
 
278
  ),
279
- 'Headers' => array(
280
- 'Quantity' => 1,
281
- 'Items' => array(
282
- 'Name' => 'Host'
283
- )
284
- )
285
  ),
286
- 'AllowedMethods' => array(
287
- 'Quantity' => 7,
288
  'Items' => array(
289
- 'Method' => array(
290
- 'GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'PATCH',
291
- 'DELETE'
292
- )
 
 
 
 
 
 
 
293
  ),
294
- 'CachedMethods' => array(
295
- 'Quantity' => 2,
296
- 'Items' => array(
297
- 'Method' => array(
298
- 'GET', 'HEAD'
299
- )
300
- )
301
- )
302
  ),
303
- 'MinTTL' => 0,
 
 
304
  )
305
  );
306
 
307
  try {
308
- $api = new Cdnfsd_CloudFront_Api( $access_key, $secret_key );
309
  if ( empty( $distribution_id ) ) {
310
- $distribution['DefaultCacheBehavior']['TrustedSigners'] = array(
311
- 'Enabled' => 'false',
312
- 'Quantity' => 0
313
- );
314
- $distribution['DefaultCacheBehavior']['ViewerProtocolPolicy'] =
315
- 'allow-all';
316
 
317
- $response = $api->distribution_create( $distribution );
318
- $distribution_id = $response['Id'];
319
  } else {
320
- $response = $api->distribution_update( $distribution_id, $distribution );
 
321
  }
 
 
 
 
 
322
  } catch ( \Exception $ex ) {
323
  $this->render_intro( array(
324
  'error_message' => 'Failed to configure distribution: ' . $ex->getMessage()
@@ -326,17 +340,18 @@ class Cdnfsd_CloudFront_Popup {
326
  exit();
327
  }
328
 
329
- $distribution_domain = $response['DomainName'];
330
 
331
  $c = Dispatcher::config();
332
  $c->set( 'cdnfsd.cloudfront.access_key', $access_key );
333
  $c->set( 'cdnfsd.cloudfront.secret_key', $secret_key );
334
  $c->set( 'cdnfsd.cloudfront.distribution_id', $distribution_id );
335
  $c->set( 'cdnfsd.cloudfront.distribution_domain', $distribution_domain );
 
336
  $c->save();
337
 
338
  $details = array(
339
- 'name' => $distribution['Comment'],
340
  'home_domain' => Util_Environment::home_url_host(),
341
  'dns_cname_target' => $distribution_domain,
342
  );
@@ -355,8 +370,10 @@ class Cdnfsd_CloudFront_Popup {
355
  $origin_id = rand();
356
 
357
  try {
358
- $api = new Cdnfsd_CloudFront_Api( $access_key, $secret_key );
359
- $distribution = $api->distribution_get( $distribution_id );
 
 
360
  } catch ( \Exception $ex ) {
361
  $this->render_intro( array(
362
  'error_message' => 'Failed to configure distribution: ' . $ex->getMessage()
@@ -364,8 +381,8 @@ class Cdnfsd_CloudFront_Popup {
364
  exit();
365
  }
366
 
367
- if ( isset( $distribution['DomainName'] ) )
368
- $distribution_domain = $distribution['DomainName'];
369
  else
370
  $distribution_domain = 'n/a';
371
 
@@ -377,7 +394,7 @@ class Cdnfsd_CloudFront_Popup {
377
  $c->save();
378
 
379
  $details = array(
380
- 'name' => $distribution['Comment'],
381
  'home_domain' => Util_Environment::home_url_host(),
382
  'dns_cname_target' => $distribution_domain,
383
  );
@@ -385,4 +402,18 @@ class Cdnfsd_CloudFront_Popup {
385
  include W3TC_DIR . '/Cdnfsd_CloudFront_Popup_View_Success.php';
386
  exit();
387
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  }
1
  <?php
2
  namespace W3TC;
3
 
4
+ if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
5
+ require_once W3TC_LIB_DIR . '/Aws/aws-autoloader.php';
6
+ }
7
+
8
 
9
 
10
  class Cdnfsd_CloudFront_Popup {
48
  $access_key = $_REQUEST['access_key'];
49
  $secret_key = $_REQUEST['secret_key'];
50
 
 
51
  if ( empty( $access_key ) || empty( $secret_key ) ) {
52
  $this->render_intro( array(
53
  'error_message' => 'Can\'t authenticate: Access Key or Secret not valid'
56
  }
57
 
58
  try {
59
+ $api = $this->_api( $access_key, $secret_key );
60
+ $distributions = $api->listDistributions();
61
+ } catch ( \Aws\Exception\AwsException $ex ) {
62
+ $this->render_intro( array(
63
+ 'error_message' => 'Can\'t authenticate: ' .
64
+ $ex->getAwsErrorMessage() ) );
65
+ exit();
66
  } catch ( \Exception $ex ) {
67
  $error_message = 'Can\'t authenticate: ' . $ex->getMessage();
68
 
74
 
75
  $items = array();
76
 
77
+ if ( isset( $distributions['DistributionList']['Items'] ) ) {
78
+ foreach ( $distributions['DistributionList']['Items'] as $i ) {
79
+ if ( empty( $i['Comment'] ) ) {
80
  $i['Comment'] = $i['DomainName'];
81
+ }
82
+ if ( isset( $i['Origins']['Items'][0]['DomainName'] ) ) {
83
+ $i['Origin_DomainName'] = $i['Origins']['Items'][0]['DomainName'];
84
+ }
85
 
86
  $items[] = $i;
87
  }
120
  ),
121
  'forward_host' => array(
122
  'new' => true
 
 
 
123
  )
124
  );
125
 
127
  // create new zone mode
128
  $details['distribution_comment'] = Util_Request::get( 'comment_new' );
129
  } else {
130
+
131
 
132
  try {
133
+ $api = $this->_api( $access_key, $secret_key );
134
+ $distribution = $api->getDistribution(
135
+ array( 'Id' => $distribution_id )
136
+ );
137
  } catch ( \Exception $ex ) {
138
  $this->render_intro( array(
139
  'error_message' => 'Can\'t obtain zone: ' . $ex->getMessage()
141
  exit();
142
  }
143
 
144
+ if ( isset( $distribution['Distribution']['DistributionConfig'] ) )
145
+ $c = $distribution['Distribution']['DistributionConfig'];
146
  else
147
  $c = array();
148
 
169
  ( isset( $b['Cookies'] ) && isset( $b['Cookies']['Forward'] ) &&
170
  $b['Cookies']['Forward'] == 'all' );
171
 
 
 
 
172
  $details['forward_host']['current'] = false;
173
  if ( isset( $b['Headers']['Items']['Name'] ) ) {
174
  foreach ( $b['Headers']['Items']['Name'] as $name )
255
  $origin_id = rand();
256
 
257
  $distribution = array(
258
+ 'DistributionConfig' => array(
259
+ 'CallerReference' => $origin_id,
260
+ 'Comment' => Util_Request::get( 'distribution_comment' ),
261
+ 'DefaultCacheBehavior' => array(
262
+ 'AllowedMethods' => array(
263
+ 'CachedMethods' => array(
264
+ 'Items' => array( 'HEAD', 'GET' ),
265
+ 'Quantity' => 2,
266
+ ),
267
+ 'Items' => array( 'HEAD', 'GET' ),
268
+ 'Quantity' => 2,
269
+ ),
270
+ 'Compress' => true,
271
+ 'DefaultTTL' => 86400,
272
+ 'FieldLevelEncryptionId' => '',
273
+ 'ForwardedValues' => array(
274
+ 'Cookies' => array(
275
+ 'Forward' => 'all',
276
+ ),
277
+ 'Headers' => array(
278
+ 'Quantity' => 1,
279
+ 'Items' => array(
280
+ 'Name' => 'Host'
281
+ )
282
+ ),
283
+ 'QueryString' => true,
284
+ 'QueryStringCacheKeys' => array(
285
+ 'Quantity' => 0,
286
+ ),
287
+ ),
288
+ 'LambdaFunctionAssociations' => array( 'Quantity' => 0),
289
+ 'MinTTL' => 0,
290
+ 'SmoothStreaming' => false,
291
+ 'TargetOriginId' => $origin_id,
292
+ 'TrustedSigners' => array(
293
+ 'Enabled' => false,
294
+ 'Quantity' => 0,
295
  ),
296
+ 'ViewerProtocolPolicy' => 'allow-all',
 
 
 
 
 
297
  ),
298
+ 'Enabled' => true,
299
+ 'Origins' => array(
300
  'Items' => array(
301
+ array(
302
+ 'DomainName' => Util_Request::get( 'origin' ),
303
+ 'Id' => $origin_id,
304
+ 'OriginPath' => '',
305
+ 'CustomHeaders' => array( 'Quantity' => 0 ),
306
+ 'CustomOriginConfig' => array(
307
+ 'HTTPPort' => 80,
308
+ 'HTTPSPort' => 443,
309
+ 'OriginProtocolPolicy' => 'match-viewer'
310
+ ),
311
+ ),
312
  ),
313
+ 'Quantity' => 1,
 
 
 
 
 
 
 
314
  ),
315
+ 'Aliases' => array(
316
+ 'Quantity' => 0
317
+ )
318
  )
319
  );
320
 
321
  try {
322
+ $api = $this->_api( $access_key, $secret_key );
323
  if ( empty( $distribution_id ) ) {
 
 
 
 
 
 
324
 
325
+ $response = $api->createDistribution( $distribution );
326
+ $distribution_id = $response['Distribution']['Id'];
327
  } else {
328
+ $distribution['Id'] = $distribution_id;
329
+ $response = $api->UpdateDistribution( $distribution );
330
  }
331
+ } catch ( \Aws\Exception\AwsException $ex ) {
332
+ $this->render_intro( array(
333
+ 'error_message' => 'Unable to create distribution: ' .
334
+ $ex->getAwsErrorMessage() ) );
335
+ exit();
336
  } catch ( \Exception $ex ) {
337
  $this->render_intro( array(
338
  'error_message' => 'Failed to configure distribution: ' . $ex->getMessage()
340
  exit();
341
  }
342
 
343
+ $distribution_domain = $response['Distribution']['DomainName'];
344
 
345
  $c = Dispatcher::config();
346
  $c->set( 'cdnfsd.cloudfront.access_key', $access_key );
347
  $c->set( 'cdnfsd.cloudfront.secret_key', $secret_key );
348
  $c->set( 'cdnfsd.cloudfront.distribution_id', $distribution_id );
349
  $c->set( 'cdnfsd.cloudfront.distribution_domain', $distribution_domain );
350
+
351
  $c->save();
352
 
353
  $details = array(
354
+ 'name' => $distribution['DistributionConfig']['Comment'],
355
  'home_domain' => Util_Environment::home_url_host(),
356
  'dns_cname_target' => $distribution_domain,
357
  );
370
  $origin_id = rand();
371
 
372
  try {
373
+ $api = $this->_api( $access_key, $secret_key );
374
+ $distribution = $api->getDistribution(
375
+ array( 'Id' => $distribution_id )
376
+ );
377
  } catch ( \Exception $ex ) {
378
  $this->render_intro( array(
379
  'error_message' => 'Failed to configure distribution: ' . $ex->getMessage()
381
  exit();
382
  }
383
 
384
+ if ( isset( $distribution['Distribution']['DomainName'] ) )
385
+ $distribution_domain = $distribution['Distribution']['DomainName'];
386
  else
387
  $distribution_domain = 'n/a';
388
 
394
  $c->save();
395
 
396
  $details = array(
397
+ 'name' => $distribution['Distribution']['Comment'],
398
  'home_domain' => Util_Environment::home_url_host(),
399
  'dns_cname_target' => $distribution_domain,
400
  );
402
  include W3TC_DIR . '/Cdnfsd_CloudFront_Popup_View_Success.php';
403
  exit();
404
  }
405
+
406
+
407
+
408
+ private function _api( $access_key, $secret_key ) {
409
+ $credentials = new \Aws\Credentials\Credentials(
410
+ $access_key, $secret_key );
411
+
412
+ return new \Aws\CloudFront\CloudFrontClient( array(
413
+ 'credentials' => $credentials,
414
+ 'region' => 'us-east-1',
415
+ 'version' => '2018-11-05'
416
+ )
417
+ );
418
+ }
419
  }
Cdnfsd_CloudFront_Popup_View_Distribution.php CHANGED
@@ -5,59 +5,57 @@ if ( !defined( 'W3TC' ) )
5
  die();
6
  ?>
7
  <form class="w3tc_popup_form" method="post">
8
- <?php
9
  Util_Ui::hidden( '', 'access_key', $details['access_key'] );
10
  Util_Ui::hidden( '', 'secret_key', $details['secret_key'] );
11
  Util_Ui::hidden( '', 'distribution_id', $details['distribution_id'] );
12
  Util_Ui::hidden( '', 'distribution_comment', $details['distribution_comment'] );
13
  ?>
14
 
15
- <div class="metabox-holder">
16
- <?php Util_Ui::postbox_header( __( 'Configure distribution', 'w3-total-cache' ) ); ?>
17
- <table class="form-table">
18
- <tr>
19
- <th>Distribution:</th>
20
- <td><?php echo $details['distribution_comment'] ?></td>
21
- </tr>
22
- <tr>
23
- <th>Origin:</th>
24
- <td><?php $this->render_zone_ip_change( $details, 'origin' ) ?><br />
25
- <span class="description">
26
- Create an apex <acronym title="Domain Name System">DNS</acronym> record pointing to your WordPress host <acronym title="Internet Protocol">IP</acronym>.
27
- CloudFront will use this host to mirror your site.
28
 
29
- Tip: If your real domain name is domain.com, then the host
30
- for the apex record should be origin.domain.com with the host
31
- <acronym title="Internet Protocol">IP</acronym> of domain.com, e.g.:
32
- </span>
33
- </td>
34
- </tr>
35
- <tr>
36
- <th>Alias Domain:</th>
37
- <td><?php $this->render_zone_value_change( $details, 'alias' ) ?></td>
38
- </tr>
39
- <tr>
40
- <th>Forward Cookies:</th>
41
- <td><?php $this->render_zone_boolean_change( $details, 'forward_cookies' ) ?></td>
42
- </tr>
43
- <tr>
44
- <th>Forward Query String:</th>
45
- <td><?php $this->render_zone_boolean_change( $details, 'forward_querystring' ) ?></td>
46
- </tr>
47
- <tr>
48
- <th>Forward Host Header:</th>
49
- <td><?php $this->render_zone_boolean_change( $details, 'forward_host' ) ?></td>
50
- </tr>
51
- </table>
52
 
53
- <p class="submit">
54
- <input type="button"
55
- class="w3tc_cdn_cloudfront_fsd_configure_distribution w3tc-button-save button-primary"
56
- value="<?php _e( 'Apply', 'w3-total-cache' ); ?>" />
57
- <input type="button"
58
- class="w3tc_cdn_cloudfront_fsd_configure_distribution_skip w3tc-button-save button"
59
- value="<?php _e( 'Don\'t reconfigure, I know what I\'m doing', 'w3-total-cache' ); ?>" />
60
- </p>
61
- <?php Util_Ui::postbox_footer(); ?>
62
- </div>
 
 
63
  </form>
5
  die();
6
  ?>
7
  <form class="w3tc_popup_form" method="post">
8
+ <?php
9
  Util_Ui::hidden( '', 'access_key', $details['access_key'] );
10
  Util_Ui::hidden( '', 'secret_key', $details['secret_key'] );
11
  Util_Ui::hidden( '', 'distribution_id', $details['distribution_id'] );
12
  Util_Ui::hidden( '', 'distribution_comment', $details['distribution_comment'] );
13
  ?>
14
 
15
+ <div class="metabox-holder">
16
+ <?php Util_Ui::postbox_header( __( 'Configure distribution', 'w3-total-cache' ) ); ?>
17
+ <table class="form-table">
18
+ <tr>
19
+ <th>Distribution:</th>
20
+ <td><?php echo $details['distribution_comment'] ?></td>
21
+ </tr>
22
+ <tr>
23
+ <th>Origin:</th>
24
+ <td><?php $this->render_zone_ip_change( $details, 'origin' ) ?><br />
25
+ <span class="description">
26
+ Create an apex <acronym title="Domain Name System">DNS</acronym> record pointing to your WordPress host <acronym title="Internet Protocol">IP</acronym>.
27
+ CloudFront will use this host to mirror your site.
28
 
29
+ Tip: If your real domain name is domain.com, then the host
30
+ for the apex record should be origin.domain.com with the host
31
+ <acronym title="Internet Protocol">IP</acronym> of domain.com, e.g.:
32
+ </span>
33
+ </td>
34
+ </tr>
35
+ <tr>
36
+ <th>Forward Cookies:</th>
37
+ <td><?php $this->render_zone_boolean_change( $details, 'forward_cookies' ) ?></td>
38
+ </tr>
39
+ <tr>
40
+ <th>Forward Query String:</th>
41
+ <td><?php $this->render_zone_boolean_change( $details, 'forward_querystring' ) ?></td>
42
+ </tr>
43
+ <tr>
44
+ <th>Forward Host Header:</th>
45
+ <td><?php $this->render_zone_boolean_change( $details, 'forward_host' ) ?></td>
46
+ </tr>
47
+ </table>
 
 
 
 
48
 
49
+ <p class="submit">
50
+ <input type="button"
51
+ class="w3tc_cdn_cloudfront_fsd_configure_distribution w3tc-button-save button-primary"
52
+ value="<?php _e( 'Apply', 'w3-total-cache' ); ?>" />
53
+ <?php if ( !empty( $details['distribution_id'] ) ): ?>
54
+ <input type="button"
55
+ class="w3tc_cdn_cloudfront_fsd_configure_distribution_skip w3tc-button-save button"
56
+ value="<?php _e( 'Don\'t reconfigure, I know what I\'m doing', 'w3-total-cache' ); ?>" />
57
+ <?php endif ?>
58
+ </p>
59
+ <?php Util_Ui::postbox_footer(); ?>
60
+ </div>
61
  </form>
Config.php CHANGED
@@ -7,16 +7,16 @@ namespace W3TC;
7
  */
8
  class Config {
9
  /*
10
- * blog id of loaded config
11
- * @var integer
12
- */
13
  private $_blog_id;
14
  private $_is_master;
15
 
16
  /*
17
- * Is this preview config
18
- * @var boolean
19
- */
20
  private $_preview;
21
 
22
  private $_md5;
@@ -71,9 +71,9 @@ class Config {
71
 
72
 
73
  /*
74
- * Returns config filename
75
- * Stored in this class to limit class loading
76
- */
77
  static public function util_config_filename( $blog_id, $preview ) {
78
  $postfix = ( $preview ? '-preview' : '' ) . '.php';
79
 
@@ -86,10 +86,10 @@ class Config {
86
 
87
 
88
  /*
89
- * Returns config filename
90
- * Stored in this class to limit class loading
91
- * v = 0.9.5 - 0.9.5.1
92
- */
93
  static public function util_config_filename_legacy_v2( $blog_id, $preview ) {
94
  $postfix = ( $preview ? '-preview' : '' ) . '.json';
95
 
7
  */
8
  class Config {
9
  /*
10
+ * blog id of loaded config
11
+ * @var integer
12
+ */
13
  private $_blog_id;
14
  private $_is_master;
15
 
16
  /*
17
+ * Is this preview config
18
+ * @var boolean
19
+ */
20
  private $_preview;
21
 
22
  private $_md5;
71
 
72
 
73
  /*
74
+ * Returns config filename
75
+ * Stored in this class to limit class loading
76
+ */
77
  static public function util_config_filename( $blog_id, $preview ) {
78
  $postfix = ( $preview ? '-preview' : '' ) . '.php';
79
 
86
 
87
 
88
  /*
89
+ * Returns config filename
90
+ * Stored in this class to limit class loading
91
+ * v = 0.9.5 - 0.9.5.1
92
+ */
93
  static public function util_config_filename_legacy_v2( $blog_id, $preview ) {
94
  $postfix = ( $preview ? '-preview' : '' ) . '.json';
95
 
ConfigKeys.php CHANGED
@@ -2260,10 +2260,6 @@ $keys = array(
2260
  'type' => 'integer',
2261
  'default' => 300
2262
  ),
2263
- 'timelimit.cdn_container_create' => array(
2264
- 'type' => 'integer',
2265
- 'default' => 300
2266
- ),
2267
  'timelimit.domain_rename' => array(
2268
  'type' => 'integer',
2269
  'default' => 120
2260
  'type' => 'integer',
2261
  'default' => 300
2262
  ),
 
 
 
 
2263
  'timelimit.domain_rename' => array(
2264
  'type' => 'integer',
2265
  'default' => 120
DbCache_Wpdb.php CHANGED
@@ -225,6 +225,13 @@ class DbCache_Wpdb extends DbCache_WpdbBase {
225
  return $this->active_processor->set_charset( $dbh, $charset, $collate );
226
  }
227
 
 
 
 
 
 
 
 
228
  /**
229
  * Overriten logic of wp_db by processor.
230
  */
@@ -327,6 +334,13 @@ class DbCache_Wpdb extends DbCache_WpdbBase {
327
  return parent::set_charset( $dbh, $charset, $collate );
328
  }
329
 
 
 
 
 
 
 
 
330
  /**
331
  * Default implementation, calls wp_db apropriate method
332
  */
225
  return $this->active_processor->set_charset( $dbh, $charset, $collate );
226
  }
227
 
228
+ /**
229
+ * Overriten logic of wp_db by processor.
230
+ */
231
+ public function set_sql_mode( $modes = array() ) {
232
+ return $this->active_processor->set_sql_mode( $modes );
233
+ }
234
+
235
  /**
236
  * Overriten logic of wp_db by processor.
237
  */
334
  return parent::set_charset( $dbh, $charset, $collate );
335
  }
336
 
337
+ /**
338
+ * Default implementation, calls wp_db apropriate method
339
+ */
340
+ public function default_set_sql_mode( $modes = array() ) {
341
+ return parent::set_sql_mode( $modes );
342
+ }
343
+
344
  /**
345
  * Default implementation, calls wp_db apropriate method
346
  */
DbCache_WpdbInjection.php CHANGED
@@ -110,6 +110,14 @@ class DbCache_WpdbInjection {
110
  return $this->wpdb_mixin->default_set_charset( $dbh, $charset, $collate );
111
  }
112
 
 
 
 
 
 
 
 
 
113
  /**
114
  * Placeholder for apropriate wp_db method replacement.
115
  * By default calls wp_db implementation
110
  return $this->wpdb_mixin->default_set_charset( $dbh, $charset, $collate );
111
  }
112
 
113
+ /**
114
+ * Placeholder for apropriate wp_db method replacement.
115
+ * By default calls wp_db implementation
116
+ */
117
+ function set_sql_mode( $modes = array() ) {
118
+ return $this->wpdb_mixin->default_set_sql_mode( $modes );
119
+ }
120
+
121
  /**
122
  * Placeholder for apropriate wp_db method replacement.
123
  * By default calls wp_db implementation
Enterprise_CacheFlush_MakeSnsEvent.php CHANGED
@@ -215,9 +215,12 @@ class Enterprise_CacheFlush_MakeSnsEvent extends Enterprise_SnsBase {
215
  }
216
  $this->_log( 'Backtrace ', $backtrace_optimized );
217
 
218
- $r = $api->publish( $this->_topic_arn, $v );
219
- if ( $r->status != 200 ) {
220
- $this->_log( "Error: {$r->body->Error->Message}" );
 
 
 
221
  return false;
222
  }
223
  } catch ( \Exception $e ) {
215
  }
216
  $this->_log( 'Backtrace ', $backtrace_optimized );
217
 
218
+ $r = $api->publish( array(
219
+ 'Message' => $v,
220
+ 'TopicArn' => $this->_topic_arn ) );
221
+ if ( $r['@metadata']['statusCode'] != 200 ) {
222
+ $this->_log( "Error" );
223
+ $this->_log( json_encode($r) );
224
  return false;
225
  }
226
  } catch ( \Exception $e ) {
Enterprise_Dbcache_WpdbInjection_Cluster.php CHANGED
@@ -447,6 +447,7 @@ class Enterprise_Dbcache_WpdbInjection_Cluster extends DbCache_WpdbInjection {
447
  $dbh = $this->_connections[$dbhname]['dbh'];
448
  $this->wpdb_mixin->dbh = $dbh; // needed by $wpdb->_real_escape()
449
  $this->set_charset( $dbh, $this->charset, $this->collate );
 
450
 
451
  return $dbh;
452
  }
447
  $dbh = $this->_connections[$dbhname]['dbh'];
448
  $this->wpdb_mixin->dbh = $dbh; // needed by $wpdb->_real_escape()
449
  $this->set_charset( $dbh, $this->charset, $this->collate );
450
+ $this->set_sql_mode();
451
 
452
  return $dbh;
453
  }
Enterprise_SnsBase.php CHANGED
@@ -1,14 +1,14 @@
1
  <?php
2
  namespace W3TC;
3
 
 
 
 
4
 
5
 
6
- /**
7
- * Purge using AmazonSNS object
8
- */
9
 
10
  /**
11
- * class Sns
12
  */
13
  class Enterprise_SnsBase {
14
  /**
@@ -39,11 +39,15 @@ class Enterprise_SnsBase {
39
  if ( $this->_api_secret == '' )
40
  throw new \Exception( 'API Secret is not configured' );
41
 
42
- require_once W3TC_LIB_DIR . '/SNS/sdk.class.php';
43
- $this->_api = new \AmazonSNS( $this->_api_key, $this->_api_secret );
44
- if ( $this->_region != '' ) {
45
- $this->_api->set_region( $this->_region );
46
- }
 
 
 
 
47
  }
48
 
49
  return $this->_api;
1
  <?php
2
  namespace W3TC;
3
 
4
+ if ( !defined( 'W3TC_SKIPLIB_AWS' ) ) {
5
+ require_once W3TC_LIB_DIR . '/Aws/aws-autoloader.php';
6
+ }
7
 
8
 
 
 
 
9
 
10
  /**
11
+ * Base class for Sns communication
12
  */
13
  class Enterprise_SnsBase {
14
  /**
39
  if ( $this->_api_secret == '' )
40
  throw new \Exception( 'API Secret is not configured' );
41
 
42
+
43
+ $credentials = new \Aws\Credentials\Credentials(
44
+ $this->_api_key, $this->_api_secret );
45
+
46
+ $this->_api = new \Aws\Sns\SnsClient( array(
47
+ 'credentials' => $credentials,
48
+ 'region' => $this->_region,
49
+ 'version' => '2010-03-31'
50
+ ) );
51
  }
52
 
53
  return $this->_api;
Enterprise_SnsServer.php CHANGED
@@ -4,13 +4,6 @@ namespace W3TC;
4
  /**
5
  * Purge using AmazonSNS object
6
  */
7
-
8
- require_once W3TC_LIB_DIR . '/SNS/services/MessageValidator/Message.php';
9
- require_once W3TC_LIB_DIR . '/SNS/services/MessageValidator/MessageValidator.php';
10
-
11
- /**
12
- * class Sns
13
- */
14
  class Enterprise_SnsServer extends Enterprise_SnsBase {
15
 
16
  /**
@@ -18,24 +11,24 @@ class Enterprise_SnsServer extends Enterprise_SnsBase {
18
  *
19
  * @throws Exception
20
  */
21
- function process_message() {
22
  $this->_log( 'Received message' );
23
 
24
  try {
25
- $message = \Message::fromRawPostData();
26
- $validator = new \MessageValidator();
27
  $error = '';
28
  if ( $validator->isValid( $message ) ) {
29
  $topic_arn = $this->_config->get_string( 'cluster.messagebus.sns.topic_arn' );
30
 
31
- if ( empty( $topic_arn ) || $topic_arn != $message->get( 'TopicArn' ) )
32
- throw new \Exception ( 'Not my Topic. Request came from ' .
33
- $message->get( 'TopicArn' ) );
34
 
35
- if ( $message->get( 'Type' ) == 'SubscriptionConfirmation' )
36
  $this->_subscription_confirmation( $message );
37
- elseif ( $message->get( 'Type' ) == 'Notification' )
38
- $this->_notification( $message->get( 'Message' ) );
39
  } else {
40
  $this->_log( 'Error processing message it was not valid.' );
41
  }
@@ -55,10 +48,12 @@ class Enterprise_SnsServer extends Enterprise_SnsBase {
55
  $this->_log( 'Issuing confirm_subscription' );
56
  $topic_arn = $this->_config->get_string( 'cluster.messagebus.sns.topic_arn' );
57
 
58
- $response = $this->_get_api()->confirm_subscription(
59
- $topic_arn, $message->get( 'Token' ) );
 
 
60
  $this->_log( 'Subscription confirmed: ' .
61
- ( $response->isOK() ? 'OK' : 'Error' ) );
62
  }
63
 
64
  /**
4
  /**
5
  * Purge using AmazonSNS object
6
  */
 
 
 
 
 
 
 
7
  class Enterprise_SnsServer extends Enterprise_SnsBase {
8
 
9
  /**
11
  *
12
  * @throws Exception
13
  */
14
+ function process_message( $message ) {
15
  $this->_log( 'Received message' );
16
 
17
  try {
18
+ $message = new \Aws\Sns\Message( $message );
19
+ $validator = new \Aws\Sns\MessageValidator();
20
  $error = '';
21
  if ( $validator->isValid( $message ) ) {
22
  $topic_arn = $this->_config->get_string( 'cluster.messagebus.sns.topic_arn' );
23
 
24
+ if ( empty( $topic_arn ) || $topic_arn != $message['TopicArn'] )
25
+ throw new \Exception( 'Not my Topic. Request came from ' .
26
+ $message['TopicArn'] );
27
 
28
+ if ( $message['Type'] == 'SubscriptionConfirmation' )
29
  $this->_subscription_confirmation( $message );
30
+ elseif ( $message['Type'] == 'Notification' )
31
+ $this->_notification( $message['Message'] );
32
  } else {
33
  $this->_log( 'Error processing message it was not valid.' );
34
  }
48
  $this->_log( 'Issuing confirm_subscription' );
49
  $topic_arn = $this->_config->get_string( 'cluster.messagebus.sns.topic_arn' );
50
 
51
+ $response = $this->_get_api()->confirmSubscription( array(
52
+ 'Token' => $message['Token'],
53
+ 'TopicArn' => $topic_arn
54
+ ) );
55
  $this->_log( 'Subscription confirmed: ' .
56
+ ( $response['@metadata']['statusCode'] == 200 ? 'OK' : 'Error' ) );
57
  }
58
 
59
  /**
Generic_Plugin.php CHANGED
@@ -36,6 +36,10 @@ class Generic_Plugin {
36
  $this,
37
  'admin_bar_menu'
38
  ), 150 );
 
 
 
 
39
 
40
  if ( isset( $_REQUEST['w3tc_theme'] ) && isset( $_SERVER['HTTP_USER_AGENT'] ) &&
41
  stristr( $_SERVER['HTTP_USER_AGENT'], W3TC_POWERED_BY ) !== false ) {
@@ -233,6 +237,27 @@ class Generic_Plugin {
233
  }
234
  }
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  /**
237
  * Admin bar menu
238
  *
@@ -259,12 +284,10 @@ class Generic_Plugin {
259
 
260
  $menu_items['00010.generic'] = array(
261
  'id' => 'w3tc',
262
- 'title' =>
263
- '<img src="' .
264
- plugins_url( 'pub/img/w3tc-sprite-admin-bar.png', W3TC_FILE ) .
265
- '" style="vertical-align:middle; margin-right:5px; width: 29px; height: 29px" />' .
266
- __( 'Performance', 'w3-total-cache' ) .
267
- $menu_postfix,
268
  'href' => network_admin_url( 'admin.php?page=w3tc_dashboard' )
269
  );
270
 
@@ -366,13 +389,13 @@ class Generic_Plugin {
366
  '?action=w3tc_monitoring_score&' . md5( $_SERVER['REQUEST_URI'] );
367
 
368
  ?>
369
- <script type= "text/javascript">
370
- var w3tc_monitoring_score = document.createElement('script');
371
- w3tc_monitoring_score.type = 'text/javascript';
372
- w3tc_monitoring_score.src = '<?php echo $url ?>';
373
- document.getElementsByTagName('HEAD')[0].appendChild(w3tc_monitoring_score);
374
- </script>
375
- <?php
376
  }
377
 
378
  /**
@@ -501,25 +524,25 @@ class Generic_Plugin {
501
  if ( Util_Environment::is_preview_mode() )
502
  $buffer .= "\r\n<!-- W3 Total Cache used in preview mode -->";
503
 
504
- $strings = array();
505
 
506
- if ( $this->_config->get_string( 'common.support' ) == '' &&
507
- !$this->_config->get_boolean( 'common.tweeted' ) ) {
508
- $strings[] = 'Performance optimized by W3 Total Cache. Learn more: https://www.w3-edge.com/products/';
509
- $strings[] = '';
510
- }
511
 
512
- $strings = apply_filters( 'w3tc_footer_comment', $strings );
513
 
514
- if ( count( $strings ) ) {
515
- $strings[] = '';
516
- $strings[] = sprintf( "Served from: %s @ %s by W3 Total Cache",
517
- Util_Content::escape_comment( $host ), $date );
518
 
519
- $buffer .= "\r\n<!--\r\n" .
520
- Util_Content::escape_comment( implode( "\r\n", $strings ) ) .
521
- "\r\n-->";
522
- }
523
  }
524
 
525
  $buffer = Util_Bus::do_ob_callbacks(
@@ -635,12 +658,12 @@ class Generic_Plugin {
635
 
636
  function popup_script() {
637
  ?>
638
- <script type="text/javascript">
639
- function w3tc_popupadmin_bar(url) {
640
- return window.open(url, '', 'width=800,height=600,status=no,toolbar=no,menubar=no,scrollbars=yes');
641
- }
642
- </script>
643
- <?php
644
  }
645
 
646
  private function is_debugging() {
36
  $this,
37
  'admin_bar_menu'
38
  ), 150 );
39
+ add_action( 'admin_bar_init', array(
40
+ $this,
41
+ 'admin_bar_init'
42
+ ) );
43
 
44
  if ( isset( $_REQUEST['w3tc_theme'] ) && isset( $_SERVER['HTTP_USER_AGENT'] ) &&
45
  stristr( $_SERVER['HTTP_USER_AGENT'], W3TC_POWERED_BY ) !== false ) {
237
  }
238
  }
239
 
240
+ public function admin_bar_init() {
241
+ $font_base = plugins_url( 'pub/fonts/w3tc', W3TC_FILE );
242
+ $css = "
243
+ @font-face {
244
+ font-family: 'w3tc';
245
+ src: url('$font_base.eot');
246
+ src: url('$font_base.eot?#iefix') format('embedded-opentype'),
247
+ url('$font_base.woff') format('woff'),
248
+ url('$font_base.ttf') format('truetype'),
249
+ url('$font_base.svg#w3tc') format('svg');
250
+ font-weight: normal;
251
+ font-style: normal;
252
+ }
253
+ .w3tc-icon:before{
254
+ content:'\\0041'; top: 2px;
255
+ font-family: 'w3tc';
256
+ }";
257
+
258
+ wp_add_inline_style( 'admin-bar', $css);
259
+ }
260
+
261
  /**
262
  * Admin bar menu
263
  *
284
 
285
  $menu_items['00010.generic'] = array(
286
  'id' => 'w3tc',
287
+ 'title' => sprintf(
288
+ '<span class="w3tc-icon ab-icon"></span><span class="ab-label">%s</span>' .
289
+ $menu_postfix,
290
+ __( 'Performance', 'w3-total-cache' ) ),
 
 
291
  'href' => network_admin_url( 'admin.php?page=w3tc_dashboard' )
292
  );
293
 
389
  '?action=w3tc_monitoring_score&' . md5( $_SERVER['REQUEST_URI'] );
390
 
391
  ?>
392
+ <script type= "text/javascript">
393
+ var w3tc_monitoring_score = document.createElement('script');
394
+ w3tc_monitoring_score.type = 'text/javascript';
395
+ w3tc_monitoring_score.src = '<?php echo $url ?>';
396
+ document.getElementsByTagName('HEAD')[0].appendChild(w3tc_monitoring_score);
397
+ </script>
398
+ <?php
399
  }
400
 
401
  /**
524
  if ( Util_Environment::is_preview_mode() )
525
  $buffer .= "\r\n<!-- W3 Total Cache used in preview mode -->";
526
 
527
+ $strings = array();
528
 
529
+ if ( $this->_config->get_string( 'common.support' ) == '' &&
530
+ !$this->_config->get_boolean( 'common.tweeted' ) ) {
531
+ $strings[] = 'Performance optimized by W3 Total Cache. Learn more: https://www.w3-edge.com/products/';
532
+ $strings[] = '';
533
+ }
534
 
535
+ $strings = apply_filters( 'w3tc_footer_comment', $strings );
536
 
537
+ if ( count( $strings ) ) {
538
+ $strings[] = '';
539
+ $strings[] = sprintf( "Served from: %s @ %s by W3 Total Cache",
540
+ Util_Content::escape_comment( $host ), $date );
541
 
542
+ $buffer .= "\r\n<!--\r\n" .
543
+ Util_Content::escape_comment( implode( "\r\n", $strings ) ) .
544
+ "\r\n-->";
545
+ }
546
  }
547
 
548
  $buffer = Util_Bus::do_ob_callbacks(
658
 
659
  function popup_script() {
660
  ?>
661
+ <script type="text/javascript">
662
+ function w3tc_popupadmin_bar(url) {
663
+ return window.open(url, '', 'width=800,height=600,status=no,toolbar=no,menubar=no,scrollbars=yes');
664
+ }
665
+ </script>
666
+ <?php
667
  }
668
 
669
  private function is_debugging() {
Generic_Plugin_Admin.php CHANGED
@@ -296,36 +296,10 @@ class Generic_Plugin_Admin {
296
 
297
  ?>
298
  <style type="text/css" media="screen">
299
- #toplevel_page_w3tc_dashboard .wp-menu-image {
300
- background: url(<?php echo plugins_url( 'pub/img/w3tc-sprite.png', W3TC_FILE )?>) no-repeat 0 -32px !important;
301
- }
302
- #toplevel_page_w3tc_dashboard:hover .wp-menu-image,
303
- #toplevel_page_w3tc_dashboard.wp-has-current-submenu .wp-menu-image {
304
- background-position:0 0 !important;
305
- }
306
- #icon-edit.icon32-posts-casestudy {
307
- background: url(<?php echo plugins_url( 'pub/img/w3tc-sprite.png', W3TC_FILE ) ?>) no-repeat;
308
- }
309
- /**
310
- * HiDPI Displays
311
- */
312
- @media print,
313
- (-o-min-device-pixel-ratio: 5/4),
314
- (-webkit-min-device-pixel-ratio: 1.25),
315
- (min-resolution: 120dpi) {
316
-
317
- #toplevel_page_w3tc_dashboard .wp-menu-image {
318
- background-image: url(<?php echo plugins_url( 'pub/img/w3tc-sprite-retina.png', W3TC_FILE )?>) !important;
319
- background-size: 30px 64px !important;
320
- }
321
- #toplevel_page_w3tc_dashboard:hover .wp-menu-image,
322
- #toplevel_page_w3tc_dashboard.wp-has-current-submenu .wp-menu-image {
323
- background-position:0 0 !important;
324
- }
325
- #icon-edit.icon32-posts-casestudy {
326
- background-image: url(<?php echo plugins_url( 'pub/img/w3tc-sprite-retina.png', W3TC_FILE ) ?>) !important;
327
- background-size: 30px 64px !important;
328
- }
329
  }
330
  </style>
331
  <?php
296
 
297
  ?>
298
  <style type="text/css" media="screen">
299
+ .toplevel_page_w3tc_dashboard .wp-menu-image:before{
300
+ content:'\0041';
301
+ top: 2px;
302
+ font-family: 'w3tc';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
  </style>
305
  <?php
Generic_WidgetServices_View.php CHANGED
@@ -10,26 +10,26 @@ if ( !defined( 'W3TC' ) )
10
 
11
  <ul>
12
  <?php
13
- for ( $n = 0; $n < count( $items ); $n++ ): ?>
14
- <li>
15
- <div class="w3tc_generic_widgetservice_radio_outer">
16
- <input id="service<?php echo $n ?>"
17
- type="radio"
18
- class="w3tc_generic_widgetservice_radio w3tc-ignore-change"
19
- name="service_item"
20
- value="<?php echo $n ?>"
21
- />
22
- </div>
23
- <label for="service<?php echo $n ?>" class="w3tc_generic_widgetservice_label">
24
- <?php echo htmlspecialchars( $items[$n]['name'] ) ?>
25
- </label>
26
- </li>
27
  <?php endfor ?>
28
  </ul>
29
  <div id="buy-w3-service-area"></div>
30
  <p>
31
- <input id="buy-w3-service" name="buy-w3-service" type="submit"
32
- class="button button-primary button-large"
33
- value="<?php _e( 'Buy now', 'w3-total-cache' ) ?>" />
34
  </p>
35
  </form>
10
 
11
  <ul>
12
  <?php
13
+ for ( $n = 0; $n < ( is_array( $items ) ? count( $items ) : 0 ); $n++ ): ?>
14
+ <li>
15
+ <div class="w3tc_generic_widgetservice_radio_outer">
16
+ <input id="service<?php echo $n ?>"
17
+ type="radio"
18
+ class="w3tc_generic_widgetservice_radio w3tc-ignore-change"
19
+ name="service_item"
20
+ value="<?php echo $n ?>"
21
+ />
22
+ </div>
23
+ <label for="service<?php echo $n ?>" class="w3tc_generic_widgetservice_label">
24
+ <?php echo htmlspecialchars( $items[$n]['name'] ) ?>
25
+ </label>
26
+ </li>
27
  <?php endfor ?>
28
  </ul>
29
  <div id="buy-w3-service-area"></div>
30
  <p>
31
+ <input id="buy-w3-service" name="buy-w3-service" type="submit"
32
+ class="button button-primary button-large"
33
+ value="<?php _e( 'Buy now', 'w3-total-cache' ) ?>" />
34
  </p>
35
  </form>
PgCache_ContentGrabber.php CHANGED
@@ -121,7 +121,7 @@ class PgCache_ContentGrabber {
121
  $this->_config = Dispatcher::config();
122
  $this->_debug = $this->_config->get_boolean( 'pgcache.debug' );
123
 
124
- $request_host = Util_Environment::host();
125
  $this->_request_host = $request_host;
126
 
127
  $this->_request_uri = $_SERVER['REQUEST_URI'];
@@ -1318,6 +1318,8 @@ class PgCache_ContentGrabber {
1318
  if ( $request_url ) {
1319
  $parts = parse_url( $request_url );
1320
  $key = $parts['host'] .
 
 
1321
  ( isset( $parts['path'] ) ? $parts['path'] : '' ) .
1322
  ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );
1323
  } else {
121
  $this->_config = Dispatcher::config();
122
  $this->_debug = $this->_config->get_boolean( 'pgcache.debug' );
123
 
124
+ $request_host = Util_Environment::host_port();
125
  $this->_request_host = $request_host;
126
 
127
  $this->_request_uri = $_SERVER['REQUEST_URI'];
1318
  if ( $request_url ) {
1319
  $parts = parse_url( $request_url );
1320
  $key = $parts['host'] .
1321
+
1322
+ ( isset( $parts['port'] ) ? ':' . $parts['port'] : '' ) .
1323
  ( isset( $parts['path'] ) ? $parts['path'] : '' ) .
1324
  ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );
1325
  } else {
PgCache_Flush.php CHANGED
@@ -200,27 +200,31 @@ class PgCache_Flush extends PgCache_ContentGrabber {
200
  * @param unknown $cache
201
  * @param unknown $mobile_groups
202
  * @param unknown $referrer_groups
 
203
  * @param unknown $encryptions
204
  * @param unknown $compressions
205
  */
206
  function _flush_url( $url, $cache, $mobile_groups, $referrer_groups,
207
- $encryptions, $compressions ) {
208
  foreach ( $mobile_groups as $mobile_group ) {
209
  foreach ( $referrer_groups as $referrer_group ) {
210
- foreach ( $encryptions as $encryption ) {
211
- foreach ( $compressions as $compression ) {
212
- $page_keys = array();
213
- $page_keys[] = $this->_get_page_key( array(
214
- 'useragent' => $mobile_group,
215
- 'referrer' => $referrer_group,
216
- 'encryption' => $encryption,
217
- 'compression' => $compression ),
218
- $url );
219
- $page_keys = apply_filters(
220
- 'w3tc_pagecache_flush_url_keys', $page_keys );
221
-
222
- foreach ( $page_keys as $page_key )
223
- $cache->delete( $page_key );
 
 
 
224
  }
225
  }
226
  }
@@ -233,7 +237,7 @@ class PgCache_Flush extends PgCache_ContentGrabber {
233
  * @param unknown $url
234
  */
235
  function flush_url( $url ) {
236
- static $cache, $mobile_groups, $referrer_groups, $encryptions;
237
  static $compressions;
238
 
239
  if ( !isset( $cache ) )
@@ -242,12 +246,14 @@ class PgCache_Flush extends PgCache_ContentGrabber {
242
  $mobile_groups = $this->_get_mobile_groups();
243
  if ( !isset( $referrer_groups ) )
244
  $referrer_groups = $this->_get_referrer_groups();
 
 
245
  if ( !isset( $encryptions ) )
246
  $encryptions = $this->_get_encryptions();
247
  if ( !isset( $compressions ) )
248
  $compressions = $this->_get_compressions();
249
  $this->_flush_url( $url, $cache, $mobile_groups, $referrer_groups,
250
- $encryptions, $compressions );
251
  }
252
 
253
  /**
@@ -290,12 +296,13 @@ class PgCache_Flush extends PgCache_ContentGrabber {
290
  $cache = $this->_get_cache();
291
  $mobile_groups = $this->_get_mobile_groups();
292
  $referrer_groups = $this->_get_referrer_groups();
 
293
  $encryptions = $this->_get_encryptions();
294
  $compressions = $this->_get_compressions();
295
 
296
  foreach ( $this->queued_urls as $url => $flag ) {
297
  $this->_flush_url( $url, $cache, $mobile_groups,
298
- $referrer_groups, $encryptions, $compressions );
299
  }
300
 
301
  // Purge sitemaps if a sitemap option has a regex
@@ -343,6 +350,19 @@ class PgCache_Flush extends PgCache_ContentGrabber {
343
  return $referrer_groups;
344
  }
345
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  /**
347
  * Returns array of encryptions
348
  *
200
  * @param unknown $cache
201
  * @param unknown $mobile_groups
202
  * @param unknown $referrer_groups
203
+ * @param unknown $cookies
204
  * @param unknown $encryptions
205
  * @param unknown $compressions
206
  */
207
  function _flush_url( $url, $cache, $mobile_groups, $referrer_groups,
208
+ $cookies, $encryptions, $compressions ) {
209
  foreach ( $mobile_groups as $mobile_group ) {
210
  foreach ( $referrer_groups as $referrer_group ) {
211
+ foreach ( $cookies as $cookie ) {
212
+ foreach ( $encryptions as $encryption ) {
213
+ foreach ( $compressions as $compression ) {
214
+ $page_keys = array();
215
+ $page_keys[] = $this->_get_page_key( array(
216
+ 'useragent' => $mobile_group,
217
+ 'referrer' => $referrer_group,
218
+ 'cookie' => $cookie,
219
+ 'encryption' => $encryption,
220
+ 'compression' => $compression ),
221
+ $url );
222
+ $page_keys = apply_filters(
223
+ 'w3tc_pagecache_flush_url_keys', $page_keys );
224
+
225
+ foreach ( $page_keys as $page_key )
226
+ $cache->delete( $page_key );
227
+ }
228
  }
229
  }
230
  }
237
  * @param unknown $url
238
  */
239
  function flush_url( $url ) {
240
+ static $cache, $mobile_groups, $referrer_groups, $cookies, $encryptions;
241
  static $compressions;
242
 
243
  if ( !isset( $cache ) )
246
  $mobile_groups = $this->_get_mobile_groups();
247
  if ( !isset( $referrer_groups ) )
248
  $referrer_groups = $this->_get_referrer_groups();
249
+ if ( !isset( $cookies ) )
250
+ $cookies = $this->_get_cookies();
251
  if ( !isset( $encryptions ) )
252
  $encryptions = $this->_get_encryptions();
253
  if ( !isset( $compressions ) )
254
  $compressions = $this->_get_compressions();
255
  $this->_flush_url( $url, $cache, $mobile_groups, $referrer_groups,
256
+ $cookies, $encryptions, $compressions );
257
  }
258
 
259
  /**
296
  $cache = $this->_get_cache();
297
  $mobile_groups = $this->_get_mobile_groups();
298
  $referrer_groups = $this->_get_referrer_groups();
299
+ $cookies = $this->_get_cookies();
300
  $encryptions = $this->_get_encryptions();
301
  $compressions = $this->_get_compressions();
302
 
303
  foreach ( $this->queued_urls as $url => $flag ) {
304
  $this->_flush_url( $url, $cache, $mobile_groups,
305
+ $referrer_groups, $cookies, $encryptions, $compressions );
306
  }
307
 
308
  // Purge sitemaps if a sitemap option has a regex
350
  return $referrer_groups;
351
  }
352
 
353
+ /**
354
+ * Returns array of cookies
355
+ *
356
+ * @return array
357
+ */
358
+ function _get_cookies() {
359
+ $cookies = array( '' );
360
+
361
+ $cookies = array_merge( $cookies, array_keys( $this->_config->get_array( 'pgcache.cookiegroups.groups' ) ) );
362
+
363
+ return $cookies;
364
+ }
365
+
366
  /**
367
  * Returns array of encryptions
368
  *
Root_AdminMenu.php CHANGED
@@ -117,7 +117,7 @@ class Root_AdminMenu {
117
  $pages = $this->generate_menu_array();
118
 
119
  uasort( $pages, function($a, $b) {
120
- return ($a['order'] - $b['order']);
121
  }
122
  );
123
 
@@ -125,7 +125,7 @@ class Root_AdminMenu {
125
  __( 'Performance', 'w3-total-cache' ),
126
  apply_filters( 'w3tc_capability_menu_w3tc_dashboard',
127
  $base_capability ),
128
- 'w3tc_dashboard', '', 'div' );
129
 
130
  $submenu_pages = array();
131
  $is_master = ( is_network_admin() || !Util_Environment::is_wpmu() );
@@ -167,8 +167,8 @@ class Root_AdminMenu {
167
  $this->_page = 'w3tc_dashboard';
168
 
169
  /*
170
- * Hidden pages
171
- */
172
  if ( isset( $_REQUEST['w3tc_dbcluster_config'] ) ) {
173
  $options_dbcache = new DbCache_Page();
174
  $options_dbcache->dbcluster_config();
117
  $pages = $this->generate_menu_array();
118
 
119
  uasort( $pages, function($a, $b) {
120
+ return ($a['order'] - $b['order']);
121
  }
122
  );
123
 
125
  __( 'Performance', 'w3-total-cache' ),
126
  apply_filters( 'w3tc_capability_menu_w3tc_dashboard',
127
  $base_capability ),
128
+ 'w3tc_dashboard', '', 'none' );
129
 
130
  $submenu_pages = array();
131
  $is_master = ( is_network_admin() || !Util_Environment::is_wpmu() );
167
  $this->_page = 'w3tc_dashboard';
168
 
169
  /*
170
+ * Hidden pages
171
+ */
172
  if ( isset( $_REQUEST['w3tc_dbcluster_config'] ) ) {
173
  $options_dbcache = new DbCache_Page();
174
  $options_dbcache->dbcluster_config();
Support_Page.php CHANGED
@@ -11,22 +11,6 @@ class Support_Page {
11
  $url = substr( $url, 7 );
12
  elseif ( substr( $url, 0, 8 ) == 'https://' )
13
  $url = substr( $url, 8 );
14
- // aw3tc-options script is already queued so attach to it
15
- // just to make vars printed (while it's not related by semantics)
16
- wp_localize_script( 'w3tc-options',
17
- 'w3tc_support_postprocess', urlencode( urlencode(
18
- Util_Ui::admin_url(
19
- wp_nonce_url( 'admin.php', 'w3tc' ) . '&page=w3tc_support&done'
20
- ) ) ) );
21
- wp_localize_script( 'w3tc-options',
22
- 'w3tc_support_home_url', $url );
23
- wp_localize_script( 'w3tc-options',
24
- 'w3tc_support_email', get_bloginfo( 'admin_email' ) );
25
- $u = wp_get_current_user();
26
- wp_localize_script( 'w3tc-options',
27
- 'w3tc_support_first_name', $u->first_name );
28
- wp_localize_script( 'w3tc-options',
29
- 'w3tc_support_last_name', $u->last_name );
30
 
31
  // values from widget
32
  $w3tc_support_form_hash = 'm5pom8z0qy59rm';
@@ -49,12 +33,25 @@ class Support_Page {
49
  }
50
  }
51
 
52
- wp_localize_script( 'w3tc-options', 'w3tc_support_form_hash',
53
- $w3tc_support_form_hash );
54
- wp_localize_script( 'w3tc-options', 'w3tc_support_field_name',
55
- $w3tc_support_field_name );
56
- wp_localize_script( 'w3tc-options', 'w3tc_support_field_value',
57
- $w3tc_support_field_value );
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  }
59
  /**
60
  * Support tab
11
  $url = substr( $url, 7 );
12
  elseif ( substr( $url, 0, 8 ) == 'https://' )
13
  $url = substr( $url, 8 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  // values from widget
16
  $w3tc_support_form_hash = 'm5pom8z0qy59rm';
33
  }
34
  }
35
 
36
+ $u = wp_get_current_user();
37
+
38
+ // w3tc-options script is already queued so attach to it
39
+ // just to make vars printed (while it's not related by semantics)
40
+ wp_localize_script( 'w3tc-options', 'w3tc_support_data',
41
+ array(
42
+ 'home_url' => $url,
43
+ 'email' => get_bloginfo( 'admin_email' ),
44
+ 'first_name' => $u->first_name,
45
+ 'last_name' => $u->last_name,
46
+ 'form_hash' => $w3tc_support_form_hash,
47
+ 'field_name' => $w3tc_support_field_name,
48
+ 'field_value' => $w3tc_support_field_value,
49
+ 'postprocess' => urlencode( urlencode(
50
+ Util_Ui::admin_url(
51
+ wp_nonce_url( 'admin.php', 'w3tc' ) . '&page=w3tc_support&done'
52
+ ) ) )
53
+ )
54
+ );
55
  }
56
  /**
57
  * Support tab
Support_Page_View_PageContent.php CHANGED
@@ -7,25 +7,25 @@
7
  var s = d.createElement(t);
8
  var options = {
9
  'userName':'w3edge',
10
- 'formHash': w3tc_support_form_hash,
11
  'autoResize':true,
12
  'height':'1145',
13
  'async':true,
14
  'host':'wufoo.com',
15
  'header':'show',
16
  'defaultValues':
17
- 'field221=' + encodeURI(w3tc_support_postprocess) +
18
- '&field6=' + encodeURI(w3tc_support_first_name) +
19
- '&field7=' + encodeURI(w3tc_support_last_name) +
20
- '&field8=' + encodeURI(w3tc_support_home_url) +
21
- '&field9=' + encodeURI(w3tc_support_email),
22
  'ssl':true
23
  };
24
 
25
- if (w3tc_support_field_name.length > 0)
26
- options.defaultValues += '&' +
27
- encodeURI(w3tc_support_field_name) + '=' +
28
- encodeURI(w3tc_support_field_value);
29
 
30
 
31
  s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'www.wufoo.com/scripts/embed/form.js';
7
  var s = d.createElement(t);
8
  var options = {
9
  'userName':'w3edge',
10
+ 'formHash': w3tc_support_data.form_hash,
11
  'autoResize':true,
12
  'height':'1145',
13
  'async':true,
14
  'host':'wufoo.com',
15
  'header':'show',
16
  'defaultValues':
17
+ 'field221=' + encodeURI(w3tc_support_data.postprocess) +
18
+ '&field6=' + encodeURI(w3tc_support_data.first_name) +
19
+ '&field7=' + encodeURI(w3tc_support_data.last_name) +
20
+ '&field8=' + encodeURI(w3tc_support_data.home_url) +
21
+ '&field9=' + encodeURI(w3tc_support_data.email),
22
  'ssl':true
23
  };
24
 
25
+ if (w3tc_support_data.field_name.length > 0)
26
+ options.defaultValues += '&' +
27
+ encodeURI(w3tc_support_data.field_name) + '=' +
28
+ encodeURI(w3tc_support_data.field_value);
29
 
30
 
31
  s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'www.wufoo.com/scripts/embed/form.js';
Util_Admin.php CHANGED
@@ -101,11 +101,11 @@ class Util_Admin {
101
  }
102
 
103
  /*
104
- * Checks if contains single message item
105
- *
106
- * @param $a array
107
- * @return boolean
108
- */
109
  static public function single_system_item( $a ) {
110
  if ( !is_array( $a ) || count( $a ) != 1 )
111
  return false;
@@ -594,8 +594,6 @@ class Util_Admin {
594
  /**
595
  * Update CloudFront CNAMEs
596
  */
597
- $update_cf_cnames = false;
598
-
599
  if ( $new_config->get_boolean( 'cdn.enabled' ) && in_array( $new_config->get_string( 'cdn.engine' ), array( 'cf', 'cf2' ) ) ) {
600
  if ( $new_config->get_string( 'cdn.engine' ) == 'cf' ) {
601
  $old_cnames = $old_config->get_array( 'cdn.cf.cname' );
@@ -604,10 +602,6 @@ class Util_Admin {
604
  $old_cnames = $old_config->get_array( 'cdn.cf2.cname' );
605
  $new_cnames = $new_config->get_array( 'cdn.cf2.cname' );
606
  }
607
-
608
- if ( count( $old_cnames ) != count( $new_cnames ) || count( array_diff( $old_cnames, $new_cnames ) ) ) {
609
- $update_cf_cnames = true;
610
- }
611
  }
612
 
613
  /**
@@ -634,14 +628,6 @@ class Util_Admin {
634
  Util_Admin::cdn_upload_browsercache( $current_config );
635
  }
636
 
637
- /**
638
- * Update CloudFront CNAMEs
639
- */
640
- if ( $update_cf_cnames ) {
641
- $error = null;
642
- $w3_plugin_cdn->update_cnames( $error );
643
- }
644
-
645
  return true;
646
  }
647
 
@@ -735,8 +721,8 @@ class Util_Admin {
735
  }
736
 
737
  /*
738
- * Returns current w3tc admin page
739
- */
740
  static public function get_current_page() {
741
  $page = Util_Request::get_string( 'page' );
742
 
101
  }
102
 
103
  /*
104
+ * Checks if contains single message item
105
+ *
106
+ * @param $a array
107
+ * @return boolean
108
+ */
109
  static public function single_system_item( $a ) {
110
  if ( !is_array( $a ) || count( $a ) != 1 )
111
  return false;
594
  /**
595
  * Update CloudFront CNAMEs
596
  */
 
 
597
  if ( $new_config->get_boolean( 'cdn.enabled' ) && in_array( $new_config->get_string( 'cdn.engine' ), array( 'cf', 'cf2' ) ) ) {
598
  if ( $new_config->get_string( 'cdn.engine' ) == 'cf' ) {
599
  $old_cnames = $old_config->get_array( 'cdn.cf.cname' );
602
  $old_cnames = $old_config->get_array( 'cdn.cf2.cname' );
603
  $new_cnames = $new_config->get_array( 'cdn.cf2.cname' );
604
  }
 
 
 
 
605
  }
606
 
607
  /**
628
  Util_Admin::cdn_upload_browsercache( $current_config );
629
  }
630
 
 
 
 
 
 
 
 
 
631
  return true;
632
  }
633
 
721
  }
722
 
723
  /*
724
+ * Returns current w3tc admin page
725
+ */
726
  static public function get_current_page() {
727
  $page = Util_Request::get_string( 'page' );
728
 
inc/mime/html.php CHANGED
@@ -6,7 +6,6 @@
6
  return array(
7
  'html|htm' => 'text/html',
8
  'rtf|rtx' => 'text/richtext',
9
- 'svg' => 'image/svg+xml',
10
  'txt' => 'text/plain',
11
  'xsd' => 'text/xsd',
12
  'xsl' => 'text/xsl',
6
  return array(
7
  'html|htm' => 'text/html',
8
  'rtf|rtx' => 'text/richtext',
 
9
  'txt' => 'text/plain',
10
  'xsd' => 'text/xsd',
11
  'xsl' => 'text/xsl',
inc/options/browsercache.php CHANGED
@@ -202,7 +202,7 @@ Util_Ui::config_item( array(
202
  <option value="cache_validation"<?php selected( $value, 'cache_validation' ); ?>><?php _e( 'cache with validation ("public, must-revalidate, proxy-revalidate")', 'w3-total-cache' ); ?></option>
203
  <option value="cache_maxage"<?php selected( $value, 'cache_maxage' ); disabled( $is_nginx && $cssjs_expires ); ?>><?php _e( 'cache with max-age and validation ("max-age=EXPIRES_SECONDS, public, must-revalidate, proxy-revalidate")', 'w3-total-cache' ); ?></option>
204
  <option value="cache_noproxy"<?php selected( $value, 'cache_noproxy' ); ?>><?php _e( 'cache without proxy ("private, must-revalidate")', 'w3-total-cache' ); ?></option>
205
- <option value="no_cache"<?php selected( $value, 'no_cache' ); ?>><?php _e( 'no-cache ("max-age=0, private, no-store, no-cache, must-revalidate")', 'w3-total-cache' ); ?></option>
206
  </select>
207
  <?php if ( $is_nginx && $cssjs_expires ) : ?>
208
  <br /><span class="description"><?php _e( 'The Expires header already sets the max-age.', 'w3-total-cache' ); ?></span>
202
  <option value="cache_validation"<?php selected( $value, 'cache_validation' ); ?>><?php _e( 'cache with validation ("public, must-revalidate, proxy-revalidate")', 'w3-total-cache' ); ?></option>
203
  <option value="cache_maxage"<?php selected( $value, 'cache_maxage' ); disabled( $is_nginx && $cssjs_expires ); ?>><?php _e( 'cache with max-age and validation ("max-age=EXPIRES_SECONDS, public, must-revalidate, proxy-revalidate")', 'w3-total-cache' ); ?></option>
204
  <option value="cache_noproxy"<?php selected( $value, 'cache_noproxy' ); ?>><?php _e( 'cache without proxy ("private, must-revalidate")', 'w3-total-cache' ); ?></option>
205
+ <option value="no_cache"<?php selected( $value, 'no_cache' ); ?>><?php _e( 'don\'t cache ("max-age=0, private, no-store, no-cache, must-revalidate")', 'w3-total-cache' ); ?></option>
206
  </select>
207
  <?php if ( $is_nginx && $cssjs_expires ) : ?>
208
  <br /><span class="description"><?php _e( 'The Expires header already sets the max-age.', 'w3-total-cache' ); ?></span>
inc/options/general.php CHANGED
@@ -13,39 +13,39 @@ echo sprintf( 'The plugin is currently %1$s If an option is disabled it means th
13
  ?>
14
  </p>
15
  <form id="w3tc_form" action="admin.php?page=<?php echo $this->_page; ?>" method="post">
16
- <div class="metabox-holder">
17
- <?php Util_Ui::postbox_header( __( 'General', 'w3-total-cache' ), '' ); ?>
18
- <table class="form-table">
19
- <tr>
20
- <th>Preview mode:</th>
21
- <td>
22
- <?php echo Util_Ui::nonce_field( 'w3tc' ); ?>
23
- <?php if ( $this->_config->is_preview() ): ?>
24
- <input type="submit" name="w3tc_config_preview_disable" class="button-primary" value="<?php _e( 'Disable', 'w3-total-cache' ); ?>" />
25
- <?php echo Util_Ui::button_link( __( 'Deploy', 'w3-total-cache' ), wp_nonce_url( sprintf( 'admin.php?page=%s&w3tc_config_preview_deploy', $this->_page ), 'w3tc' ) ); ?>
26
- <br /><span class="description"> <?php printf( __( 'To preview any changed settings (without deploying): %s', 'w3-total-cache' ), Util_Ui::preview_link() ) ?> </span>
27
- <?php else: ?>
28
- <input type="submit" name="w3tc_config_preview_enable" class="button-primary" value="<?php _e( 'Enable', 'w3-total-cache' ); ?>" />
29
- <?php endif; ?>
30
- <br /><span class="description"><?php _e( 'Use preview mode to test configuration scenarios prior to releasing them (deploy) on the actual site. Preview mode remains active even after deploying settings until the feature is disabled.', 'w3-total-cache' ); ?></span>
31
- </td>
32
- </tr>
33
- </table>
34
-
35
- <?php Util_Ui::button_config_save( 'general_general' ); ?>
36
- <?php Util_Ui::postbox_footer(); ?>
37
-
38
- <?php
39
  Util_Ui::postbox_header( __( 'Page Cache', 'w3-total-cache' ), '', 'page_cache' );
40
  Util_Ui::config_overloading_button( array(
41
  'key' => 'pgcache.configuration_overloaded'
42
  ) );
43
  ?>
44
 
45
- <p><?php _e( 'Enable page caching to decrease the response time of the site.', 'w3-total-cache' ); ?></p>
46
 
47
- <table class="form-table">
48
- <?php
49
  Util_Ui::config_item( array(
50
  'key' => 'pgcache.enabled',
51
  'control' => 'checkbox',
@@ -109,27 +109,27 @@ Util_Ui::config_item( array(
109
  )
110
  ) );
111
  ?>
112
- </table>
113
 
114
- <?php
115
  Util_Ui::button_config_save( 'general_pagecache',
116
  '<input type="submit" name="w3tc_flush_pgcache" value="' .
117
  __( 'Empty cache', 'w3-total-cache' ) . '"' .
118
  ( $pgcache_enabled ? '' : ' disabled="disabled" ' ) .
119
  ' class="button" />' );
120
  ?>
121
- <?php Util_Ui::postbox_footer(); ?>
122
 
123
- <?php
124
  Util_Ui::postbox_header( __( 'Minify', 'w3-total-cache' ), '', 'minify' );
125
  Util_Ui::config_overloading_button( array(
126
  'key' => 'minify.configuration_overloaded'
127
  ) );
128
  ?>
129
- <p><?php w3tc_e( 'minify.general.header', 'Reduce load time by decreasing the size and number of <acronym title="Cascading Style Sheet">CSS</acronym> and <acronym title="JavaScript">JS</acronym> files. Automatically remove unncessary data from <acronym title="Cascading Style Sheet">CSS</acronym>, <acronym title="JavaScript">JS</acronym>, feed, page and post <acronym title="Hypertext Markup Language">HTML</acronym>.' ) ?></p>
130
 
131
- <table class="form-table">
132
- <?php
133
  Util_Ui::config_item( array(
134
  'key' => 'minify.enabled',
135
  'control' => 'checkbox',
@@ -182,31 +182,31 @@ Util_Ui::config_item( array(
182
  )
183
  ) );
184
  ?>
185
- </table>
186
 
187
- <?php
188
  Util_Ui::button_config_save( 'general_minify',
189
  '<input type="submit" name="w3tc_flush_minify" value="' .
190
  __( 'Empty cache', 'w3-total-cache' ) . '" ' .
191
  ( $minify_enabled ? '' : ' disabled="disabled" ' ) .
192
  ' class="button" />' );
193
  ?>
194
- <?php Util_Ui::postbox_footer(); ?>
195
 
196
 
197
- <?php
198
 
199
  do_action( 'w3tc_settings_general_boxarea_system_opcache' ) ?>
200
- <?php
201
  Util_Ui::postbox_header( __( 'Database Cache', 'w3-total-cache' ), '', 'database_cache' );
202
  Util_Ui::config_overloading_button( array(
203
  'key' => 'dbcache.configuration_overloaded'
204
  ) );
205
  ?>
206
- <p><?php _e( 'Enable database caching to reduce post, page and feed creation time.', 'w3-total-cache' ); ?></p>
207
 
208
- <table class="form-table">
209
- <?php
210
  Util_Ui::config_item( array(
211
  'key' => 'dbcache.enabled',
212
  'control' => 'checkbox',
@@ -218,30 +218,30 @@ Util_Ui::config_item_engine( array(
218
  ) );
219
  ?>
220
 
221
- <?php if ( Util_Environment::is_w3tc_pro() && is_network_admin() ): ?>
222
- <?php include W3TC_INC_OPTIONS_DIR . '/enterprise/dbcluster_general_section.php' ?>
223
- <?php endif; ?>
224
- </table>
225
 
226
- <?php
227
  Util_Ui::button_config_save( 'general_dbcache',
228
  '<input type="submit" name="w3tc_flush_dbcache" value="' .
229
  __( 'Empty cache', 'w3-total-cache' ) . '" ' .
230
  ( $dbcache_enabled ? '' : ' disabled="disabled" ' ) .
231
  ' class="button" />' );
232
  ?>
233
- <?php Util_Ui::postbox_footer(); ?>
234
 
235
- <?php
236
  Util_Ui::postbox_header( 'Object Cache', '', 'object_cache' );
237
  Util_Ui::config_overloading_button( array(
238
  'key' => 'objectcache.configuration_overloaded'
239
  ) );
240
  ?>
241
- <p><?php _e( 'Enable object caching to further reduce execution time for common operations.', 'w3-total-cache' ); ?></p>
242
 
243
- <table class="form-table">
244
- <?php
245
  Util_Ui::config_item( array(
246
  'key' => 'objectcache.enabled',
247
  'control' => 'checkbox',
@@ -252,27 +252,27 @@ Util_Ui::config_item_engine( array(
252
  'key' => 'objectcache.engine'
253
  ) );
254
  ?>
255
- </table>
256
 
257
- <?php
258
  Util_Ui::button_config_save( 'general_objectcache',
259
  '<input type="submit" name="w3tc_flush_objectcache" value="' .
260
  __( 'Empty cache', 'w3-total-cache' ) . '" ' .
261
  ( $objectcache_enabled ? '' : ' disabled="disabled" ' ) .
262
  ' class="button" />' );
263
  ?>
264
- <?php Util_Ui::postbox_footer(); ?>
265
 
266
- <?php
267
  Util_Ui::postbox_header( __( 'Browser Cache', 'w3-total-cache' ), '', 'browser_cache' );
268
  Util_Ui::config_overloading_button( array(
269
  'key' => 'browsercache.configuration_overloaded'
270
  ) );
271
  ?>
272
- <p><?php _e( 'Reduce server load and decrease response time by using the cache available in site visitor\'s web browser.', 'w3-total-cache' ); ?></p>
273
 
274
- <table class="form-table">
275
- <?php
276
  Util_Ui::config_item( array(
277
  'key' => 'browsercache.enabled',
278
  'control' => 'checkbox',
@@ -280,138 +280,138 @@ Util_Ui::config_item( array(
280
  'description' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> compression and add headers to reduce server load and decrease file load time.', 'w3-total-cache' )
281
  ) );
282
  ?>
283
- </table>
284
 
285
- <?php Util_Ui::button_config_save( 'general_browsercache' ); ?>
286
- <?php Util_Ui::postbox_footer(); ?>
287
 
288
- <?php do_action( 'w3tc_settings_general_boxarea_cdn' ); ?>
289
 
290
- <?php
291
  Util_Ui::postbox_header( __( 'Reverse Proxy', 'w3-total-cache' ), '', 'reverse_proxy' );
292
  Util_Ui::config_overloading_button( array(
293
  'key' => 'varnish.configuration_overloaded'
294
  ) );
295
  ?>
296
- <p>
297
- <?php
298
  echo sprintf(
299
  w3tc_er( 'reverseproxy.general.header', 'A reverse proxy adds scale to an server by handling requests before WordPress does. Purge settings are set on the <a href="%s">Page Cache settings</a> page and <a href="%s">Browser Cache settings</a> are set on the browser cache settings page.' ),
300
  self_admin_url( 'admin.php?page=w3tc_pgcache' ),
301
  self_admin_url( 'admin.php?page=w3tc_browsercache' ) );
302
  ?>
303
- </p>
304
- <table class="form-table">
305
- <tr>
306
- <th colspan="2">
307
- <?php $this->checkbox( 'varnish.enabled' ); ?> <?php Util_Ui::e_config_label( 'varnish.enabled' ) ?></label><br />
308
- </th>
309
- </tr>
310
- <tr>
311
- <th><label for="pgcache_varnish_servers"><?php Util_Ui::e_config_label( 'varnish.servers' ) ?></label></th>
312
- <td>
313
- <textarea id="pgcache_varnish_servers" name="varnish__servers"
314
- cols="40" rows="5" <?php Util_Ui::sealing_disabled( 'varnish.' ); ?>><?php echo esc_textarea( implode( "\r\n", $this->_config->get_array( 'varnish.servers' ) ) ); ?></textarea><br />
315
- <span class="description"><?php _e( 'Specify the IP addresses of your varnish instances above. The <acronym title="Varnish Configuration Language">VCL</acronym>\'s <acronym title="Access Control List">ACL</acronym> must allow this request.', 'w3-total-cache' ); ?></span>
316
- </td>
317
- </tr>
318
- </table>
319
-
320
- <?php
321
  Util_Ui::button_config_save( 'general_varnish',
322
  '<input type="submit" name="w3tc_flush_varnish" value="' .
323
  __( 'Purge cache', 'w3-total-cache' ) . '"' .
324
  ( $varnish_enabled ? '' : ' disabled="disabled" ' ) .
325
  ' class="button" />' );
326
  ?>
327
- <?php Util_Ui::postbox_footer(); ?>
328
-
329
- <?php if ( $is_pro ): ?>
330
- <?php Util_Ui::postbox_header( 'Message Bus', '', 'amazon_sns' ); ?>
331
- <p>
332
- Allows policy management to be shared between a dynamic pool of servers. For example, each server in a pool to use opcode caching (which is not a shared resource) and purging is then syncronized between any number of servers in real-time; each server therefore behaves identically even though resources are not shared.
333
- </p>
334
- <table class="form-table">
335
- <tr>
336
- <th colspan="2">
337
- <input type="hidden" name="cluster__messagebus__enabled" value="0" />
338
- <label><input class="enabled" type="checkbox" name="cluster__messagebus__enabled" value="1"<?php checked( $this->_config->get_boolean( 'cluster.messagebus.enabled' ), true ); ?> /> <?php Util_Ui::e_config_label( 'cluster.messagebus.enabled' ) ?></label><br />
339
- </th>
340
- </tr>
341
- <tr>
342
- <th><label for="cluster_messagebus_sns_region"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.region' ) ?></label></th>
343
- <td>
344
- <input id="cluster_messagebus_sns_region"
345
- class="w3tc-ignore-change" type="text"
346
- name="cluster__messagebus__sns__region"
347
- value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.region' ) ); ?>" size="60" /><br />
348
- <span class="description"><?php _e( 'Specify the Amazon <acronym title="Simple Notification Service">SNS</acronym> service endpoint hostname. If empty, then default "sns.us-east-1.amazonaws.com" will be used.', 'w3-total-cache' ); ?></span>
349
- </td>
350
- </tr>
351
- <tr>
352
- <th><label for="cluster_messagebus_sns_api_key"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.api_key' ) ?></label></th>
353
- <td>
354
- <input id="cluster_messagebus_sns_api_key"
355
- class="w3tc-ignore-change" type="text"
356
- name="cluster__messagebus__sns__api_key"
357
- value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.api_key' ) ); ?>" size="60" /><br />
358
- <span class="description"><?php _e( 'Specify the <acronym title="Application Programming Interface">API</acronym> Key.', 'w3-total-cache' ); ?></span>
359
- </td>
360
- </tr>
361
- <tr>
362
- <th><label for="cluster_messagebus_sns_api_secret"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.api_secret' ) ?></label></th>
363
- <td>
364
- <input id="cluster_messagebus_sns_api_secret"
365
- class="w3tc-ignore-change" type="text"
366
- name="cluster__messagebus__sns__api_secret"
367
- value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.api_secret' ) ); ?>" size="60" /><br />
368
- <span class="description"><?php _e( 'Specify the <acronym title="Application Programming Interface">API</acronym> secret.', 'w3-total-cache' ); ?></span>
369
- </td>
370
- </tr>
371
- <tr>
372
- <th><label for="cluster_messagebus_sns_topic_arn"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.topic_arn' ) ?></label></th>
373
- <td>
374
- <input id="cluster_messagebus_sns_topic_arn"
375
- class="w3tc-ignore-change" type="text"
376
- name="cluster__messagebus__sns__topic_arn"
377
- value="<?php echo esc_attr( $this->_config->get_string( 'cluster_messagebus_sns_topic_arn' ) ); ?>" size="60" /><br />
378
- <span class="description"><?php _e( 'Specify the <acronym title="Simple Notification Service">SNS</acronym> topic.', 'w3-total-cache' ); ?></span>
379
- </td>
380
- </tr>
381
- </table>
382
-
383
- <?php Util_Ui::button_config_save( 'general_dbcluster' ); ?>
384
- <?php Util_Ui::postbox_footer(); ?>
385
- <?php endif; ?>
386
-
387
- <?php
388
  foreach ( $custom_areas as $area )
389
  do_action( "w3tc_settings_general_boxarea_{$area['id']}" );
390
  ?>
391
- <?php if ( $licensing_visible ): ?>
392
- <?php Util_Ui::postbox_header( __( 'Licensing', 'w3-total-cache' ), '', 'licensing' ); ?>
393
- <table class="form-table">
394
- <tr>
395
- <th>
396
- <label for="plugin_license_key"><?php Util_Ui::e_config_label( 'plugin.license_key' ) ?></label>
397
- </th>
398
- <td>
399
- <input id="plugin_license_key" name="plugin__license_key" type="text" value="<?php echo esc_attr( $this->_config->get_string( 'plugin.license_key' ) )?>" size="45"/>
400
- <input id="plugin_license_key_verify" type="button" class="button" value="<?php _e( 'Verify license key', 'w3-total-cache' ) ?>"/>
401
- <span class="w3tc_license_verification"></span>
402
- <br />
403
- <span class="description"><?php printf( __( 'Please enter the license key provided after %s.', 'w3-total-cache' ), '<a class="button-buy-plugin" href="#">' . __( 'upgrading', 'w3-total-cache' ) . '</a>' )?></span>
404
- </td>
405
- </tr>
406
-
407
- </table>
408
- <?php Util_Ui::button_config_save( 'general_licensing' ); ?>
409
- <?php Util_Ui::postbox_footer(); ?>
410
- <?php endif ?>
411
-
412
- <?php Util_Ui::postbox_header( __( 'Miscellaneous', 'w3-total-cache' ), '', 'miscellaneous' ); ?>
413
- <table class="form-table">
414
- <?php
415
  Util_Ui::config_item( array(
416
  'key' => 'widget.pagespeed.enabled',
417
  'control' => 'checkbox',
@@ -420,21 +420,21 @@ Util_Ui::config_item( array(
420
  'style' => '2'
421
  ) );
422
  ?>
423
- <tr>
424
- <th><label for="widget_pagespeed_key"><?php Util_Ui::e_config_label( 'widget.pagespeed.key' ) ?></label></th>
425
- <td>
426
- <input id="widget_pagespeed_key" type="text" name="widget__pagespeed__key" value="<?php echo esc_attr( $this->_config->get_string( 'widget.pagespeed.key' ) ); ?>" <?php Util_Ui::sealing_disabled( 'common.' ) ?> size="60" /><br />
427
- <span class="description"><?php _e( 'Learn more about obtaining a <a href="https://support.google.com/cloud/answer/6158862" target="_blank"><acronym title="Application Programming Interface">API</acronym> key here</a>.', 'w3-total-cache' ); ?></span>
428
- </td>
429
- </tr>
430
- <tr>
431
- <th><label for="widget_pagespeed_key"><?php Util_Ui::e_config_label( 'widget.pagespeed.key.restrict.referrer', 'general' ) ?></label></th>
432
- <td>
433
- <input id="widget_pagespeed_key_restrict_referrer" type="text" name="widget__pagespeed__key__restrict__referrer" value="<?php echo esc_attr( $this->_config->get_string( 'widget.pagespeed.key.restrict.referrer' ) ); ?>" size="60" /><br>
434
- <span class="description">Although not required, to prevent unauthorized use and quota theft, you have the option to restrict your key using a designated HTTP referrer. If you decide to use it, you will need to set this referrer within the API Console's "Http Referrers (web sites)" key restriction area (under Credentials).</span>
435
- </td>
436
- </tr>
437
- <?php
438
  Util_Ui::config_item( array(
439
  'key' => 'widget.pagespeed.show_in_admin_bar',
440
  'control' => 'checkbox',
@@ -443,45 +443,45 @@ Util_Ui::config_item( array(
443
  ) );
444
  ?>
445
 
446
- <?php if ( is_network_admin() ): ?>
447
- <tr>
448
- <th colspan="2">
449
- <?php $this->checkbox( 'common.force_master' ) ?> <?php Util_Ui::e_config_label( 'common.force_master' ) ?></label>
450
- <br /><span class="description"><?php _e( 'Only one configuration file for whole network will be created and used. Recommended if all sites have the same configuration.', 'w3-total-cache' ); ?></span>
451
- </th>
452
- </tr>
453
- <?php endif; ?>
454
- <?php if ( Util_Environment::is_nginx() ): ?>
455
- <tr>
456
- <th><?php Util_Ui::e_config_label( 'config.path' ) ?></th>
457
- <td>
458
- <input type="text" name="config__path" value="<?php echo esc_attr( $this->_config->get_string( 'config.path' ) ); ?>" size="80" <?php Util_Ui::sealing_disabled( 'common.' ) ?>/>
459
- <br /><span class="description"><?php _e( 'If empty the default path will be used..', 'w3-total-cache' ); ?></span>
460
- </td>
461
- </tr>
462
- <?php endif; ?>
463
- <tr>
464
- <th colspan="2">
465
- <input type="hidden" name="config__check" value="0" <?php Util_Ui::sealing_disabled( 'common.' ) ?> />
466
- <label><input type="checkbox" name="config__check" value="1"<?php checked( $this->_config->get_boolean( 'config.check' ), true ); Util_Ui::sealing_disabled( 'common.' ); ?> /> <?php Util_Ui::e_config_label( 'config.check' ) ?></label>
467
- <br /><span class="description"><?php _e( 'Notify of server configuration errors, if this option is disabled, the server configuration for active settings can be found on the <a href="admin.php?page=w3tc_install">install</a> tab.', 'w3-total-cache' ); ?></span>
468
- </th>
469
- </tr>
470
- <tr>
471
- <th colspan="2">
472
- <input type="hidden" name="file_locking" value="0"<?php Util_Ui::sealing_disabled( 'common.' ) ?> />
473
- <label><input type="checkbox" name="file_locking" value="1"<?php checked( $file_locking, true ); Util_Ui::sealing_disabled( 'common.' ) ?> /> <?php _e( 'Enable file locking', 'w3-total-cache' ); ?></label>
474
- <br /><span class="description"><?php _e( 'Not recommended for <acronym title="Network File System">NFS</acronym> systems.', 'w3-total-cache' ); ?></span>
475
- </th>
476
- </tr>
477
- <tr>
478
- <th colspan="2">
479
- <input type="hidden" name="file_nfs" value="0" <?php Util_Ui::sealing_disabled( 'common.' ) ?> />
480
- <label><input type="checkbox" name="file_nfs" value="1"<?php checked( $file_nfs, true ); Util_Ui::sealing_disabled( 'common.' ); ?> /> <?php _e( 'Optimize disk enhanced page and minify disk caching for <acronym title="Network File System">NFS</acronym>', 'w3-total-cache' ); ?></label>
481
- <br /><span class="description"><?php _e( 'Try this option if your hosting environment uses a network based file system for a possible performance improvement.', 'w3-total-cache' ); ?></span>
482
- </th>
483
- </tr>
484
- <?php
485
  Util_Ui::config_item( array(
486
  'key' => 'common.track_usage',
487
  'control' => 'checkbox',
@@ -490,71 +490,71 @@ Util_Ui::config_item( array(
490
  ) );
491
  ?>
492
 
493
- <?php do_action( 'w3tc_settings_general_boxarea_miscellaneous_content' ); ?>
494
- </table>
495
-
496
- <?php Util_Ui::button_config_save( 'general_misc' ); ?>
497
- <?php Util_Ui::postbox_footer(); ?>
498
-
499
- <?php Util_Ui::postbox_header( 'Debug', '', 'debug' ); ?>
500
- <p><?php _e( 'Detailed information about each cache will be appended in (publicly available) <acronym title="Hypertext Markup Language">HTML</acronym> comments in the page\'s source code. Performance in this mode will not be optimal, use sparingly and disable when not in use.', 'w3-total-cache' ); ?></p>
501
-
502
- <table class="form-table">
503
- <tr>
504
- <th><?php _e( 'Debug mode:', 'w3-total-cache' ); ?></th>
505
- <td>
506
- <?php $this->checkbox_debug( 'pgcache.debug' ) ?> <?php Util_Ui::e_config_label( 'pgcache.debug' ) ?></label><br />
507
- <?php $this->checkbox_debug( 'minify.debug' ) ?> <?php Util_Ui::e_config_label( 'minify.debug' ) ?></label><br />
508
- <?php $this->checkbox_debug( 'dbcache.debug' ) ?> <?php Util_Ui::e_config_label( 'dbcache.debug' ) ?></label><br />
509
- <?php $this->checkbox_debug( 'objectcache.debug' ) ?> <?php Util_Ui::e_config_label( 'objectcache.debug' ) ?></label><br />
510
- <?php if ( Util_Environment::is_w3tc_pro( $this->_config ) ): ?>
511
- <?php $this->checkbox_debug( array( 'fragmentcache', 'debug' ) ) ?> <?php _e( 'Fragment Cache', 'w3-total-cache' ) ?></label><br />
512
- <?php endif; ?>
513
- <?php $this->checkbox_debug( 'cdn.debug' ) ?> <?php Util_Ui::e_config_label( 'cdn.debug' ) ?></label><br />
514
- <?php $this->checkbox_debug( 'cdnfsd.debug' ) ?> <?php Util_Ui::e_config_label( 'cdnfsd.debug' ) ?></label><br />
515
- <?php $this->checkbox_debug( 'varnish.debug' ) ?> <?php Util_Ui::e_config_label( 'varnish.debug' ) ?></label><br />
516
- <?php if ( Util_Environment::is_w3tc_pro() ): ?>
517
- <?php $this->checkbox_debug( 'cluster.messagebus.debug' ) ?> <?php Util_Ui::e_config_label( 'cluster.messagebus.debug' ) ?></label><br />
518
- <?php endif; ?>
519
- <span class="description"><?php _e( 'If selected, detailed caching information will appear at the end of each page in a <acronym title="Hypertext Markup Language">HTML</acronym> comment. View a page\'s source code to review.', 'w3-total-cache' ); ?></span>
520
- </td>
521
- </tr>
522
- </table>
523
-
524
- <?php Util_Ui::button_config_save( 'general_debug' ); ?>
525
- <?php Util_Ui::postbox_footer(); ?>
526
- </div>
527
  </form>
528
 
529
  <form action="admin.php?page=<?php echo $this->_page; ?>" method="post" enctype="multipart/form-data">
530
- <div class="metabox-holder">
531
- <?php Util_Ui::postbox_header( __( 'Import / Export Settings', 'w3-total-cache' ), '', 'settings' ); ?>
532
- <?php echo Util_Ui::nonce_field( 'w3tc' ); ?>
533
- <table class="form-table">
534
- <tr>
535
- <th><?php _e( 'Import configuration:', 'w3-total-cache' ); ?></th>
536
- <td>
537
- <input type="file" name="config_file" />
538
- <input type="submit" name="w3tc_config_import" class="w3tc-button-save button" value="<?php _e( 'Upload', 'w3-total-cache' ); ?>" />
539
- <br /><span class="description"><?php _e( 'Upload and replace the active settings file.', 'w3-total-cache' ); ?></span>
540
- </td>
541
- </tr>
542
- <tr>
543
- <th><?php _e( 'Export configuration:', 'w3-total-cache' ); ?></th>
544
- <td>
545
- <input type="submit" name="w3tc_config_export" class="button" value="<?php _e( 'Download', 'w3-total-cache' ); ?>" />
546
- <br /><span class="description"><?php _e( 'Download the active settings file.', 'w3-total-cache' ); ?></span>
547
- </td>
548
- </tr>
549
- <tr>
550
- <th><?php _e( 'Reset configuration:', 'w3-total-cache' ); ?></th>
551
- <td>
552
- <input type="submit" name="w3tc_config_reset" class="button" value="<?php _e( 'Restore Default Settings', 'w3-total-cache' ); ?>" />
553
- <br /><span class="description"><?php _e( 'Revert all settings to the defaults. Any settings staged in preview mode will not be modified.', 'w3-total-cache' ); ?></span>
554
- </td>
555
- </tr>
556
- </table>
557
- <?php Util_Ui::postbox_footer(); ?>
558
- </div>
559
  </form>
560
  <?php include W3TC_INC_DIR . '/options/common/footer.php'; ?>
13
  ?>
14
  </p>
15
  <form id="w3tc_form" action="admin.php?page=<?php echo $this->_page; ?>" method="post">
16
+ <div class="metabox-holder">
17
+ <?php Util_Ui::postbox_header( __( 'General', 'w3-total-cache' ), '' ); ?>
18
+ <table class="form-table">
19
+ <tr>
20
+ <th>Preview mode:</th>
21
+ <td>
22
+ <?php echo Util_Ui::nonce_field( 'w3tc' ); ?>
23
+ <?php if ( $this->_config->is_preview() ): ?>
24
+ <input type="submit" name="w3tc_config_preview_disable" class="button-primary" value="<?php _e( 'Disable', 'w3-total-cache' ); ?>" />
25
+ <?php echo Util_Ui::button_link( __( 'Deploy', 'w3-total-cache' ), wp_nonce_url( sprintf( 'admin.php?page=%s&w3tc_config_preview_deploy', $this->_page ), 'w3tc' ) ); ?>
26
+ <br /><span class="description"> <?php printf( __( 'To preview any changed settings (without deploying): %s', 'w3-total-cache' ), Util_Ui::preview_link() ) ?> </span>
27
+ <?php else: ?>
28
+ <input type="submit" name="w3tc_config_preview_enable" class="button-primary" value="<?php _e( 'Enable', 'w3-total-cache' ); ?>" />
29
+ <?php endif; ?>
30
+ <br /><span class="description"><?php _e( 'Use preview mode to test configuration scenarios prior to releasing them (deploy) on the actual site. Preview mode remains active even after deploying settings until the feature is disabled.', 'w3-total-cache' ); ?></span>
31
+ </td>
32
+ </tr>
33
+ </table>
34
+
35
+ <?php Util_Ui::button_config_save( 'general_general' ); ?>
36
+ <?php Util_Ui::postbox_footer(); ?>
37
+
38
+ <?php
39
  Util_Ui::postbox_header( __( 'Page Cache', 'w3-total-cache' ), '', 'page_cache' );
40
  Util_Ui::config_overloading_button( array(
41
  'key' => 'pgcache.configuration_overloaded'
42
  ) );
43
  ?>
44
 
45
+ <p><?php _e( 'Enable page caching to decrease the response time of the site.', 'w3-total-cache' ); ?></p>
46
 
47
+ <table class="form-table">
48
+ <?php
49
  Util_Ui::config_item( array(
50
  'key' => 'pgcache.enabled',
51
  'control' => 'checkbox',
109
  )
110
  ) );
111
  ?>
112
+ </table>
113
 
114
+ <?php
115
  Util_Ui::button_config_save( 'general_pagecache',
116
  '<input type="submit" name="w3tc_flush_pgcache" value="' .
117
  __( 'Empty cache', 'w3-total-cache' ) . '"' .
118
  ( $pgcache_enabled ? '' : ' disabled="disabled" ' ) .
119
  ' class="button" />' );
120
  ?>
121
+ <?php Util_Ui::postbox_footer(); ?>
122
 
123
+ <?php
124
  Util_Ui::postbox_header( __( 'Minify', 'w3-total-cache' ), '', 'minify' );
125
  Util_Ui::config_overloading_button( array(
126
  'key' => 'minify.configuration_overloaded'
127
  ) );
128
  ?>
129
+ <p><?php w3tc_e( 'minify.general.header', 'Reduce load time by decreasing the size and number of <acronym title="Cascading Style Sheet">CSS</acronym> and <acronym title="JavaScript">JS</acronym> files. Automatically remove unncessary data from <acronym title="Cascading Style Sheet">CSS</acronym>, <acronym title="JavaScript">JS</acronym>, feed, page and post <acronym title="Hypertext Markup Language">HTML</acronym>.' ) ?></p>
130
 
131
+ <table class="form-table">
132
+ <?php
133
  Util_Ui::config_item( array(
134
  'key' => 'minify.enabled',
135
  'control' => 'checkbox',
182
  )
183
  ) );
184
  ?>
185
+ </table>
186
 
187
+ <?php
188
  Util_Ui::button_config_save( 'general_minify',
189
  '<input type="submit" name="w3tc_flush_minify" value="' .
190
  __( 'Empty cache', 'w3-total-cache' ) . '" ' .
191
  ( $minify_enabled ? '' : ' disabled="disabled" ' ) .
192
  ' class="button" />' );
193
  ?>
194
+ <?php Util_Ui::postbox_footer(); ?>
195
 
196
 
197
+ <?php
198
 
199
  do_action( 'w3tc_settings_general_boxarea_system_opcache' ) ?>
200
+ <?php
201
  Util_Ui::postbox_header( __( 'Database Cache', 'w3-total-cache' ), '', 'database_cache' );
202
  Util_Ui::config_overloading_button( array(
203
  'key' => 'dbcache.configuration_overloaded'
204
  ) );
205
  ?>
206
+ <p><?php _e( 'Enable database caching to reduce post, page and feed creation time.', 'w3-total-cache' ); ?></p>
207
 
208
+ <table class="form-table">
209
+ <?php
210
  Util_Ui::config_item( array(
211
  'key' => 'dbcache.enabled',
212
  'control' => 'checkbox',
218
  ) );
219
  ?>
220
 
221
+ <?php if ( Util_Environment::is_w3tc_pro() && is_network_admin() ): ?>
222
+ <?php include W3TC_INC_OPTIONS_DIR . '/enterprise/dbcluster_general_section.php' ?>
223
+ <?php endif; ?>
224
+ </table>
225
 
226
+ <?php
227
  Util_Ui::button_config_save( 'general_dbcache',
228
  '<input type="submit" name="w3tc_flush_dbcache" value="' .
229
  __( 'Empty cache', 'w3-total-cache' ) . '" ' .
230
  ( $dbcache_enabled ? '' : ' disabled="disabled" ' ) .
231
  ' class="button" />' );
232
  ?>
233
+ <?php Util_Ui::postbox_footer(); ?>
234
 
235
+ <?php
236
  Util_Ui::postbox_header( 'Object Cache', '', 'object_cache' );
237
  Util_Ui::config_overloading_button( array(
238
  'key' => 'objectcache.configuration_overloaded'
239
  ) );
240
  ?>
241
+ <p><?php _e( 'Enable object caching to further reduce execution time for common operations.', 'w3-total-cache' ); ?></p>
242
 
243
+ <table class="form-table">
244
+ <?php
245
  Util_Ui::config_item( array(
246
  'key' => 'objectcache.enabled',
247
  'control' => 'checkbox',
252
  'key' => 'objectcache.engine'
253
  ) );
254
  ?>
255
+ </table>
256
 
257
+ <?php
258
  Util_Ui::button_config_save( 'general_objectcache',
259
  '<input type="submit" name="w3tc_flush_objectcache" value="' .
260
  __( 'Empty cache', 'w3-total-cache' ) . '" ' .
261
  ( $objectcache_enabled ? '' : ' disabled="disabled" ' ) .
262
  ' class="button" />' );
263
  ?>
264
+ <?php Util_Ui::postbox_footer(); ?>
265
 
266
+ <?php
267
  Util_Ui::postbox_header( __( 'Browser Cache', 'w3-total-cache' ), '', 'browser_cache' );
268
  Util_Ui::config_overloading_button( array(
269
  'key' => 'browsercache.configuration_overloaded'
270
  ) );
271
  ?>
272
+ <p><?php _e( 'Reduce server load and decrease response time by using the cache available in site visitor\'s web browser.', 'w3-total-cache' ); ?></p>
273
 
274
+ <table class="form-table">
275
+ <?php
276
  Util_Ui::config_item( array(
277
  'key' => 'browsercache.enabled',
278
  'control' => 'checkbox',
280
  'description' => __( 'Enable <acronym title="Hypertext Transfer Protocol">HTTP</acronym> compression and add headers to reduce server load and decrease file load time.', 'w3-total-cache' )
281
  ) );
282
  ?>
283
+ </table>
284
 
285
+ <?php Util_Ui::button_config_save( 'general_browsercache' ); ?>
286
+ <?php Util_Ui::postbox_footer(); ?>
287
 
288
+ <?php do_action( 'w3tc_settings_general_boxarea_cdn' ); ?>
289
 
290
+ <?php
291
  Util_Ui::postbox_header( __( 'Reverse Proxy', 'w3-total-cache' ), '', 'reverse_proxy' );
292
  Util_Ui::config_overloading_button( array(
293
  'key' => 'varnish.configuration_overloaded'
294
  ) );
295
  ?>
296
+ <p>
297
+ <?php
298
  echo sprintf(
299
  w3tc_er( 'reverseproxy.general.header', 'A reverse proxy adds scale to an server by handling requests before WordPress does. Purge settings are set on the <a href="%s">Page Cache settings</a> page and <a href="%s">Browser Cache settings</a> are set on the browser cache settings page.' ),
300
  self_admin_url( 'admin.php?page=w3tc_pgcache' ),
301
  self_admin_url( 'admin.php?page=w3tc_browsercache' ) );
302
  ?>
303
+ </p>
304
+ <table class="form-table">
305
+ <tr>
306
+ <th colspan="2">
307
+ <?php $this->checkbox( 'varnish.enabled' ); ?> <?php Util_Ui::e_config_label( 'varnish.enabled' ) ?></label><br />
308
+ </th>
309
+ </tr>
310
+ <tr>
311
+ <th><label for="pgcache_varnish_servers"><?php Util_Ui::e_config_label( 'varnish.servers' ) ?></label></th>
312
+ <td>
313
+ <textarea id="pgcache_varnish_servers" name="varnish__servers"
314
+ cols="40" rows="5" <?php Util_Ui::sealing_disabled( 'varnish.' ); ?>><?php echo esc_textarea( implode( "\r\n", $this->_config->get_array( 'varnish.servers' ) ) ); ?></textarea><br />
315
+ <span class="description"><?php _e( 'Specify the IP addresses of your varnish instances above. The <acronym title="Varnish Configuration Language">VCL</acronym>\'s <acronym title="Access Control List">ACL</acronym> must allow this request.', 'w3-total-cache' ); ?></span>
316
+ </td>
317
+ </tr>
318
+ </table>
319
+
320
+ <?php
321
  Util_Ui::button_config_save( 'general_varnish',
322
  '<input type="submit" name="w3tc_flush_varnish" value="' .
323
  __( 'Purge cache', 'w3-total-cache' ) . '"' .
324
  ( $varnish_enabled ? '' : ' disabled="disabled" ' ) .
325
  ' class="button" />' );
326
  ?>
327
+ <?php Util_Ui::postbox_footer(); ?>
328
+
329
+ <?php if ( $is_pro ): ?>
330
+ <?php Util_Ui::postbox_header( 'Message Bus', '', 'amazon_sns' ); ?>
331
+ <p>
332
+ Allows policy management to be shared between a dynamic pool of servers. For example, each server in a pool to use opcode caching (which is not a shared resource) and purging is then syncronized between any number of servers in real-time; each server therefore behaves identically even though resources are not shared.
333
+ </p>
334
+ <table class="form-table">
335
+ <tr>
336
+ <th colspan="2">
337
+ <input type="hidden" name="cluster__messagebus__enabled" value="0" />
338
+ <label><input class="enabled" type="checkbox" name="cluster__messagebus__enabled" value="1"<?php checked( $this->_config->get_boolean( 'cluster.messagebus.enabled' ), true ); ?> /> <?php Util_Ui::e_config_label( 'cluster.messagebus.enabled' ) ?></label><br />
339
+ </th>
340
+ </tr>
341
+ <tr>
342
+ <th><label for="cluster_messagebus_sns_region"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.region' ) ?></label></th>
343
+ <td>
344
+ <input id="cluster_messagebus_sns_region"
345
+ class="w3tc-ignore-change" type="text"
346
+ name="cluster__messagebus__sns__region"
347
+ value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.region' ) ); ?>" size="60" /><br />
348
+ <span class="description"><?php _e( 'Specify the Amazon <acronym title="Simple Notification Service">SNS</acronym> service endpoint hostname. If empty, then default "sns.us-east-1.amazonaws.com" will be used.', 'w3-total-cache' ); ?></span>
349
+ </td>
350
+ </tr>
351
+ <tr>
352
+ <th><label for="cluster_messagebus_sns_api_key"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.api_key' ) ?></label></th>
353
+ <td>
354
+ <input id="cluster_messagebus_sns_api_key"
355
+ class="w3tc-ignore-change" type="text"
356
+ name="cluster__messagebus__sns__api_key"
357
+ value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.api_key' ) ); ?>" size="60" /><br />
358
+ <span class="description"><?php _e( 'Specify the <acronym title="Application Programming Interface">API</acronym> Key.', 'w3-total-cache' ); ?></span>
359
+ </td>
360
+ </tr>
361
+ <tr>
362
+ <th><label for="cluster_messagebus_sns_api_secret"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.api_secret' ) ?></label></th>
363
+ <td>
364
+ <input id="cluster_messagebus_sns_api_secret"
365
+ class="w3tc-ignore-change" type="text"
366
+ name="cluster__messagebus__sns__api_secret"
367
+ value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.api_secret' ) ); ?>" size="60" /><br />
368
+ <span class="description"><?php _e( 'Specify the <acronym title="Application Programming Interface">API</acronym> secret.', 'w3-total-cache' ); ?></span>
369
+ </td>
370
+ </tr>
371
+ <tr>
372
+ <th><label for="cluster_messagebus_sns_topic_arn"><?php Util_Ui::e_config_label( 'cluster.messagebus.sns.topic_arn' ) ?></label></th>
373
+ <td>
374
+ <input id="cluster_messagebus_sns_topic_arn"
375
+ class="w3tc-ignore-change" type="text"
376
+ name="cluster__messagebus__sns__topic_arn"
377
+ value="<?php echo esc_attr( $this->_config->get_string( 'cluster.messagebus.sns.topic_arn' ) ); ?>" size="60" /><br />
378
+ <span class="description"><?php _e( 'Specify the <acronym title="Simple Notification Service">SNS</acronym> topic.', 'w3-total-cache' ); ?></span>
379
+ </td>
380
+ </tr>
381
+ </table>
382
+
383
+ <?php Util_Ui::button_config_save( 'general_dbcluster' ); ?>
384
+ <?php Util_Ui::postbox_footer(); ?>
385
+ <?php endif; ?>
386
+
387
+ <?php
388
  foreach ( $custom_areas as $area )
389
  do_action( "w3tc_settings_general_boxarea_{$area['id']}" );
390
  ?>
391
+ <?php if ( $licensing_visible ): ?>
392
+ <?php Util_Ui::postbox_header( __( 'Licensing', 'w3-total-cache' ), '', 'licensing' ); ?>
393
+ <table class="form-table">
394
+ <tr>
395
+ <th>
396
+ <label for="plugin_license_key"><?php Util_Ui::e_config_label( 'plugin.license_key' ) ?></label>
397
+ </th>
398
+ <td>
399
+ <input id="plugin_license_key" name="plugin__license_key" type="text" value="<?php echo esc_attr( $this->_config->get_string( 'plugin.license_key' ) )?>" size="45"/>
400
+ <input id="plugin_license_key_verify" type="button" class="button" value="<?php _e( 'Verify license key', 'w3-total-cache' ) ?>"/>
401
+ <span class="w3tc_license_verification"></span>
402
+ <br />
403
+ <span class="description"><?php printf( __( 'Please enter the license key provided after %s.', 'w3-total-cache' ), '<a class="button-buy-plugin" href="#">' . __( 'upgrading', 'w3-total-cache' ) . '</a>' )?></span>
404
+ </td>
405
+ </tr>
406
+
407
+ </table>
408
+ <?php Util_Ui::button_config_save( 'general_licensing' ); ?>
409
+ <?php Util_Ui::postbox_footer(); ?>
410
+ <?php endif ?>
411
+
412
+ <?php Util_Ui::postbox_header( __( 'Miscellaneous', 'w3-total-cache' ), '', 'miscellaneous' ); ?>
413
+ <table class="form-table">
414
+ <?php
415
  Util_Ui::config_item( array(
416
  'key' => 'widget.pagespeed.enabled',
417
  'control' => 'checkbox',
420
  'style' => '2'
421
  ) );
422
  ?>
423
+ <tr>
424
+ <th><label for="widget_pagespeed_key"><?php Util_Ui::e_config_label( 'widget.pagespeed.key' ) ?></label></th>
425
+ <td>
426
+ <input id="widget_pagespeed_key" type="text" name="widget__pagespeed__key" value="<?php echo esc_attr( $this->_config->get_string( 'widget.pagespeed.key' ) ); ?>" <?php Util_Ui::sealing_disabled( 'common.' ) ?> size="60" /><br />
427
+ <span class="description"><?php _e( 'Learn more about obtaining a <a href="https://support.google.com/cloud/answer/6158862" target="_blank"><acronym title="Application Programming Interface">API</acronym> key here</a>.', 'w3-total-cache' ); ?></span>
428
+ </td>
429
+ </tr>
430
+ <tr>
431
+ <th><label for="widget_pagespeed_key"><?php Util_Ui::e_config_label( 'widget.pagespeed.key.restrict.referrer', 'general' ) ?></label></th>
432
+ <td>
433
+ <input id="widget_pagespeed_key_restrict_referrer" type="text" name="widget__pagespeed__key__restrict__referrer" value="<?php echo esc_attr( $this->_config->get_string( 'widget.pagespeed.key.restrict.referrer' ) ); ?>" size="60" /><br>
434
+ <span class="description">Although not required, to prevent unauthorized use and quota theft, you have the option to restrict your key using a designated HTTP referrer. If you decide to use it, you will need to set this referrer within the API Console's "Http Referrers (web sites)" key restriction area (under Credentials).</span>
435
+ </td>
436
+ </tr>
437
+ <?php
438
  Util_Ui::config_item( array(
439
  'key' => 'widget.pagespeed.show_in_admin_bar',
440
  'control' => 'checkbox',
443
  ) );
444
  ?>
445
 
446
+ <?php if ( is_network_admin() ): ?>
447
+ <tr>
448
+ <th colspan="2">
449
+ <?php $this->checkbox( 'common.force_master' ) ?> <?php Util_Ui::e_config_label( 'common.force_master' ) ?></label>
450
+ <br /><span class="description"><?php _e( 'Only one configuration file for whole network will be created and used. Recommended if all sites have the same configuration.', 'w3-total-cache' ); ?></span>
451
+ </th>
452
+ </tr>
453
+ <?php endif; ?>
454
+ <?php if ( Util_Environment::is_nginx() ): ?>
455
+ <tr>
456
+ <th><?php Util_Ui::e_config_label( 'config.path' ) ?></th>
457
+ <td>
458
+ <input type="text" name="config__path" value="<?php echo esc_attr( $this->_config->get_string( 'config.path' ) ); ?>" size="80" <?php Util_Ui::sealing_disabled( 'common.' ) ?>/>
459
+ <br /><span class="description"><?php _e( 'If empty the default path will be used..', 'w3-total-cache' ); ?></span>
460
+ </td>
461
+ </tr>
462
+ <?php endif; ?>
463
+ <tr>
464
+ <th colspan="2">
465
+ <input type="hidden" name="config__check" value="0" <?php Util_Ui::sealing_disabled( 'common.' ) ?> />
466
+ <label><input type="checkbox" name="config__check" value="1"<?php checked( $this->_config->get_boolean( 'config.check' ), true ); Util_Ui::sealing_disabled( 'common.' ); ?> /> <?php Util_Ui::e_config_label( 'config.check' ) ?></label>
467
+ <br /><span class="description"><?php _e( 'Notify of server configuration errors, if this option is disabled, the server configuration for active settings can be found on the <a href="admin.php?page=w3tc_install">install</a> tab.', 'w3-total-cache' ); ?></span>
468
+ </th>
469
+ </tr>
470
+ <tr>
471
+ <th colspan="2">
472
+ <input type="hidden" name="file_locking" value="0"<?php Util_Ui::sealing_disabled( 'common.' ) ?> />
473
+ <label><input type="checkbox" name="file_locking" value="1"<?php checked( $file_locking, true ); Util_Ui::sealing_disabled( 'common.' ) ?> /> <?php _e( 'Enable file locking', 'w3-total-cache' ); ?></label>
474
+ <br /><span class="description"><?php _e( 'Not recommended for <acronym title="Network File System">NFS</acronym> systems.', 'w3-total-cache' ); ?></span>
475
+ </th>
476
+ </tr>
477
+ <tr>
478
+ <th colspan="2">
479
+ <input type="hidden" name="file_nfs" value="0" <?php Util_Ui::sealing_disabled( 'common.' ) ?> />
480
+ <label><input type="checkbox" name="file_nfs" value="1"<?php checked( $file_nfs, true ); Util_Ui::sealing_disabled( 'common.' ); ?> /> <?php _e( 'Optimize disk enhanced page and minify disk caching for <acronym title="Network File System">NFS</acronym>', 'w3-total-cache' ); ?></label>
481
+ <br /><span class="description"><?php _e( 'Try this option if your hosting environment uses a network based file system for a possible performance improvement.', 'w3-total-cache' ); ?></span>
482
+ </th>
483
+ </tr>
484
+ <?php
485
  Util_Ui::config_item( array(
486
  'key' => 'common.track_usage',
487
  'control' => 'checkbox',
490
  ) );
491
  ?>
492
 
493
+ <?php do_action( 'w3tc_settings_general_boxarea_miscellaneous_content' ); ?>
494
+ </table>
495
+
496
+ <?php Util_Ui::button_config_save( 'general_misc' ); ?>
497
+ <?php Util_Ui::postbox_footer(); ?>
498
+
499
+ <?php Util_Ui::postbox_header( 'Debug', '', 'debug' ); ?>
500
+ <p><?php _e( 'Detailed information about each cache will be appended in (publicly available) <acronym title="Hypertext Markup Language">HTML</acronym> comments in the page\'s source code. Performance in this mode will not be optimal, use sparingly and disable when not in use.', 'w3-total-cache' ); ?></p>
501
+
502
+ <table class="form-table">
503
+ <tr>
504
+ <th><?php _e( 'Debug mode:', 'w3-total-cache' ); ?></th>
505
+ <td>
506
+ <?php $this->checkbox_debug( 'pgcache.debug' ) ?> <?php Util_Ui::e_config_label( 'pgcache.debug' ) ?></label><br />
507
+ <?php $this->checkbox_debug( 'minify.debug' ) ?> <?php Util_Ui::e_config_label( 'minify.debug' ) ?></label><br />
508
+ <?php $this->checkbox_debug( 'dbcache.debug' ) ?> <?php Util_Ui::e_config_label( 'dbcache.debug' ) ?></label><br />
509
+ <?php $this->checkbox_debug( 'objectcache.debug' ) ?> <?php Util_Ui::e_config_label( 'objectcache.debug' ) ?></label><br />
510
+ <?php if ( Util_Environment::is_w3tc_pro( $this->_config ) ): ?>
511
+ <?php $this->checkbox_debug( array( 'fragmentcache', 'debug' ) ) ?> <?php _e( 'Fragment Cache', 'w3-total-cache' ) ?></label><br />
512
+ <?php endif; ?>
513
+ <?php $this->checkbox_debug( 'cdn.debug' ) ?> <?php Util_Ui::e_config_label( 'cdn.debug' ) ?></label><br />
514
+ <?php $this->checkbox_debug( 'cdnfsd.debug' ) ?> <?php Util_Ui::e_config_label( 'cdnfsd.debug' ) ?></label><br />
515
+ <?php $this->checkbox_debug( 'varnish.debug' ) ?> <?php Util_Ui::e_config_label( 'varnish.debug' ) ?></label><br />
516
+ <?php if ( Util_Environment::is_w3tc_pro() ): ?>
517
+ <?php $this->checkbox_debug( 'cluster.messagebus.debug' ) ?> <?php Util_Ui::e_config_label( 'cluster.messagebus.debug' ) ?></label><br />
518
+ <?php endif; ?>
519
+ <span class="description"><?php _e( 'If selected, detailed caching information will appear at the end of each page in a <acronym title="Hypertext Markup Language">HTML</acronym> comment. View a page\'s source code to review.', 'w3-total-cache' ); ?></span>
520
+ </td>
521
+ </tr>
522
+ </table>
523
+
524
+ <?php Util_Ui::button_config_save( 'general_debug' ); ?>
525
+ <?php Util_Ui::postbox_footer(); ?>
526
+ </div>
527
  </form>
528
 
529
  <form action="admin.php?page=<?php echo $this->_page; ?>" method="post" enctype="multipart/form-data">
530
+ <div class="metabox-holder">
531
+ <?php Util_Ui::postbox_header( __( 'Import / Export Settings', 'w3-total-cache' ), '', 'settings' ); ?>
532
+ <?php echo Util_Ui::nonce_field( 'w3tc' ); ?>
533
+ <table class="form-table">
534
+ <tr>
535
+ <th><?php _e( 'Import configuration:', 'w3-total-cache' ); ?></th>
536
+ <td>
537
+ <input type="file" name="config_file" />
538
+ <input type="submit" name="w3tc_config_import" class="w3tc-button-save button" value="<?php _e( 'Upload', 'w3-total-cache' ); ?>" />
539
+ <br /><span class="description"><?php _e( 'Upload and replace the active settings file.', 'w3-total-cache' ); ?></span>
540
+ </td>
541
+ </tr>
542
+ <tr>
543
+ <th><?php _e( 'Export configuration:', 'w3-total-cache' ); ?></th>
544
+ <td>
545
+ <input type="submit" name="w3tc_config_export" class="button" value="<?php _e( 'Download', 'w3-total-cache' ); ?>" />
546
+ <br /><span class="description"><?php _e( 'Download the active settings file.', 'w3-total-cache' ); ?></span>
547
+ </td>
548
+ </tr>
549
+ <tr>
550
+ <th><?php _e( 'Reset configuration:', 'w3-total-cache' ); ?></th>
551
+ <td>
552
+ <input type="submit" name="w3tc_config_reset" class="button" value="<?php _e( 'Restore Default Settings', 'w3-total-cache' ); ?>" />
553
+ <br /><span class="description"><?php _e( 'Revert all settings to the defaults. Any settings staged in preview mode will not be modified.', 'w3-total-cache' ); ?></span>
554
+ </td>
555
+ </tr>
556
+ </table>
557
+ <?php Util_Ui::postbox_footer(); ?>
558
+ </div>
559
  </form>
560
  <?php include W3TC_INC_DIR . '/options/common/footer.php'; ?>
lib/Aws/Aws/Api/AbstractModel.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Base class that is used by most API shapes
6
+ */
7
+ abstract class AbstractModel implements \ArrayAccess
8
+ {
9
+ /** @var array */
10
+ protected $definition;
11
+
12
+ /** @var ShapeMap */
13
+ protected $shapeMap;
14
+
15
+ /**
16
+ * @param array $definition Service description
17
+ * @param ShapeMap $shapeMap Shapemap used for creating shapes
18
+ */
19
+ public function __construct(array $definition, ShapeMap $shapeMap)
20
+ {
21
+ $this->definition = $definition;
22
+ $this->shapeMap = $shapeMap;
23
+ }
24
+
25
+ public function toArray()
26
+ {
27
+ return $this->definition;
28
+ }
29
+
30
+ public function offsetGet($offset)
31
+ {
32
+ return isset($this->definition[$offset])
33
+ ? $this->definition[$offset] : null;
34
+ }
35
+
36
+ public function offsetSet($offset, $value)
37
+ {
38
+ $this->definition[$offset] = $value;
39
+ }
40
+
41
+ public function offsetExists($offset)
42
+ {
43
+ return isset($this->definition[$offset]);
44
+ }
45
+
46
+ public function offsetUnset($offset)
47
+ {
48
+ unset($this->definition[$offset]);
49
+ }
50
+
51
+ protected function shapeAt($key)
52
+ {
53
+ if (!isset($this->definition[$key])) {
54
+ throw new \InvalidArgumentException('Expected shape definition at '
55
+ . $key);
56
+ }
57
+
58
+ return $this->shapeFor($this->definition[$key]);
59
+ }
60
+
61
+ protected function shapeFor(array $definition)
62
+ {
63
+ return isset($definition['shape'])
64
+ ? $this->shapeMap->resolve($definition)
65
+ : Shape::create($definition, $this->shapeMap);
66
+ }
67
+ }
lib/Aws/Aws/Api/ApiProvider.php ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ use Aws\Exception\UnresolvedApiException;
5
+
6
+ /**
7
+ * API providers.
8
+ *
9
+ * An API provider is a function that accepts a type, service, and version and
10
+ * returns an array of API data on success or NULL if no API data can be created
11
+ * for the provided arguments.
12
+ *
13
+ * You can wrap your calls to an API provider with the
14
+ * {@see ApiProvider::resolve} method to ensure that API data is created. If the
15
+ * API data is not created, then the resolve() method will throw a
16
+ * {@see Aws\Exception\UnresolvedApiException}.
17
+ *
18
+ * use Aws\Api\ApiProvider;
19
+ * $provider = ApiProvider::defaultProvider();
20
+ * // Returns an array or NULL.
21
+ * $data = $provider('api', 's3', '2006-03-01');
22
+ * // Returns an array or throws.
23
+ * $data = ApiProvider::resolve($provider, 'api', 'elasticfood', '2020-01-01');
24
+ *
25
+ * You can compose multiple providers into a single provider using
26
+ * {@see Aws\or_chain}. This method accepts providers as arguments and
27
+ * returns a new function that will invoke each provider until a non-null value
28
+ * is returned.
29
+ *
30
+ * $a = ApiProvider::filesystem(sys_get_temp_dir() . '/aws-beta-models');
31
+ * $b = ApiProvider::manifest();
32
+ *
33
+ * $c = \Aws\or_chain($a, $b);
34
+ * $data = $c('api', 'betaservice', '2015-08-08'); // $a handles this.
35
+ * $data = $c('api', 's3', '2006-03-01'); // $b handles this.
36
+ * $data = $c('api', 'invalid', '2014-12-15'); // Neither handles this.
37
+ */
38
+ class ApiProvider
39
+ {
40
+ /** @var array A map of public API type names to their file suffix. */
41
+ private static $typeMap = [
42
+ 'api' => 'api-2',
43
+ 'paginator' => 'paginators-1',
44
+ 'waiter' => 'waiters-2',
45
+ 'docs' => 'docs-2',
46
+ ];
47
+
48
+ /** @var array API manifest */
49
+ private $manifest;
50
+
51
+ /** @var string The directory containing service models. */
52
+ private $modelsDir;
53
+
54
+ /**
55
+ * Resolves an API provider and ensures a non-null return value.
56
+ *
57
+ * @param callable $provider Provider function to invoke.
58
+ * @param string $type Type of data ('api', 'waiter', 'paginator').
59
+ * @param string $service Service name.
60
+ * @param string $version API version.
61
+ *
62
+ * @return array
63
+ * @throws UnresolvedApiException
64
+ */
65
+ public static function resolve(callable $provider, $type, $service, $version)
66
+ {
67
+ // Execute the provider and return the result, if there is one.
68
+ $result = $provider($type, $service, $version);
69
+ if (is_array($result)) {
70
+ if (!isset($result['metadata']['serviceIdentifier'])) {
71
+ $result['metadata']['serviceIdentifier'] = $service;
72
+ }
73
+ return $result;
74
+ }
75
+
76
+ // Throw an exception with a message depending on the inputs.
77
+ if (!isset(self::$typeMap[$type])) {
78
+ $msg = "The type must be one of: " . implode(', ', self::$typeMap);
79
+ } elseif ($service) {
80
+ $msg = "The {$service} service does not have version: {$version}.";
81
+ } else {
82
+ $msg = "You must specify a service name to retrieve its API data.";
83
+ }
84
+
85
+ throw new UnresolvedApiException($msg);
86
+ }
87
+
88
+ /**
89
+ * Default SDK API provider.
90
+ *
91
+ * This provider loads pre-built manifest data from the `data` directory.
92
+ *
93
+ * @return self
94
+ */
95
+ public static function defaultProvider()
96
+ {
97
+ return new self(__DIR__ . '/../data', \Aws\manifest());
98
+ }
99
+
100
+ /**
101
+ * Loads API data after resolving the version to the latest, compatible,
102
+ * available version based on the provided manifest data.
103
+ *
104
+ * Manifest data is essentially an associative array of service names to
105
+ * associative arrays of API version aliases.
106
+ *
107
+ * [
108
+ * ...
109
+ * 'ec2' => [
110
+ * 'latest' => '2014-10-01',
111
+ * '2014-10-01' => '2014-10-01',
112
+ * '2014-09-01' => '2014-10-01',
113
+ * '2014-06-15' => '2014-10-01',
114
+ * ...
115
+ * ],
116
+ * 'ecs' => [...],
117
+ * 'elasticache' => [...],
118
+ * ...
119
+ * ]
120
+ *
121
+ * @param string $dir Directory containing service models.
122
+ * @param array $manifest The API version manifest data.
123
+ *
124
+ * @return self
125
+ */
126
+ public static function manifest($dir, array $manifest)
127
+ {
128
+ return new self($dir, $manifest);
129
+ }
130
+
131
+ /**
132
+ * Loads API data from the specified directory.
133
+ *
134
+ * If "latest" is specified as the version, this provider must glob the
135
+ * directory to find which is the latest available version.
136
+ *
137
+ * @param string $dir Directory containing service models.
138
+ *
139
+ * @return self
140
+ * @throws \InvalidArgumentException if the provided `$dir` is invalid.
141
+ */
142
+ public static function filesystem($dir)
143
+ {
144
+ return new self($dir);
145
+ }
146
+
147
+ /**
148
+ * Retrieves a list of valid versions for the specified service.
149
+ *
150
+ * @param string $service Service name
151
+ *
152
+ * @return array
153
+ */
154
+ public function getVersions($service)
155
+ {
156
+ if (!isset($this->manifest)) {
157
+ $this->buildVersionsList($service);
158
+ }
159
+
160
+ if (!isset($this->manifest[$service]['versions'])) {
161
+ return [];
162
+ }
163
+
164
+ return array_values(array_unique($this->manifest[$service]['versions']));
165
+ }
166
+
167
+ /**
168
+ * Execute the the provider.
169
+ *
170
+ * @param string $type Type of data ('api', 'waiter', 'paginator').
171
+ * @param string $service Service name.
172
+ * @param string $version API version.
173
+ *
174
+ * @return array|null
175
+ */
176
+ public function __invoke($type, $service, $version)
177
+ {
178
+ // Resolve the type or return null.
179
+ if (isset(self::$typeMap[$type])) {
180
+ $type = self::$typeMap[$type];
181
+ } else {
182
+ return null;
183
+ }
184
+
185
+ // Resolve the version or return null.
186
+ if (!isset($this->manifest)) {
187
+ $this->buildVersionsList($service);
188
+ }
189
+
190
+ if (!isset($this->manifest[$service]['versions'][$version])) {
191
+ return null;
192
+ }
193
+
194
+ $version = $this->manifest[$service]['versions'][$version];
195
+ $path = "{$this->modelsDir}/{$service}/{$version}/{$type}.json";
196
+
197
+ try {
198
+ return \Aws\load_compiled_json($path);
199
+ } catch (\InvalidArgumentException $e) {
200
+ return null;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * @param string $modelsDir Directory containing service models.
206
+ * @param array $manifest The API version manifest data.
207
+ */
208
+ private function __construct($modelsDir, array $manifest = null)
209
+ {
210
+ $this->manifest = $manifest;
211
+ $this->modelsDir = rtrim($modelsDir, '/');
212
+ if (!is_dir($this->modelsDir)) {
213
+ throw new \InvalidArgumentException(
214
+ "The specified models directory, {$modelsDir}, was not found."
215
+ );
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Build the versions list for the specified service by globbing the dir.
221
+ */
222
+ private function buildVersionsList($service)
223
+ {
224
+ $dir = "{$this->modelsDir}/{$service}/";
225
+
226
+ if (!is_dir($dir)) {
227
+ return;
228
+ }
229
+
230
+ // Get versions, remove . and .., and sort in descending order.
231
+ $results = array_diff(scandir($dir, SCANDIR_SORT_DESCENDING), ['..', '.']);
232
+
233
+ if (!$results) {
234
+ $this->manifest[$service] = ['versions' => []];
235
+ } else {
236
+ $this->manifest[$service] = [
237
+ 'versions' => [
238
+ 'latest' => $results[0]
239
+ ]
240
+ ];
241
+ $this->manifest[$service]['versions'] += array_combine($results, $results);
242
+ }
243
+ }
244
+ }
lib/Aws/Aws/Api/DateTimeResult.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * DateTime overrides that make DateTime work more seamlessly as a string,
6
+ * with JSON documents, and with JMESPath.
7
+ */
8
+ class DateTimeResult extends \DateTime implements \JsonSerializable
9
+ {
10
+ /**
11
+ * Create a new DateTimeResult from a unix timestamp.
12
+ *
13
+ * @param $unixTimestamp
14
+ *
15
+ * @return DateTimeResult
16
+ */
17
+ public static function fromEpoch($unixTimestamp)
18
+ {
19
+ return new self(gmdate('c', $unixTimestamp));
20
+ }
21
+
22
+ /**
23
+ * Serialize the DateTimeResult as an ISO 8601 date string.
24
+ *
25
+ * @return string
26
+ */
27
+ public function __toString()
28
+ {
29
+ return $this->format('c');
30
+ }
31
+
32
+ /**
33
+ * Serialize the date as an ISO 8601 date when serializing as JSON.
34
+ *
35
+ * @return mixed|string
36
+ */
37
+ public function jsonSerialize()
38
+ {
39
+ return (string) $this;
40
+ }
41
+ }
lib/Aws/Aws/Api/DocModel.php ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Encapsulates the documentation strings for a given service-version and
6
+ * provides methods for extracting the desired parts related to a service,
7
+ * operation, error, or shape (i.e., parameter).
8
+ */
9
+ class DocModel
10
+ {
11
+ /** @var array */
12
+ private $docs;
13
+
14
+ /**
15
+ * @param array $docs
16
+ *
17
+ * @throws \RuntimeException
18
+ */
19
+ public function __construct(array $docs)
20
+ {
21
+ if (!extension_loaded('tidy')) {
22
+ throw new \RuntimeException('The "tidy" PHP extension is required.');
23
+ }
24
+
25
+ $this->docs = $docs;
26
+ }
27
+
28
+ /**
29
+ * Convert the doc model to an array.
30
+ *
31
+ * @return array
32
+ */
33
+ public function toArray()
34
+ {
35
+ return $this->docs;
36
+ }
37
+
38
+ /**
39
+ * Retrieves documentation about the service.
40
+ *
41
+ * @return null|string
42
+ */
43
+ public function getServiceDocs()
44
+ {
45
+ return isset($this->docs['service']) ? $this->docs['service'] : null;
46
+ }
47
+
48
+ /**
49
+ * Retrieves documentation about an operation.
50
+ *
51
+ * @param string $operation Name of the operation
52
+ *
53
+ * @return null|string
54
+ */
55
+ public function getOperationDocs($operation)
56
+ {
57
+ return isset($this->docs['operations'][$operation])
58
+ ? $this->docs['operations'][$operation]
59
+ : null;
60
+ }
61
+
62
+ /**
63
+ * Retrieves documentation about an error.
64
+ *
65
+ * @param string $error Name of the error
66
+ *
67
+ * @return null|string
68
+ */
69
+ public function getErrorDocs($error)
70
+ {
71
+ return isset($this->docs['shapes'][$error]['base'])
72
+ ? $this->docs['shapes'][$error]['base']
73
+ : null;
74
+ }
75
+
76
+ /**
77
+ * Retrieves documentation about a shape, specific to the context.
78
+ *
79
+ * @param string $shapeName Name of the shape.
80
+ * @param string $parentName Name of the parent/context shape.
81
+ * @param string $ref Name used by the context to reference the shape.
82
+ *
83
+ * @return null|string
84
+ */
85
+ public function getShapeDocs($shapeName, $parentName, $ref)
86
+ {
87
+ if (!isset($this->docs['shapes'][$shapeName])) {
88
+ return '';
89
+ }
90
+
91
+ $result = '';
92
+ $d = $this->docs['shapes'][$shapeName];
93
+ if (isset($d['refs']["{$parentName}\$${ref}"])) {
94
+ $result = $d['refs']["{$parentName}\$${ref}"];
95
+ } elseif (isset($d['base'])) {
96
+ $result = $d['base'];
97
+ }
98
+
99
+ if (isset($d['append'])) {
100
+ $result .= $d['append'];
101
+ }
102
+
103
+ return $this->clean($result);
104
+ }
105
+
106
+ private function clean($content)
107
+ {
108
+ if (!$content) {
109
+ return '';
110
+ }
111
+
112
+ $tidy = new \Tidy();
113
+ $tidy->parseString($content, [
114
+ 'indent' => true,
115
+ 'doctype' => 'omit',
116
+ 'output-html' => true,
117
+ 'show-body-only' => true,
118
+ 'drop-empty-paras' => true,
119
+ 'drop-font-tags' => true,
120
+ 'drop-proprietary-attributes' => true,
121
+ 'hide-comments' => true,
122
+ 'logical-emphasis' => true
123
+ ]);
124
+ $tidy->cleanRepair();
125
+
126
+ return (string) $content;
127
+ }
128
+ }
lib/Aws/Aws/Api/ErrorParser/JsonParserTrait.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\ErrorParser;
3
+
4
+ use Aws\Api\Parser\PayloadParserTrait;
5
+ use Psr\Http\Message\ResponseInterface;
6
+
7
+ /**
8
+ * Provides basic JSON error parsing functionality.
9
+ */
10
+ trait JsonParserTrait
11
+ {
12
+ use PayloadParserTrait;
13
+
14
+ private function genericHandler(ResponseInterface $response)
15
+ {
16
+ $code = (string) $response->getStatusCode();
17
+
18
+ return [
19
+ 'request_id' => (string) $response->getHeaderLine('x-amzn-requestid'),
20
+ 'code' => null,
21
+ 'message' => null,
22
+ 'type' => $code[0] == '4' ? 'client' : 'server',
23
+ 'parsed' => $this->parseJson($response->getBody(), $response)
24
+ ];
25
+ }
26
+ }
lib/Aws/Aws/Api/ErrorParser/JsonRpcErrorParser.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\ErrorParser;
3
+
4
+ use Psr\Http\Message\ResponseInterface;
5
+
6
+ /**
7
+ * Parsers JSON-RPC errors.
8
+ */
9
+ class JsonRpcErrorParser
10
+ {
11
+ use JsonParserTrait;
12
+
13
+ public function __invoke(ResponseInterface $response)
14
+ {
15
+ $data = $this->genericHandler($response);
16
+ // Make the casing consistent across services.
17
+ if ($data['parsed']) {
18
+ $data['parsed'] = array_change_key_case($data['parsed']);
19
+ }
20
+
21
+ if (isset($data['parsed']['__type'])) {
22
+ $parts = explode('#', $data['parsed']['__type']);
23
+ $data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
24
+ $data['message'] = isset($data['parsed']['message'])
25
+ ? $data['parsed']['message']
26
+ : null;
27
+ }
28
+
29
+ return $data;
30
+ }
31
+ }
lib/Aws/Aws/Api/ErrorParser/RestJsonErrorParser.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\ErrorParser;
3
+
4
+ use Psr\Http\Message\ResponseInterface;
5
+
6
+ /**
7
+ * Parses JSON-REST errors.
8
+ */
9
+ class RestJsonErrorParser
10
+ {
11
+ use JsonParserTrait;
12
+
13
+ public function __invoke(ResponseInterface $response)
14
+ {
15
+ $data = $this->genericHandler($response);
16
+
17
+ // Merge in error data from the JSON body
18
+ if ($json = $data['parsed']) {
19
+ $data = array_replace($data, $json);
20
+ }
21
+
22
+ // Correct error type from services like Amazon Glacier
23
+ if (!empty($data['type'])) {
24
+ $data['type'] = strtolower($data['type']);
25
+ }
26
+
27
+ // Retrieve the error code from services like Amazon Elastic Transcoder
28
+ if ($code = $response->getHeaderLine('x-amzn-errortype')) {
29
+ $colon = strpos($code, ':');
30
+ $data['code'] = $colon ? substr($code, 0, $colon) : $code;
31
+ }
32
+
33
+ return $data;
34
+ }
35
+ }
lib/Aws/Aws/Api/ErrorParser/XmlErrorParser.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\ErrorParser;
3
+
4
+ use Aws\Api\Parser\PayloadParserTrait;
5
+ use Psr\Http\Message\ResponseInterface;
6
+
7
+ /**
8
+ * Parses XML errors.
9
+ */
10
+ class XmlErrorParser
11
+ {
12
+ use PayloadParserTrait;
13
+
14
+ public function __invoke(ResponseInterface $response)
15
+ {
16
+ $code = (string) $response->getStatusCode();
17
+
18
+ $data = [
19
+ 'type' => $code[0] == '4' ? 'client' : 'server',
20
+ 'request_id' => null,
21
+ 'code' => null,
22
+ 'message' => null,
23
+ 'parsed' => null
24
+ ];
25
+
26
+ $body = $response->getBody();
27
+ if ($body->getSize() > 0) {
28
+ $this->parseBody($this->parseXml($body, $response), $data);
29
+ } else {
30
+ $this->parseHeaders($response, $data);
31
+ }
32
+
33
+ return $data;
34
+ }
35
+
36
+ private function parseHeaders(ResponseInterface $response, array &$data)
37
+ {
38
+ if ($response->getStatusCode() == '404') {
39
+ $data['code'] = 'NotFound';
40
+ }
41
+
42
+ $data['message'] = $response->getStatusCode() . ' '
43
+ . $response->getReasonPhrase();
44
+
45
+ if ($requestId = $response->getHeaderLine('x-amz-request-id')) {
46
+ $data['request_id'] = $requestId;
47
+ $data['message'] .= " (Request-ID: $requestId)";
48
+ }
49
+ }
50
+
51
+ private function parseBody(\SimpleXMLElement $body, array &$data)
52
+ {
53
+ $data['parsed'] = $body;
54
+
55
+ $namespaces = $body->getDocNamespaces();
56
+ if (!isset($namespaces[''])) {
57
+ $prefix = '';
58
+ } else {
59
+ // Account for the default namespace being defined and PHP not
60
+ // being able to handle it :(.
61
+ $body->registerXPathNamespace('ns', $namespaces['']);
62
+ $prefix = 'ns:';
63
+ }
64
+
65
+ if ($tempXml = $body->xpath("//{$prefix}Code[1]")) {
66
+ $data['code'] = (string) $tempXml[0];
67
+ }
68
+
69
+ if ($tempXml = $body->xpath("//{$prefix}Message[1]")) {
70
+ $data['message'] = (string) $tempXml[0];
71
+ }
72
+
73
+ $tempXml = $body->xpath("//{$prefix}RequestId[1]");
74
+ if (empty($tempXml)) {
75
+ $tempXml = $body->xpath("//{$prefix}RequestID[1]");
76
+ }
77
+
78
+ if (isset($tempXml[0])) {
79
+ $data['request_id'] = (string) $tempXml[0];
80
+ }
81
+ }
82
+ }
lib/Aws/Aws/Api/ListShape.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Represents a list shape.
6
+ */
7
+ class ListShape extends Shape
8
+ {
9
+ private $member;
10
+
11
+ public function __construct(array $definition, ShapeMap $shapeMap)
12
+ {
13
+ $definition['type'] = 'list';
14
+ parent::__construct($definition, $shapeMap);
15
+ }
16
+
17
+ /**
18
+ * @return Shape
19
+ * @throws \RuntimeException if no member is specified
20
+ */
21
+ public function getMember()
22
+ {
23
+ if (!$this->member) {
24
+ if (!isset($this->definition['member'])) {
25
+ throw new \RuntimeException('No member attribute specified');
26
+ }
27
+ $this->member = Shape::create(
28
+ $this->definition['member'],
29
+ $this->shapeMap
30
+ );
31
+ }
32
+
33
+ return $this->member;
34
+ }
35
+ }
lib/Aws/Aws/Api/MapShape.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Represents a map shape.
6
+ */
7
+ class MapShape extends Shape
8
+ {
9
+ /** @var Shape */
10
+ private $value;
11
+
12
+ /** @var Shape */
13
+ private $key;
14
+
15
+ public function __construct(array $definition, ShapeMap $shapeMap)
16
+ {
17
+ $definition['type'] = 'map';
18
+ parent::__construct($definition, $shapeMap);
19
+ }
20
+
21
+ /**
22
+ * @return Shape
23
+ * @throws \RuntimeException if no value is specified
24
+ */
25
+ public function getValue()
26
+ {
27
+ if (!$this->value) {
28
+ if (!isset($this->definition['value'])) {
29
+ throw new \RuntimeException('No value specified');
30
+ }
31
+
32
+ $this->value = Shape::create(
33
+ $this->definition['value'],
34
+ $this->shapeMap
35
+ );
36
+ }
37
+
38
+ return $this->value;
39
+ }
40
+
41
+ /**
42
+ * @return Shape
43
+ */
44
+ public function getKey()
45
+ {
46
+ if (!$this->key) {
47
+ $this->key = isset($this->definition['key'])
48
+ ? Shape::create($this->definition['key'], $this->shapeMap)
49
+ : new Shape(['type' => 'string'], $this->shapeMap);
50
+ }
51
+
52
+ return $this->key;
53
+ }
54
+ }
lib/Aws/Aws/Api/Operation.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Represents an API operation.
6
+ */
7
+ class Operation extends AbstractModel
8
+ {
9
+ private $input;
10
+ private $output;
11
+ private $errors;
12
+
13
+ public function __construct(array $definition, ShapeMap $shapeMap)
14
+ {
15
+ $definition['type'] = 'structure';
16
+
17
+ if (!isset($definition['http']['method'])) {
18
+ $definition['http']['method'] = 'POST';
19
+ }
20
+
21
+ if (!isset($definition['http']['requestUri'])) {
22
+ $definition['http']['requestUri'] = '/';
23
+ }
24
+
25
+ parent::__construct($definition, $shapeMap);
26
+ }
27
+
28
+ /**
29
+ * Returns an associative array of the HTTP attribute of the operation:
30
+ *
31
+ * - method: HTTP method of the operation
32
+ * - requestUri: URI of the request (can include URI template placeholders)
33
+ *
34
+ * @return array
35
+ */
36
+ public function getHttp()
37
+ {
38
+ return $this->definition['http'];
39
+ }
40
+
41
+ /**
42
+ * Get the input shape of the operation.
43
+ *
44
+ * @return StructureShape
45
+ */
46
+ public function getInput()
47
+ {
48
+ if (!$this->input) {
49
+ if ($input = $this['input']) {
50
+ $this->input = $this->shapeFor($input);
51
+ } else {
52
+ $this->input = new StructureShape([], $this->shapeMap);
53
+ }
54
+ }
55
+
56
+ return $this->input;
57
+ }
58
+
59
+ /**
60
+ * Get the output shape of the operation.
61
+ *
62
+ * @return StructureShape
63
+ */
64
+ public function getOutput()
65
+ {
66
+ if (!$this->output) {
67
+ if ($output = $this['output']) {
68
+ $this->output = $this->shapeFor($output);
69
+ } else {
70
+ $this->output = new StructureShape([], $this->shapeMap);
71
+ }
72
+ }
73
+
74
+ return $this->output;
75
+ }
76
+
77
+ /**
78
+ * Get an array of operation error shapes.
79
+ *
80
+ * @return Shape[]
81
+ */
82
+ public function getErrors()
83
+ {
84
+ if ($this->errors === null) {
85
+ if ($errors = $this['errors']) {
86
+ foreach ($errors as $key => $error) {
87
+ $errors[$key] = $this->shapeFor($error);
88
+ }
89
+ $this->errors = $errors;
90
+ } else {
91
+ $this->errors = [];
92
+ }
93
+ }
94
+
95
+ return $this->errors;
96
+ }
97
+ }
lib/Aws/Aws/Api/Parser/AbstractParser.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\Api\StructureShape;
6
+ use Aws\CommandInterface;
7
+ use Aws\ResultInterface;
8
+ use Psr\Http\Message\ResponseInterface;
9
+ use Psr\Http\Message\StreamInterface;
10
+
11
+ /**
12
+ * @internal
13
+ */
14
+ abstract class AbstractParser
15
+ {
16
+ /** @var \Aws\Api\Service Representation of the service API*/
17
+ protected $api;
18
+
19
+ /** @var callable */
20
+ protected $parser;
21
+
22
+ /**
23
+ * @param Service $api Service description.
24
+ */
25
+ public function __construct(Service $api)
26
+ {
27
+ $this->api = $api;
28
+ }
29
+
30
+ /**
31
+ * @param CommandInterface $command Command that was executed.
32
+ * @param ResponseInterface $response Response that was received.
33
+ *
34
+ * @return ResultInterface
35
+ */
36
+ abstract public function __invoke(
37
+ CommandInterface $command,
38
+ ResponseInterface $response
39
+ );
40
+
41
+ abstract public function parseMemberFromStream(
42
+ StreamInterface $stream,
43
+ StructureShape $member,
44
+ $response
45
+ );
46
+ }
lib/Aws/Aws/Api/Parser/AbstractRestParser.php ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\DateTimeResult;
5
+ use Aws\Api\Shape;
6
+ use Aws\Api\StructureShape;
7
+ use Aws\Result;
8
+ use Aws\CommandInterface;
9
+ use Psr\Http\Message\ResponseInterface;
10
+
11
+ /**
12
+ * @internal
13
+ */
14
+ abstract class AbstractRestParser extends AbstractParser
15
+ {
16
+ use PayloadParserTrait;
17
+ /**
18
+ * Parses a payload from a response.
19
+ *
20
+ * @param ResponseInterface $response Response to parse.
21
+ * @param StructureShape $member Member to parse
22
+ * @param array $result Result value
23
+ *
24
+ * @return mixed
25
+ */
26
+ abstract protected function payload(
27
+ ResponseInterface $response,
28
+ StructureShape $member,
29
+ array &$result
30
+ );
31
+
32
+ public function __invoke(
33
+ CommandInterface $command,
34
+ ResponseInterface $response
35
+ ) {
36
+ $output = $this->api->getOperation($command->getName())->getOutput();
37
+ $result = [];
38
+
39
+ if ($payload = $output['payload']) {
40
+ $this->extractPayload($payload, $output, $response, $result);
41
+ }
42
+
43
+ foreach ($output->getMembers() as $name => $member) {
44
+ switch ($member['location']) {
45
+ case 'header':
46
+ $this->extractHeader($name, $member, $response, $result);
47
+ break;
48
+ case 'headers':
49
+ $this->extractHeaders($name, $member, $response, $result);
50
+ break;
51
+ case 'statusCode':
52
+ $this->extractStatus($name, $response, $result);
53
+ break;
54
+ }
55
+ }
56
+
57
+ if (!$payload
58
+ && $response->getBody()->getSize() > 0
59
+ && count($output->getMembers()) > 0
60
+ ) {
61
+ // if no payload was found, then parse the contents of the body
62
+ $this->payload($response, $output, $result);
63
+ }
64
+
65
+ return new Result($result);
66
+ }
67
+
68
+ private function extractPayload(
69
+ $payload,
70
+ StructureShape $output,
71
+ ResponseInterface $response,
72
+ array &$result
73
+ ) {
74
+ $member = $output->getMember($payload);
75
+
76
+ if (!empty($member['eventstream'])) {
77
+ $result[$payload] = new EventParsingIterator(
78
+ $response->getBody(),
79
+ $member,
80
+ $this
81
+ );
82
+ } else if ($member instanceof StructureShape) {
83
+ // Structure members parse top-level data into a specific key.
84
+ $result[$payload] = [];
85
+ $this->payload($response, $member, $result[$payload]);
86
+ } else {
87
+ // Streaming data is just the stream from the response body.
88
+ $result[$payload] = $response->getBody();
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Extract a single header from the response into the result.
94
+ */
95
+ private function extractHeader(
96
+ $name,
97
+ Shape $shape,
98
+ ResponseInterface $response,
99
+ &$result
100
+ ) {
101
+ $value = $response->getHeaderLine($shape['locationName'] ?: $name);
102
+
103
+ switch ($shape->getType()) {
104
+ case 'float':
105
+ case 'double':
106
+ $value = (float) $value;
107
+ break;
108
+ case 'long':
109
+ $value = (int) $value;
110
+ break;
111
+ case 'boolean':
112
+ $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
113
+ break;
114
+ case 'blob':
115
+ $value = base64_decode($value);
116
+ break;
117
+ case 'timestamp':
118
+ try {
119
+ if (!empty($shape['timestampFormat'])
120
+ && $shape['timestampFormat'] === 'unixTimestamp') {
121
+ $value = DateTimeResult::fromEpoch($value);
122
+ }
123
+ $value = new DateTimeResult($value);
124
+ break;
125
+ } catch (\Exception $e) {
126
+ // If the value cannot be parsed, then do not add it to the
127
+ // output structure.
128
+ return;
129
+ }
130
+ case 'string':
131
+ if ($shape['jsonvalue']) {
132
+ $value = $this->parseJson(base64_decode($value), $response);
133
+ }
134
+ break;
135
+ }
136
+
137
+ $result[$name] = $value;
138
+ }
139
+
140
+ /**
141
+ * Extract a map of headers with an optional prefix from the response.
142
+ */
143
+ private function extractHeaders(
144
+ $name,
145
+ Shape $shape,
146
+ ResponseInterface $response,
147
+ &$result
148
+ ) {
149
+ // Check if the headers are prefixed by a location name
150
+ $result[$name] = [];
151
+ $prefix = $shape['locationName'];
152
+ $prefixLen = strlen($prefix);
153
+
154
+ foreach ($response->getHeaders() as $k => $values) {
155
+ if (!$prefixLen) {
156
+ $result[$name][$k] = implode(', ', $values);
157
+ } elseif (stripos($k, $prefix) === 0) {
158
+ $result[$name][substr($k, $prefixLen)] = implode(', ', $values);
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Places the status code of the response into the result array.
165
+ */
166
+ private function extractStatus(
167
+ $name,
168
+ ResponseInterface $response,
169
+ array &$result
170
+ ) {
171
+ $result[$name] = (int) $response->getStatusCode();
172
+ }
173
+ }
lib/Aws/Aws/Api/Parser/Crc32ValidatingParser.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\StructureShape;
5
+ use Aws\CommandInterface;
6
+ use Aws\Exception\AwsException;
7
+ use Psr\Http\Message\ResponseInterface;
8
+ use Psr\Http\Message\StreamInterface;
9
+ use GuzzleHttp\Psr7;
10
+
11
+ /**
12
+ * @internal Decorates a parser and validates the x-amz-crc32 header.
13
+ */
14
+ class Crc32ValidatingParser extends AbstractParser
15
+ {
16
+ /**
17
+ * @param callable $parser Parser to wrap.
18
+ */
19
+ public function __construct(callable $parser)
20
+ {
21
+ $this->parser = $parser;
22
+ }
23
+
24
+ public function __invoke(
25
+ CommandInterface $command,
26
+ ResponseInterface $response
27
+ ) {
28
+ if ($expected = $response->getHeaderLine('x-amz-crc32')) {
29
+ $hash = hexdec(Psr7\hash($response->getBody(), 'crc32b'));
30
+ if ($expected != $hash) {
31
+ throw new AwsException(
32
+ "crc32 mismatch. Expected {$expected}, found {$hash}.",
33
+ $command,
34
+ [
35
+ 'code' => 'ClientChecksumMismatch',
36
+ 'connection_error' => true,
37
+ 'response' => $response
38
+ ]
39
+ );
40
+ }
41
+ }
42
+
43
+ $fn = $this->parser;
44
+ return $fn($command, $response);
45
+ }
46
+
47
+ public function parseMemberFromStream(
48
+ StreamInterface $stream,
49
+ StructureShape $member,
50
+ $response
51
+ ) {
52
+ return $this->parser->parseMemberFromStream($stream, $member, $response);
53
+ }
54
+ }
lib/Aws/Aws/Api/Parser/DecodingEventStreamIterator.php ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws\Api\Parser;
4
+
5
+ use \Iterator;
6
+ use Aws\Api\DateTimeResult;
7
+ use GuzzleHttp\Psr7;
8
+ use Psr\Http\Message\StreamInterface;
9
+ use Aws\Api\Parser\Exception\ParserException;
10
+
11
+ /**
12
+ * @internal Implements a decoder for a binary encoded event stream that will
13
+ * decode, validate, and provide individual events from the stream.
14
+ */
15
+ class DecodingEventStreamIterator implements Iterator
16
+ {
17
+ const HEADERS = 'headers';
18
+ const PAYLOAD = 'payload';
19
+
20
+ const LENGTH_TOTAL = 'total_length';
21
+ const LENGTH_HEADERS = 'headers_length';
22
+
23
+ const CRC_PRELUDE = 'prelude_crc';
24
+
25
+ const BYTES_PRELUDE = 12;
26
+ const BYTES_TRAILING = 4;
27
+
28
+ private static $preludeFormat = [
29
+ self::LENGTH_TOTAL => 'decodeUint32',
30
+ self::LENGTH_HEADERS => 'decodeUint32',
31
+ self::CRC_PRELUDE => 'decodeUint32',
32
+ ];
33
+
34
+ private static $lengthFormatMap = [
35
+ 1 => 'decodeUint8',
36
+ 2 => 'decodeUint16',
37
+ 4 => 'decodeUint32',
38
+ 8 => 'decodeUint64',
39
+ ];
40
+
41
+ private static $headerTypeMap = [
42
+ 0 => 'decodeBooleanTrue',
43
+ 1 => 'decodeBooleanFalse',
44
+ 2 => 'decodeInt8',
45
+ 3 => 'decodeInt16',
46
+ 4 => 'decodeInt32',
47
+ 5 => 'decodeInt64',
48
+ 6 => 'decodeBytes',
49
+ 7 => 'decodeString',
50
+ 8 => 'decodeTimestamp',
51
+ 9 => 'decodeUuid',
52
+ ];
53
+
54
+ /** @var StreamInterface Stream of eventstream shape to parse. */
55
+ private $stream;
56
+
57
+ /** @var array Currently parsed event. */
58
+ private $currentEvent;
59
+
60
+ /** @var int Current in-order event key. */
61
+ private $key;
62
+
63
+ /** @var resource|HashContext CRC32 hash context for event validation */
64
+ private $hashContext;
65
+
66
+ /** @var int $currentPosition */
67
+ private $currentPosition;
68
+
69
+ /**
70
+ * DecodingEventStreamIterator constructor.
71
+ *
72
+ * @param StreamInterface $stream
73
+ */
74
+ public function __construct(StreamInterface $stream)
75
+ {
76
+ $this->stream = $stream;
77
+ $this->rewind();
78
+ }
79
+
80
+ private function parseHeaders($headerBytes)
81
+ {
82
+ $headers = [];
83
+ $bytesRead = 0;
84
+
85
+ while ($bytesRead < $headerBytes) {
86
+ list($key, $numBytes) = $this->decodeString(1);
87
+ $bytesRead += $numBytes;
88
+
89
+ list($type, $numBytes) = $this->decodeUint8();
90
+ $bytesRead += $numBytes;
91
+
92
+ $f = self::$headerTypeMap[$type];
93
+ list($value, $numBytes) = $this->{$f}();
94
+ $bytesRead += $numBytes;
95
+
96
+ if (isset($headers[$key])) {
97
+ throw new ParserException('Duplicate key in event headers.');
98
+ }
99
+ $headers[$key] = $value;
100
+ }
101
+
102
+ return [$headers, $bytesRead];
103
+ }
104
+
105
+ private function parsePrelude()
106
+ {
107
+ $prelude = [];
108
+ $bytesRead = 0;
109
+
110
+ $calculatedCrc = null;
111
+ foreach (self::$preludeFormat as $key => $decodeFunction) {
112
+ if ($key === self::CRC_PRELUDE) {
113
+ $hashCopy = hash_copy($this->hashContext);
114
+ $calculatedCrc = hash_final($this->hashContext, true);
115
+ $this->hashContext = $hashCopy;
116
+ }
117
+ list($value, $numBytes) = $this->{$decodeFunction}();
118
+ $bytesRead += $numBytes;
119
+
120
+ $prelude[$key] = $value;
121
+ }
122
+
123
+ if (unpack('N', $calculatedCrc)[1] !== $prelude[self::CRC_PRELUDE]) {
124
+ throw new ParserException('Prelude checksum mismatch.');
125
+ }
126
+
127
+ return [$prelude, $bytesRead];
128
+ }
129
+
130
+ private function parseEvent()
131
+ {
132
+ $event = [];
133
+
134
+ if ($this->stream->tell() < $this->stream->getSize()) {
135
+ $this->hashContext = hash_init('crc32b');
136
+
137
+ $bytesLeft = $this->stream->getSize() - $this->stream->tell();
138
+ list($prelude, $numBytes) = $this->parsePrelude();
139
+ if ($prelude[self::LENGTH_TOTAL] > $bytesLeft) {
140
+ throw new ParserException('Message length too long.');
141
+ }
142
+ $bytesLeft -= $numBytes;
143
+
144
+ if ($prelude[self::LENGTH_HEADERS] > $bytesLeft) {
145
+ throw new ParserException('Headers length too long.');
146
+ }
147
+
148
+ list(
149
+ $event[self::HEADERS],
150
+ $numBytes
151
+ ) = $this->parseHeaders($prelude[self::LENGTH_HEADERS]);
152
+
153
+ $event[self::PAYLOAD] = Psr7\stream_for(
154
+ $this->readAndHashBytes(
155
+ $prelude[self::LENGTH_TOTAL] - self::BYTES_PRELUDE
156
+ - $numBytes - self::BYTES_TRAILING
157
+ )
158
+ );
159
+
160
+ $calculatedCrc = hash_final($this->hashContext, true);
161
+ $messageCrc = $this->stream->read(4);
162
+ if ($calculatedCrc !== $messageCrc) {
163
+ throw new ParserException('Message checksum mismatch.');
164
+ }
165
+ }
166
+
167
+ return $event;
168
+ }
169
+
170
+ // Iterator Functionality
171
+
172
+ /**
173
+ * @return array
174
+ */
175
+ public function current()
176
+ {
177
+ return $this->currentEvent;
178
+ }
179
+
180
+ /**
181
+ * @return int
182
+ */
183
+ public function key()
184
+ {
185
+ return $this->key;
186
+ }
187
+
188
+ public function next()
189
+ {
190
+ $this->currentPosition = $this->stream->tell();
191
+ if ($this->valid()) {
192
+ $this->key++;
193
+ $this->currentEvent = $this->parseEvent();
194
+ }
195
+ }
196
+
197
+ public function rewind()
198
+ {
199
+ $this->stream->rewind();
200
+ $this->key = 0;
201
+ $this->currentPosition = 0;
202
+ $this->currentEvent = $this->parseEvent();
203
+ }
204
+
205
+ /**
206
+ * @return bool
207
+ */
208
+ public function valid()
209
+ {
210
+ return $this->currentPosition < $this->stream->getSize();
211
+ }
212
+
213
+ // Decoding Utilities
214
+
215
+ private function readAndHashBytes($num)
216
+ {
217
+ $bytes = $this->stream->read($num);
218
+ hash_update($this->hashContext, $bytes);
219
+ return $bytes;
220
+ }
221
+
222
+ private function decodeBooleanTrue()
223
+ {
224
+ return [true, 0];
225
+ }
226
+
227
+ private function decodeBooleanFalse()
228
+ {
229
+ return [false, 0];
230
+ }
231
+
232
+ private function uintToInt($val, $size)
233
+ {
234
+ $signedCap = pow(2, $size - 1);
235
+ if ($val > $signedCap) {
236
+ $val -= (2 * $signedCap);
237
+ }
238
+ return $val;
239
+ }
240
+
241
+ private function decodeInt8()
242
+ {
243
+ $val = (int)unpack('C', $this->readAndHashBytes(1))[1];
244
+ return [$this->uintToInt($val, 8), 1];
245
+ }
246
+
247
+ private function decodeUint8()
248
+ {
249
+ return [unpack('C', $this->readAndHashBytes(1))[1], 1];
250
+ }
251
+
252
+ private function decodeInt16()
253
+ {
254
+ $val = (int)unpack('n', $this->readAndHashBytes(2))[1];
255
+ return [$this->uintToInt($val, 16), 2];
256
+ }
257
+
258
+ private function decodeUint16()
259
+ {
260
+ return [unpack('n', $this->readAndHashBytes(2))[1], 2];
261
+ }
262
+
263
+ private function decodeInt32()
264
+ {
265
+ $val = (int)unpack('N', $this->readAndHashBytes(4))[1];
266
+ return [$this->uintToInt($val, 32), 4];
267
+ }
268
+
269
+ private function decodeUint32()
270
+ {
271
+ return [unpack('N', $this->readAndHashBytes(4))[1], 4];
272
+ }
273
+
274
+ private function decodeInt64()
275
+ {
276
+ $val = $this->unpackInt64($this->readAndHashBytes(8))[1];
277
+ return [$this->uintToInt($val, 64), 8];
278
+ }
279
+
280
+ private function decodeUint64()
281
+ {
282
+ return [$this->unpackInt64($this->readAndHashBytes(8))[1], 8];
283
+ }
284
+
285
+ private function unpackInt64($bytes)
286
+ {
287
+ if (version_compare(PHP_VERSION, '5.6.3', '<')) {
288
+ $d = unpack('N2', $bytes);
289
+ return [1 => $d[1] << 32 | $d[2]];
290
+ }
291
+ return unpack('J', $bytes);
292
+ }
293
+
294
+ private function decodeBytes($lengthBytes=2)
295
+ {
296
+ if (!isset(self::$lengthFormatMap[$lengthBytes])) {
297
+ throw new ParserException('Undefined variable length format.');
298
+ }
299
+ $f = self::$lengthFormatMap[$lengthBytes];
300
+ list($len, $bytes) = $this->{$f}();
301
+ return [$this->readAndHashBytes($len), $len + $bytes];
302
+ }
303
+
304
+ private function decodeString($lengthBytes=2)
305
+ {
306
+ if (!isset(self::$lengthFormatMap[$lengthBytes])) {
307
+ throw new ParserException('Undefined variable length format.');
308
+ }
309
+ $f = self::$lengthFormatMap[$lengthBytes];
310
+ list($len, $bytes) = $this->{$f}();
311
+ return [$this->readAndHashBytes($len), $len + $bytes];
312
+ }
313
+
314
+ private function decodeTimestamp()
315
+ {
316
+ list($val, $bytes) = $this->decodeInt64();
317
+ return [
318
+ DateTimeResult::createFromFormat('U.u', $val / 1000),
319
+ $bytes
320
+ ];
321
+ }
322
+
323
+ private function decodeUuid()
324
+ {
325
+ $val = unpack('H32', $this->readAndHashBytes(16))[1];
326
+ return [
327
+ substr($val, 0, 8) . '-'
328
+ . substr($val, 8, 4) . '-'
329
+ . substr($val, 12, 4) . '-'
330
+ . substr($val, 16, 4) . '-'
331
+ . substr($val, 20, 12),
332
+ 16
333
+ ];
334
+ }
335
+ }
lib/Aws/Aws/Api/Parser/EventParsingIterator.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws\Api\Parser;
4
+
5
+ use \Iterator;
6
+ use Aws\Exception\EventStreamDataException;
7
+ use Aws\Api\Parser\Exception\ParserException;
8
+ use Aws\Api\StructureShape;
9
+ use Psr\Http\Message\StreamInterface;
10
+
11
+ /**
12
+ * @internal Implements a decoder for a binary encoded event stream that will
13
+ * decode, validate, and provide individual events from the stream.
14
+ */
15
+ class EventParsingIterator implements Iterator
16
+ {
17
+ /** @var StreamInterface */
18
+ private $decodingIterator;
19
+
20
+ /** @var StructureShape */
21
+ private $shape;
22
+
23
+ /** @var AbstractParser */
24
+ private $parser;
25
+
26
+ public function __construct(
27
+ StreamInterface $stream,
28
+ StructureShape $shape,
29
+ AbstractParser $parser
30
+ ) {
31
+ $this->decodingIterator = new DecodingEventStreamIterator($stream);
32
+ $this->shape = $shape;
33
+ $this->parser = $parser;
34
+ }
35
+
36
+ public function current()
37
+ {
38
+ return $this->parseEvent($this->decodingIterator->current());
39
+ }
40
+
41
+ public function key()
42
+ {
43
+ return $this->decodingIterator->key();
44
+ }
45
+
46
+ public function next()
47
+ {
48
+ $this->decodingIterator->next();
49
+ }
50
+
51
+ public function rewind()
52
+ {
53
+ $this->decodingIterator->rewind();
54
+ }
55
+
56
+ public function valid()
57
+ {
58
+ return $this->decodingIterator->valid();
59
+ }
60
+
61
+ private function parseEvent(array $event)
62
+ {
63
+ if (!empty($event['headers'][':message-type'])) {
64
+ if ($event['headers'][':message-type'] === 'error') {
65
+ return $this->parseError($event);
66
+ }
67
+ if ($event['headers'][':message-type'] !== 'event') {
68
+ throw new ParserException('Failed to parse unknown message type.');
69
+ }
70
+ }
71
+
72
+ if (empty($event['headers'][':event-type'])) {
73
+ throw new ParserException('Failed to parse without event type.');
74
+ }
75
+ $eventShape = $this->shape->getMember($event['headers'][':event-type']);
76
+
77
+ $parsedEvent = [];
78
+ foreach ($eventShape['members'] as $shape => $details) {
79
+ if (!empty($details['eventpayload'])) {
80
+ $payloadShape = $eventShape->getMember($shape);
81
+ if ($payloadShape['type'] === 'blob') {
82
+ $parsedEvent[$shape] = $event['payload'];
83
+ } else {
84
+ $parsedEvent[$shape] = $this->parser->parseMemberFromStream(
85
+ $event['payload'],
86
+ $payloadShape,
87
+ null
88
+ );
89
+ }
90
+ } else {
91
+ $parsedEvent[$shape] = $event['headers'][$shape];
92
+ }
93
+ }
94
+
95
+ return [
96
+ $event['headers'][':event-type'] => $parsedEvent
97
+ ];
98
+ }
99
+
100
+ private function parseError(array $event)
101
+ {
102
+ throw new EventStreamDataException(
103
+ $event['headers'][':error-code'],
104
+ $event['headers'][':error-message']
105
+ );
106
+ }
107
+ }
lib/Aws/Aws/Api/Parser/Exception/ParserException.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+ use Aws\ResponseContainerInterface;
7
+
8
+ class ParserException extends \RuntimeException implements
9
+ MonitoringEventsInterface,
10
+ ResponseContainerInterface
11
+ {
12
+ use HasMonitoringEventsTrait;
13
+
14
+ private $response;
15
+
16
+ public function __construct($message = '', $code = 0, $previous = null, array $context = [])
17
+ {
18
+ $this->response = isset($context['response']) ? $context['response'] : null;
19
+ parent::__construct($message, $code, $previous);
20
+ }
21
+
22
+ /**
23
+ * Get the received HTTP response if any.
24
+ *
25
+ * @return ResponseInterface|null
26
+ */
27
+ public function getResponse()
28
+ {
29
+ return $this->response;
30
+ }
31
+ }
lib/Aws/Aws/Api/Parser/JsonParser.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\DateTimeResult;
5
+ use Aws\Api\Shape;
6
+
7
+ /**
8
+ * @internal Implements standard JSON parsing.
9
+ */
10
+ class JsonParser
11
+ {
12
+ public function parse(Shape $shape, $value)
13
+ {
14
+ if ($value === null) {
15
+ return $value;
16
+ }
17
+
18
+ switch ($shape['type']) {
19
+ case 'structure':
20
+ $target = [];
21
+ foreach ($shape->getMembers() as $name => $member) {
22
+ $locationName = $member['locationName'] ?: $name;
23
+ if (isset($value[$locationName])) {
24
+ $target[$name] = $this->parse($member, $value[$locationName]);
25
+ }
26
+ }
27
+ return $target;
28
+
29
+ case 'list':
30
+ $member = $shape->getMember();
31
+ $target = [];
32
+ foreach ($value as $v) {
33
+ $target[] = $this->parse($member, $v);
34
+ }
35
+ return $target;
36
+
37
+ case 'map':
38
+ $values = $shape->getValue();
39
+ $target = [];
40
+ foreach ($value as $k => $v) {
41
+ $target[$k] = $this->parse($values, $v);
42
+ }
43
+ return $target;
44
+
45
+ case 'timestamp':
46
+ if (!empty($shape['timestampFormat'])
47
+ && $shape['timestampFormat'] !== 'unixTimestamp') {
48
+ return new DateTimeResult($value);
49
+ }
50
+ // The Unix epoch (or Unix time or POSIX time or Unix
51
+ // timestamp) is the number of seconds that have elapsed since
52
+ // January 1, 1970 (midnight UTC/GMT).
53
+ return DateTimeResult::fromEpoch($value);
54
+
55
+ case 'blob':
56
+ return base64_decode($value);
57
+
58
+ default:
59
+ return $value;
60
+ }
61
+ }
62
+ }
lib/Aws/Aws/Api/Parser/JsonRpcParser.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\StructureShape;
5
+ use Aws\Api\Service;
6
+ use Aws\Result;
7
+ use Aws\CommandInterface;
8
+ use Psr\Http\Message\ResponseInterface;
9
+ use Psr\Http\Message\StreamInterface;
10
+
11
+ /**
12
+ * @internal Implements JSON-RPC parsing (e.g., DynamoDB)
13
+ */
14
+ class JsonRpcParser extends AbstractParser
15
+ {
16
+ use PayloadParserTrait;
17
+
18
+ /**
19
+ * @param Service $api Service description
20
+ * @param JsonParser $parser JSON body builder
21
+ */
22
+ public function __construct(Service $api, JsonParser $parser = null)
23
+ {
24
+ parent::__construct($api);
25
+ $this->parser = $parser ?: new JsonParser();
26
+ }
27
+
28
+ public function __invoke(
29
+ CommandInterface $command,
30
+ ResponseInterface $response
31
+ ) {
32
+ $operation = $this->api->getOperation($command->getName());
33
+ $result = null === $operation['output']
34
+ ? null
35
+ : $this->parseMemberFromStream(
36
+ $response->getBody(),
37
+ $operation->getOutput(),
38
+ $response
39
+ );
40
+
41
+ return new Result($result ?: []);
42
+ }
43
+
44
+ public function parseMemberFromStream(
45
+ StreamInterface $stream,
46
+ StructureShape $member,
47
+ $response
48
+ ) {
49
+ return $this->parser->parse($member, $this->parseJson($stream, $response));
50
+ }
51
+ }
lib/Aws/Aws/Api/Parser/PayloadParserTrait.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\Parser\Exception\ParserException;
5
+ use Psr\Http\Message\ResponseInterface;
6
+
7
+ trait PayloadParserTrait
8
+ {
9
+ /**
10
+ * @param string $json
11
+ *
12
+ * @throws ParserException
13
+ *
14
+ * @return array
15
+ */
16
+ private function parseJson($json, $response)
17
+ {
18
+ $jsonPayload = json_decode($json, true);
19
+
20
+ if (JSON_ERROR_NONE !== json_last_error()) {
21
+ throw new ParserException(
22
+ 'Error parsing JSON: ' . json_last_error_msg(),
23
+ 0,
24
+ null,
25
+ ['response' => $response]
26
+ );
27
+ }
28
+
29
+ return $jsonPayload;
30
+ }
31
+
32
+ /**
33
+ * @param string $xml
34
+ *
35
+ * @throws ParserException
36
+ *
37
+ * @return \SimpleXMLElement
38
+ */
39
+ private function parseXml($xml, $response)
40
+ {
41
+ $priorSetting = libxml_use_internal_errors(true);
42
+ try {
43
+ libxml_clear_errors();
44
+ $xmlPayload = new \SimpleXMLElement($xml);
45
+ if ($error = libxml_get_last_error()) {
46
+ throw new \RuntimeException($error->message);
47
+ }
48
+ } catch (\Exception $e) {
49
+ throw new ParserException(
50
+ "Error parsing XML: {$e->getMessage()}",
51
+ 0,
52
+ $e,
53
+ ['response' => $response]
54
+ );
55
+ } finally {
56
+ libxml_use_internal_errors($priorSetting);
57
+ }
58
+
59
+ return $xmlPayload;
60
+ }
61
+ }
lib/Aws/Aws/Api/Parser/QueryParser.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\Api\StructureShape;
6
+ use Aws\Result;
7
+ use Aws\CommandInterface;
8
+ use Psr\Http\Message\ResponseInterface;
9
+ use Psr\Http\Message\StreamInterface;
10
+
11
+ /**
12
+ * @internal Parses query (XML) responses (e.g., EC2, SQS, and many others)
13
+ */
14
+ class QueryParser extends AbstractParser
15
+ {
16
+ use PayloadParserTrait;
17
+
18
+ /** @var bool */
19
+ private $honorResultWrapper;
20
+
21
+ /**
22
+ * @param Service $api Service description
23
+ * @param XmlParser $xmlParser Optional XML parser
24
+ * @param bool $honorResultWrapper Set to false to disable the peeling
25
+ * back of result wrappers from the
26
+ * output structure.
27
+ */
28
+ public function __construct(
29
+ Service $api,
30
+ XmlParser $xmlParser = null,
31
+ $honorResultWrapper = true
32
+ ) {
33
+ parent::__construct($api);
34
+ $this->parser = $xmlParser ?: new XmlParser();
35
+ $this->honorResultWrapper = $honorResultWrapper;
36
+ }
37
+
38
+ public function __invoke(
39
+ CommandInterface $command,
40
+ ResponseInterface $response
41
+ ) {
42
+ $output = $this->api->getOperation($command->getName())->getOutput();
43
+ $xml = $this->parseXml($response->getBody(), $response);
44
+
45
+ if ($this->honorResultWrapper && $output['resultWrapper']) {
46
+ $xml = $xml->{$output['resultWrapper']};
47
+ }
48
+
49
+ return new Result($this->parser->parse($output, $xml));
50
+ }
51
+
52
+ public function parseMemberFromStream(
53
+ StreamInterface $stream,
54
+ StructureShape $member,
55
+ $response
56
+ ) {
57
+ $xml = $this->parseXml($stream, $response);
58
+ return $this->parser->parse($member, $xml);
59
+ }
60
+ }
lib/Aws/Aws/Api/Parser/RestJsonParser.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\Api\StructureShape;
6
+ use Psr\Http\Message\ResponseInterface;
7
+ use Psr\Http\Message\StreamInterface;
8
+
9
+ /**
10
+ * @internal Implements REST-JSON parsing (e.g., Glacier, Elastic Transcoder)
11
+ */
12
+ class RestJsonParser extends AbstractRestParser
13
+ {
14
+ use PayloadParserTrait;
15
+
16
+ /**
17
+ * @param Service $api Service description
18
+ * @param JsonParser $parser JSON body builder
19
+ */
20
+ public function __construct(Service $api, JsonParser $parser = null)
21
+ {
22
+ parent::__construct($api);
23
+ $this->parser = $parser ?: new JsonParser();
24
+ }
25
+
26
+ protected function payload(
27
+ ResponseInterface $response,
28
+ StructureShape $member,
29
+ array &$result
30
+ ) {
31
+ $jsonBody = $this->parseJson($response->getBody(), $response);
32
+
33
+ if ($jsonBody) {
34
+ $result += $this->parser->parse($member, $jsonBody);
35
+ }
36
+ }
37
+
38
+ public function parseMemberFromStream(
39
+ StreamInterface $stream,
40
+ StructureShape $member,
41
+ $response
42
+ ) {
43
+ $jsonBody = $this->parseJson($stream, $response);
44
+ if ($jsonBody) {
45
+ return $this->parser->parse($member, $jsonBody);
46
+ }
47
+ return [];
48
+ }
49
+ }
lib/Aws/Aws/Api/Parser/RestXmlParser.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\StructureShape;
5
+ use Aws\Api\Service;
6
+ use Psr\Http\Message\ResponseInterface;
7
+ use Psr\Http\Message\StreamInterface;
8
+
9
+ /**
10
+ * @internal Implements REST-XML parsing (e.g., S3, CloudFront, etc...)
11
+ */
12
+ class RestXmlParser extends AbstractRestParser
13
+ {
14
+ use PayloadParserTrait;
15
+
16
+ /**
17
+ * @param Service $api Service description
18
+ * @param XmlParser $parser XML body parser
19
+ */
20
+ public function __construct(Service $api, XmlParser $parser = null)
21
+ {
22
+ parent::__construct($api);
23
+ $this->parser = $parser ?: new XmlParser();
24
+ }
25
+
26
+ protected function payload(
27
+ ResponseInterface $response,
28
+ StructureShape $member,
29
+ array &$result
30
+ ) {
31
+ $result += $this->parseMemberFromStream($response->getBody(), $member, $response);
32
+ }
33
+
34
+ public function parseMemberFromStream(
35
+ StreamInterface $stream,
36
+ StructureShape $member,
37
+ $response
38
+ ) {
39
+ $xml = $this->parseXml($stream, $response);
40
+ return $this->parser->parse($member, $xml);
41
+ }
42
+ }
lib/Aws/Aws/Api/Parser/XmlParser.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Parser;
3
+
4
+ use Aws\Api\DateTimeResult;
5
+ use Aws\Api\ListShape;
6
+ use Aws\Api\MapShape;
7
+ use Aws\Api\Shape;
8
+ use Aws\Api\StructureShape;
9
+
10
+ /**
11
+ * @internal Implements standard XML parsing for REST-XML and Query protocols.
12
+ */
13
+ class XmlParser
14
+ {
15
+ public function parse(StructureShape $shape, \SimpleXMLElement $value)
16
+ {
17
+ return $this->dispatch($shape, $value);
18
+ }
19
+
20
+ private function dispatch($shape, \SimpleXMLElement $value)
21
+ {
22
+ static $methods = [
23
+ 'structure' => 'parse_structure',
24
+ 'list' => 'parse_list',
25
+ 'map' => 'parse_map',
26
+ 'blob' => 'parse_blob',
27
+ 'boolean' => 'parse_boolean',
28
+ 'integer' => 'parse_integer',
29
+ 'float' => 'parse_float',
30
+ 'double' => 'parse_float',
31
+ 'timestamp' => 'parse_timestamp',
32
+ ];
33
+
34
+ $type = $shape['type'];
35
+ if (isset($methods[$type])) {
36
+ return $this->{$methods[$type]}($shape, $value);
37
+ }
38
+
39
+ return (string) $value;
40
+ }
41
+
42
+ private function parse_structure(
43
+ StructureShape $shape,
44
+ \SimpleXMLElement $value
45
+ ) {
46
+ $target = [];
47
+
48
+ foreach ($shape->getMembers() as $name => $member) {
49
+ // Extract the name of the XML node
50
+ $node = $this->memberKey($member, $name);
51
+ if (isset($value->{$node})) {
52
+ $target[$name] = $this->dispatch($member, $value->{$node});
53
+ }
54
+ }
55
+
56
+ return $target;
57
+ }
58
+
59
+ private function memberKey(Shape $shape, $name)
60
+ {
61
+ if (null !== $shape['locationName']) {
62
+ return $shape['locationName'];
63
+ }
64
+
65
+ if ($shape instanceof ListShape && $shape['flattened']) {
66
+ return $shape->getMember()['locationName'] ?: $name;
67
+ }
68
+
69
+ return $name;
70
+ }
71
+
72
+ private function parse_list(ListShape $shape, \SimpleXMLElement $value)
73
+ {
74
+ $target = [];
75
+ $member = $shape->getMember();
76
+
77
+ if (!$shape['flattened']) {
78
+ $value = $value->{$member['locationName'] ?: 'member'};
79
+ }
80
+
81
+ foreach ($value as $v) {
82
+ $target[] = $this->dispatch($member, $v);
83
+ }
84
+
85
+ return $target;
86
+ }
87
+
88
+ private function parse_map(MapShape $shape, \SimpleXMLElement $value)
89
+ {
90
+ $target = [];
91
+
92
+ if (!$shape['flattened']) {
93
+ $value = $value->entry;
94
+ }
95
+
96
+ $mapKey = $shape->getKey();
97
+ $mapValue = $shape->getValue();
98
+ $keyName = $shape->getKey()['locationName'] ?: 'key';
99
+ $valueName = $shape->getValue()['locationName'] ?: 'value';
100
+
101
+ foreach ($value as $node) {
102
+ $key = $this->dispatch($mapKey, $node->{$keyName});
103
+ $value = $this->dispatch($mapValue, $node->{$valueName});
104
+ $target[$key] = $value;
105
+ }
106
+
107
+ return $target;
108
+ }
109
+
110
+ private function parse_blob(Shape $shape, $value)
111
+ {
112
+ return base64_decode((string) $value);
113
+ }
114
+
115
+ private function parse_float(Shape $shape, $value)
116
+ {
117
+ return (float) (string) $value;
118
+ }
119
+
120
+ private function parse_integer(Shape $shape, $value)
121
+ {
122
+ return (int) (string) $value;
123
+ }
124
+
125
+ private function parse_boolean(Shape $shape, $value)
126
+ {
127
+ return $value == 'true';
128
+ }
129
+
130
+ private function parse_timestamp(Shape $shape, $value)
131
+ {
132
+ if (!empty($shape['timestampFormat'])
133
+ && $shape['timestampFormat'] === 'unixTimestamp') {
134
+ return DateTimeResult::fromEpoch((string) $value);
135
+ }
136
+ return new DateTimeResult($value);
137
+ }
138
+ }
lib/Aws/Aws/Api/Serializer/Ec2ParamBuilder.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\Shape;
5
+ use Aws\Api\ListShape;
6
+
7
+ /**
8
+ * @internal
9
+ */
10
+ class Ec2ParamBuilder extends QueryParamBuilder
11
+ {
12
+ protected function queryName(Shape $shape, $default = null)
13
+ {
14
+ return ($shape['queryName']
15
+ ?: ucfirst($shape['locationName']))
16
+ ?: $default;
17
+ }
18
+
19
+ protected function isFlat(Shape $shape)
20
+ {
21
+ return false;
22
+ }
23
+
24
+ protected function format_list(
25
+ ListShape $shape,
26
+ array $value,
27
+ $prefix,
28
+ &$query
29
+ ) {
30
+ // Handle empty list serialization
31
+ if (!$value) {
32
+ $query[$prefix] = false;
33
+ } else {
34
+ $items = $shape->getMember();
35
+ foreach ($value as $k => $v) {
36
+ $this->format($items, $v, $prefix . '.' . ($k + 1), $query);
37
+ }
38
+ }
39
+ }
40
+ }
lib/Aws/Aws/Api/Serializer/JsonBody.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\Api\Shape;
6
+ use Aws\Api\TimestampShape;
7
+
8
+ /**
9
+ * Formats the JSON body of a JSON-REST or JSON-RPC operation.
10
+ * @internal
11
+ */
12
+ class JsonBody
13
+ {
14
+ private $api;
15
+
16
+ public function __construct(Service $api)
17
+ {
18
+ $this->api = $api;
19
+ }
20
+
21
+ /**
22
+ * Gets the JSON Content-Type header for a service API
23
+ *
24
+ * @param Service $service
25
+ *
26
+ * @return string
27
+ */
28
+ public static function getContentType(Service $service)
29
+ {
30
+ return 'application/x-amz-json-'
31
+ . number_format($service->getMetadata('jsonVersion'), 1);
32
+ }
33
+
34
+ /**
35
+ * Builds the JSON body based on an array of arguments.
36
+ *
37
+ * @param Shape $shape Operation being constructed
38
+ * @param array $args Associative array of arguments
39
+ *
40
+ * @return string
41
+ */
42
+ public function build(Shape $shape, array $args)
43
+ {
44
+ $result = json_encode($this->format($shape, $args));
45
+
46
+ return $result == '[]' ? '{}' : $result;
47
+ }
48
+
49
+ private function format(Shape $shape, $value)
50
+ {
51
+ switch ($shape['type']) {
52
+ case 'structure':
53
+ $data = [];
54
+ foreach ($value as $k => $v) {
55
+ if ($v !== null && $shape->hasMember($k)) {
56
+ $valueShape = $shape->getMember($k);
57
+ $data[$valueShape['locationName'] ?: $k]
58
+ = $this->format($valueShape, $v);
59
+ }
60
+ }
61
+ if (empty($data)) {
62
+ return new \stdClass;
63
+ }
64
+ return $data;
65
+
66
+ case 'list':
67
+ $items = $shape->getMember();
68
+ foreach ($value as $k => $v) {
69
+ $value[$k] = $this->format($items, $v);
70
+ }
71
+ return $value;
72
+
73
+ case 'map':
74
+ if (empty($value)) {
75
+ return new \stdClass;
76
+ }
77
+ $values = $shape->getValue();
78
+ foreach ($value as $k => $v) {
79
+ $value[$k] = $this->format($values, $v);
80
+ }
81
+ return $value;
82
+
83
+ case 'blob':
84
+ return base64_encode($value);
85
+
86
+ case 'timestamp':
87
+ $timestampFormat = !empty($shape['timestampFormat'])
88
+ ? $shape['timestampFormat']
89
+ : 'unixTimestamp';
90
+ return TimestampShape::format($value, $timestampFormat);
91
+
92
+ default:
93
+ return $value;
94
+ }
95
+ }
96
+ }
lib/Aws/Aws/Api/Serializer/JsonRpcSerializer.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\CommandInterface;
6
+ use GuzzleHttp\Psr7\Request;
7
+ use Psr\Http\Message\RequestInterface;
8
+
9
+ /**
10
+ * Prepares a JSON-RPC request for transfer.
11
+ * @internal
12
+ */
13
+ class JsonRpcSerializer
14
+ {
15
+ /** @var JsonBody */
16
+ private $jsonFormatter;
17
+
18
+ /** @var string */
19
+ private $endpoint;
20
+
21
+ /** @var Service */
22
+ private $api;
23
+
24
+ /** @var string */
25
+ private $contentType;
26
+
27
+ /**
28
+ * @param Service $api Service description
29
+ * @param string $endpoint Endpoint to connect to
30
+ * @param JsonBody $jsonFormatter Optional JSON formatter to use
31
+ */
32
+ public function __construct(
33
+ Service $api,
34
+ $endpoint,
35
+ JsonBody $jsonFormatter = null
36
+ ) {
37
+ $this->endpoint = $endpoint;
38
+ $this->api = $api;
39
+ $this->jsonFormatter = $jsonFormatter ?: new JsonBody($this->api);
40
+ $this->contentType = JsonBody::getContentType($api);
41
+ }
42
+
43
+ /**
44
+ * When invoked with an AWS command, returns a serialization array
45
+ * containing "method", "uri", "headers", and "body" key value pairs.
46
+ *
47
+ * @param CommandInterface $command
48
+ *
49
+ * @return RequestInterface
50
+ */
51
+ public function __invoke(CommandInterface $command)
52
+ {
53
+ $name = $command->getName();
54
+ $operation = $this->api->getOperation($name);
55
+
56
+ return new Request(
57
+ $operation['http']['method'],
58
+ $this->endpoint,
59
+ [
60
+ 'X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $name,
61
+ 'Content-Type' => $this->contentType
62
+ ],
63
+ $this->jsonFormatter->build(
64
+ $operation->getInput(),
65
+ $command->toArray()
66
+ )
67
+ );
68
+ }
69
+ }
lib/Aws/Aws/Api/Serializer/QueryParamBuilder.php ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\StructureShape;
5
+ use Aws\Api\ListShape;
6
+ use Aws\Api\MapShape;
7
+ use Aws\Api\Shape;
8
+ use Aws\Api\TimestampShape;
9
+
10
+ /**
11
+ * @internal
12
+ */
13
+ class QueryParamBuilder
14
+ {
15
+ private $methods;
16
+
17
+ protected function queryName(Shape $shape, $default = null)
18
+ {
19
+ if (null !== $shape['queryName']) {
20
+ return $shape['queryName'];
21
+ }
22
+
23
+ if (null !== $shape['locationName']) {
24
+ return $shape['locationName'];
25
+ }
26
+
27
+ if ($this->isFlat($shape) && !empty($shape['member']['locationName'])) {
28
+ return $shape['member']['locationName'];
29
+ }
30
+
31
+ return $default;
32
+ }
33
+
34
+ protected function isFlat(Shape $shape)
35
+ {
36
+ return $shape['flattened'] === true;
37
+ }
38
+
39
+ public function __invoke(StructureShape $shape, array $params)
40
+ {
41
+ if (!$this->methods) {
42
+ $this->methods = array_fill_keys(get_class_methods($this), true);
43
+ }
44
+
45
+ $query = [];
46
+ $this->format_structure($shape, $params, '', $query);
47
+
48
+ return $query;
49
+ }
50
+
51
+ protected function format(Shape $shape, $value, $prefix, array &$query)
52
+ {
53
+ $type = 'format_' . $shape['type'];
54
+ if (isset($this->methods[$type])) {
55
+ $this->{$type}($shape, $value, $prefix, $query);
56
+ } else {
57
+ $query[$prefix] = (string) $value;
58
+ }
59
+ }
60
+
61
+ protected function format_structure(
62
+ StructureShape $shape,
63
+ array $value,
64
+ $prefix,
65
+ &$query
66
+ ) {
67
+ if ($prefix) {
68
+ $prefix .= '.';
69
+ }
70
+
71
+ foreach ($value as $k => $v) {
72
+ if ($shape->hasMember($k)) {
73
+ $member = $shape->getMember($k);
74
+ $this->format(
75
+ $member,
76
+ $v,
77
+ $prefix . $this->queryName($member, $k),
78
+ $query
79
+ );
80
+ }
81
+ }
82
+ }
83
+
84
+ protected function format_list(
85
+ ListShape $shape,
86
+ array $value,
87
+ $prefix,
88
+ &$query
89
+ ) {
90
+ // Handle empty list serialization
91
+ if (!$value) {
92
+ $query[$prefix] = '';
93
+ return;
94
+ }
95
+
96
+ $items = $shape->getMember();
97
+
98
+ if (!$this->isFlat($shape)) {
99
+ $locationName = $shape->getMember()['locationName'] ?: 'member';
100
+ $prefix .= ".$locationName";
101
+ } elseif ($name = $this->queryName($items)) {
102
+ $parts = explode('.', $prefix);
103
+ $parts[count($parts) - 1] = $name;
104
+ $prefix = implode('.', $parts);
105
+ }
106
+
107
+ foreach ($value as $k => $v) {
108
+ $this->format($items, $v, $prefix . '.' . ($k + 1), $query);
109
+ }
110
+ }
111
+
112
+ protected function format_map(
113
+ MapShape $shape,
114
+ array $value,
115
+ $prefix,
116
+ array &$query
117
+ ) {
118
+ $vals = $shape->getValue();
119
+ $keys = $shape->getKey();
120
+
121
+ if (!$this->isFlat($shape)) {
122
+ $prefix .= '.entry';
123
+ }
124
+
125
+ $i = 0;
126
+ $keyName = '%s.%d.' . $this->queryName($keys, 'key');
127
+ $valueName = '%s.%s.' . $this->queryName($vals, 'value');
128
+
129
+ foreach ($value as $k => $v) {
130
+ $i++;
131
+ $this->format($keys, $k, sprintf($keyName, $prefix, $i), $query);
132
+ $this->format($vals, $v, sprintf($valueName, $prefix, $i), $query);
133
+ }
134
+ }
135
+
136
+ protected function format_blob(Shape $shape, $value, $prefix, array &$query)
137
+ {
138
+ $query[$prefix] = base64_encode($value);
139
+ }
140
+
141
+ protected function format_timestamp(
142
+ TimestampShape $shape,
143
+ $value,
144
+ $prefix,
145
+ array &$query
146
+ ) {
147
+ $timestampFormat = !empty($shape['timestampFormat'])
148
+ ? $shape['timestampFormat']
149
+ : 'iso8601';
150
+ $query[$prefix] = TimestampShape::format($value, $timestampFormat);
151
+ }
152
+
153
+ protected function format_boolean(Shape $shape, $value, $prefix, array &$query)
154
+ {
155
+ $query[$prefix] = ($value) ? 'true' : 'false';
156
+ }
157
+ }
lib/Aws/Aws/Api/Serializer/QuerySerializer.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\CommandInterface;
6
+ use GuzzleHttp\Psr7\Request;
7
+ use Psr\Http\Message\RequestInterface;
8
+
9
+ /**
10
+ * Serializes a query protocol request.
11
+ * @internal
12
+ */
13
+ class QuerySerializer
14
+ {
15
+ private $endpoint;
16
+ private $api;
17
+ private $paramBuilder;
18
+
19
+ public function __construct(
20
+ Service $api,
21
+ $endpoint,
22
+ callable $paramBuilder = null
23
+ ) {
24
+ $this->api = $api;
25
+ $this->endpoint = $endpoint;
26
+ $this->paramBuilder = $paramBuilder ?: new QueryParamBuilder();
27
+ }
28
+
29
+ /**
30
+ * When invoked with an AWS command, returns a serialization array
31
+ * containing "method", "uri", "headers", and "body" key value pairs.
32
+ *
33
+ * @param CommandInterface $command
34
+ *
35
+ * @return RequestInterface
36
+ */
37
+ public function __invoke(CommandInterface $command)
38
+ {
39
+ $operation = $this->api->getOperation($command->getName());
40
+
41
+ $body = [
42
+ 'Action' => $command->getName(),
43
+ 'Version' => $this->api->getMetadata('apiVersion')
44
+ ];
45
+
46
+ $params = $command->toArray();
47
+
48
+ // Only build up the parameters when there are parameters to build
49
+ if ($params) {
50
+ $body += call_user_func(
51
+ $this->paramBuilder,
52
+ $operation->getInput(),
53
+ $params
54
+ );
55
+ }
56
+
57
+ $body = http_build_query($body, null, '&', PHP_QUERY_RFC3986);
58
+
59
+ return new Request(
60
+ 'POST',
61
+ $this->endpoint,
62
+ [
63
+ 'Content-Length' => strlen($body),
64
+ 'Content-Type' => 'application/x-www-form-urlencoded'
65
+ ],
66
+ $body
67
+ );
68
+ }
69
+ }
lib/Aws/Aws/Api/Serializer/RestJsonSerializer.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\Api\StructureShape;
6
+
7
+ /**
8
+ * Serializes requests for the REST-JSON protocol.
9
+ * @internal
10
+ */
11
+ class RestJsonSerializer extends RestSerializer
12
+ {
13
+ /** @var JsonBody */
14
+ private $jsonFormatter;
15
+
16
+ /** @var string */
17
+ private $contentType;
18
+
19
+ /**
20
+ * @param Service $api Service API description
21
+ * @param string $endpoint Endpoint to connect to
22
+ * @param JsonBody $jsonFormatter Optional JSON formatter to use
23
+ */
24
+ public function __construct(
25
+ Service $api,
26
+ $endpoint,
27
+ JsonBody $jsonFormatter = null
28
+ ) {
29
+ parent::__construct($api, $endpoint);
30
+ $this->contentType = 'application/json';
31
+ $this->jsonFormatter = $jsonFormatter ?: new JsonBody($api);
32
+ }
33
+
34
+ protected function payload(StructureShape $member, array $value, array &$opts)
35
+ {
36
+ $opts['headers']['Content-Type'] = $this->contentType;
37
+ $opts['body'] = (string) $this->jsonFormatter->build($member, $value);
38
+ }
39
+ }
lib/Aws/Aws/Api/Serializer/RestSerializer.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\MapShape;
5
+ use Aws\Api\Service;
6
+ use Aws\Api\Operation;
7
+ use Aws\Api\Shape;
8
+ use Aws\Api\StructureShape;
9
+ use Aws\Api\TimestampShape;
10
+ use Aws\CommandInterface;
11
+ use GuzzleHttp\Psr7;
12
+ use GuzzleHttp\Psr7\Uri;
13
+ use GuzzleHttp\Psr7\UriResolver;
14
+ use Psr\Http\Message\RequestInterface;
15
+
16
+ /**
17
+ * Serializes HTTP locations like header, uri, payload, etc...
18
+ * @internal
19
+ */
20
+ abstract class RestSerializer
21
+ {
22
+ /** @var Service */
23
+ private $api;
24
+
25
+ /** @var Psr7\Uri */
26
+ private $endpoint;
27
+
28
+ /**
29
+ * @param Service $api Service API description
30
+ * @param string $endpoint Endpoint to connect to
31
+ */
32
+ public function __construct(Service $api, $endpoint)
33
+ {
34
+ $this->api = $api;
35
+ $this->endpoint = Psr7\uri_for($endpoint);
36
+ }
37
+
38
+ /**
39
+ * @param CommandInterface $command Command to serialized
40
+ *
41
+ * @return RequestInterface
42
+ */
43
+ public function __invoke(CommandInterface $command)
44
+ {
45
+ $operation = $this->api->getOperation($command->getName());
46
+ $args = $command->toArray();
47
+ $opts = $this->serialize($operation, $args);
48
+ $uri = $this->buildEndpoint($operation, $args, $opts);
49
+
50
+ return new Psr7\Request(
51
+ $operation['http']['method'],
52
+ $uri,
53
+ isset($opts['headers']) ? $opts['headers'] : [],
54
+ isset($opts['body']) ? $opts['body'] : null
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Modifies a hash of request options for a payload body.
60
+ *
61
+ * @param StructureShape $member Member to serialize
62
+ * @param array $value Value to serialize
63
+ * @param array $opts Request options to modify.
64
+ */
65
+ abstract protected function payload(
66
+ StructureShape $member,
67
+ array $value,
68
+ array &$opts
69
+ );
70
+
71
+ private function serialize(Operation $operation, array $args)
72
+ {
73
+ $opts = [];
74
+ $input = $operation->getInput();
75
+
76
+ // Apply the payload trait if present
77
+ if ($payload = $input['payload']) {
78
+ $this->applyPayload($input, $payload, $args, $opts);
79
+ }
80
+
81
+ foreach ($args as $name => $value) {
82
+ if ($input->hasMember($name)) {
83
+ $member = $input->getMember($name);
84
+ $location = $member['location'];
85
+ if (!$payload && !$location) {
86
+ $bodyMembers[$name] = $value;
87
+ } elseif ($location == 'header') {
88
+ $this->applyHeader($name, $member, $value, $opts);
89
+ } elseif ($location == 'querystring') {
90
+ $this->applyQuery($name, $member, $value, $opts);
91
+ } elseif ($location == 'headers') {
92
+ $this->applyHeaderMap($name, $member, $value, $opts);
93
+ }
94
+ }
95
+ }
96
+
97
+ if (isset($bodyMembers)) {
98
+ $this->payload($operation->getInput(), $bodyMembers, $opts);
99
+ }
100
+
101
+ return $opts;
102
+ }
103
+
104
+ private function applyPayload(StructureShape $input, $name, array $args, array &$opts)
105
+ {
106
+ if (!isset($args[$name])) {
107
+ return;
108
+ }
109
+
110
+ $m = $input->getMember($name);
111
+
112
+ if ($m['streaming'] ||
113
+ ($m['type'] == 'string' || $m['type'] == 'blob')
114
+ ) {
115
+ // Streaming bodies or payloads that are strings are
116
+ // always just a stream of data.
117
+ $opts['body'] = Psr7\stream_for($args[$name]);
118
+ return;
119
+ }
120
+
121
+ $this->payload($m, $args[$name], $opts);
122
+ }
123
+
124
+ private function applyHeader($name, Shape $member, $value, array &$opts)
125
+ {
126
+ if ($member->getType() === 'timestamp') {
127
+ $timestampFormat = !empty($member['timestampFormat'])
128
+ ? $member['timestampFormat']
129
+ : 'rfc822';
130
+ $value = TimestampShape::format($value, $timestampFormat);
131
+ }
132
+ if ($member['jsonvalue']) {
133
+ $value = json_encode($value);
134
+ if (empty($value) && JSON_ERROR_NONE !== json_last_error()) {
135
+ throw new \InvalidArgumentException('Unable to encode the provided value'
136
+ . ' with \'json_encode\'. ' . json_last_error_msg());
137
+ }
138
+
139
+ $value = base64_encode($value);
140
+ }
141
+
142
+ $opts['headers'][$member['locationName'] ?: $name] = $value;
143
+ }
144
+
145
+ /**
146
+ * Note: This is currently only present in the Amazon S3 model.
147
+ */
148
+ private function applyHeaderMap($name, Shape $member, array $value, array &$opts)
149
+ {
150
+ $prefix = $member['locationName'];
151
+ foreach ($value as $k => $v) {
152
+ $opts['headers'][$prefix . $k] = $v;
153
+ }
154
+ }
155
+
156
+ private function applyQuery($name, Shape $member, $value, array &$opts)
157
+ {
158
+ if ($member instanceof MapShape) {
159
+ $opts['query'] = isset($opts['query']) && is_array($opts['query'])
160
+ ? $opts['query'] + $value
161
+ : $value;
162
+ } elseif ($value !== null) {
163
+ $type = $member->getType();
164
+ if ($type === 'boolean') {
165
+ $value = $value ? 'true' : 'false';
166
+ } elseif ($type === 'timestamp') {
167
+ $timestampFormat = !empty($member['timestampFormat'])
168
+ ? $member['timestampFormat']
169
+ : 'iso8601';
170
+ $value = TimestampShape::format($value, $timestampFormat);
171
+ }
172
+
173
+ $opts['query'][$member['locationName'] ?: $name] = $value;
174
+ }
175
+ }
176
+
177
+ private function buildEndpoint(Operation $operation, array $args, array $opts)
178
+ {
179
+ $varspecs = [];
180
+
181
+ // Create an associative array of varspecs used in expansions
182
+ foreach ($operation->getInput()->getMembers() as $name => $member) {
183
+ if ($member['location'] == 'uri') {
184
+ $varspecs[$member['locationName'] ?: $name] =
185
+ isset($args[$name])
186
+ ? $args[$name]
187
+ : null;
188
+ }
189
+ }
190
+
191
+ $relative = preg_replace_callback(
192
+ '/\{([^\}]+)\}/',
193
+ function (array $matches) use ($varspecs) {
194
+ $isGreedy = substr($matches[1], -1, 1) == '+';
195
+ $k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];
196
+ if (!isset($varspecs[$k])) {
197
+ return '';
198
+ }
199
+
200
+ if ($isGreedy) {
201
+ return str_replace('%2F', '/', rawurlencode($varspecs[$k]));
202
+ }
203
+
204
+ return rawurlencode($varspecs[$k]);
205
+ },
206
+ $operation['http']['requestUri']
207
+ );
208
+
209
+ // Add the query string variables or appending to one if needed.
210
+ if (!empty($opts['query'])) {
211
+ $append = Psr7\build_query($opts['query']);
212
+ $relative .= strpos($relative, '?') ? "&{$append}" : "?$append";
213
+ }
214
+
215
+ // Expand path place holders using Amazon's slightly different URI
216
+ // template syntax.
217
+ return UriResolver::resolve($this->endpoint, new Uri($relative));
218
+ }
219
+ }
lib/Aws/Aws/Api/Serializer/RestXmlSerializer.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\StructureShape;
5
+ use Aws\Api\Service;
6
+
7
+ /**
8
+ * @internal
9
+ */
10
+ class RestXmlSerializer extends RestSerializer
11
+ {
12
+ /** @var XmlBody */
13
+ private $xmlBody;
14
+
15
+ /**
16
+ * @param Service $api Service API description
17
+ * @param string $endpoint Endpoint to connect to
18
+ * @param XmlBody $xmlBody Optional XML formatter to use
19
+ */
20
+ public function __construct(
21
+ Service $api,
22
+ $endpoint,
23
+ XmlBody $xmlBody = null
24
+ ) {
25
+ parent::__construct($api, $endpoint);
26
+ $this->xmlBody = $xmlBody ?: new XmlBody($api);
27
+ }
28
+
29
+ protected function payload(StructureShape $member, array $value, array &$opts)
30
+ {
31
+ $opts['headers']['Content-Type'] = 'application/xml';
32
+ $opts['body'] = (string) $this->xmlBody->build($member, $value);
33
+ }
34
+ }
lib/Aws/Aws/Api/Serializer/XmlBody.php ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api\Serializer;
3
+
4
+ use Aws\Api\MapShape;
5
+ use Aws\Api\Service;
6
+ use Aws\Api\Shape;
7
+ use Aws\Api\StructureShape;
8
+ use Aws\Api\ListShape;
9
+ use Aws\Api\TimestampShape;
10
+ use XMLWriter;
11
+
12
+ /**
13
+ * @internal Formats the XML body of a REST-XML services.
14
+ */
15
+ class XmlBody
16
+ {
17
+ /** @var \Aws\Api\Service */
18
+ private $api;
19
+
20
+ /**
21
+ * @param Service $api API being used to create the XML body.
22
+ */
23
+ public function __construct(Service $api)
24
+ {
25
+ $this->api = $api;
26
+ }
27
+
28
+ /**
29
+ * Builds the XML body based on an array of arguments.
30
+ *
31
+ * @param Shape $shape Operation being constructed
32
+ * @param array $args Associative array of arguments
33
+ *
34
+ * @return string
35
+ */
36
+ public function build(Shape $shape, array $args)
37
+ {
38
+ $xml = new XMLWriter();
39
+ $xml->openMemory();
40
+ $xml->startDocument('1.0', 'UTF-8');
41
+ $this->format($shape, $shape['locationName'] ?: $shape['name'], $args, $xml);
42
+ $xml->endDocument();
43
+
44
+ return $xml->outputMemory();
45
+ }
46
+
47
+ private function startElement(Shape $shape, $name, XMLWriter $xml)
48
+ {
49
+ $xml->startElement($name);
50
+
51
+ if ($ns = $shape['xmlNamespace']) {
52
+ $xml->writeAttribute(
53
+ isset($ns['prefix']) ? "xmlns:{$ns['prefix']}" : 'xmlns',
54
+ $shape['xmlNamespace']['uri']
55
+ );
56
+ }
57
+ }
58
+
59
+ private function format(Shape $shape, $name, $value, XMLWriter $xml)
60
+ {
61
+ // Any method mentioned here has a custom serialization handler.
62
+ static $methods = [
63
+ 'add_structure' => true,
64
+ 'add_list' => true,
65
+ 'add_blob' => true,
66
+ 'add_timestamp' => true,
67
+ 'add_boolean' => true,
68
+ 'add_map' => true,
69
+ 'add_string' => true
70
+ ];
71
+
72
+ $type = 'add_' . $shape['type'];
73
+ if (isset($methods[$type])) {
74
+ $this->{$type}($shape, $name, $value, $xml);
75
+ } else {
76
+ $this->defaultShape($shape, $name, $value, $xml);
77
+ }
78
+ }
79
+
80
+ private function defaultShape(Shape $shape, $name, $value, XMLWriter $xml)
81
+ {
82
+ $this->startElement($shape, $name, $xml);
83
+ $xml->writeRaw($value);
84
+ $xml->endElement();
85
+ }
86
+
87
+ private function add_structure(
88
+ StructureShape $shape,
89
+ $name,
90
+ array $value,
91
+ \XMLWriter $xml
92
+ ) {
93
+ $this->startElement($shape, $name, $xml);
94
+
95
+ foreach ($this->getStructureMembers($shape, $value) as $k => $definition) {
96
+ $this->format(
97
+ $definition['member'],
98
+ $definition['member']['locationName'] ?: $k,
99
+ $definition['value'],
100
+ $xml
101
+ );
102
+ }
103
+
104
+ $xml->endElement();
105
+ }
106
+
107
+ private function getStructureMembers(StructureShape $shape, array $value)
108
+ {
109
+ $members = [];
110
+
111
+ foreach ($value as $k => $v) {
112
+ if ($v !== null && $shape->hasMember($k)) {
113
+ $definition = [
114
+ 'member' => $shape->getMember($k),
115
+ 'value' => $v,
116
+ ];
117
+
118
+ if ($definition['member']['xmlAttribute']) {
119
+ // array_unshift_associative
120
+ $members = [$k => $definition] + $members;
121
+ } else {
122
+ $members[$k] = $definition;
123
+ }
124
+ }
125
+ }
126
+
127
+ return $members;
128
+ }
129
+
130
+ private function add_list(
131
+ ListShape $shape,
132
+ $name,
133
+ array $value,
134
+ XMLWriter $xml
135
+ ) {
136
+ $items = $shape->getMember();
137
+
138
+ if ($shape['flattened']) {
139
+ $elementName = $name;
140
+ } else {
141
+ $this->startElement($shape, $name, $xml);
142
+ $elementName = $items['locationName'] ?: 'member';
143
+ }
144
+
145
+ foreach ($value as $v) {
146
+ $this->format($items, $elementName, $v, $xml);
147
+ }
148
+
149
+ if (!$shape['flattened']) {
150
+ $xml->endElement();
151
+ }
152
+ }
153
+
154
+ private function add_map(
155
+ MapShape $shape,
156
+ $name,
157
+ array $value,
158
+ XMLWriter $xml
159
+ ) {
160
+ $xmlEntry = $shape['flattened'] ? $shape['locationName'] : 'entry';
161
+ $xmlKey = $shape->getKey()['locationName'] ?: 'key';
162
+ $xmlValue = $shape->getValue()['locationName'] ?: 'value';
163
+
164
+ $this->startElement($shape, $name, $xml);
165
+
166
+ foreach ($value as $key => $v) {
167
+ $this->startElement($shape, $xmlEntry, $xml);
168
+ $this->format($shape->getKey(), $xmlKey, $key, $xml);
169
+ $this->format($shape->getValue(), $xmlValue, $v, $xml);
170
+ $xml->endElement();
171
+ }
172
+
173
+ $xml->endElement();
174
+ }
175
+
176
+ private function add_blob(Shape $shape, $name, $value, XMLWriter $xml)
177
+ {
178
+ $this->startElement($shape, $name, $xml);
179
+ $xml->writeRaw(base64_encode($value));
180
+ $xml->endElement();
181
+ }
182
+
183
+ private function add_timestamp(
184
+ TimestampShape $shape,
185
+ $name,
186
+ $value,
187
+ XMLWriter $xml
188
+ ) {
189
+ $this->startElement($shape, $name, $xml);
190
+ $timestampFormat = !empty($shape['timestampFormat'])
191
+ ? $shape['timestampFormat']
192
+ : 'iso8601';
193
+ $xml->writeRaw(TimestampShape::format($value, $timestampFormat));
194
+ $xml->endElement();
195
+ }
196
+
197
+ private function add_boolean(
198
+ Shape $shape,
199
+ $name,
200
+ $value,
201
+ XMLWriter $xml
202
+ ) {
203
+ $this->startElement($shape, $name, $xml);
204
+ $xml->writeRaw($value ? 'true' : 'false');
205
+ $xml->endElement();
206
+ }
207
+
208
+ private function add_string(
209
+ Shape $shape,
210
+ $name,
211
+ $value,
212
+ XMLWriter $xml
213
+ ) {
214
+ if ($shape['xmlAttribute']) {
215
+ $xml->writeAttribute($shape['locationName'] ?: $name, $value);
216
+ } else {
217
+ $this->defaultShape($shape, $name, $value, $xml);
218
+ }
219
+ }
220
+ }
lib/Aws/Aws/Api/Service.php ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ use Aws\Api\Serializer\QuerySerializer;
5
+ use Aws\Api\Serializer\Ec2ParamBuilder;
6
+ use Aws\Api\Parser\QueryParser;
7
+
8
+ /**
9
+ * Represents a web service API model.
10
+ */
11
+ class Service extends AbstractModel
12
+ {
13
+ /** @var callable */
14
+ private $apiProvider;
15
+
16
+ /** @var string */
17
+ private $serviceName;
18
+
19
+ /** @var string */
20
+ private $apiVersion;
21
+
22
+ /** @var Operation[] */
23
+ private $operations = [];
24
+
25
+ /** @var array */
26
+ private $paginators = null;
27
+
28
+ /** @var array */
29
+ private $waiters = null;
30
+
31
+ /**
32
+ * @param array $definition
33
+ * @param callable $provider
34
+ *
35
+ * @internal param array $definition Service description
36
+ */
37
+ public function __construct(array $definition, callable $provider)
38
+ {
39
+ static $defaults = [
40
+ 'operations' => [],
41
+ 'shapes' => [],
42
+ 'metadata' => []
43
+ ], $defaultMeta = [
44
+ 'apiVersion' => null,
45
+ 'serviceFullName' => null,
46
+ 'serviceId' => null,
47
+ 'endpointPrefix' => null,
48
+ 'signingName' => null,
49
+ 'signatureVersion' => null,
50
+ 'protocol' => null,
51
+ 'uid' => null
52
+ ];
53
+
54
+ $definition += $defaults;
55
+ $definition['metadata'] += $defaultMeta;
56
+ $this->definition = $definition;
57
+ $this->apiProvider = $provider;
58
+ parent::__construct($definition, new ShapeMap($definition['shapes']));
59
+
60
+ if (isset($definition['metadata']['serviceIdentifier'])) {
61
+ $this->serviceName = $this->getServiceName();
62
+ } else {
63
+ $this->serviceName = $this->getEndpointPrefix();
64
+ }
65
+
66
+ $this->apiVersion = $this->getApiVersion();
67
+ }
68
+
69
+ /**
70
+ * Creates a request serializer for the provided API object.
71
+ *
72
+ * @param Service $api API that contains a protocol.
73
+ * @param string $endpoint Endpoint to send requests to.
74
+ *
75
+ * @return callable
76
+ * @throws \UnexpectedValueException
77
+ */
78
+ public static function createSerializer(Service $api, $endpoint)
79
+ {
80
+ static $mapping = [
81
+ 'json' => 'Aws\Api\Serializer\JsonRpcSerializer',
82
+ 'query' => 'Aws\Api\Serializer\QuerySerializer',
83
+ 'rest-json' => 'Aws\Api\Serializer\RestJsonSerializer',
84
+ 'rest-xml' => 'Aws\Api\Serializer\RestXmlSerializer'
85
+ ];
86
+
87
+ $proto = $api->getProtocol();
88
+
89
+ if (isset($mapping[$proto])) {
90
+ return new $mapping[$proto]($api, $endpoint);
91
+ }
92
+
93
+ if ($proto == 'ec2') {
94
+ return new QuerySerializer($api, $endpoint, new Ec2ParamBuilder());
95
+ }
96
+
97
+ throw new \UnexpectedValueException(
98
+ 'Unknown protocol: ' . $api->getProtocol()
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Creates an error parser for the given protocol.
104
+ *
105
+ * @param string $protocol Protocol to parse (e.g., query, json, etc.)
106
+ *
107
+ * @return callable
108
+ * @throws \UnexpectedValueException
109
+ */
110
+ public static function createErrorParser($protocol)
111
+ {
112
+ static $mapping = [
113
+ 'json' => 'Aws\Api\ErrorParser\JsonRpcErrorParser',
114
+ 'query' => 'Aws\Api\ErrorParser\XmlErrorParser',
115
+ 'rest-json' => 'Aws\Api\ErrorParser\RestJsonErrorParser',
116
+ 'rest-xml' => 'Aws\Api\ErrorParser\XmlErrorParser',
117
+ 'ec2' => 'Aws\Api\ErrorParser\XmlErrorParser'
118
+ ];
119
+
120
+ if (isset($mapping[$protocol])) {
121
+ return new $mapping[$protocol]();
122
+ }
123
+
124
+ throw new \UnexpectedValueException("Unknown protocol: $protocol");
125
+ }
126
+
127
+ /**
128
+ * Applies the listeners needed to parse client models.
129
+ *
130
+ * @param Service $api API to create a parser for
131
+ * @return callable
132
+ * @throws \UnexpectedValueException
133
+ */
134
+ public static function createParser(Service $api)
135
+ {
136
+ static $mapping = [
137
+ 'json' => 'Aws\Api\Parser\JsonRpcParser',
138
+ 'query' => 'Aws\Api\Parser\QueryParser',
139
+ 'rest-json' => 'Aws\Api\Parser\RestJsonParser',
140
+ 'rest-xml' => 'Aws\Api\Parser\RestXmlParser'
141
+ ];
142
+
143
+ $proto = $api->getProtocol();
144
+ if (isset($mapping[$proto])) {
145
+ return new $mapping[$proto]($api);
146
+ }
147
+
148
+ if ($proto == 'ec2') {
149
+ return new QueryParser($api, null, false);
150
+ }
151
+
152
+ throw new \UnexpectedValueException(
153
+ 'Unknown protocol: ' . $api->getProtocol()
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Get the full name of the service
159
+ *
160
+ * @return string
161
+ */
162
+ public function getServiceFullName()
163
+ {
164
+ return $this->definition['metadata']['serviceFullName'];
165
+ }
166
+
167
+ /**
168
+ * Get the service id
169
+ *
170
+ * @return string
171
+ */
172
+ public function getServiceId()
173
+ {
174
+ return $this->definition['metadata']['serviceId'];
175
+ }
176
+
177
+ /**
178
+ * Get the API version of the service
179
+ *
180
+ * @return string
181
+ */
182
+ public function getApiVersion()
183
+ {
184
+ return $this->definition['metadata']['apiVersion'];
185
+ }
186
+
187
+ /**
188
+ * Get the API version of the service
189
+ *
190
+ * @return string
191
+ */
192
+ public function getEndpointPrefix()
193
+ {
194
+ return $this->definition['metadata']['endpointPrefix'];
195
+ }
196
+
197
+ /**
198
+ * Get the signing name used by the service.
199
+ *
200
+ * @return string
201
+ */
202
+ public function getSigningName()
203
+ {
204
+ return $this->definition['metadata']['signingName']
205
+ ?: $this->definition['metadata']['endpointPrefix'];
206
+ }
207
+
208
+ /**
209
+ * Get the service name.
210
+ *
211
+ * @return string
212
+ */
213
+ public function getServiceName()
214
+ {
215
+ return $this->definition['metadata']['serviceIdentifier'];
216
+ }
217
+
218
+ /**
219
+ * Get the default signature version of the service.
220
+ *
221
+ * Note: this method assumes "v4" when not specified in the model.
222
+ *
223
+ * @return string
224
+ */
225
+ public function getSignatureVersion()
226
+ {
227
+ return $this->definition['metadata']['signatureVersion'] ?: 'v4';
228
+ }
229
+
230
+ /**
231
+ * Get the protocol used by the service.
232
+ *
233
+ * @return string
234
+ */
235
+ public function getProtocol()
236
+ {
237
+ return $this->definition['metadata']['protocol'];
238
+ }
239
+
240
+ /**
241
+ * Get the uid string used by the service
242
+ *
243
+ * @return string
244
+ */
245
+ public function getUid()
246
+ {
247
+ return $this->definition['metadata']['uid'];
248
+ }
249
+
250
+ /**
251
+ * Check if the description has a specific operation by name.
252
+ *
253
+ * @param string $name Operation to check by name
254
+ *
255
+ * @return bool
256
+ */
257
+ public function hasOperation($name)
258
+ {
259
+ return isset($this['operations'][$name]);
260
+ }
261
+
262
+ /**
263
+ * Get an operation by name.
264
+ *
265
+ * @param string $name Operation to retrieve by name
266
+ *
267
+ * @return Operation
268
+ * @throws \InvalidArgumentException If the operation is not found
269
+ */
270
+ public function getOperation($name)
271
+ {
272
+ if (!isset($this->operations[$name])) {
273
+ if (!isset($this->definition['operations'][$name])) {
274
+ throw new \InvalidArgumentException("Unknown operation: $name");
275
+ }
276
+ $this->operations[$name] = new Operation(
277
+ $this->definition['operations'][$name],
278
+ $this->shapeMap
279
+ );
280
+ }
281
+
282
+ return $this->operations[$name];
283
+ }
284
+
285
+ /**
286
+ * Get all of the operations of the description.
287
+ *
288
+ * @return Operation[]
289
+ */
290
+ public function getOperations()
291
+ {
292
+ $result = [];
293
+ foreach ($this->definition['operations'] as $name => $definition) {
294
+ $result[$name] = $this->getOperation($name);
295
+ }
296
+
297
+ return $result;
298
+ }
299
+
300
+ /**
301
+ * Get all of the service metadata or a specific metadata key value.
302
+ *
303
+ * @param string|null $key Key to retrieve or null to retrieve all metadata
304
+ *
305
+ * @return mixed Returns the result or null if the key is not found
306
+ */
307
+ public function getMetadata($key = null)
308
+ {
309
+ if (!$key) {
310
+ return $this['metadata'];
311
+ }
312
+
313
+ if (isset($this->definition['metadata'][$key])) {
314
+ return $this->definition['metadata'][$key];
315
+ }
316
+
317
+ return null;
318
+ }
319
+
320
+ /**
321
+ * Gets an associative array of available paginator configurations where
322
+ * the key is the name of the paginator, and the value is the paginator
323
+ * configuration.
324
+ *
325
+ * @return array
326
+ * @unstable The configuration format of paginators may change in the future
327
+ */
328
+ public function getPaginators()
329
+ {
330
+ if (!isset($this->paginators)) {
331
+ $res = call_user_func(
332
+ $this->apiProvider,
333
+ 'paginator',
334
+ $this->serviceName,
335
+ $this->apiVersion
336
+ );
337
+ $this->paginators = isset($res['pagination'])
338
+ ? $res['pagination']
339
+ : [];
340
+ }
341
+
342
+ return $this->paginators;
343
+ }
344
+
345
+ /**
346
+ * Determines if the service has a paginator by name.
347
+ *
348
+ * @param string $name Name of the paginator.
349
+ *
350
+ * @return bool
351
+ */
352
+ public function hasPaginator($name)
353
+ {
354
+ return isset($this->getPaginators()[$name]);
355
+ }
356
+
357
+ /**
358
+ * Retrieve a paginator by name.
359
+ *
360
+ * @param string $name Paginator to retrieve by name. This argument is
361
+ * typically the operation name.
362
+ * @return array
363
+ * @throws \UnexpectedValueException if the paginator does not exist.
364
+ * @unstable The configuration format of paginators may change in the future
365
+ */
366
+ public function getPaginatorConfig($name)
367
+ {
368
+ static $defaults = [
369
+ 'input_token' => null,
370
+ 'output_token' => null,
371
+ 'limit_key' => null,
372
+ 'result_key' => null,
373
+ 'more_results' => null,
374
+ ];
375
+
376
+ if ($this->hasPaginator($name)) {
377
+ return $this->paginators[$name] + $defaults;
378
+ }
379
+
380
+ throw new \UnexpectedValueException("There is no {$name} "
381
+ . "paginator defined for the {$this->serviceName} service.");
382
+ }
383
+
384
+ /**
385
+ * Gets an associative array of available waiter configurations where the
386
+ * key is the name of the waiter, and the value is the waiter
387
+ * configuration.
388
+ *
389
+ * @return array
390
+ */
391
+ public function getWaiters()
392
+ {
393
+ if (!isset($this->waiters)) {
394
+ $res = call_user_func(
395
+ $this->apiProvider,
396
+ 'waiter',
397
+ $this->serviceName,
398
+ $this->apiVersion
399
+ );
400
+ $this->waiters = isset($res['waiters'])
401
+ ? $res['waiters']
402
+ : [];
403
+ }
404
+
405
+ return $this->waiters;
406
+ }
407
+
408
+ /**
409
+ * Determines if the service has a waiter by name.
410
+ *
411
+ * @param string $name Name of the waiter.
412
+ *
413
+ * @return bool
414
+ */
415
+ public function hasWaiter($name)
416
+ {
417
+ return isset($this->getWaiters()[$name]);
418
+ }
419
+
420
+ /**
421
+ * Get a waiter configuration by name.
422
+ *
423
+ * @param string $name Name of the waiter by name.
424
+ *
425
+ * @return array
426
+ * @throws \UnexpectedValueException if the waiter does not exist.
427
+ */
428
+ public function getWaiterConfig($name)
429
+ {
430
+ // Error if the waiter is not defined
431
+ if ($this->hasWaiter($name)) {
432
+ return $this->waiters[$name];
433
+ }
434
+
435
+ throw new \UnexpectedValueException("There is no {$name} waiter "
436
+ . "defined for the {$this->serviceName} service.");
437
+ }
438
+
439
+ /**
440
+ * Get the shape map used by the API.
441
+ *
442
+ * @return ShapeMap
443
+ */
444
+ public function getShapeMap()
445
+ {
446
+ return $this->shapeMap;
447
+ }
448
+ }
lib/Aws/Aws/Api/Shape.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Base class representing a modeled shape.
6
+ */
7
+ class Shape extends AbstractModel
8
+ {
9
+ /**
10
+ * Get a concrete shape for the given definition.
11
+ *
12
+ * @param array $definition
13
+ * @param ShapeMap $shapeMap
14
+ *
15
+ * @return mixed
16
+ * @throws \RuntimeException if the type is invalid
17
+ */
18
+ public static function create(array $definition, ShapeMap $shapeMap)
19
+ {
20
+ static $map = [
21
+ 'structure' => 'Aws\Api\StructureShape',
22
+ 'map' => 'Aws\Api\MapShape',
23
+ 'list' => 'Aws\Api\ListShape',
24
+ 'timestamp' => 'Aws\Api\TimestampShape',
25
+ 'integer' => 'Aws\Api\Shape',
26
+ 'double' => 'Aws\Api\Shape',
27
+ 'float' => 'Aws\Api\Shape',
28
+ 'long' => 'Aws\Api\Shape',
29
+ 'string' => 'Aws\Api\Shape',
30
+ 'byte' => 'Aws\Api\Shape',
31
+ 'character' => 'Aws\Api\Shape',
32
+ 'blob' => 'Aws\Api\Shape',
33
+ 'boolean' => 'Aws\Api\Shape'
34
+ ];
35
+
36
+ if (isset($definition['shape'])) {
37
+ return $shapeMap->resolve($definition);
38
+ }
39
+
40
+ if (!isset($map[$definition['type']])) {
41
+ throw new \RuntimeException('Invalid type: '
42
+ . print_r($definition, true));
43
+ }
44
+
45
+ $type = $map[$definition['type']];
46
+
47
+ return new $type($definition, $shapeMap);
48
+ }
49
+
50
+ /**
51
+ * Get the type of the shape
52
+ *
53
+ * @return string
54
+ */
55
+ public function getType()
56
+ {
57
+ return $this->definition['type'];
58
+ }
59
+
60
+ /**
61
+ * Get the name of the shape
62
+ *
63
+ * @return string
64
+ */
65
+ public function getName()
66
+ {
67
+ return $this->definition['name'];
68
+ }
69
+ }
lib/Aws/Aws/Api/ShapeMap.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Builds shape based on shape references.
6
+ */
7
+ class ShapeMap
8
+ {
9
+ /** @var array */
10
+ private $definitions;
11
+
12
+ /** @var Shape[] */
13
+ private $simple;
14
+
15
+ /**
16
+ * @param array $shapeModels Associative array of shape definitions.
17
+ */
18
+ public function __construct(array $shapeModels)
19
+ {
20
+ $this->definitions = $shapeModels;
21
+ }
22
+
23
+ /**
24
+ * Get an array of shape names.
25
+ *
26
+ * @return array
27
+ */
28
+ public function getShapeNames()
29
+ {
30
+ return array_keys($this->definitions);
31
+ }
32
+
33
+ /**
34
+ * Resolve a shape reference
35
+ *
36
+ * @param array $shapeRef Shape reference shape
37
+ *
38
+ * @return Shape
39
+ * @throws \InvalidArgumentException
40
+ */
41
+ public function resolve(array $shapeRef)
42
+ {
43
+ $shape = $shapeRef['shape'];
44
+
45
+ if (!isset($this->definitions[$shape])) {
46
+ throw new \InvalidArgumentException('Shape not found: ' . $shape);
47
+ }
48
+
49
+ $isSimple = count($shapeRef) == 1;
50
+ if ($isSimple && isset($this->simple[$shape])) {
51
+ return $this->simple[$shape];
52
+ }
53
+
54
+ $definition = $shapeRef + $this->definitions[$shape];
55
+ $definition['name'] = $definition['shape'];
56
+ unset($definition['shape']);
57
+
58
+ $result = Shape::create($definition, $this);
59
+
60
+ if ($isSimple) {
61
+ $this->simple[$shape] = $result;
62
+ }
63
+
64
+ return $result;
65
+ }
66
+ }
lib/Aws/Aws/Api/StructureShape.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Represents a structure shape and resolve member shape references.
6
+ */
7
+ class StructureShape extends Shape
8
+ {
9
+ /**
10
+ * @var Shape[]
11
+ */
12
+ private $members;
13
+
14
+ public function __construct(array $definition, ShapeMap $shapeMap)
15
+ {
16
+ $definition['type'] = 'structure';
17
+
18
+ if (!isset($definition['members'])) {
19
+ $definition['members'] = [];
20
+ }
21
+
22
+ parent::__construct($definition, $shapeMap);
23
+ }
24
+
25
+ /**
26
+ * Gets a list of all members
27
+ *
28
+ * @return Shape[]
29
+ */
30
+ public function getMembers()
31
+ {
32
+ if (empty($this->members)) {
33
+ $this->generateMembersHash();
34
+ }
35
+
36
+ return $this->members;
37
+ }
38
+
39
+ /**
40
+ * Check if a specific member exists by name.
41
+ *
42
+ * @param string $name Name of the member to check
43
+ *
44
+ * @return bool
45
+ */
46
+ public function hasMember($name)
47
+ {
48
+ return isset($this->definition['members'][$name]);
49
+ }
50
+
51
+ /**
52
+ * Retrieve a member by name.
53
+ *
54
+ * @param string $name Name of the member to retrieve
55
+ *
56
+ * @return Shape
57
+ * @throws \InvalidArgumentException if the member is not found.
58
+ */
59
+ public function getMember($name)
60
+ {
61
+ $members = $this->getMembers();
62
+
63
+ if (!isset($members[$name])) {
64
+ throw new \InvalidArgumentException('Unknown member ' . $name);
65
+ }
66
+
67
+ return $members[$name];
68
+ }
69
+
70
+
71
+ private function generateMembersHash()
72
+ {
73
+ $this->members = [];
74
+
75
+ foreach ($this->definition['members'] as $name => $definition) {
76
+ $this->members[$name] = $this->shapeFor($definition);
77
+ }
78
+ }
79
+ }
lib/Aws/Aws/Api/TimestampShape.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ /**
5
+ * Represents a timestamp shape.
6
+ */
7
+ class TimestampShape extends Shape
8
+ {
9
+ public function __construct(array $definition, ShapeMap $shapeMap)
10
+ {
11
+ $definition['type'] = 'timestamp';
12
+ parent::__construct($definition, $shapeMap);
13
+ }
14
+
15
+ /**
16
+ * Formats a timestamp value for a service.
17
+ *
18
+ * @param mixed $value Value to format
19
+ * @param string $format Format used to serialize the value
20
+ *
21
+ * @return int|string
22
+ * @throws \UnexpectedValueException if the format is unknown.
23
+ * @throws \InvalidArgumentException if the value is an unsupported type.
24
+ */
25
+ public static function format($value, $format)
26
+ {
27
+ if ($value instanceof \DateTime) {
28
+ $value = $value->getTimestamp();
29
+ } elseif (is_string($value)) {
30
+ $value = strtotime($value);
31
+ } elseif (!is_int($value)) {
32
+ throw new \InvalidArgumentException('Unable to handle the provided'
33
+ . ' timestamp type: ' . gettype($value));
34
+ }
35
+
36
+ switch ($format) {
37
+ case 'iso8601':
38
+ return gmdate('Y-m-d\TH:i:s\Z', $value);
39
+ case 'rfc822':
40
+ return gmdate('D, d M Y H:i:s \G\M\T', $value);
41
+ case 'unixTimestamp':
42
+ return $value;
43
+ default:
44
+ throw new \UnexpectedValueException('Unknown timestamp format: '
45
+ . $format);
46
+ }
47
+ }
48
+ }
lib/Aws/Aws/Api/Validator.php ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Api;
3
+
4
+ use Aws;
5
+
6
+ /**
7
+ * Validates a schema against a hash of input.
8
+ */
9
+ class Validator
10
+ {
11
+ private $path = [];
12
+ private $errors = [];
13
+ private $constraints = [];
14
+
15
+ private static $defaultConstraints = [
16
+ 'required' => true,
17
+ 'min' => true,
18
+ 'max' => false,
19
+ 'pattern' => false
20
+ ];
21
+
22
+ /**
23
+ * @param array $constraints Associative array of constraints to enforce.
24
+ * Accepts the following keys: "required", "min",
25
+ * "max", and "pattern". If a key is not
26
+ * provided, the constraint will assume false.
27
+ */
28
+ public function __construct(array $constraints = null)
29
+ {
30
+ static $assumedFalseValues = [
31
+ 'required' => false,
32
+ 'min' => false,
33
+ 'max' => false,
34
+ 'pattern' => false
35
+ ];
36
+ $this->constraints = empty($constraints)
37
+ ? self::$defaultConstraints
38
+ : $constraints + $assumedFalseValues;
39
+ }
40
+
41
+ /**
42
+ * Validates the given input against the schema.
43
+ *
44
+ * @param string $name Operation name
45
+ * @param Shape $shape Shape to validate
46
+ * @param array $input Input to validate
47
+ *
48
+ * @throws \InvalidArgumentException if the input is invalid.
49
+ */
50
+ public function validate($name, Shape $shape, array $input)
51
+ {
52
+ $this->dispatch($shape, $input);
53
+
54
+ if ($this->errors) {
55
+ $message = sprintf(
56
+ "Found %d error%s while validating the input provided for the "
57
+ . "%s operation:\n%s",
58
+ count($this->errors),
59
+ count($this->errors) > 1 ? 's' : '',
60
+ $name,
61
+ implode("\n", $this->errors)
62
+ );
63
+ $this->errors = [];
64
+
65
+ throw new \InvalidArgumentException($message);
66
+ }
67
+ }
68
+
69
+ private function dispatch(Shape $shape, $value)
70
+ {
71
+ static $methods = [
72
+ 'structure' => 'check_structure',
73
+ 'list' => 'check_list',
74
+ 'map' => 'check_map',
75
+ 'blob' => 'check_blob',
76
+ 'boolean' => 'check_boolean',
77
+ 'integer' => 'check_numeric',
78
+ 'float' => 'check_numeric',
79
+ 'long' => 'check_numeric',
80
+ 'string' => 'check_string',
81
+ 'byte' => 'check_string',
82
+ 'char' => 'check_string'
83
+ ];
84
+
85
+ $type = $shape->getType();
86
+ if (isset($methods[$type])) {
87
+ $this->{$methods[$type]}($shape, $value);
88
+ }
89
+ }
90
+
91
+ private function check_structure(StructureShape $shape, $value)
92
+ {
93
+ if (!$this->checkAssociativeArray($value)) {
94
+ return;
95
+ }
96
+
97
+ if ($this->constraints['required'] && $shape['required']) {
98
+ foreach ($shape['required'] as $req) {
99
+ if (!isset($value[$req])) {
100
+ $this->path[] = $req;
101
+ $this->addError('is missing and is a required parameter');
102
+ array_pop($this->path);
103
+ }
104
+ }
105
+ }
106
+
107
+ foreach ($value as $name => $v) {
108
+ if ($shape->hasMember($name)) {
109
+ $this->path[] = $name;
110
+ $this->dispatch(
111
+ $shape->getMember($name),
112
+ isset($value[$name]) ? $value[$name] : null
113
+ );
114
+ array_pop($this->path);
115
+ }
116
+ }
117
+ }
118
+
119
+ private function check_list(ListShape $shape, $value)
120
+ {
121
+ if (!is_array($value)) {
122
+ $this->addError('must be an array. Found '
123
+ . Aws\describe_type($value));
124
+ return;
125
+ }
126
+
127
+ $this->validateRange($shape, count($value), "list element count");
128
+
129
+ $items = $shape->getMember();
130
+ foreach ($value as $index => $v) {
131
+ $this->path[] = $index;
132
+ $this->dispatch($items, $v);
133
+ array_pop($this->path);
134
+ }
135
+ }
136
+
137
+ private function check_map(MapShape $shape, $value)
138
+ {
139
+ if (!$this->checkAssociativeArray($value)) {
140
+ return;
141
+ }
142
+
143
+ $values = $shape->getValue();
144
+ foreach ($value as $key => $v) {
145
+ $this->path[] = $key;
146
+ $this->dispatch($values, $v);
147
+ array_pop($this->path);
148
+ }
149
+ }
150
+
151
+ private function check_blob(Shape $shape, $value)
152
+ {
153
+ static $valid = [
154
+ 'string' => true,
155
+ 'integer' => true,
156
+ 'double' => true,
157
+ 'resource' => true
158
+ ];
159
+
160
+ $type = gettype($value);
161
+ if (!isset($valid[$type])) {
162
+ if ($type != 'object' || !method_exists($value, '__toString')) {
163
+ $this->addError('must be an fopen resource, a '
164
+ . 'GuzzleHttp\Stream\StreamInterface object, or something '
165
+ . 'that can be cast to a string. Found '
166
+ . Aws\describe_type($value));
167
+ }
168
+ }
169
+ }
170
+
171
+ private function check_numeric(Shape $shape, $value)
172
+ {
173
+ if (!is_numeric($value)) {
174
+ $this->addError('must be numeric. Found '
175
+ . Aws\describe_type($value));
176
+ return;
177
+ }
178
+
179
+ $this->validateRange($shape, $value, "numeric value");
180
+ }
181
+
182
+ private function check_boolean(Shape $shape, $value)
183
+ {
184
+ if (!is_bool($value)) {
185
+ $this->addError('must be a boolean. Found '
186
+ . Aws\describe_type($value));
187
+ }
188
+ }
189
+
190
+ private function check_string(Shape $shape, $value)
191
+ {
192
+ if ($shape['jsonvalue']) {
193
+ if (!self::canJsonEncode($value)) {
194
+ $this->addError('must be a value encodable with \'json_encode\'.'
195
+ . ' Found ' . Aws\describe_type($value));
196
+ }
197
+ return;
198
+ }
199
+
200
+ if (!$this->checkCanString($value)) {
201
+ $this->addError('must be a string or an object that implements '
202
+ . '__toString(). Found ' . Aws\describe_type($value));
203
+ return;
204
+ }
205
+
206
+ $this->validateRange($shape, strlen($value), "string length");
207
+
208
+ if ($this->constraints['pattern']) {
209
+ $pattern = $shape['pattern'];
210
+ if ($pattern && !preg_match("/$pattern/", $value)) {
211
+ $this->addError("Pattern /$pattern/ failed to match '$value'");
212
+ }
213
+ }
214
+ }
215
+
216
+ private function validateRange(Shape $shape, $length, $descriptor)
217
+ {
218
+ if ($this->constraints['min']) {
219
+ $min = $shape['min'];
220
+ if ($min && $length < $min) {
221
+ $this->addError("expected $descriptor to be >= $min, but "
222
+ . "found $descriptor of $length");
223
+ }
224
+ }
225
+
226
+ if ($this->constraints['max']) {
227
+ $max = $shape['max'];
228
+ if ($max && $length > $max) {
229
+ $this->addError("expected $descriptor to be <= $max, but "
230
+ . "found $descriptor of $length");
231
+ }
232
+ }
233
+ }
234
+
235
+ private function checkCanString($value)
236
+ {
237
+ static $valid = [
238
+ 'string' => true,
239
+ 'integer' => true,
240
+ 'double' => true,
241
+ 'NULL' => true,
242
+ ];
243
+
244
+ $type = gettype($value);
245
+
246
+ return isset($valid[$type]) ||
247
+ ($type == 'object' && method_exists($value, '__toString'));
248
+ }
249
+
250
+ private function checkAssociativeArray($value)
251
+ {
252
+ $isAssociative = false;
253
+
254
+ if (is_array($value)) {
255
+ $expectedIndex = 0;
256
+ $key = key($value);
257
+
258
+ do {
259
+ $isAssociative = $key !== $expectedIndex++;
260
+ next($value);
261
+ $key = key($value);
262
+ } while (!$isAssociative && null !== $key);
263
+ }
264
+
265
+ if (!$isAssociative) {
266
+ $this->addError('must be an associative array. Found '
267
+ . Aws\describe_type($value));
268
+ return false;
269
+ }
270
+
271
+ return true;
272
+ }
273
+
274
+ private function addError($message)
275
+ {
276
+ $this->errors[] =
277
+ implode('', array_map(function ($s) { return "[{$s}]"; }, $this->path))
278
+ . ' '
279
+ . $message;
280
+ }
281
+
282
+ private function canJsonEncode($data)
283
+ {
284
+ return !is_resource($data);
285
+ }
286
+ }
lib/Aws/Aws/AwsClient.php ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\ApiProvider;
5
+ use Aws\Api\DocModel;
6
+ use Aws\Api\Service;
7
+ use Aws\ClientSideMonitoring\ApiCallAttemptMonitoringMiddleware;
8
+ use Aws\ClientSideMonitoring\ApiCallMonitoringMiddleware;
9
+ use Aws\ClientSideMonitoring\ConfigurationProvider;
10
+ use Aws\EndpointDiscovery\EndpointDiscoveryMiddleware;
11
+ use Aws\Signature\SignatureProvider;
12
+ use GuzzleHttp\Psr7\Uri;
13
+
14
+ /**
15
+ * Default AWS client implementation
16
+ */
17
+ class AwsClient implements AwsClientInterface
18
+ {
19
+ use AwsClientTrait;
20
+
21
+ /** @var array */
22
+ private $config;
23
+
24
+ /** @var string */
25
+ private $region;
26
+
27
+ /** @var string */
28
+ private $endpoint;
29
+
30
+ /** @var Service */
31
+ private $api;
32
+
33
+ /** @var callable */
34
+ private $signatureProvider;
35
+
36
+ /** @var callable */
37
+ private $credentialProvider;
38
+
39
+ /** @var HandlerList */
40
+ private $handlerList;
41
+
42
+ /** @var array*/
43
+ private $defaultRequestOptions;
44
+
45
+ /**
46
+ * Get an array of client constructor arguments used by the client.
47
+ *
48
+ * @return array
49
+ */
50
+ public static function getArguments()
51
+ {
52
+ return ClientResolver::getDefaultArguments();
53
+ }
54
+
55
+ /**
56
+ * The client constructor accepts the following options:
57
+ *
58
+ * - api_provider: (callable) An optional PHP callable that accepts a
59
+ * type, service, and version argument, and returns an array of
60
+ * corresponding configuration data. The type value can be one of api,
61
+ * waiter, or paginator.
62
+ * - credentials:
63
+ * (Aws\Credentials\CredentialsInterface|array|bool|callable) Specifies
64
+ * the credentials used to sign requests. Provide an
65
+ * Aws\Credentials\CredentialsInterface object, an associative array of
66
+ * "key", "secret", and an optional "token" key, `false` to use null
67
+ * credentials, or a callable credentials provider used to create
68
+ * credentials or return null. See Aws\Credentials\CredentialProvider for
69
+ * a list of built-in credentials providers. If no credentials are
70
+ * provided, the SDK will attempt to load them from the environment.
71
+ * - debug: (bool|array) Set to true to display debug information when
72
+ * sending requests. Alternatively, you can provide an associative array
73
+ * with the following keys: logfn: (callable) Function that is invoked
74
+ * with log messages; stream_size: (int) When the size of a stream is
75
+ * greater than this number, the stream data will not be logged (set to
76
+ * "0" to not log any stream data); scrub_auth: (bool) Set to false to
77
+ * disable the scrubbing of auth data from the logged messages; http:
78
+ * (bool) Set to false to disable the "debug" feature of lower level HTTP
79
+ * adapters (e.g., verbose curl output).
80
+ * - stats: (bool|array) Set to true to gather transfer statistics on
81
+ * requests sent. Alternatively, you can provide an associative array with
82
+ * the following keys: retries: (bool) Set to false to disable reporting
83
+ * on retries attempted; http: (bool) Set to true to enable collecting
84
+ * statistics from lower level HTTP adapters (e.g., values returned in
85
+ * GuzzleHttp\TransferStats). HTTP handlers must support an
86
+ * `http_stats_receiver` option for this to have an effect; timer: (bool)
87
+ * Set to true to enable a command timer that reports the total wall clock
88
+ * time spent on an operation in seconds.
89
+ * - disable_host_prefix_injection: (bool) Set to true to disable host prefix
90
+ * injection logic for services that use it. This disables the entire
91
+ * prefix injection, including the portions supplied by user-defined
92
+ * parameters. Setting this flag will have no effect on services that do
93
+ * not use host prefix injection.
94
+ * - endpoint: (string) The full URI of the webservice. This is only
95
+ * required when connecting to a custom endpoint (e.g., a local version
96
+ * of S3).
97
+ * - endpoint_discovery: (Aws\EndpointDiscovery\ConfigurationInterface,
98
+ * Aws\CacheInterface, array, callable) Settings for endpoint discovery.
99
+ * Provide an instance of Aws\EndpointDiscovery\ConfigurationInterface,
100
+ * an instance Aws\CacheInterface, a callable that provides a promise for
101
+ * a Configuration object, or an associative array with the following
102
+ * keys: enabled: (bool) Set to true to enable endpoint discovery,
103
+ * defaults to false; cache_limit: (int) The maximum number of keys in the
104
+ * endpoints cache, defaults to 1000.
105
+ * - endpoint_provider: (callable) An optional PHP callable that
106
+ * accepts a hash of options including a "service" and "region" key and
107
+ * returns NULL or a hash of endpoint data, of which the "endpoint" key
108
+ * is required. See Aws\Endpoint\EndpointProvider for a list of built-in
109
+ * providers.
110
+ * - handler: (callable) A handler that accepts a command object,
111
+ * request object and returns a promise that is fulfilled with an
112
+ * Aws\ResultInterface object or rejected with an
113
+ * Aws\Exception\AwsException. A handler does not accept a next handler
114
+ * as it is terminal and expected to fulfill a command. If no handler is
115
+ * provided, a default Guzzle handler will be utilized.
116
+ * - http: (array, default=array(0)) Set to an array of SDK request
117
+ * options to apply to each request (e.g., proxy, verify, etc.).
118
+ * - http_handler: (callable) An HTTP handler is a function that
119
+ * accepts a PSR-7 request object and returns a promise that is fulfilled
120
+ * with a PSR-7 response object or rejected with an array of exception
121
+ * data. NOTE: This option supersedes any provided "handler" option.
122
+ * - idempotency_auto_fill: (bool|callable) Set to false to disable SDK to
123
+ * populate parameters that enabled 'idempotencyToken' trait with a random
124
+ * UUID v4 value on your behalf. Using default value 'true' still allows
125
+ * parameter value to be overwritten when provided. Note: auto-fill only
126
+ * works when cryptographically secure random bytes generator functions
127
+ * (random_bytes, openssl_random_pseudo_bytes or mcrypt_create_iv) can be
128
+ * found. You may also provide a callable source of random bytes.
129
+ * - profile: (string) Allows you to specify which profile to use when
130
+ * credentials are created from the AWS credentials file in your HOME
131
+ * directory. This setting overrides the AWS_PROFILE environment
132
+ * variable. Note: Specifying "profile" will cause the "credentials" key
133
+ * to be ignored.
134
+ * - region: (string, required) Region to connect to. See
135
+ * http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of
136
+ * available regions.
137
+ * - retries: (int, default=int(3)) Configures the maximum number of
138
+ * allowed retries for a client (pass 0 to disable retries).
139
+ * - scheme: (string, default=string(5) "https") URI scheme to use when
140
+ * connecting connect. The SDK will utilize "https" endpoints (i.e.,
141
+ * utilize SSL/TLS connections) by default. You can attempt to connect to
142
+ * a service over an unencrypted "http" endpoint by setting ``scheme`` to
143
+ * "http".
144
+ * - signature_provider: (callable) A callable that accepts a signature
145
+ * version name (e.g., "v4"), a service name, and region, and
146
+ * returns a SignatureInterface object or null. This provider is used to
147
+ * create signers utilized by the client. See
148
+ * Aws\Signature\SignatureProvider for a list of built-in providers
149
+ * - signature_version: (string) A string representing a custom
150
+ * signature version to use with a service (e.g., v4). Note that
151
+ * per/operation signature version MAY override this requested signature
152
+ * version.
153
+ * - validate: (bool, default=bool(true)) Set to false to disable
154
+ * client-side parameter validation.
155
+ * - version: (string, required) The version of the webservice to
156
+ * utilize (e.g., 2006-03-01).
157
+ *
158
+ * @param array $args Client configuration arguments.
159
+ *
160
+ * @throws \InvalidArgumentException if any required options are missing or
161
+ * the service is not supported.
162
+ */
163
+ public function __construct(array $args)
164
+ {
165
+ list($service, $exceptionClass) = $this->parseClass();
166
+ if (!isset($args['service'])) {
167
+ $args['service'] = manifest($service)['endpoint'];
168
+ }
169
+ if (!isset($args['exception_class'])) {
170
+ $args['exception_class'] = $exceptionClass;
171
+ }
172
+ $this->handlerList = new HandlerList();
173
+ $resolver = new ClientResolver(static::getArguments());
174
+ $config = $resolver->resolve($args, $this->handlerList);
175
+ $this->api = $config['api'];
176
+ $this->signatureProvider = $config['signature_provider'];
177
+ $this->endpoint = new Uri($config['endpoint']);
178
+ $this->credentialProvider = $config['credentials'];
179
+ $this->region = isset($config['region']) ? $config['region'] : null;
180
+ $this->config = $config['config'];
181
+ $this->defaultRequestOptions = $config['http'];
182
+ $this->addSignatureMiddleware();
183
+ $this->addInvocationId();
184
+ $this->addClientSideMonitoring($args);
185
+ $this->addEndpointParameterMiddleware($args);
186
+ $this->addEndpointDiscoveryMiddleware($config, $args);
187
+
188
+ if (isset($args['with_resolved'])) {
189
+ $args['with_resolved']($config);
190
+ }
191
+ }
192
+
193
+ public function getHandlerList()
194
+ {
195
+ return $this->handlerList;
196
+ }
197
+
198
+ public function getConfig($option = null)
199
+ {
200
+ return $option === null
201
+ ? $this->config
202
+ : (isset($this->config[$option])
203
+ ? $this->config[$option]
204
+ : null);
205
+ }
206
+
207
+ public function getCredentials()
208
+ {
209
+ $fn = $this->credentialProvider;
210
+ return $fn();
211
+ }
212
+
213
+ public function getEndpoint()
214
+ {
215
+ return $this->endpoint;
216
+ }
217
+
218
+ public function getRegion()
219
+ {
220
+ return $this->region;
221
+ }
222
+
223
+ public function getApi()
224
+ {
225
+ return $this->api;
226
+ }
227
+
228
+ public function getCommand($name, array $args = [])
229
+ {
230
+ // Fail fast if the command cannot be found in the description.
231
+ if (!isset($this->getApi()['operations'][$name])) {
232
+ $name = ucfirst($name);
233
+ if (!isset($this->getApi()['operations'][$name])) {
234
+ throw new \InvalidArgumentException("Operation not found: $name");
235
+ }
236
+ }
237
+
238
+ if (!isset($args['@http'])) {
239
+ $args['@http'] = $this->defaultRequestOptions;
240
+ } else {
241
+ $args['@http'] += $this->defaultRequestOptions;
242
+ }
243
+
244
+ return new Command($name, $args, clone $this->getHandlerList());
245
+ }
246
+
247
+ public function __sleep()
248
+ {
249
+ throw new \RuntimeException('Instances of ' . static::class
250
+ . ' cannot be serialized');
251
+ }
252
+
253
+ /**
254
+ * Get the signature_provider function of the client.
255
+ *
256
+ * @return callable
257
+ */
258
+ final protected function getSignatureProvider()
259
+ {
260
+ return $this->signatureProvider;
261
+ }
262
+
263
+ /**
264
+ * Parse the class name and setup the custom exception class of the client
265
+ * and return the "service" name of the client and "exception_class".
266
+ *
267
+ * @return array
268
+ */
269
+ private function parseClass()
270
+ {
271
+ $klass = get_class($this);
272
+
273
+ if ($klass === __CLASS__) {
274
+ return ['', 'Aws\Exception\AwsException'];
275
+ }
276
+
277
+ $service = substr($klass, strrpos($klass, '\\') + 1, -6);
278
+
279
+ return [
280
+ strtolower($service),
281
+ "Aws\\{$service}\\Exception\\{$service}Exception"
282
+ ];
283
+ }
284
+
285
+ private function addEndpointParameterMiddleware($args)
286
+ {
287
+ if (empty($args['disable_host_prefix_injection'])) {
288
+ $list = $this->getHandlerList();
289
+ $list->appendBuild(
290
+ EndpointParameterMiddleware::wrap(
291
+ $this->api
292
+ ),
293
+ 'endpoint_parameter'
294
+ );
295
+ }
296
+ }
297
+
298
+ private function addEndpointDiscoveryMiddleware($config, $args)
299
+ {
300
+ $list = $this->getHandlerList();
301
+
302
+ if (!isset($args['endpoint'])) {
303
+ $list->appendBuild(
304
+ EndpointDiscoveryMiddleware::wrap(
305
+ $this,
306
+ $args,
307
+ $config['endpoint_discovery']
308
+ ),
309
+ 'EndpointDiscoveryMiddleware'
310
+ );
311
+ }
312
+ }
313
+
314
+ private function addSignatureMiddleware()
315
+ {
316
+ $api = $this->getApi();
317
+ $provider = $this->signatureProvider;
318
+ $version = $this->config['signature_version'];
319
+ $name = $this->config['signing_name'];
320
+ $region = $this->config['signing_region'];
321
+
322
+ $resolver = static function (
323
+ CommandInterface $c
324
+ ) use ($api, $provider, $name, $region, $version) {
325
+ $authType = $api->getOperation($c->getName())['authtype'];
326
+ switch ($authType){
327
+ case 'none':
328
+ $version = 'anonymous';
329
+ break;
330
+ case 'v4-unsigned-body':
331
+ $version = 'v4-unsigned-body';
332
+ break;
333
+ }
334
+ return SignatureProvider::resolve($provider, $version, $name, $region);
335
+ };
336
+ $this->handlerList->appendSign(
337
+ Middleware::signer($this->credentialProvider, $resolver),
338
+ 'signer'
339
+ );
340
+ }
341
+
342
+ private function addInvocationId()
343
+ {
344
+ // Add invocation id to each request
345
+ $this->handlerList->prependSign(Middleware::invocationId(), 'invocation-id');
346
+ }
347
+
348
+ private function addClientSideMonitoring($args)
349
+ {
350
+ $options = ConfigurationProvider::defaultProvider($args);
351
+
352
+ $this->handlerList->appendBuild(
353
+ ApiCallMonitoringMiddleware::wrap(
354
+ $this->credentialProvider,
355
+ $options,
356
+ $this->region,
357
+ $this->getApi()->getServiceId()
358
+ ),
359
+ 'ApiCallMonitoringMiddleware'
360
+ );
361
+
362
+ $callAttemptMiddleware = ApiCallAttemptMonitoringMiddleware::wrap(
363
+ $this->credentialProvider,
364
+ $options,
365
+ $this->region,
366
+ $this->getApi()->getServiceId()
367
+ );
368
+ $this->handlerList->appendAttempt (
369
+ $callAttemptMiddleware,
370
+ 'ApiCallAttemptMonitoringMiddleware'
371
+ );
372
+ }
373
+
374
+ /**
375
+ * Returns a service model and doc model with any necessary changes
376
+ * applied.
377
+ *
378
+ * @param array $api Array of service data being documented.
379
+ * @param array $docs Array of doc model data.
380
+ *
381
+ * @return array Tuple containing a [Service, DocModel]
382
+ *
383
+ * @internal This should only used to document the service API.
384
+ * @codeCoverageIgnore
385
+ */
386
+ public static function applyDocFilters(array $api, array $docs)
387
+ {
388
+ return [
389
+ new Service($api, ApiProvider::defaultProvider()),
390
+ new DocModel($docs)
391
+ ];
392
+ }
393
+
394
+ /**
395
+ * @deprecated
396
+ * @return static
397
+ */
398
+ public static function factory(array $config = [])
399
+ {
400
+ return new static($config);
401
+ }
402
+ }
lib/Aws/Aws/AwsClientInterface.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Psr\Http\Message\UriInterface;
5
+ use GuzzleHttp\Promise\PromiseInterface;
6
+
7
+ /**
8
+ * Represents an AWS client.
9
+ */
10
+ interface AwsClientInterface
11
+ {
12
+ /**
13
+ * Creates and executes a command for an operation by name.
14
+ *
15
+ * Suffixing an operation name with "Async" will return a
16
+ * promise that can be used to execute commands asynchronously.
17
+ *
18
+ * @param string $name Name of the command to execute.
19
+ * @param array $arguments Arguments to pass to the getCommand method.
20
+ *
21
+ * @return ResultInterface
22
+ * @throws \Exception
23
+ */
24
+ public function __call($name, array $arguments);
25
+
26
+ /**
27
+ * Create a command for an operation name.
28
+ *
29
+ * Special keys may be set on the command to control how it behaves,
30
+ * including:
31
+ *
32
+ * - @http: Associative array of transfer specific options to apply to the
33
+ * request that is serialized for this command. Available keys include
34
+ * "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and
35
+ * "headers".
36
+ *
37
+ * @param string $name Name of the operation to use in the command
38
+ * @param array $args Arguments to pass to the command
39
+ *
40
+ * @return CommandInterface
41
+ * @throws \InvalidArgumentException if no command can be found by name
42
+ */
43
+ public function getCommand($name, array $args = []);
44
+
45
+ /**
46
+ * Execute a single command.
47
+ *
48
+ * @param CommandInterface $command Command to execute
49
+ *
50
+ * @return ResultInterface
51
+ * @throws \Exception
52
+ */
53
+ public function execute(CommandInterface $command);
54
+
55
+ /**
56
+ * Execute a command asynchronously.
57
+ *
58
+ * @param CommandInterface $command Command to execute
59
+ *
60
+ * @return \GuzzleHttp\Promise\PromiseInterface
61
+ */
62
+ public function executeAsync(CommandInterface $command);
63
+
64
+ /**
65
+ * Returns a promise that is fulfilled with an
66
+ * {@see \Aws\Credentials\CredentialsInterface} object.
67
+ *
68
+ * If you need the credentials synchronously, then call the wait() method
69
+ * on the returned promise.
70
+ *
71
+ * @return PromiseInterface
72
+ */
73
+ public function getCredentials();
74
+
75
+ /**
76
+ * Get the region to which the client is configured to send requests.
77
+ *
78
+ * @return string
79
+ */
80
+ public function getRegion();
81
+
82
+ /**
83
+ * Gets the default endpoint, or base URL, used by the client.
84
+ *
85
+ * @return UriInterface
86
+ */
87
+ public function getEndpoint();
88
+
89
+ /**
90
+ * Get the service description associated with the client.
91
+ *
92
+ * @return \Aws\Api\Service
93
+ */
94
+ public function getApi();
95
+
96
+ /**
97
+ * Get a client configuration value.
98
+ *
99
+ * @param string|null $option The option to retrieve. Pass null to retrieve
100
+ * all options.
101
+ * @return mixed|null
102
+ */
103
+ public function getConfig($option = null);
104
+
105
+ /**
106
+ * Get the handler list used to transfer commands.
107
+ *
108
+ * This list can be modified to add middleware or to change the underlying
109
+ * handler used to send HTTP requests.
110
+ *
111
+ * @return HandlerList
112
+ */
113
+ public function getHandlerList();
114
+
115
+ /**
116
+ * Get a resource iterator for the specified operation.
117
+ *
118
+ * @param string $name Name of the iterator to retrieve.
119
+ * @param array $args Command arguments to use with each command.
120
+ *
121
+ * @return \Iterator
122
+ * @throws \UnexpectedValueException if the iterator config is invalid.
123
+ */
124
+ public function getIterator($name, array $args = []);
125
+
126
+ /**
127
+ * Get a result paginator for the specified operation.
128
+ *
129
+ * @param string $name Name of the operation used for iterator
130
+ * @param array $args Command args to be used with each command
131
+ *
132
+ * @return \Aws\ResultPaginator
133
+ * @throws \UnexpectedValueException if the iterator config is invalid.
134
+ */
135
+ public function getPaginator($name, array $args = []);
136
+
137
+ /**
138
+ * Wait until a resource is in a particular state.
139
+ *
140
+ * @param string|callable $name Name of the waiter that defines the wait
141
+ * configuration and conditions.
142
+ * @param array $args Args to be used with each command executed
143
+ * by the waiter. Waiter configuration options
144
+ * can be provided in an associative array in
145
+ * the @waiter key.
146
+ * @return void
147
+ * @throws \UnexpectedValueException if the waiter is invalid.
148
+ */
149
+ public function waitUntil($name, array $args = []);
150
+
151
+ /**
152
+ * Get a waiter that waits until a resource is in a particular state.
153
+ *
154
+ * Retrieving a waiter can be useful when you wish to wait asynchronously:
155
+ *
156
+ * $waiter = $client->getWaiter('foo', ['bar' => 'baz']);
157
+ * $waiter->promise()->then(function () { echo 'Done!'; });
158
+ *
159
+ * @param string|callable $name Name of the waiter that defines the wait
160
+ * configuration and conditions.
161
+ * @param array $args Args to be used with each command executed
162
+ * by the waiter. Waiter configuration options
163
+ * can be provided in an associative array in
164
+ * the @waiter key.
165
+ * @return \Aws\Waiter
166
+ * @throws \UnexpectedValueException if the waiter is invalid.
167
+ */
168
+ public function getWaiter($name, array $args = []);
169
+ }
lib/Aws/Aws/AwsClientTrait.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\Service;
5
+
6
+ /**
7
+ * A trait providing generic functionality for interacting with Amazon Web
8
+ * Services. This is meant to be used in classes implementing
9
+ * \Aws\AwsClientInterface
10
+ */
11
+ trait AwsClientTrait
12
+ {
13
+ public function getPaginator($name, array $args = [])
14
+ {
15
+ $config = $this->getApi()->getPaginatorConfig($name);
16
+
17
+ return new ResultPaginator($this, $name, $args, $config);
18
+ }
19
+
20
+ public function getIterator($name, array $args = [])
21
+ {
22
+ $config = $this->getApi()->getPaginatorConfig($name);
23
+ if (!$config['result_key']) {
24
+ throw new \UnexpectedValueException(sprintf(
25
+ 'There are no resources to iterate for the %s operation of %s',
26
+ $name, $this->getApi()['serviceFullName']
27
+ ));
28
+ }
29
+
30
+ $key = is_array($config['result_key'])
31
+ ? $config['result_key'][0]
32
+ : $config['result_key'];
33
+
34
+ if ($config['output_token'] && $config['input_token']) {
35
+ return $this->getPaginator($name, $args)->search($key);
36
+ }
37
+
38
+ $result = $this->execute($this->getCommand($name, $args))->search($key);
39
+
40
+ return new \ArrayIterator((array) $result);
41
+ }
42
+
43
+ public function waitUntil($name, array $args = [])
44
+ {
45
+ return $this->getWaiter($name, $args)->promise()->wait();
46
+ }
47
+
48
+ public function getWaiter($name, array $args = [])
49
+ {
50
+ $config = isset($args['@waiter']) ? $args['@waiter'] : [];
51
+ $config += $this->getApi()->getWaiterConfig($name);
52
+
53
+ return new Waiter($this, $name, $args, $config);
54
+ }
55
+
56
+ public function execute(CommandInterface $command)
57
+ {
58
+ return $this->executeAsync($command)->wait();
59
+ }
60
+
61
+ public function executeAsync(CommandInterface $command)
62
+ {
63
+ $handler = $command->getHandlerList()->resolve();
64
+ return $handler($command);
65
+ }
66
+
67
+ public function __call($name, array $args)
68
+ {
69
+ $params = isset($args[0]) ? $args[0] : [];
70
+
71
+ if (substr($name, -5) === 'Async') {
72
+ return $this->executeAsync(
73
+ $this->getCommand(substr($name, 0, -5), $params)
74
+ );
75
+ }
76
+
77
+ return $this->execute($this->getCommand($name, $params));
78
+ }
79
+
80
+ /**
81
+ * @param string $name
82
+ * @param array $args
83
+ *
84
+ * @return CommandInterface
85
+ */
86
+ abstract public function getCommand($name, array $args = []);
87
+
88
+ /**
89
+ * @return Service
90
+ */
91
+ abstract public function getApi();
92
+ }
lib/Aws/Aws/CacheInterface.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Represents a simple cache interface.
6
+ */
7
+ interface CacheInterface
8
+ {
9
+ /**
10
+ * Get a cache item by key.
11
+ *
12
+ * @param string $key Key to retrieve.
13
+ *
14
+ * @return mixed|null Returns the value or null if not found.
15
+ */
16
+ public function get($key);
17
+
18
+ /**
19
+ * Set a cache key value.
20
+ *
21
+ * @param string $key Key to set
22
+ * @param mixed $value Value to set.
23
+ * @param int $ttl Number of seconds the item is allowed to live. Set
24
+ * to 0 to allow an unlimited lifetime.
25
+ */
26
+ public function set($key, $value, $ttl = 0);
27
+
28
+ /**
29
+ * Remove a cache key.
30
+ *
31
+ * @param string $key Key to remove.
32
+ */
33
+ public function remove($key);
34
+ }
lib/Aws/Aws/ClientResolver.php ADDED
@@ -0,0 +1,768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\Validator;
5
+ use Aws\Api\ApiProvider;
6
+ use Aws\Api\Service;
7
+ use Aws\Credentials\Credentials;
8
+ use Aws\Credentials\CredentialsInterface;
9
+ use Aws\Endpoint\PartitionEndpointProvider;
10
+ use Aws\EndpointDiscovery\ConfigurationInterface;
11
+ use Aws\EndpointDiscovery\ConfigurationProvider;
12
+ use Aws\EndpointDiscovery\EndpointDiscoveryMiddleware;
13
+ use Aws\Signature\SignatureProvider;
14
+ use Aws\Endpoint\EndpointProvider;
15
+ use Aws\Credentials\CredentialProvider;
16
+ use InvalidArgumentException as IAE;
17
+ use Psr\Http\Message\RequestInterface;
18
+
19
+ /**
20
+ * @internal Resolves a hash of client arguments to construct a client.
21
+ */
22
+ class ClientResolver
23
+ {
24
+ /** @var array */
25
+ private $argDefinitions;
26
+
27
+ /** @var array Map of types to a corresponding function */
28
+ private static $typeMap = [
29
+ 'resource' => 'is_resource',
30
+ 'callable' => 'is_callable',
31
+ 'int' => 'is_int',
32
+ 'bool' => 'is_bool',
33
+ 'string' => 'is_string',
34
+ 'object' => 'is_object',
35
+ 'array' => 'is_array',
36
+ ];
37
+
38
+ private static $defaultArgs = [
39
+ 'service' => [
40
+ 'type' => 'value',
41
+ 'valid' => ['string'],
42
+ 'doc' => 'Name of the service to utilize. This value will be supplied by default when using one of the SDK clients (e.g., Aws\\S3\\S3Client).',
43
+ 'required' => true,
44
+ 'internal' => true
45
+ ],
46
+ 'exception_class' => [
47
+ 'type' => 'value',
48
+ 'valid' => ['string'],
49
+ 'doc' => 'Exception class to create when an error occurs.',
50
+ 'default' => 'Aws\Exception\AwsException',
51
+ 'internal' => true
52
+ ],
53
+ 'scheme' => [
54
+ 'type' => 'value',
55
+ 'valid' => ['string'],
56
+ 'default' => 'https',
57
+ 'doc' => 'URI scheme to use when connecting connect. The SDK will utilize "https" endpoints (i.e., utilize SSL/TLS connections) by default. You can attempt to connect to a service over an unencrypted "http" endpoint by setting ``scheme`` to "http".',
58
+ ],
59
+ 'disable_host_prefix_injection' => [
60
+ 'type' => 'value',
61
+ 'valid' => ['bool'],
62
+ 'doc' => 'Set to true to disable host prefix injection logic for services that use it. This disables the entire prefix injection, including the portions supplied by user-defined parameters. Setting this flag will have no effect on services that do not use host prefix injection.',
63
+ 'default' => false,
64
+ ],
65
+ 'endpoint' => [
66
+ 'type' => 'value',
67
+ 'valid' => ['string'],
68
+ 'doc' => 'The full URI of the webservice. This is only required when connecting to a custom endpoint (e.g., a local version of S3).',
69
+ 'fn' => [__CLASS__, '_apply_endpoint'],
70
+ ],
71
+ 'region' => [
72
+ 'type' => 'value',
73
+ 'valid' => ['string'],
74
+ 'required' => [__CLASS__, '_missing_region'],
75
+ 'doc' => 'Region to connect to. See http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of available regions.',
76
+ ],
77
+ 'version' => [
78
+ 'type' => 'value',
79
+ 'valid' => ['string'],
80
+ 'required' => [__CLASS__, '_missing_version'],
81
+ 'doc' => 'The version of the webservice to utilize (e.g., 2006-03-01).',
82
+ ],
83
+ 'signature_provider' => [
84
+ 'type' => 'value',
85
+ 'valid' => ['callable'],
86
+ 'doc' => 'A callable that accepts a signature version name (e.g., "v4"), a service name, and region, and returns a SignatureInterface object or null. This provider is used to create signers utilized by the client. See Aws\\Signature\\SignatureProvider for a list of built-in providers',
87
+ 'default' => [__CLASS__, '_default_signature_provider'],
88
+ ],
89
+ 'api_provider' => [
90
+ 'type' => 'value',
91
+ 'valid' => ['callable'],
92
+ 'doc' => 'An optional PHP callable that accepts a type, service, and version argument, and returns an array of corresponding configuration data. The type value can be one of api, waiter, or paginator.',
93
+ 'fn' => [__CLASS__, '_apply_api_provider'],
94
+ 'default' => [ApiProvider::class, 'defaultProvider'],
95
+ ],
96
+ 'endpoint_provider' => [
97
+ 'type' => 'value',
98
+ 'valid' => ['callable'],
99
+ 'fn' => [__CLASS__, '_apply_endpoint_provider'],
100
+ 'doc' => 'An optional PHP callable that accepts a hash of options including a "service" and "region" key and returns NULL or a hash of endpoint data, of which the "endpoint" key is required. See Aws\\Endpoint\\EndpointProvider for a list of built-in providers.',
101
+ 'default' => [__CLASS__, '_default_endpoint_provider'],
102
+ ],
103
+ 'serializer' => [
104
+ 'default' => [__CLASS__, '_default_serializer'],
105
+ 'fn' => [__CLASS__, '_apply_serializer'],
106
+ 'internal' => true,
107
+ 'type' => 'value',
108
+ 'valid' => ['callable'],
109
+ ],
110
+ 'signature_version' => [
111
+ 'type' => 'config',
112
+ 'valid' => ['string'],
113
+ 'doc' => 'A string representing a custom signature version to use with a service (e.g., v4). Note that per/operation signature version MAY override this requested signature version.',
114
+ 'default' => [__CLASS__, '_default_signature_version'],
115
+ ],
116
+ 'signing_name' => [
117
+ 'type' => 'config',
118
+ 'valid' => ['string'],
119
+ 'doc' => 'A string representing a custom service name to be used when calculating a request signature.',
120
+ 'default' => [__CLASS__, '_default_signing_name'],
121
+ ],
122
+ 'signing_region' => [
123
+ 'type' => 'config',
124
+ 'valid' => ['string'],
125
+ 'doc' => 'A string representing a custom region name to be used when calculating a request signature.',
126
+ 'default' => [__CLASS__, '_default_signing_region'],
127
+ ],
128
+ 'profile' => [
129
+ 'type' => 'config',
130
+ 'valid' => ['string'],
131
+ 'doc' => 'Allows you to specify which profile to use when credentials are created from the AWS credentials file in your HOME directory. This setting overrides the AWS_PROFILE environment variable. Note: Specifying "profile" will cause the "credentials" key to be ignored.',
132
+ 'fn' => [__CLASS__, '_apply_profile'],
133
+ ],
134
+ 'credentials' => [
135
+ 'type' => 'value',
136
+ 'valid' => [CredentialsInterface::class, CacheInterface::class, 'array', 'bool', 'callable'],
137
+ 'doc' => 'Specifies the credentials used to sign requests. Provide an Aws\Credentials\CredentialsInterface object, an associative array of "key", "secret", and an optional "token" key, `false` to use null credentials, or a callable credentials provider used to create credentials or return null. See Aws\\Credentials\\CredentialProvider for a list of built-in credentials providers. If no credentials are provided, the SDK will attempt to load them from the environment.',
138
+ 'fn' => [__CLASS__, '_apply_credentials'],
139
+ 'default' => [CredentialProvider::class, 'defaultProvider'],
140
+ ],
141
+ 'endpoint_discovery' => [
142
+ 'type' => 'value',
143
+ 'valid' => [ConfigurationInterface::class, CacheInterface::class, 'array', 'callable'],
144
+ 'doc' => 'Specifies settings for endpoint discovery. Provide an instance of Aws\EndpointDiscovery\ConfigurationInterface, an instance Aws\CacheInterface, a callable that provides a promise for a Configuration object, or an associative array with the following keys: enabled: (bool) Set to true to enable endpoint discovery. Defaults to false; cache_limit: (int) The maximum number of keys in the endpoints cache. Defaults to 1000.',
145
+ 'fn' => [__CLASS__, '_apply_endpoint_discovery'],
146
+ 'default' => [__CLASS__, '_default_endpoint_discovery_provider']
147
+ ],
148
+ 'stats' => [
149
+ 'type' => 'value',
150
+ 'valid' => ['bool', 'array'],
151
+ 'default' => false,
152
+ 'doc' => 'Set to true to gather transfer statistics on requests sent. Alternatively, you can provide an associative array with the following keys: retries: (bool) Set to false to disable reporting on retries attempted; http: (bool) Set to true to enable collecting statistics from lower level HTTP adapters (e.g., values returned in GuzzleHttp\TransferStats). HTTP handlers must support an http_stats_receiver option for this to have an effect; timer: (bool) Set to true to enable a command timer that reports the total wall clock time spent on an operation in seconds.',
153
+ 'fn' => [__CLASS__, '_apply_stats'],
154
+ ],
155
+ 'retries' => [
156
+ 'type' => 'value',
157
+ 'valid' => ['int'],
158
+ 'doc' => 'Configures the maximum number of allowed retries for a client (pass 0 to disable retries). ',
159
+ 'fn' => [__CLASS__, '_apply_retries'],
160
+ 'default' => 3,
161
+ ],
162
+ 'validate' => [
163
+ 'type' => 'value',
164
+ 'valid' => ['bool', 'array'],
165
+ 'default' => true,
166
+ 'doc' => 'Set to false to disable client-side parameter validation. Set to true to utilize default validation constraints. Set to an associative array of validation options to enable specific validation constraints.',
167
+ 'fn' => [__CLASS__, '_apply_validate'],
168
+ ],
169
+ 'debug' => [
170
+ 'type' => 'value',
171
+ 'valid' => ['bool', 'array'],
172
+ 'doc' => 'Set to true to display debug information when sending requests. Alternatively, you can provide an associative array with the following keys: logfn: (callable) Function that is invoked with log messages; stream_size: (int) When the size of a stream is greater than this number, the stream data will not be logged (set to "0" to not log any stream data); scrub_auth: (bool) Set to false to disable the scrubbing of auth data from the logged messages; http: (bool) Set to false to disable the "debug" feature of lower level HTTP adapters (e.g., verbose curl output).',
173
+ 'fn' => [__CLASS__, '_apply_debug'],
174
+ ],
175
+ 'http' => [
176
+ 'type' => 'value',
177
+ 'valid' => ['array'],
178
+ 'default' => [],
179
+ 'doc' => 'Set to an array of SDK request options to apply to each request (e.g., proxy, verify, etc.).',
180
+ ],
181
+ 'http_handler' => [
182
+ 'type' => 'value',
183
+ 'valid' => ['callable'],
184
+ 'doc' => 'An HTTP handler is a function that accepts a PSR-7 request object and returns a promise that is fulfilled with a PSR-7 response object or rejected with an array of exception data. NOTE: This option supersedes any provided "handler" option.',
185
+ 'fn' => [__CLASS__, '_apply_http_handler']
186
+ ],
187
+ 'handler' => [
188
+ 'type' => 'value',
189
+ 'valid' => ['callable'],
190
+ 'doc' => 'A handler that accepts a command object, request object and returns a promise that is fulfilled with an Aws\ResultInterface object or rejected with an Aws\Exception\AwsException. A handler does not accept a next handler as it is terminal and expected to fulfill a command. If no handler is provided, a default Guzzle handler will be utilized.',
191
+ 'fn' => [__CLASS__, '_apply_handler'],
192
+ 'default' => [__CLASS__, '_default_handler']
193
+ ],
194
+ 'ua_append' => [
195
+ 'type' => 'value',
196
+ 'valid' => ['string', 'array'],
197
+ 'doc' => 'Provide a string or array of strings to send in the User-Agent header.',
198
+ 'fn' => [__CLASS__, '_apply_user_agent'],
199
+ 'default' => [],
200
+ ],
201
+ 'idempotency_auto_fill' => [
202
+ 'type' => 'value',
203
+ 'valid' => ['bool', 'callable'],
204
+ 'doc' => 'Set to false to disable SDK to populate parameters that enabled \'idempotencyToken\' trait with a random UUID v4 value on your behalf. Using default value \'true\' still allows parameter value to be overwritten when provided. Note: auto-fill only works when cryptographically secure random bytes generator functions(random_bytes, openssl_random_pseudo_bytes or mcrypt_create_iv) can be found. You may also provide a callable source of random bytes.',
205
+ 'default' => true,
206
+ 'fn' => [__CLASS__, '_apply_idempotency_auto_fill']
207
+ ],
208
+ ];
209
+
210
+ /**
211
+ * Gets an array of default client arguments, each argument containing a
212
+ * hash of the following:
213
+ *
214
+ * - type: (string, required) option type described as follows:
215
+ * - value: The default option type.
216
+ * - config: The provided value is made available in the client's
217
+ * getConfig() method.
218
+ * - valid: (array, required) Valid PHP types or class names. Note: null
219
+ * is not an allowed type.
220
+ * - required: (bool, callable) Whether or not the argument is required.
221
+ * Provide a function that accepts an array of arguments and returns a
222
+ * string to provide a custom error message.
223
+ * - default: (mixed) The default value of the argument if not provided. If
224
+ * a function is provided, then it will be invoked to provide a default
225
+ * value. The function is provided the array of options and is expected
226
+ * to return the default value of the option. The default value can be a
227
+ * closure and can not be a callable string that is not part of the
228
+ * defaultArgs array.
229
+ * - doc: (string) The argument documentation string.
230
+ * - fn: (callable) Function used to apply the argument. The function
231
+ * accepts the provided value, array of arguments by reference, and an
232
+ * event emitter.
233
+ *
234
+ * Note: Order is honored and important when applying arguments.
235
+ *
236
+ * @return array
237
+ */
238
+ public static function getDefaultArguments()
239
+ {
240
+ return self::$defaultArgs;
241
+ }
242
+
243
+ /**
244
+ * @param array $argDefinitions Client arguments.
245
+ */
246
+ public function __construct(array $argDefinitions)
247
+ {
248
+ $this->argDefinitions = $argDefinitions;
249
+ }
250
+
251
+ /**
252
+ * Resolves client configuration options and attached event listeners.
253
+ * Check for missing keys in passed arguments
254
+ *
255
+ * @param array $args Provided constructor arguments.
256
+ * @param HandlerList $list Handler list to augment.
257
+ *
258
+ * @return array Returns the array of provided options.
259
+ * @throws \InvalidArgumentException
260
+ * @see Aws\AwsClient::__construct for a list of available options.
261
+ */
262
+ public function resolve(array $args, HandlerList $list)
263
+ {
264
+ $args['config'] = [];
265
+ foreach ($this->argDefinitions as $key => $a) {
266
+ // Add defaults, validate required values, and skip if not set.
267
+ if (!isset($args[$key])) {
268
+ if (isset($a['default'])) {
269
+ // Merge defaults in when not present.
270
+ if (is_callable($a['default'])
271
+ && (
272
+ is_array($a['default'])
273
+ || $a['default'] instanceof \Closure
274
+ )
275
+ ) {
276
+ $args[$key] = $a['default']($args);
277
+ } else {
278
+ $args[$key] = $a['default'];
279
+ }
280
+ } elseif (empty($a['required'])) {
281
+ continue;
282
+ } else {
283
+ $this->throwRequired($args);
284
+ }
285
+ }
286
+
287
+ // Validate the types against the provided value.
288
+ foreach ($a['valid'] as $check) {
289
+ if (isset(self::$typeMap[$check])) {
290
+ $fn = self::$typeMap[$check];
291
+ if ($fn($args[$key])) {
292
+ goto is_valid;
293
+ }
294
+ } elseif ($args[$key] instanceof $check) {
295
+ goto is_valid;
296
+ }
297
+ }
298
+
299
+ $this->invalidType($key, $args[$key]);
300
+
301
+ // Apply the value
302
+ is_valid:
303
+ if (isset($a['fn'])) {
304
+ $a['fn']($args[$key], $args, $list);
305
+ }
306
+
307
+ if ($a['type'] === 'config') {
308
+ $args['config'][$key] = $args[$key];
309
+ }
310
+ }
311
+
312
+ return $args;
313
+ }
314
+
315
+ /**
316
+ * Creates a verbose error message for an invalid argument.
317
+ *
318
+ * @param string $name Name of the argument that is missing.
319
+ * @param array $args Provided arguments
320
+ * @param bool $useRequired Set to true to show the required fn text if
321
+ * available instead of the documentation.
322
+ * @return string
323
+ */
324
+ private function getArgMessage($name, $args = [], $useRequired = false)
325
+ {
326
+ $arg = $this->argDefinitions[$name];
327
+ $msg = '';
328
+ $modifiers = [];
329
+ if (isset($arg['valid'])) {
330
+ $modifiers[] = implode('|', $arg['valid']);
331
+ }
332
+ if (isset($arg['choice'])) {
333
+ $modifiers[] = 'One of ' . implode(', ', $arg['choice']);
334
+ }
335
+ if ($modifiers) {
336
+ $msg .= '(' . implode('; ', $modifiers) . ')';
337
+ }
338
+ $msg = wordwrap("{$name}: {$msg}", 75, "\n ");
339
+
340
+ if ($useRequired && is_callable($arg['required'])) {
341
+ $msg .= "\n\n ";
342
+ $msg .= str_replace("\n", "\n ", call_user_func($arg['required'], $args));
343
+ } elseif (isset($arg['doc'])) {
344
+ $msg .= wordwrap("\n\n {$arg['doc']}", 75, "\n ");
345
+ }
346
+
347
+ return $msg;
348
+ }
349
+
350
+ /**
351
+ * Throw when an invalid type is encountered.
352
+ *
353
+ * @param string $name Name of the value being validated.
354
+ * @param mixed $provided The provided value.
355
+ * @throws \InvalidArgumentException
356
+ */
357
+ private function invalidType($name, $provided)
358
+ {
359
+ $expected = implode('|', $this->argDefinitions[$name]['valid']);
360
+ $msg = "Invalid configuration value "
361
+ . "provided for \"{$name}\". Expected {$expected}, but got "
362
+ . describe_type($provided) . "\n\n"
363
+ . $this->getArgMessage($name);
364
+ throw new IAE($msg);
365
+ }
366
+
367
+ /**
368
+ * Throws an exception for missing required arguments.
369
+ *
370
+ * @param array $args Passed in arguments.
371
+ * @throws \InvalidArgumentException
372
+ */
373
+ private function throwRequired(array $args)
374
+ {
375
+ $missing = [];
376
+ foreach ($this->argDefinitions as $k => $a) {
377
+ if (empty($a['required'])
378
+ || isset($a['default'])
379
+ || isset($args[$k])
380
+ ) {
381
+ continue;
382
+ }
383
+ $missing[] = $this->getArgMessage($k, $args, true);
384
+ }
385
+ $msg = "Missing required client configuration options: \n\n";
386
+ $msg .= implode("\n\n", $missing);
387
+ throw new IAE($msg);
388
+ }
389
+
390
+ public static function _apply_retries($value, array &$args, HandlerList $list)
391
+ {
392
+ if ($value) {
393
+ $decider = RetryMiddleware::createDefaultDecider($value);
394
+ $list->appendSign(
395
+ Middleware::retry($decider, null, $args['stats']['retries']),
396
+ 'retry'
397
+ );
398
+ }
399
+ }
400
+
401
+ public static function _apply_credentials($value, array &$args)
402
+ {
403
+ if (is_callable($value)) {
404
+ return;
405
+ }
406
+
407
+ if ($value instanceof CredentialsInterface) {
408
+ $args['credentials'] = CredentialProvider::fromCredentials($value);
409
+ } elseif (is_array($value)
410
+ && isset($value['key'])
411
+ && isset($value['secret'])
412
+ ) {
413
+ $args['credentials'] = CredentialProvider::fromCredentials(
414
+ new Credentials(
415
+ $value['key'],
416
+ $value['secret'],
417
+ isset($value['token']) ? $value['token'] : null,
418
+ isset($value['expires']) ? $value['expires'] : null
419
+ )
420
+ );
421
+ } elseif ($value === false) {
422
+ $args['credentials'] = CredentialProvider::fromCredentials(
423
+ new Credentials('', '')
424
+ );
425
+ $args['config']['signature_version'] = 'anonymous';
426
+ } elseif ($value instanceof CacheInterface) {
427
+ $args['credentials'] = CredentialProvider::defaultProvider($args);
428
+ } else {
429
+ throw new IAE('Credentials must be an instance of '
430
+ . 'Aws\Credentials\CredentialsInterface, an associative '
431
+ . 'array that contains "key", "secret", and an optional "token" '
432
+ . 'key-value pairs, a credentials provider function, or false.');
433
+ }
434
+ }
435
+
436
+ public static function _apply_api_provider(callable $value, array &$args)
437
+ {
438
+ $api = new Service(
439
+ ApiProvider::resolve(
440
+ $value,
441
+ 'api',
442
+ $args['service'],
443
+ $args['version']
444
+ ),
445
+ $value
446
+ );
447
+
448
+ if (
449
+ empty($args['config']['signing_name'])
450
+ && isset($api['metadata']['signingName'])
451
+ ) {
452
+ $args['config']['signing_name'] = $api['metadata']['signingName'];
453
+ }
454
+
455
+ $args['api'] = $api;
456
+ $args['parser'] = Service::createParser($api);
457
+ $args['error_parser'] = Service::createErrorParser($api->getProtocol());
458
+ }
459
+
460
+ public static function _apply_endpoint_provider(callable $value, array &$args)
461
+ {
462
+ if (!isset($args['endpoint'])) {
463
+ $endpointPrefix = isset($args['api']['metadata']['endpointPrefix'])
464
+ ? $args['api']['metadata']['endpointPrefix']
465
+ : $args['service'];
466
+
467
+ // Invoke the endpoint provider and throw if it does not resolve.
468
+ $result = EndpointProvider::resolve($value, [
469
+ 'service' => $endpointPrefix,
470
+ 'region' => $args['region'],
471
+ 'scheme' => $args['scheme']
472
+ ]);
473
+
474
+ $args['endpoint'] = $result['endpoint'];
475
+
476
+ if (
477
+ empty($args['config']['signature_version'])
478
+ && isset($result['signatureVersion'])
479
+ ) {
480
+ $args['config']['signature_version']
481
+ = $result['signatureVersion'];
482
+ }
483
+
484
+ if (
485
+ empty($args['config']['signing_region'])
486
+ && isset($result['signingRegion'])
487
+ ) {
488
+ $args['config']['signing_region'] = $result['signingRegion'];
489
+ }
490
+
491
+ if (
492
+ empty($args['config']['signing_name'])
493
+ && isset($result['signingName'])
494
+ ) {
495
+ $args['config']['signing_name'] = $result['signingName'];
496
+ }
497
+ }
498
+ }
499
+
500
+ public static function _apply_endpoint_discovery($value, array &$args) {
501
+ $args['endpoint_discovery'] = $value;
502
+ }
503
+
504
+ public static function _default_endpoint_discovery_provider(array $args)
505
+ {
506
+ return ConfigurationProvider::defaultProvider($args);
507
+ }
508
+
509
+ public static function _apply_serializer($value, array &$args, HandlerList $list)
510
+ {
511
+ $list->prependBuild(Middleware::requestBuilder($value), 'builder');
512
+ }
513
+
514
+ public static function _apply_debug($value, array &$args, HandlerList $list)
515
+ {
516
+ if ($value !== false) {
517
+ $list->interpose(new TraceMiddleware($value === true ? [] : $value));
518
+ }
519
+ }
520
+
521
+ public static function _apply_stats($value, array &$args, HandlerList $list)
522
+ {
523
+ // Create an array of stat collectors that are disabled (set to false)
524
+ // by default. If the user has passed in true, enable all stat
525
+ // collectors.
526
+ $defaults = array_fill_keys(
527
+ ['http', 'retries', 'timer'],
528
+ $value === true
529
+ );
530
+ $args['stats'] = is_array($value)
531
+ ? array_replace($defaults, $value)
532
+ : $defaults;
533
+
534
+ if ($args['stats']['timer']) {
535
+ $list->prependInit(Middleware::timer(), 'timer');
536
+ }
537
+ }
538
+
539
+ public static function _apply_profile($_, array &$args)
540
+ {
541
+ $args['credentials'] = CredentialProvider::ini($args['profile']);
542
+ }
543
+
544
+ public static function _apply_validate($value, array &$args, HandlerList $list)
545
+ {
546
+ if ($value === false) {
547
+ return;
548
+ }
549
+
550
+ $validator = $value === true
551
+ ? new Validator()
552
+ : new Validator($value);
553
+ $list->appendValidate(
554
+ Middleware::validation($args['api'], $validator),
555
+ 'validation'
556
+ );
557
+ }
558
+
559
+ public static function _apply_handler($value, array &$args, HandlerList $list)
560
+ {
561
+ $list->setHandler($value);
562
+ }
563
+
564
+ public static function _default_handler(array &$args)
565
+ {
566
+ return new WrappedHttpHandler(
567
+ default_http_handler(),
568
+ $args['parser'],
569
+ $args['error_parser'],
570
+ $args['exception_class'],
571
+ $args['stats']['http']
572
+ );
573
+ }
574
+
575
+ public static function _apply_http_handler($value, array &$args, HandlerList $list)
576
+ {
577
+ $args['handler'] = new WrappedHttpHandler(
578
+ $value,
579
+ $args['parser'],
580
+ $args['error_parser'],
581
+ $args['exception_class'],
582
+ $args['stats']['http']
583
+ );
584
+ }
585
+
586
+ public static function _apply_user_agent($value, array &$args, HandlerList $list)
587
+ {
588
+ if (!is_array($value)) {
589
+ $value = [$value];
590
+ }
591
+
592
+ $value = array_map('strval', $value);
593
+
594
+ if (defined('HHVM_VERSION')) {
595
+ array_unshift($value, 'HHVM/' . HHVM_VERSION);
596
+ }
597
+ array_unshift($value, 'aws-sdk-php/' . Sdk::VERSION);
598
+ $args['ua_append'] = $value;
599
+
600
+ $list->appendBuild(static function (callable $handler) use ($value) {
601
+ return function (
602
+ CommandInterface $command,
603
+ RequestInterface $request
604
+ ) use ($handler, $value) {
605
+ return $handler($command, $request->withHeader(
606
+ 'User-Agent',
607
+ implode(' ', array_merge(
608
+ $value,
609
+ $request->getHeader('User-Agent')
610
+ ))
611
+ ));
612
+ };
613
+ });
614
+ }
615
+
616
+ public static function _apply_endpoint($value, array &$args, HandlerList $list)
617
+ {
618
+ $parts = parse_url($value);
619
+ if (empty($parts['scheme']) || empty($parts['host'])) {
620
+ throw new IAE(
621
+ 'Endpoints must be full URIs and include a scheme and host'
622
+ );
623
+ }
624
+
625
+ $args['endpoint'] = $value;
626
+ }
627
+
628
+ public static function _apply_idempotency_auto_fill(
629
+ $value,
630
+ array &$args,
631
+ HandlerList $list
632
+ ) {
633
+ $enabled = false;
634
+ $generator = null;
635
+
636
+
637
+ if (is_bool($value)) {
638
+ $enabled = $value;
639
+ } elseif (is_callable($value)) {
640
+ $enabled = true;
641
+ $generator = $value;
642
+ }
643
+
644
+ if ($enabled) {
645
+ $list->prependInit(
646
+ IdempotencyTokenMiddleware::wrap($args['api'], $generator),
647
+ 'idempotency_auto_fill'
648
+ );
649
+ }
650
+ }
651
+
652
+ public static function _default_endpoint_provider(array $args)
653
+ {
654
+ return PartitionEndpointProvider::defaultProvider()
655
+ ->getPartition($args['region'], $args['service']);
656
+ }
657
+
658
+ public static function _default_serializer(array $args)
659
+ {
660
+ return Service::createSerializer(
661
+ $args['api'],
662
+ $args['endpoint']
663
+ );
664
+ }
665
+
666
+ public static function _default_signature_provider()
667
+ {
668
+ return SignatureProvider::defaultProvider();
669
+ }
670
+
671
+ public static function _default_signature_version(array &$args)
672
+ {
673
+ if (isset($args['config']['signature_version'])) {
674
+ return $args['config']['signature_version'];
675
+ }
676
+
677
+ $args['__partition_result'] = isset($args['__partition_result'])
678
+ ? isset($args['__partition_result'])
679
+ : call_user_func(PartitionEndpointProvider::defaultProvider(), [
680
+ 'service' => $args['service'],
681
+ 'region' => $args['region'],
682
+ ]);
683
+
684
+ return isset($args['__partition_result']['signatureVersion'])
685
+ ? $args['__partition_result']['signatureVersion']
686
+ : $args['api']->getSignatureVersion();
687
+ }
688
+
689
+ public static function _default_signing_name(array &$args)
690
+ {
691
+ if (isset($args['config']['signing_name'])) {
692
+ return $args['config']['signing_name'];
693
+ }
694
+
695
+ $args['__partition_result'] = isset($args['__partition_result'])
696
+ ? isset($args['__partition_result'])
697
+ : call_user_func(PartitionEndpointProvider::defaultProvider(), [
698
+ 'service' => $args['service'],
699
+ 'region' => $args['region'],
700
+ ]);
701
+
702
+ if (isset($args['__partition_result']['signingName'])) {
703
+ return $args['__partition_result']['signingName'];
704
+ }
705
+
706
+ if ($signingName = $args['api']->getSigningName()) {
707
+ return $signingName;
708
+ }
709
+
710
+ return $args['service'];
711
+ }
712
+
713
+ public static function _default_signing_region(array &$args)
714
+ {
715
+ if (isset($args['config']['signing_region'])) {
716
+ return $args['config']['signing_region'];
717
+ }
718
+
719
+ $args['__partition_result'] = isset($args['__partition_result'])
720
+ ? isset($args['__partition_result'])
721
+ : call_user_func(PartitionEndpointProvider::defaultProvider(), [
722
+ 'service' => $args['service'],
723
+ 'region' => $args['region'],
724
+ ]);
725
+
726
+ return isset($args['__partition_result']['signingRegion'])
727
+ ? $args['__partition_result']['signingRegion']
728
+ : $args['region'];
729
+ }
730
+
731
+ public static function _missing_version(array $args)
732
+ {
733
+ $service = isset($args['service']) ? $args['service'] : '';
734
+ $versions = ApiProvider::defaultProvider()->getVersions($service);
735
+ $versions = implode("\n", array_map(function ($v) {
736
+ return "* \"$v\"";
737
+ }, $versions)) ?: '* (none found)';
738
+
739
+ return <<<EOT
740
+ A "version" configuration value is required. Specifying a version constraint
741
+ ensures that your code will not be affected by a breaking change made to the
742
+ service. For example, when using Amazon S3, you can lock your API version to
743
+ "2006-03-01".
744
+
745
+ Your build of the SDK has the following version(s) of "{$service}": {$versions}
746
+
747
+ You may provide "latest" to the "version" configuration value to utilize the
748
+ most recent available API version that your client's API provider can find.
749
+ Note: Using 'latest' in a production application is not recommended.
750
+
751
+ A list of available API versions can be found on each client's API documentation
752
+ page: http://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html. If you are
753
+ unable to load a specific API version, then you may need to update your copy of
754
+ the SDK.
755
+ EOT;
756
+ }
757
+
758
+ public static function _missing_region(array $args)
759
+ {
760
+ $service = isset($args['service']) ? $args['service'] : '';
761
+
762
+ return <<<EOT
763
+ A "region" configuration value is required for the "{$service}" service
764
+ (e.g., "us-west-2"). A list of available public regions and endpoints can be
765
+ found at http://docs.aws.amazon.com/general/latest/gr/rande.html.
766
+ EOT;
767
+ }
768
+ }
lib/Aws/Aws/ClientSideMonitoring/AbstractMonitoringMiddleware.php ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws\ClientSideMonitoring;
4
+
5
+ use Aws\CommandInterface;
6
+ use Aws\Exception\AwsException;
7
+ use Aws\MonitoringEventsInterface;
8
+ use Aws\ResponseContainerInterface;
9
+ use Aws\ResultInterface;
10
+ use GuzzleHttp\Promise;
11
+ use Psr\Http\Message\RequestInterface;
12
+ use Psr\Http\Message\ResponseInterface;
13
+
14
+ /**
15
+ * @internal
16
+ */
17
+ abstract class AbstractMonitoringMiddleware
18
+ implements MonitoringMiddlewareInterface
19
+ {
20
+ private static $socket;
21
+
22
+ private $nextHandler;
23
+ private $options;
24
+ protected $credentialProvider;
25
+ protected $region;
26
+ protected $service;
27
+
28
+ protected static function getAwsExceptionHeader(AwsException $e, $headerName)
29
+ {
30
+ $response = $e->getResponse();
31
+ if ($response !== null) {
32
+ $header = $response->getHeader($headerName);
33
+ if (!empty($header[0])) {
34
+ return $header[0];
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+ protected static function getResultHeader(ResultInterface $result, $headerName)
41
+ {
42
+ if (isset($result['@metadata']['headers'][$headerName])) {
43
+ return $result['@metadata']['headers'][$headerName];
44
+ }
45
+ return null;
46
+ }
47
+
48
+ protected static function getExceptionHeader(\Exception $e, $headerName)
49
+ {
50
+ if ($e instanceof ResponseContainerInterface) {
51
+ $response = $e->getResponse();
52
+ if ($response instanceof ResponseInterface) {
53
+ $header = $response->getHeader($headerName);
54
+ if (!empty($header[0])) {
55
+ return $header[0];
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * Constructor stores the passed in handler and options.
64
+ *
65
+ * @param callable $handler
66
+ * @param callable $credentialProvider
67
+ * @param array $options
68
+ * @param $region
69
+ * @param $service
70
+ */
71
+ public function __construct(
72
+ callable $handler,
73
+ callable $credentialProvider,
74
+ $options,
75
+ $region,
76
+ $service
77
+ ) {
78
+ $this->nextHandler = $handler;
79
+ $this->credentialProvider = $credentialProvider;
80
+ $this->options = $options;
81
+ $this->region = $region;
82
+ $this->service = $service;
83
+ }
84
+
85
+ /**
86
+ * Standard invoke pattern for middleware execution to be implemented by
87
+ * child classes.
88
+ *
89
+ * @param CommandInterface $cmd
90
+ * @param RequestInterface $request
91
+ * @return Promise\PromiseInterface
92
+ */
93
+ public function __invoke(CommandInterface $cmd, RequestInterface $request)
94
+ {
95
+ $handler = $this->nextHandler;
96
+ $eventData = null;
97
+ $enabled = $this->isEnabled();
98
+
99
+ if ($enabled) {
100
+ $cmd['@http']['collect_stats'] = true;
101
+ $eventData = $this->populateRequestEventData(
102
+ $cmd,
103
+ $request,
104
+ $this->getNewEvent($cmd, $request)
105
+ );
106
+ }
107
+
108
+ $g = function ($value) use ($eventData, $enabled) {
109
+ if ($enabled) {
110
+ $eventData = $this->populateResultEventData(
111
+ $value,
112
+ $eventData
113
+ );
114
+ $this->sendEventData($eventData);
115
+
116
+ if ($value instanceof MonitoringEventsInterface) {
117
+ $value->appendMonitoringEvent($eventData);
118
+ }
119
+ }
120
+ if ($value instanceof \Exception || $value instanceof \Throwable) {
121
+ return Promise\rejection_for($value);
122
+ }
123
+ return $value;
124
+ };
125
+
126
+ return Promise\promise_for($handler($cmd, $request))->then($g, $g);
127
+ }
128
+
129
+ private function getClientId()
130
+ {
131
+ return $this->unwrappedOptions()->getClientId();
132
+ }
133
+
134
+ private function getNewEvent(
135
+ CommandInterface $cmd,
136
+ RequestInterface $request
137
+ ) {
138
+ $event = [
139
+ 'Api' => $cmd->getName(),
140
+ 'ClientId' => $this->getClientId(),
141
+ 'Region' => $this->getRegion(),
142
+ 'Service' => $this->getService(),
143
+ 'Timestamp' => (int) floor(microtime(true) * 1000),
144
+ 'UserAgent' => substr(
145
+ $request->getHeaderLine('User-Agent') . ' ' . \Aws\default_user_agent(),
146
+ 0,
147
+ 256
148
+ ),
149
+ 'Version' => 1
150
+ ];
151
+ return $event;
152
+ }
153
+
154
+ private function getPort()
155
+ {
156
+ return $this->unwrappedOptions()->getPort();
157
+ }
158
+
159
+ private function getRegion()
160
+ {
161
+ return $this->region;
162
+ }
163
+
164
+ private function getService()
165
+ {
166
+ return $this->service;
167
+ }
168
+
169
+ /**
170
+ * Returns enabled flag from options, unwrapping options if necessary.
171
+ *
172
+ * @return bool
173
+ */
174
+ private function isEnabled()
175
+ {
176
+ return $this->unwrappedOptions()->isEnabled();
177
+ }
178
+
179
+ /**
180
+ * Returns $eventData array with information from the request and command.
181
+ *
182
+ * @param CommandInterface $cmd
183
+ * @param RequestInterface $request
184
+ * @param array $event
185
+ * @return array
186
+ */
187
+ protected function populateRequestEventData(
188
+ CommandInterface $cmd,
189
+ RequestInterface $request,
190
+ array $event
191
+ ) {
192
+ $dataFormat = static::getRequestData($request);
193
+ foreach ($dataFormat as $eventKey => $value) {
194
+ if ($value !== null) {
195
+ $event[$eventKey] = $value;
196
+ }
197
+ }
198
+ return $event;
199
+ }
200
+
201
+ /**
202
+ * Returns $eventData array with information from the response, including
203
+ * the calculation for attempt latency.
204
+ *
205
+ * @param ResultInterface|\Exception $result
206
+ * @param array $event
207
+ * @return array
208
+ */
209
+ protected function populateResultEventData(
210
+ $result,
211
+ array $event
212
+ ) {
213
+ $dataFormat = static::getResponseData($result);
214
+ foreach ($dataFormat as $eventKey => $value) {
215
+ if ($value !== null) {
216
+ $event[$eventKey] = $value;
217
+ }
218
+ }
219
+ return $event;
220
+ }
221
+
222
+ /**
223
+ * Creates a UDP socket resource and stores it with the class, or retrieves
224
+ * it if already instantiated and connected. Handles error-checking and
225
+ * re-connecting if necessary. If $forceNewConnection is set to true, a new
226
+ * socket will be created.
227
+ *
228
+ * @param bool $forceNewConnection
229
+ * @return Resource
230
+ */
231
+ private function prepareSocket($forceNewConnection = false)
232
+ {
233
+ if (!is_resource(self::$socket)
234
+ || $forceNewConnection
235
+ || socket_last_error(self::$socket)
236
+ ) {
237
+ self::$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
238
+ socket_clear_error(self::$socket);
239
+ socket_connect(self::$socket, '127.0.0.1', $this->getPort());
240
+ }
241
+
242
+ return self::$socket;
243
+ }
244
+
245
+ /**
246
+ * Sends formatted monitoring event data via the UDP socket connection to
247
+ * the CSM agent endpoint.
248
+ *
249
+ * @param array $eventData
250
+ * @return int
251
+ */
252
+ private function sendEventData(array $eventData)
253
+ {
254
+ $socket = $this->prepareSocket();
255
+ $datagram = json_encode($eventData);
256
+ $result = socket_write($socket, $datagram, strlen($datagram));
257
+ if ($result === false) {
258
+ $this->prepareSocket(true);
259
+ }
260
+ return $result;
261
+ }
262
+
263
+ /**
264
+ * Unwraps options, if needed, and returns them.
265
+ *
266
+ * @return ConfigurationInterface
267
+ */
268
+ private function unwrappedOptions()
269
+ {
270
+ if (!($this->options instanceof ConfigurationInterface)) {
271
+ $this->options = ConfigurationProvider::unwrap($this->options);
272
+ }
273
+ return $this->options;
274
+ }
275
+ }
lib/Aws/Aws/ClientSideMonitoring/ApiCallAttemptMonitoringMiddleware.php ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws\ClientSideMonitoring;
4
+
5
+ use Aws\CommandInterface;
6
+ use Aws\Credentials\CredentialsInterface;
7
+ use Aws\Exception\AwsException;
8
+ use Aws\ResponseContainerInterface;
9
+ use Aws\ResultInterface;
10
+ use Psr\Http\Message\RequestInterface;
11
+ use Psr\Http\Message\ResponseInterface;
12
+
13
+ /**
14
+ * @internal
15
+ */
16
+ class ApiCallAttemptMonitoringMiddleware extends AbstractMonitoringMiddleware
17
+ {
18
+
19
+ /**
20
+ * Standard middleware wrapper function with CSM options passed in.
21
+ *
22
+ * @param callable $credentialProvider
23
+ * @param mixed $options
24
+ * @param string $region
25
+ * @param string $service
26
+ * @return callable
27
+ */
28
+ public static function wrap(
29
+ callable $credentialProvider,
30
+ $options,
31
+ $region,
32
+ $service
33
+ ) {
34
+ return function (callable $handler) use (
35
+ $credentialProvider,
36
+ $options,
37
+ $region,
38
+ $service
39
+ ) {
40
+ return new static(
41
+ $handler,
42
+ $credentialProvider,
43
+ $options,
44
+ $region,
45
+ $service
46
+ );
47
+ };
48
+ }
49
+
50
+ /**
51
+ * {@inheritdoc}
52
+ */
53
+ public static function getRequestData(RequestInterface $request)
54
+ {
55
+ return [
56
+ 'Fqdn' => $request->getUri()->getHost(),
57
+ ];
58
+ }
59
+
60
+ /**
61
+ * {@inheritdoc}
62
+ */
63
+ public static function getResponseData($klass)
64
+ {
65
+ if ($klass instanceof ResultInterface) {
66
+ return [
67
+ 'AttemptLatency' => self::getResultAttemptLatency($klass),
68
+ 'DestinationIp' => self::getResultDestinationIp($klass),
69
+ 'DnsLatency' => self::getResultDnsLatency($klass),
70
+ 'HttpStatusCode' => self::getResultHttpStatusCode($klass),
71
+ 'XAmzId2' => self::getResultHeader($klass, 'x-amz-id-2'),
72
+ 'XAmzRequestId' => self::getResultHeader($klass, 'x-amz-request-id'),
73
+ 'XAmznRequestId' => self::getResultHeader($klass, 'x-amzn-RequestId'),
74
+ ];
75
+ }
76
+ if ($klass instanceof AwsException) {
77
+ return [
78
+ 'AttemptLatency' => self::getAwsExceptionAttemptLatency($klass),
79
+ 'AwsException' => substr(
80
+ self::getAwsExceptionErrorCode($klass),
81
+ 0,
82
+ 128
83
+ ),
84
+ 'AwsExceptionMessage' => substr(
85
+ self::getAwsExceptionMessage($klass),
86
+ 0,
87
+ 512
88
+ ),
89
+ 'DestinationIp' => self::getAwsExceptionDestinationIp($klass),
90
+ 'DnsLatency' => self::getAwsExceptionDnsLatency($klass),
91
+ 'HttpStatusCode' => self::getAwsExceptionHttpStatusCode($klass),
92
+ 'XAmzId2' => self::getAwsExceptionHeader($klass, 'x-amz-id-2'),
93
+ 'XAmzRequestId' => self::getAwsExceptionHeader(
94
+ $klass,
95
+ 'x-amz-request-id'
96
+ ),
97
+ 'XAmznRequestId' => self::getAwsExceptionHeader(
98
+ $klass,
99
+ 'x-amzn-RequestId'
100
+ ),
101
+ ];
102
+ }
103
+ if ($klass instanceof \Exception) {
104
+ return [
105
+ 'HttpStatusCode' => self::getExceptionHttpStatusCode($klass),
106
+ 'SdkException' => substr(
107
+ self::getExceptionCode($klass),
108
+ 0,
109
+ 128
110
+ ),
111
+ 'SdkExceptionMessage' => substr(
112
+ self::getExceptionMessage($klass),
113
+ 0,
114
+ 512
115
+ ),
116
+ 'XAmzId2' => self::getExceptionHeader($klass, 'x-amz-id-2'),
117
+ 'XAmzRequestId' => self::getExceptionHeader($klass, 'x-amz-request-id'),
118
+ 'XAmznRequestId' => self::getExceptionHeader($klass, 'x-amzn-RequestId'),
119
+ ];
120
+ }
121
+
122
+ throw new \InvalidArgumentException('Parameter must be an instance of ResultInterface, AwsException or Exception.');
123
+ }
124
+
125
+ private static function getResultAttemptLatency(ResultInterface $result)
126
+ {
127
+ if (isset($result['@metadata']['transferStats']['http'])) {
128
+ $attempt = end($result['@metadata']['transferStats']['http']);
129
+ if (isset($attempt['total_time'])) {
130
+ return (int) floor($attempt['total_time'] * 1000);
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+
136
+ private static function getResultDestinationIp(ResultInterface $result)
137
+ {
138
+ if (isset($result['@metadata']['transferStats']['http'])) {
139
+ $attempt = end($result['@metadata']['transferStats']['http']);
140
+ if (isset($attempt['primary_ip'])) {
141
+ return $attempt['primary_ip'];
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+
147
+ private static function getResultDnsLatency(ResultInterface $result)
148
+ {
149
+ if (isset($result['@metadata']['transferStats']['http'])) {
150
+ $attempt = end($result['@metadata']['transferStats']['http']);
151
+ if (isset($attempt['namelookup_time'])) {
152
+ return (int) floor($attempt['namelookup_time'] * 1000);
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+
158
+ private static function getResultHttpStatusCode(ResultInterface $result)
159
+ {
160
+ return $result['@metadata']['statusCode'];
161
+ }
162
+
163
+ private static function getAwsExceptionAttemptLatency(AwsException $e) {
164
+ $attempt = $e->getTransferInfo();
165
+ if (isset($attempt['total_time'])) {
166
+ return (int) floor($attempt['total_time'] * 1000);
167
+ }
168
+ return null;
169
+ }
170
+
171
+ private static function getAwsExceptionErrorCode(AwsException $e) {
172
+ return $e->getAwsErrorCode();
173
+ }
174
+
175
+ private static function getAwsExceptionMessage(AwsException $e) {
176
+ return $e->getAwsErrorMessage();
177
+ }
178
+
179
+ private static function getAwsExceptionDestinationIp(AwsException $e) {
180
+ $attempt = $e->getTransferInfo();
181
+ if (isset($attempt['primary_ip'])) {
182
+ return $attempt['primary_ip'];
183
+ }
184
+ return null;
185
+ }
186
+
187
+ private static function getAwsExceptionDnsLatency(AwsException $e) {
188
+ $attempt = $e->getTransferInfo();
189
+ if (isset($attempt['namelookup_time'])) {
190
+ return (int) floor($attempt['namelookup_time'] * 1000);
191
+ }
192
+ return null;
193
+ }
194
+
195
+ private static function getAwsExceptionHttpStatusCode(AwsException $e) {
196
+ $response = $e->getResponse();
197
+ if ($response !== null) {
198
+ return $response->getStatusCode();
199
+ }
200
+ return null;
201
+ }
202
+
203
+ private static function getExceptionHttpStatusCode(\Exception $e) {
204
+ if ($e instanceof ResponseContainerInterface) {
205
+ $response = $e->getResponse();
206
+ if ($response instanceof ResponseInterface) {
207
+ return $response->getStatusCode();
208
+ }
209
+ }
210
+ return null;
211
+ }
212
+
213
+ private static function getExceptionCode(\Exception $e) {
214
+ if (!($e instanceof AwsException)) {
215
+ return get_class($e);
216
+ }
217
+ return null;
218
+ }
219
+
220
+ private static function getExceptionMessage(\Exception $e) {
221
+ if (!($e instanceof AwsException)) {
222
+ return $e->getMessage();
223
+ }
224
+ return null;
225
+ }
226
+
227
+ /**
228
+ * {@inheritdoc}
229
+ */
230
+ protected function populateRequestEventData(
231
+ CommandInterface $cmd,
232
+ RequestInterface $request,
233
+ array $event
234
+ ) {
235
+ $event = parent::populateRequestEventData($cmd, $request, $event);
236
+ $event['Type'] = 'ApiCallAttempt';
237
+ return $event;
238
+ }
239
+
240
+ /**
241
+ * {@inheritdoc}
242
+ */
243
+ protected function populateResultEventData(
244
+ $result,
245
+ array $event
246
+ ) {
247
+ $event = parent::populateResultEventData($result, $event);
248
+
249
+ $provider = $this->credentialProvider;
250
+ /** @var CredentialsInterface $credentials */
251
+ $credentials = $provider()->wait();
252
+ $event['AccessKey'] = $credentials->getAccessKeyId();
253
+ $sessionToken = $credentials->getSecurityToken();
254
+ if ($sessionToken !== null) {
255
+ $event['SessionToken'] = $sessionToken;
256
+ }
257
+ if (empty($event['AttemptLatency'])) {
258
+ $event['AttemptLatency'] = (int) (floor(microtime(true) * 1000) - $event['Timestamp']);
259
+ }
260
+ return $event;
261
+ }
262
+ }
lib/Aws/Aws/ClientSideMonitoring/ApiCallMonitoringMiddleware.php ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws\ClientSideMonitoring;
4
+
5
+ use Aws\CommandInterface;
6
+ use Aws\Exception\AwsException;
7
+ use Aws\MonitoringEventsInterface;
8
+ use Aws\ResultInterface;
9
+ use Psr\Http\Message\RequestInterface;
10
+
11
+ /**
12
+ * @internal
13
+ */
14
+ class ApiCallMonitoringMiddleware extends AbstractMonitoringMiddleware
15
+ {
16
+
17
+ /**
18
+ * Api Call Attempt event keys for each Api Call event key
19
+ *
20
+ * @var array
21
+ */
22
+ private static $eventKeys = [
23
+ 'FinalAwsException' => 'AwsException',
24
+ 'FinalAwsExceptionMessage' => 'AwsExceptionMessage',
25
+ 'FinalSdkException' => 'SdkException',
26
+ 'FinalSdkExceptionMessage' => 'SdkExceptionMessage',
27
+ 'FinalHttpStatusCode' => 'HttpStatusCode',
28
+ ];
29
+
30
+ /**
31
+ * Standard middleware wrapper function with CSM options passed in.
32
+ *
33
+ * @param callable $credentialProvider
34
+ * @param mixed $options
35
+ * @param string $region
36
+ * @param string $service
37
+ * @return callable
38
+ */
39
+ public static function wrap(
40
+ callable $credentialProvider,
41
+ $options,
42
+ $region,
43
+ $service
44
+ ) {
45
+ return function (callable $handler) use (
46
+ $credentialProvider,
47
+ $options,
48
+ $region,
49
+ $service
50
+ ) {
51
+ return new static(
52
+ $handler,
53
+ $credentialProvider,
54
+ $options,
55
+ $region,
56
+ $service
57
+ );
58
+ };
59
+ }
60
+
61
+ /**
62
+ * {@inheritdoc}
63
+ */
64
+ public static function getRequestData(RequestInterface $request)
65
+ {
66
+ return [];
67
+ }
68
+
69
+ /**
70
+ * {@inheritdoc}
71
+ */
72
+ public static function getResponseData($klass)
73
+ {
74
+ if ($klass instanceof ResultInterface) {
75
+ $data = [
76
+ 'AttemptCount' => self::getResultAttemptCount($klass),
77
+ 'MaxRetriesExceeded' => 0,
78
+ ];
79
+ } elseif ($klass instanceof \Exception) {
80
+ $data = [
81
+ 'AttemptCount' => self::getExceptionAttemptCount($klass),
82
+ 'MaxRetriesExceeded' => self::getMaxRetriesExceeded($klass),
83
+ ];
84
+ } else {
85
+ throw new \InvalidArgumentException('Parameter must be an instance of ResultInterface or Exception.');
86
+ }
87
+
88
+ return $data + self::getFinalAttemptData($klass);
89
+ }
90
+
91
+ private static function getResultAttemptCount(ResultInterface $result) {
92
+ if (isset($result['@metadata']['transferStats']['http'])) {
93
+ return count($result['@metadata']['transferStats']['http']);
94
+ }
95
+ return 1;
96
+ }
97
+
98
+ private static function getExceptionAttemptCount(\Exception $e) {
99
+ $attemptCount = 0;
100
+ if ($e instanceof MonitoringEventsInterface) {
101
+ foreach ($e->getMonitoringEvents() as $event) {
102
+ if (isset($event['Type']) &&
103
+ $event['Type'] === 'ApiCallAttempt') {
104
+ $attemptCount++;
105
+ }
106
+ }
107
+
108
+ }
109
+ return $attemptCount;
110
+ }
111
+
112
+ private static function getFinalAttemptData($klass)
113
+ {
114
+ $data = [];
115
+ if ($klass instanceof MonitoringEventsInterface) {
116
+ $finalAttempt = self::getFinalAttempt($klass->getMonitoringEvents());
117
+
118
+ if (!empty($finalAttempt)) {
119
+ foreach (self::$eventKeys as $callKey => $attemptKey) {
120
+ if (isset($finalAttempt[$attemptKey])) {
121
+ $data[$callKey] = $finalAttempt[$attemptKey];
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ return $data;
128
+ }
129
+
130
+ private static function getFinalAttempt(array $events)
131
+ {
132
+ for (end($events); key($events) !== null; prev($events)) {
133
+ $current = current($events);
134
+ if (isset($current['Type'])
135
+ && $current['Type'] === 'ApiCallAttempt'
136
+ ) {
137
+ return $current;
138
+ }
139
+ }
140
+
141
+ return null;
142
+ }
143
+
144
+ private static function getMaxRetriesExceeded($klass)
145
+ {
146
+ if ($klass instanceof AwsException && $klass->isMaxRetriesExceeded()) {
147
+ return 1;
148
+ }
149
+ return 0;
150
+ }
151
+
152
+ /**
153
+ * {@inheritdoc}
154
+ */
155
+ protected function populateRequestEventData(
156
+ CommandInterface $cmd,
157
+ RequestInterface $request,
158
+ array $event
159
+ ) {
160
+ $event = parent::populateRequestEventData($cmd, $request, $event);
161
+ $event['Type'] = 'ApiCall';
162
+ return $event;
163
+ }
164
+
165
+ /**
166
+ * {@inheritdoc}
167
+ */
168
+ protected function populateResultEventData(
169
+ $result,
170
+ array $event
171
+ ) {
172
+ $event = parent::populateResultEventData($result, $event);
173
+ $event['Latency'] = (int) (floor(microtime(true) * 1000) - $event['Timestamp']);
174
+ return $event;
175
+ }
176
+ }
lib/Aws/Aws/ClientSideMonitoring/Configuration.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\ClientSideMonitoring;
3
+
4
+ class Configuration implements ConfigurationInterface
5
+ {
6
+ private $clientId;
7
+ private $enabled;
8
+ private $port;
9
+
10
+ /**
11
+ * Constructs a new Configuration object with the specified CSM options set.
12
+ *
13
+ * @param mixed $enabled
14
+ * @param string|int $port
15
+ * @param string $clientId
16
+ */
17
+ public function __construct($enabled, $port, $clientId = '')
18
+ {
19
+ $this->port = filter_var($port, FILTER_VALIDATE_INT);
20
+ if ($this->port === false) {
21
+ throw new \InvalidArgumentException(
22
+ "CSM 'port' value must be an integer!");
23
+ }
24
+
25
+ // Unparsable $enabled flag errors on the side of disabling CSM
26
+ $this->enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN);
27
+ $this->clientId = trim($clientId);
28
+ }
29
+
30
+ /**
31
+ * {@inheritdoc}
32
+ */
33
+ public function isEnabled()
34
+ {
35
+ return $this->enabled;
36
+ }
37
+
38
+ /**
39
+ * {@inheritdoc}
40
+ */
41
+ public function getClientId()
42
+ {
43
+ return $this->clientId;
44
+ }
45
+
46
+ /**
47
+ * {@inheritdoc}
48
+ */
49
+ public function getPort()
50
+ {
51
+ return $this->port;
52
+ }
53
+
54
+ /**
55
+ * {@inheritdoc}
56
+ */
57
+ public function toArray()
58
+ {
59
+ return [
60
+ 'client_id' => $this->getClientId(),
61
+ 'enabled' => $this->isEnabled(),
62
+ 'port' => $this->getPort()
63
+ ];
64
+ }
65
+ }
lib/Aws/Aws/ClientSideMonitoring/ConfigurationInterface.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\ClientSideMonitoring;
3
+
4
+ /**
5
+ * Provides access to client-side monitoring configuration options:
6
+ * 'client_id', 'enabled', 'port'
7
+ */
8
+ interface ConfigurationInterface
9
+ {
10
+ /**
11
+ * Checks whether or not client-side monitoring is enabled.
12
+ *
13
+ * @return bool
14
+ */
15
+ public function isEnabled();
16
+
17
+ /**
18
+ * Returns the Client ID, if available.
19
+ *
20
+ * @return string|null
21
+ */
22
+ public function getClientId();
23
+
24
+ /**
25
+ * Returns the configured port.
26
+ *
27
+ * @return int|null
28
+ */
29
+ public function getPort();
30
+
31
+ /**
32
+ * Returns the configuration as an associative array.
33
+ *
34
+ * @return array
35
+ */
36
+ public function toArray();
37
+ }
lib/Aws/Aws/ClientSideMonitoring/ConfigurationProvider.php ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\ClientSideMonitoring;
3
+
4
+ use Aws\CacheInterface;
5
+ use Aws\ClientSideMonitoring\Exception\ConfigurationException;
6
+ use GuzzleHttp\Promise;
7
+ use GuzzleHttp\Promise\PromiseInterface;
8
+
9
+ /**
10
+ * A configuration provider is a function that accepts no arguments and returns
11
+ * a promise that is fulfilled with a {@see \Aws\ClientSideMonitoring\ConfigurationInterface}
12
+ * or rejected with an {@see \Aws\ClientSideMonitoring\Exception\ConfigurationException}.
13
+ *
14
+ * <code>
15
+ * use Aws\ClientSideMonitoring\ConfigurationProvider;
16
+ * $provider = ConfigurationProvider::defaultProvider();
17
+ * // Returns a ConfigurationInterface or throws.
18
+ * $config = $provider()->wait();
19
+ * </code>
20
+ *
21
+ * Configuration providers can be composed to create configuration using
22
+ * conditional logic that can create different configurations in different
23
+ * environments. You can compose multiple providers into a single provider using
24
+ * {@see Aws\ClientSideMonitoring\ConfigurationProvider::chain}. This function
25
+ * accepts providers as variadic arguments and returns a new function that will
26
+ * invoke each provider until a successful configuration is returned.
27
+ *
28
+ * <code>
29
+ * // First try an INI file at this location.
30
+ * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
31
+ * // Then try an INI file at this location.
32
+ * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
33
+ * // Then try loading from environment variables.
34
+ * $c = ConfigurationProvider::env();
35
+ * // Combine the three providers together.
36
+ * $composed = ConfigurationProvider::chain($a, $b, $c);
37
+ * // Returns a promise that is fulfilled with a configuration or throws.
38
+ * $promise = $composed();
39
+ * // Wait on the configuration to resolve.
40
+ * $config = $promise->wait();
41
+ * </code>
42
+ */
43
+ class ConfigurationProvider
44
+ {
45
+
46
+ const CACHE_KEY = 'aws_cached_csm_config';
47
+ const DEFAULT_CLIENT_ID = '';
48
+ const DEFAULT_ENABLED = false;
49
+ const DEFAULT_PORT = 31000;
50
+ const ENV_CLIENT_ID = 'AWS_CSM_CLIENT_ID';
51
+ const ENV_ENABLED = 'AWS_CSM_ENABLED';
52
+ const ENV_PORT = 'AWS_CSM_PORT';
53
+ const ENV_PROFILE = 'AWS_PROFILE';
54
+
55
+ /**
56
+ * Wraps a credential provider and saves provided credentials in an
57
+ * instance of Aws\CacheInterface. Forwards calls when no credentials found
58
+ * in cache and updates cache with the results.
59
+ *
60
+ * @param callable $provider Credentials provider function to wrap
61
+ * @param CacheInterface $cache Cache to store credentials
62
+ * @param string|null $cacheKey (optional) Cache key to use
63
+ *
64
+ * @return callable
65
+ */
66
+ public static function cache(
67
+ callable $provider,
68
+ CacheInterface $cache,
69
+ $cacheKey = null
70
+ ) {
71
+ $cacheKey = $cacheKey ?: self::CACHE_KEY;
72
+
73
+ return function () use ($provider, $cache, $cacheKey) {
74
+ $found = $cache->get($cacheKey);
75
+ if ($found instanceof ConfigurationInterface) {
76
+ return Promise\promise_for($found);
77
+ }
78
+
79
+ return $provider()
80
+ ->then(function (ConfigurationInterface $config) use (
81
+ $cache,
82
+ $cacheKey
83
+ ) {
84
+ $cache->set(
85
+ $cacheKey,
86
+ $config
87
+ );
88
+
89
+ return $config;
90
+ });
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Creates an aggregate credentials provider that invokes the provided
96
+ * variadic providers one after the other until a provider returns
97
+ * credentials.
98
+ *
99
+ * @return callable
100
+ */
101
+ public static function chain()
102
+ {
103
+ $links = func_get_args();
104
+ if (empty($links)) {
105
+ throw new \InvalidArgumentException('No providers in chain');
106
+ }
107
+
108
+ return function () use ($links) {
109
+ /** @var callable $parent */
110
+ $parent = array_shift($links);
111
+ $promise = $parent();
112
+ while ($next = array_shift($links)) {
113
+ $promise = $promise->otherwise($next);
114
+ }
115
+ return $promise;
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Create a default CSM config provider that first checks for environment
121
+ * variables, then checks for a specified profile in ~/.aws/config, then
122
+ * checks for the "aws_csm" profile in ~/.aws/config, and failing those uses
123
+ * a default fallback set of configuration options.
124
+ *
125
+ * This provider is automatically wrapped in a memoize function that caches
126
+ * previously provided config options.
127
+ *
128
+ * @param array $config Optional array of ecs/instance profile credentials
129
+ * provider options.
130
+ *
131
+ * @return callable
132
+ */
133
+ public static function defaultProvider(array $config = [])
134
+ {
135
+ $configProviders = [
136
+ self::env(),
137
+ self::ini(),
138
+ self::fallback()
139
+ ];
140
+
141
+ $memo = self::memoize(
142
+ call_user_func_array('self::chain', $configProviders)
143
+ );
144
+
145
+ if (isset($config['csm']) && $config['csm'] instanceof CacheInterface) {
146
+ return self::cache($memo, $config['csm'], self::CACHE_KEY);
147
+ }
148
+
149
+ return $memo;
150
+ }
151
+
152
+ /**
153
+ * Provider that creates CSM config from environment variables.
154
+ *
155
+ * @return callable
156
+ */
157
+ public static function env()
158
+ {
159
+ return function () {
160
+ // Use credentials from environment variables, if available
161
+ $enabled = getenv(self::ENV_ENABLED);
162
+ if ($enabled !== false) {
163
+ return Promise\promise_for(
164
+ new Configuration(
165
+ $enabled,
166
+ getenv(self::ENV_PORT) ?: self::DEFAULT_PORT,
167
+ getenv(self:: ENV_CLIENT_ID) ?: self::DEFAULT_CLIENT_ID
168
+ )
169
+ );
170
+ }
171
+
172
+ return self::reject('Could not find environment variable CSM config'
173
+ . ' in ' . self::ENV_ENABLED. '/' . self::ENV_PORT . '/'
174
+ . self::ENV_CLIENT_ID);
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Fallback config options when other sources are not set.
180
+ *
181
+ * @return callable
182
+ */
183
+ public static function fallback()
184
+ {
185
+ return function() {
186
+ return Promise\promise_for(
187
+ new Configuration(
188
+ self::DEFAULT_ENABLED,
189
+ self::DEFAULT_PORT,
190
+ self::DEFAULT_CLIENT_ID
191
+ )
192
+ );
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Gets the environment's HOME directory if available.
198
+ *
199
+ * @return null|string
200
+ */
201
+ private static function getHomeDir()
202
+ {
203
+ // On Linux/Unix-like systems, use the HOME environment variable
204
+ if ($homeDir = getenv('HOME')) {
205
+ return $homeDir;
206
+ }
207
+
208
+ // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
209
+ $homeDrive = getenv('HOMEDRIVE');
210
+ $homePath = getenv('HOMEPATH');
211
+
212
+ return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
213
+ }
214
+
215
+ /**
216
+ * CSM config provider that creates CSM config using an ini file stored
217
+ * in the current user's home directory.
218
+ *
219
+ * @param string|null $profile Profile to use. If not specified will use
220
+ * the "aws_csm" profile in "~/.aws/config".
221
+ * @param string|null $filename If provided, uses a custom filename rather
222
+ * than looking in the home directory.
223
+ *
224
+ * @return callable
225
+ */
226
+ public static function ini($profile = null, $filename = null)
227
+ {
228
+ $filename = $filename ?: (self::getHomeDir() . '/.aws/config');
229
+ $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'aws_csm');
230
+
231
+ return function () use ($profile, $filename) {
232
+ if (!is_readable($filename)) {
233
+ return self::reject("Cannot read CSM config from $filename");
234
+ }
235
+ $data = \Aws\parse_ini_file($filename, true);
236
+ if ($data === false) {
237
+ return self::reject("Invalid config file: $filename");
238
+ }
239
+ if (!isset($data[$profile])) {
240
+ return self::reject("'$profile' not found in config file");
241
+ }
242
+ if (!isset($data[$profile]['csm_enabled'])) {
243
+ return self::reject("Required CSM config values not present in
244
+ INI profile '{$profile}' ({$filename})");
245
+ }
246
+
247
+ // port is optional
248
+ if (empty($data[$profile]['csm_port'])) {
249
+ $data[$profile]['csm_port'] = self::DEFAULT_PORT;
250
+ }
251
+
252
+ // client_id is optional
253
+ if (empty($data[$profile]['csm_client_id'])) {
254
+ $data[$profile]['csm_client_id'] = self::DEFAULT_CLIENT_ID;
255
+ }
256
+
257
+ return Promise\promise_for(
258
+ new Configuration(
259
+ $data[$profile]['csm_enabled'],
260
+ $data[$profile]['csm_port'],
261
+ $data[$profile]['csm_client_id']
262
+ )
263
+ );
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Wraps a CSM config provider and caches previously provided configuration.
269
+ *
270
+ * Ensures that cached configuration is refreshed when it expires.
271
+ *
272
+ * @param callable $provider CSM config provider function to wrap.
273
+ *
274
+ * @return callable
275
+ */
276
+ public static function memoize(callable $provider)
277
+ {
278
+ return function () use ($provider) {
279
+ static $result;
280
+ static $isConstant;
281
+
282
+ // Constant config will be returned constantly.
283
+ if ($isConstant) {
284
+ return $result;
285
+ }
286
+
287
+ // Create the initial promise that will be used as the cached value
288
+ // until it expires.
289
+ if (null === $result) {
290
+ $result = $provider();
291
+ }
292
+
293
+ // Return config and set flag that provider is already set
294
+ return $result
295
+ ->then(function (ConfigurationInterface $config) use (&$isConstant) {
296
+ $isConstant = true;
297
+ return $config;
298
+ });
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Reject promise with standardized exception.
304
+ *
305
+ * @param $msg
306
+ * @return Promise\RejectedPromise
307
+ */
308
+ private static function reject($msg)
309
+ {
310
+ return new Promise\RejectedPromise(new ConfigurationException($msg));
311
+ }
312
+
313
+ /**
314
+ * Unwraps a configuration object in whatever valid form it is in,
315
+ * always returning a ConfigurationInterface object.
316
+ *
317
+ * @param mixed $config
318
+ * @return ConfigurationInterface
319
+ * @throws \InvalidArgumentException
320
+ */
321
+ public static function unwrap($config)
322
+ {
323
+ if (is_callable($config)) {
324
+ $config = $config();
325
+ }
326
+ if ($config instanceof PromiseInterface) {
327
+ $config = $config->wait();
328
+ }
329
+ if ($config instanceof ConfigurationInterface) {
330
+ return $config;
331
+ } elseif (is_array($config) && isset($config['enabled'])) {
332
+ $client_id = isset($config['client_id']) ? $config['client_id']
333
+ : self::DEFAULT_CLIENT_ID;
334
+ $port = isset($config['port']) ? $config['port']
335
+ : self::DEFAULT_PORT;
336
+ return new Configuration($config['enabled'], $port, $client_id);
337
+ }
338
+
339
+ throw new \InvalidArgumentException('Not a valid CSM configuration '
340
+ . 'argument.');
341
+ }
342
+ }
lib/Aws/Aws/ClientSideMonitoring/Exception/ConfigurationException.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\ClientSideMonitoring\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+
8
+ /**
9
+ * Represents an error interacting with configuration for client-side monitoring.
10
+ */
11
+ class ConfigurationException extends \RuntimeException implements
12
+ MonitoringEventsInterface
13
+ {
14
+ use HasMonitoringEventsTrait;
15
+ }
lib/Aws/Aws/ClientSideMonitoring/MonitoringMiddlewareInterface.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws\ClientSideMonitoring;
4
+
5
+ use Aws\CommandInterface;
6
+ use Aws\Exception\AwsException;
7
+ use Aws\ResultInterface;
8
+ use GuzzleHttp\Psr7\Request;
9
+ use Psr\Http\Message\RequestInterface;
10
+
11
+ /**
12
+ * @internal
13
+ */
14
+ interface MonitoringMiddlewareInterface
15
+ {
16
+
17
+ /**
18
+ * Data for event properties to be sent to the monitoring agent.
19
+ *
20
+ * @param RequestInterface $request
21
+ * @return array
22
+ */
23
+ public static function getRequestData(RequestInterface $request);
24
+
25
+
26
+ /**
27
+ * Data for event properties to be sent to the monitoring agent.
28
+ *
29
+ * @param ResultInterface|AwsException|\Exception $klass
30
+ * @return array
31
+ */
32
+ public static function getResponseData($klass);
33
+
34
+ public function __invoke(CommandInterface $cmd, RequestInterface $request);
35
+ }
lib/Aws/Aws/CloudFront/CloudFrontClient.php ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\CloudFront;
3
+
4
+ use Aws\AwsClient;
5
+
6
+ /**
7
+ * This client is used to interact with the **Amazon CloudFront** service.
8
+ *
9
+ * @method \Aws\Result createCloudFrontOriginAccessIdentity(array $args = [])
10
+ * @method \GuzzleHttp\Promise\Promise createCloudFrontOriginAccessIdentityAsync(array $args = [])
11
+ * @method \Aws\Result createDistribution(array $args = [])
12
+ * @method \GuzzleHttp\Promise\Promise createDistributionAsync(array $args = [])
13
+ * @method \Aws\Result createInvalidation(array $args = [])
14
+ * @method \GuzzleHttp\Promise\Promise createInvalidationAsync(array $args = [])
15
+ * @method \Aws\Result createStreamingDistribution(array $args = [])
16
+ * @method \GuzzleHttp\Promise\Promise createStreamingDistributionAsync(array $args = [])
17
+ * @method \Aws\Result deleteCloudFrontOriginAccessIdentity(array $args = [])
18
+ * @method \GuzzleHttp\Promise\Promise deleteCloudFrontOriginAccessIdentityAsync(array $args = [])
19
+ * @method \Aws\Result deleteDistribution(array $args = [])
20
+ * @method \GuzzleHttp\Promise\Promise deleteDistributionAsync(array $args = [])
21
+ * @method \Aws\Result deleteStreamingDistribution(array $args = [])
22
+ * @method \GuzzleHttp\Promise\Promise deleteStreamingDistributionAsync(array $args = [])
23
+ * @method \Aws\Result getCloudFrontOriginAccessIdentity(array $args = [])
24
+ * @method \GuzzleHttp\Promise\Promise getCloudFrontOriginAccessIdentityAsync(array $args = [])
25
+ * @method \Aws\Result getCloudFrontOriginAccessIdentityConfig(array $args = [])
26
+ * @method \GuzzleHttp\Promise\Promise getCloudFrontOriginAccessIdentityConfigAsync(array $args = [])
27
+ * @method \Aws\Result getDistribution(array $args = [])
28
+ * @method \GuzzleHttp\Promise\Promise getDistributionAsync(array $args = [])
29
+ * @method \Aws\Result getDistributionConfig(array $args = [])
30
+ * @method \GuzzleHttp\Promise\Promise getDistributionConfigAsync(array $args = [])
31
+ * @method \Aws\Result getInvalidation(array $args = [])
32
+ * @method \GuzzleHttp\Promise\Promise getInvalidationAsync(array $args = [])
33
+ * @method \Aws\Result getStreamingDistribution(array $args = [])
34
+ * @method \GuzzleHttp\Promise\Promise getStreamingDistributionAsync(array $args = [])
35
+ * @method \Aws\Result getStreamingDistributionConfig(array $args = [])
36
+ * @method \GuzzleHttp\Promise\Promise getStreamingDistributionConfigAsync(array $args = [])
37
+ * @method \Aws\Result listCloudFrontOriginAccessIdentities(array $args = [])
38
+ * @method \GuzzleHttp\Promise\Promise listCloudFrontOriginAccessIdentitiesAsync(array $args = [])
39
+ * @method \Aws\Result listDistributions(array $args = [])
40
+ * @method \GuzzleHttp\Promise\Promise listDistributionsAsync(array $args = [])
41
+ * @method \Aws\Result listDistributionsByWebACLId(array $args = [])
42
+ * @method \GuzzleHttp\Promise\Promise listDistributionsByWebACLIdAsync(array $args = [])
43
+ * @method \Aws\Result listInvalidations(array $args = [])
44
+ * @method \GuzzleHttp\Promise\Promise listInvalidationsAsync(array $args = [])
45
+ * @method \Aws\Result listStreamingDistributions(array $args = [])
46
+ * @method \GuzzleHttp\Promise\Promise listStreamingDistributionsAsync(array $args = [])
47
+ * @method \Aws\Result updateCloudFrontOriginAccessIdentity(array $args = [])
48
+ * @method \GuzzleHttp\Promise\Promise updateCloudFrontOriginAccessIdentityAsync(array $args = [])
49
+ * @method \Aws\Result updateDistribution(array $args = [])
50
+ * @method \GuzzleHttp\Promise\Promise updateDistributionAsync(array $args = [])
51
+ * @method \Aws\Result updateStreamingDistribution(array $args = [])
52
+ * @method \GuzzleHttp\Promise\Promise updateStreamingDistributionAsync(array $args = [])
53
+ * @method \Aws\Result createDistributionWithTags(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
54
+ * @method \GuzzleHttp\Promise\Promise createDistributionWithTagsAsync(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
55
+ * @method \Aws\Result createStreamingDistributionWithTags(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
56
+ * @method \GuzzleHttp\Promise\Promise createStreamingDistributionWithTagsAsync(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
57
+ * @method \Aws\Result listTagsForResource(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
58
+ * @method \GuzzleHttp\Promise\Promise listTagsForResourceAsync(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
59
+ * @method \Aws\Result tagResource(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
60
+ * @method \GuzzleHttp\Promise\Promise tagResourceAsync(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
61
+ * @method \Aws\Result untagResource(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
62
+ * @method \GuzzleHttp\Promise\Promise untagResourceAsync(array $args = []) (supported in versions 2016-08-01, 2016-08-20, 2016-09-07, 2016-09-29, 2016-11-25, 2017-03-25, 2017-10-30, 2018-06-18, 2018-11-05)
63
+ * @method \Aws\Result deleteServiceLinkedRole(array $args = []) (supported in versions 2017-03-25)
64
+ * @method \GuzzleHttp\Promise\Promise deleteServiceLinkedRoleAsync(array $args = []) (supported in versions 2017-03-25)
65
+ * @method \Aws\Result createFieldLevelEncryptionConfig(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
66
+ * @method \GuzzleHttp\Promise\Promise createFieldLevelEncryptionConfigAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
67
+ * @method \Aws\Result createFieldLevelEncryptionProfile(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
68
+ * @method \GuzzleHttp\Promise\Promise createFieldLevelEncryptionProfileAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
69
+ * @method \Aws\Result createPublicKey(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
70
+ * @method \GuzzleHttp\Promise\Promise createPublicKeyAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
71
+ * @method \Aws\Result deleteFieldLevelEncryptionConfig(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
72
+ * @method \GuzzleHttp\Promise\Promise deleteFieldLevelEncryptionConfigAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
73
+ * @method \Aws\Result deleteFieldLevelEncryptionProfile(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
74
+ * @method \GuzzleHttp\Promise\Promise deleteFieldLevelEncryptionProfileAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
75
+ * @method \Aws\Result deletePublicKey(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
76
+ * @method \GuzzleHttp\Promise\Promise deletePublicKeyAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
77
+ * @method \Aws\Result getFieldLevelEncryption(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
78
+ * @method \GuzzleHttp\Promise\Promise getFieldLevelEncryptionAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
79
+ * @method \Aws\Result getFieldLevelEncryptionConfig(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
80
+ * @method \GuzzleHttp\Promise\Promise getFieldLevelEncryptionConfigAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
81
+ * @method \Aws\Result getFieldLevelEncryptionProfile(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
82
+ * @method \GuzzleHttp\Promise\Promise getFieldLevelEncryptionProfileAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
83
+ * @method \Aws\Result getFieldLevelEncryptionProfileConfig(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
84
+ * @method \GuzzleHttp\Promise\Promise getFieldLevelEncryptionProfileConfigAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
85
+ * @method \Aws\Result getPublicKey(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
86
+ * @method \GuzzleHttp\Promise\Promise getPublicKeyAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
87
+ * @method \Aws\Result getPublicKeyConfig(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
88
+ * @method \GuzzleHttp\Promise\Promise getPublicKeyConfigAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
89
+ * @method \Aws\Result listFieldLevelEncryptionConfigs(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
90
+ * @method \GuzzleHttp\Promise\Promise listFieldLevelEncryptionConfigsAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
91
+ * @method \Aws\Result listFieldLevelEncryptionProfiles(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
92
+ * @method \GuzzleHttp\Promise\Promise listFieldLevelEncryptionProfilesAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
93
+ * @method \Aws\Result listPublicKeys(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
94
+ * @method \GuzzleHttp\Promise\Promise listPublicKeysAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
95
+ * @method \Aws\Result updateFieldLevelEncryptionConfig(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
96
+ * @method \GuzzleHttp\Promise\Promise updateFieldLevelEncryptionConfigAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
97
+ * @method \Aws\Result updateFieldLevelEncryptionProfile(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
98
+ * @method \GuzzleHttp\Promise\Promise updateFieldLevelEncryptionProfileAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
99
+ * @method \Aws\Result updatePublicKey(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
100
+ * @method \GuzzleHttp\Promise\Promise updatePublicKeyAsync(array $args = []) (supported in versions 2017-10-30, 2018-06-18, 2018-11-05)
101
+ */
102
+ class CloudFrontClient extends AwsClient
103
+ {
104
+ /**
105
+ * Create a signed Amazon CloudFront URL.
106
+ *
107
+ * This method accepts an array of configuration options:
108
+ *
109
+ * - url: (string) URL of the resource being signed (can include query
110
+ * string and wildcards). For example: rtmp://s5c39gqb8ow64r.cloudfront.net/videos/mp3_name.mp3
111
+ * http://d111111abcdef8.cloudfront.net/images/horizon.jpg?size=large&license=yes
112
+ * - policy: (string) JSON policy. Use this option when creating a signed
113
+ * URL for a custom policy.
114
+ * - expires: (int) UTC Unix timestamp used when signing with a canned
115
+ * policy. Not required when passing a custom 'policy' option.
116
+ * - key_pair_id: (string) The ID of the key pair used to sign CloudFront
117
+ * URLs for private distributions.
118
+ * - private_key: (string) The filepath ot the private key used to sign
119
+ * CloudFront URLs for private distributions.
120
+ *
121
+ * @param array $options Array of configuration options used when signing
122
+ *
123
+ * @return string Signed URL with authentication parameters
124
+ * @throws \InvalidArgumentException if url, key_pair_id, or private_key
125
+ * were not specified.
126
+ * @link http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/WorkingWithStreamingDistributions.html
127
+ */
128
+ public function getSignedUrl(array $options)
129
+ {
130
+ foreach (['url', 'key_pair_id', 'private_key'] as $required) {
131
+ if (!isset($options[$required])) {
132
+ throw new \InvalidArgumentException("$required is required");
133
+ }
134
+ }
135
+
136
+ $urlSigner = new UrlSigner(
137
+ $options['key_pair_id'],
138
+ $options['private_key']
139
+ );
140
+
141
+ return $urlSigner->getSignedUrl(
142
+ $options['url'],
143
+ isset($options['expires']) ? $options['expires'] : null,
144
+ isset($options['policy']) ? $options['policy'] : null
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Create a signed Amazon CloudFront cookie.
150
+ *
151
+ * This method accepts an array of configuration options:
152
+ *
153
+ * - url: (string) URL of the resource being signed (can include query
154
+ * string and wildcards). For example: http://d111111abcdef8.cloudfront.net/images/horizon.jpg?size=large&license=yes
155
+ * - policy: (string) JSON policy. Use this option when creating a signed
156
+ * URL for a custom policy.
157
+ * - expires: (int) UTC Unix timestamp used when signing with a canned
158
+ * policy. Not required when passing a custom 'policy' option.
159
+ * - key_pair_id: (string) The ID of the key pair used to sign CloudFront
160
+ * URLs for private distributions.
161
+ * - private_key: (string) The filepath ot the private key used to sign
162
+ * CloudFront URLs for private distributions.
163
+ *
164
+ * @param array $options Array of configuration options used when signing
165
+ *
166
+ * @return array Key => value pairs of signed cookies to set
167
+ * @throws \InvalidArgumentException if url, key_pair_id, or private_key
168
+ * were not specified.
169
+ * @link http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/WorkingWithStreamingDistributions.html
170
+ */
171
+ public function getSignedCookie(array $options)
172
+ {
173
+ foreach (['key_pair_id', 'private_key'] as $required) {
174
+ if (!isset($options[$required])) {
175
+ throw new \InvalidArgumentException("$required is required");
176
+ }
177
+ }
178
+
179
+ $cookieSigner = new CookieSigner(
180
+ $options['key_pair_id'],
181
+ $options['private_key']
182
+ );
183
+
184
+ return $cookieSigner->getSignedCookie(
185
+ isset($options['url']) ? $options['url'] : null,
186
+ isset($options['expires']) ? $options['expires'] : null,
187
+ isset($options['policy']) ? $options['policy'] : null
188
+ );
189
+ }
190
+ }
lib/Aws/Aws/CloudFront/CookieSigner.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\CloudFront;
3
+
4
+ class CookieSigner
5
+ {
6
+ /** @var Signer */
7
+ private $signer;
8
+
9
+ private static $schemes = [
10
+ 'http' => true,
11
+ 'https' => true,
12
+ ];
13
+
14
+ /**
15
+ * @param $keyPairId string ID of the key pair
16
+ * @param $privateKey string Path to the private key used for signing
17
+ *
18
+ * @throws \RuntimeException if the openssl extension is missing
19
+ * @throws \InvalidArgumentException if the private key cannot be found.
20
+ */
21
+ public function __construct($keyPairId, $privateKey)
22
+ {
23
+ $this->signer = new Signer($keyPairId, $privateKey);
24
+ }
25
+
26
+ /**
27
+ * Create a signed Amazon CloudFront Cookie.
28
+ *
29
+ * @param string $url URL to sign (can include query string
30
+ * and wildcards). Not required
31
+ * when passing a custom $policy.
32
+ * @param string|integer|null $expires UTC Unix timestamp used when signing
33
+ * with a canned policy. Not required
34
+ * when passing a custom $policy.
35
+ * @param string $policy JSON policy. Use this option when
36
+ * creating a signed cookie for a custom
37
+ * policy.
38
+ *
39
+ * @return array The authenticated cookie parameters
40
+ * @throws \InvalidArgumentException if the URL provided is invalid
41
+ * @link http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html
42
+ */
43
+ public function getSignedCookie($url = null, $expires = null, $policy = null)
44
+ {
45
+ if ($url) {
46
+ $this->validateUrl($url);
47
+ }
48
+
49
+ $cookieParameters = [];
50
+ $signature = $this->signer->getSignature($url, $expires, $policy);
51
+ foreach ($signature as $key => $value) {
52
+ $cookieParameters["CloudFront-$key"] = $value;
53
+ }
54
+
55
+ return $cookieParameters;
56
+ }
57
+
58
+ private function validateUrl($url)
59
+ {
60
+ $scheme = str_replace('*', '', explode('://', $url)[0]);
61
+ if (empty(self::$schemes[strtolower($scheme)])) {
62
+ throw new \InvalidArgumentException('Invalid or missing URI scheme');
63
+ }
64
+ }
65
+ }
lib/Aws/Aws/CloudFront/Exception/CloudFrontException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\CloudFront\Exception;
3
+
4
+ use Aws\Exception\AwsException;
5
+
6
+ /**
7
+ * Represents an error interacting with the Amazon CloudFront service.
8
+ */
9
+ class CloudFrontException extends AwsException {}
lib/Aws/Aws/CloudFront/Signer.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\CloudFront;
3
+
4
+ /**
5
+ * @internal
6
+ */
7
+ class Signer
8
+ {
9
+ private $keyPairId;
10
+ private $pkHandle;
11
+
12
+ /**
13
+ * A signer for creating the signature values used in CloudFront signed URLs
14
+ * and signed cookies.
15
+ *
16
+ * @param $keyPairId string ID of the key pair
17
+ * @param $privateKey string Path to the private key used for signing
18
+ *
19
+ * @throws \RuntimeException if the openssl extension is missing
20
+ * @throws \InvalidArgumentException if the private key cannot be found.
21
+ */
22
+ public function __construct($keyPairId, $privateKey)
23
+ {
24
+ if (!extension_loaded('openssl')) {
25
+ //@codeCoverageIgnoreStart
26
+ throw new \RuntimeException('The openssl extension is required to '
27
+ . 'sign CloudFront urls.');
28
+ //@codeCoverageIgnoreEnd
29
+ }
30
+
31
+ $this->keyPairId = $keyPairId;
32
+
33
+ if (!file_exists($privateKey)) {
34
+ throw new \InvalidArgumentException("PK file not found: $privateKey");
35
+ }
36
+
37
+ $this->pkHandle = openssl_pkey_get_private("file://$privateKey");
38
+
39
+ if (!$this->pkHandle) {
40
+ throw new \InvalidArgumentException(openssl_error_string());
41
+ }
42
+ }
43
+
44
+ public function __destruct()
45
+ {
46
+ $this->pkHandle && openssl_pkey_free($this->pkHandle);
47
+ }
48
+
49
+ /**
50
+ * Create the values used to construct signed URLs and cookies.
51
+ *
52
+ * @param string $resource The CloudFront resource to which
53
+ * this signature will grant access.
54
+ * Not used when a custom policy is
55
+ * provided.
56
+ * @param string|integer|null $expires UTC Unix timestamp used when
57
+ * signing with a canned policy.
58
+ * Not required when passing a
59
+ * custom $policy.
60
+ * @param string $policy JSON policy. Use this option when
61
+ * creating a signature for a custom
62
+ * policy.
63
+ *
64
+ * @return array The values needed to construct a signed URL or cookie
65
+ * @throws \InvalidArgumentException when not provided either a policy or a
66
+ * resource and a expires
67
+ *
68
+ * @link http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html
69
+ */
70
+ public function getSignature($resource = null, $expires = null, $policy = null)
71
+ {
72
+ $signatureHash = [];
73
+ if ($policy) {
74
+ $policy = preg_replace('/\s/s', '', $policy);
75
+ $signatureHash['Policy'] = $this->encode($policy);
76
+ } elseif ($resource && $expires) {
77
+ $expires = (int) $expires; // Handle epoch passed as string
78
+ $policy = $this->createCannedPolicy($resource, $expires);
79
+ $signatureHash['Expires'] = $expires;
80
+ } else {
81
+ throw new \InvalidArgumentException('Either a policy or a resource'
82
+ . ' and an expiration time must be provided.');
83
+ }
84
+
85
+ $signatureHash['Signature'] = $this->encode($this->sign($policy));
86
+ $signatureHash['Key-Pair-Id'] = $this->keyPairId;
87
+
88
+ return $signatureHash;
89
+ }
90
+
91
+ private function createCannedPolicy($resource, $expiration)
92
+ {
93
+ return json_encode([
94
+ 'Statement' => [
95
+ [
96
+ 'Resource' => $resource,
97
+ 'Condition' => [
98
+ 'DateLessThan' => ['AWS:EpochTime' => $expiration],
99
+ ],
100
+ ],
101
+ ],
102
+ ], JSON_UNESCAPED_SLASHES);
103
+ }
104
+
105
+ private function sign($policy)
106
+ {
107
+ $signature = '';
108
+ openssl_sign($policy, $signature, $this->pkHandle);
109
+
110
+ return $signature;
111
+ }
112
+
113
+ private function encode($policy)
114
+ {
115
+ return strtr(base64_encode($policy), '+=/', '-_~');
116
+ }
117
+ }
lib/Aws/Aws/CloudFront/UrlSigner.php ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\CloudFront;
3
+
4
+ use GuzzleHttp\Psr7;
5
+ use GuzzleHttp\Psr7\Uri;
6
+ use Psr\Http\Message\UriInterface;
7
+
8
+ /**
9
+ * Creates signed URLs for Amazon CloudFront resources.
10
+ */
11
+ class UrlSigner
12
+ {
13
+ private $signer;
14
+
15
+ /**
16
+ * @param $keyPairId string ID of the key pair
17
+ * @param $privateKey string Path to the private key used for signing
18
+ *
19
+ * @throws \RuntimeException if the openssl extension is missing
20
+ * @throws \InvalidArgumentException if the private key cannot be found.
21
+ */
22
+ public function __construct($keyPairId, $privateKey)
23
+ {
24
+ $this->signer = new Signer($keyPairId, $privateKey);
25
+ }
26
+
27
+ /**
28
+ * Create a signed Amazon CloudFront URL.
29
+ *
30
+ * Keep in mind that URLs meant for use in media/flash players may have
31
+ * different requirements for URL formats (e.g. some require that the
32
+ * extension be removed, some require the file name to be prefixed
33
+ * - mp4:<path>, some require you to add "/cfx/st" into your URL).
34
+ *
35
+ * @param string $url URL to sign (can include query
36
+ * string string and wildcards)
37
+ * @param string|integer|null $expires UTC Unix timestamp used when signing
38
+ * with a canned policy. Not required
39
+ * when passing a custom $policy.
40
+ * @param string $policy JSON policy. Use this option when
41
+ * creating a signed URL for a custom
42
+ * policy.
43
+ *
44
+ * @return string The file URL with authentication parameters
45
+ * @throws \InvalidArgumentException if the URL provided is invalid
46
+ * @link http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/WorkingWithStreamingDistributions.html
47
+ */
48
+ public function getSignedUrl($url, $expires = null, $policy = null)
49
+ {
50
+ // Determine the scheme of the url
51
+ $urlSections = explode('://', $url);
52
+
53
+ if (count($urlSections) < 2) {
54
+ throw new \InvalidArgumentException("Invalid URL: {$url}");
55
+ }
56
+
57
+ // Get the real scheme by removing wildcards from the scheme
58
+ $scheme = str_replace('*', '', $urlSections[0]);
59
+ $uri = new Uri($scheme . '://' . $urlSections[1]);
60
+ $query = Psr7\parse_query($uri->getQuery(), PHP_QUERY_RFC3986);
61
+ $signature = $this->signer->getSignature(
62
+ $this->createResource($scheme, (string) $uri),
63
+ $expires,
64
+ $policy
65
+ );
66
+ $uri = $uri->withQuery(
67
+ http_build_query($query + $signature, null, '&', PHP_QUERY_RFC3986)
68
+ );
69
+
70
+ return $scheme === 'rtmp'
71
+ ? $this->createRtmpUrl($uri)
72
+ : (string) $uri;
73
+ }
74
+
75
+ private function createRtmpUrl(UriInterface $uri)
76
+ {
77
+ // Use a relative URL when creating Flash player URLs
78
+ $result = ltrim($uri->getPath(), '/');
79
+
80
+ if ($query = $uri->getQuery()) {
81
+ $result .= '?' . $query;
82
+ }
83
+
84
+ return $result;
85
+ }
86
+
87
+ /**
88
+ * @param $scheme
89
+ * @param $url
90
+ *
91
+ * @return string
92
+ */
93
+ private function createResource($scheme, $url)
94
+ {
95
+ switch ($scheme) {
96
+ case 'http':
97
+ case 'http*':
98
+ case 'https':
99
+ return $url;
100
+ case 'rtmp':
101
+ $parts = parse_url($url);
102
+ $pathParts = pathinfo($parts['path']);
103
+ $resource = ltrim(
104
+ $pathParts['dirname'] . '/' . $pathParts['basename'],
105
+ '/'
106
+ );
107
+
108
+ // Add a query string if present.
109
+ if (isset($parts['query'])) {
110
+ $resource .= "?{$parts['query']}";
111
+ }
112
+
113
+ return $resource;
114
+ }
115
+
116
+ throw new \InvalidArgumentException("Invalid URI scheme: {$scheme}. "
117
+ . "Scheme must be one of: http, https, or rtmp");
118
+ }
119
+ }
lib/Aws/Aws/Command.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * AWS command object.
6
+ */
7
+ class Command implements CommandInterface
8
+ {
9
+ use HasDataTrait;
10
+
11
+ /** @var string */
12
+ private $name;
13
+
14
+ /** @var HandlerList */
15
+ private $handlerList;
16
+
17
+ /**
18
+ * Accepts an associative array of command options, including:
19
+ *
20
+ * - @http: (array) Associative array of transfer options.
21
+ *
22
+ * @param string $name Name of the command
23
+ * @param array $args Arguments to pass to the command
24
+ * @param HandlerList $list Handler list
25
+ */
26
+ public function __construct($name, array $args = [], HandlerList $list = null)
27
+ {
28
+ $this->name = $name;
29
+ $this->data = $args;
30
+ $this->handlerList = $list ?: new HandlerList();
31
+
32
+ if (!isset($this->data['@http'])) {
33
+ $this->data['@http'] = [];
34
+ }
35
+ }
36
+
37
+ public function __clone()
38
+ {
39
+ $this->handlerList = clone $this->handlerList;
40
+ }
41
+
42
+ public function getName()
43
+ {
44
+ return $this->name;
45
+ }
46
+
47
+ public function hasParam($name)
48
+ {
49
+ return array_key_exists($name, $this->data);
50
+ }
51
+
52
+ public function getHandlerList()
53
+ {
54
+ return $this->handlerList;
55
+ }
56
+
57
+ /** @deprecated */
58
+ public function get($name)
59
+ {
60
+ return $this[$name];
61
+ }
62
+ }
lib/Aws/Aws/CommandInterface.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * A command object encapsulates the input parameters used to control the
6
+ * creation of a HTTP request and processing of a HTTP response.
7
+ *
8
+ * Using the toArray() method will return the input parameters of the command
9
+ * as an associative array.
10
+ */
11
+ interface CommandInterface extends \ArrayAccess, \Countable, \IteratorAggregate
12
+ {
13
+ /**
14
+ * Converts the command parameters to an array
15
+ *
16
+ * @return array
17
+ */
18
+ public function toArray();
19
+
20
+ /**
21
+ * Get the name of the command
22
+ *
23
+ * @return string
24
+ */
25
+ public function getName();
26
+
27
+ /**
28
+ * Check if the command has a parameter by name.
29
+ *
30
+ * @param string $name Name of the parameter to check
31
+ *
32
+ * @return bool
33
+ */
34
+ public function hasParam($name);
35
+
36
+ /**
37
+ * Get the handler list used to transfer the command.
38
+ *
39
+ * @return HandlerList
40
+ */
41
+ public function getHandlerList();
42
+ }
lib/Aws/Aws/CommandPool.php ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use GuzzleHttp\Promise\PromisorInterface;
5
+ use GuzzleHttp\Promise\EachPromise;
6
+
7
+ /**
8
+ * Sends and iterator of commands concurrently using a capped pool size.
9
+ *
10
+ * The pool will read command objects from an iterator until it is cancelled or
11
+ * until the iterator is consumed.
12
+ */
13
+ class CommandPool implements PromisorInterface
14
+ {
15
+ /** @var EachPromise */
16
+ private $each;
17
+
18
+ /**
19
+ * The CommandPool constructor accepts a hash of configuration options:
20
+ *
21
+ * - concurrency: (callable|int) Maximum number of commands to execute
22
+ * concurrently. Provide a function to resize the pool dynamically. The
23
+ * function will be provided the current number of pending requests and
24
+ * is expected to return an integer representing the new pool size limit.
25
+ * - before: (callable) function to invoke before sending each command. The
26
+ * before function accepts the command and the key of the iterator of the
27
+ * command. You can mutate the command as needed in the before function
28
+ * before sending the command.
29
+ * - fulfilled: (callable) Function to invoke when a promise is fulfilled.
30
+ * The function is provided the result object, id of the iterator that the
31
+ * result came from, and the aggregate promise that can be resolved/rejected
32
+ * if you need to short-circuit the pool.
33
+ * - rejected: (callable) Function to invoke when a promise is rejected.
34
+ * The function is provided an AwsException object, id of the iterator that
35
+ * the exception came from, and the aggregate promise that can be
36
+ * resolved/rejected if you need to short-circuit the pool.
37
+ * - preserve_iterator_keys: (bool) Retain the iterator key when generating
38
+ * the commands.
39
+ *
40
+ * @param AwsClientInterface $client Client used to execute commands.
41
+ * @param array|\Iterator $commands Iterable that yields commands.
42
+ * @param array $config Associative array of options.
43
+ */
44
+ public function __construct(
45
+ AwsClientInterface $client,
46
+ $commands,
47
+ array $config = []
48
+ ) {
49
+ if (!isset($config['concurrency'])) {
50
+ $config['concurrency'] = 25;
51
+ }
52
+
53
+ $before = $this->getBefore($config);
54
+ $mapFn = function ($commands) use ($client, $before, $config) {
55
+ foreach ($commands as $key => $command) {
56
+ if (!($command instanceof CommandInterface)) {
57
+ throw new \InvalidArgumentException('Each value yielded by '
58
+ . 'the iterator must be an Aws\CommandInterface.');
59
+ }
60
+ if ($before) {
61
+ $before($command, $key);
62
+ }
63
+ if (!empty($config['preserve_iterator_keys'])) {
64
+ yield $key => $client->executeAsync($command);
65
+ } else {
66
+ yield $client->executeAsync($command);
67
+ }
68
+ }
69
+ };
70
+
71
+ $this->each = new EachPromise($mapFn($commands), $config);
72
+ }
73
+
74
+ /**
75
+ * @return \GuzzleHttp\Promise\PromiseInterface
76
+ */
77
+ public function promise()
78
+ {
79
+ return $this->each->promise();
80
+ }
81
+
82
+ /**
83
+ * Executes a pool synchronously and aggregates the results of the pool
84
+ * into an indexed array in the same order as the passed in array.
85
+ *
86
+ * @param AwsClientInterface $client Client used to execute commands.
87
+ * @param mixed $commands Iterable that yields commands.
88
+ * @param array $config Configuration options.
89
+ *
90
+ * @return array
91
+ * @see \Aws\CommandPool::__construct for available configuration options.
92
+ */
93
+ public static function batch(
94
+ AwsClientInterface $client,
95
+ $commands,
96
+ array $config = []
97
+ ) {
98
+ $results = [];
99
+ self::cmpCallback($config, 'fulfilled', $results);
100
+ self::cmpCallback($config, 'rejected', $results);
101
+
102
+ return (new self($client, $commands, $config))
103
+ ->promise()
104
+ ->then(static function () use (&$results) {
105
+ ksort($results);
106
+ return $results;
107
+ })
108
+ ->wait();
109
+ }
110
+
111
+ /**
112
+ * @return callable
113
+ */
114
+ private function getBefore(array $config)
115
+ {
116
+ if (!isset($config['before'])) {
117
+ return null;
118
+ }
119
+
120
+ if (is_callable($config['before'])) {
121
+ return $config['before'];
122
+ }
123
+
124
+ throw new \InvalidArgumentException('before must be callable');
125
+ }
126
+
127
+ /**
128
+ * Adds an onFulfilled or onRejected callback that aggregates results into
129
+ * an array. If a callback is already present, it is replaced with the
130
+ * composed function.
131
+ *
132
+ * @param array $config
133
+ * @param $name
134
+ * @param array $results
135
+ */
136
+ private static function cmpCallback(array &$config, $name, array &$results)
137
+ {
138
+ if (!isset($config[$name])) {
139
+ $config[$name] = function ($v, $k) use (&$results) {
140
+ $results[$k] = $v;
141
+ };
142
+ } else {
143
+ $currentFn = $config[$name];
144
+ $config[$name] = function ($v, $k) use (&$results, $currentFn) {
145
+ $currentFn($v, $k);
146
+ $results[$k] = $v;
147
+ };
148
+ }
149
+ }
150
+ }
lib/Aws/Aws/Credentials/AssumeRoleCredentialProvider.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Credentials;
3
+
4
+ use Aws\Exception\CredentialsException;
5
+ use Aws\Result;
6
+ use Aws\Sts\StsClient;
7
+ use GuzzleHttp\Promise\PromiseInterface;
8
+
9
+ /**
10
+ * Credential provider that provides credentials via assuming a role
11
+ * More Information, see: http://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sts-2011-06-15.html#assumerole
12
+ */
13
+ class AssumeRoleCredentialProvider
14
+ {
15
+ const ERROR_MSG = "Missing required 'AssumeRoleCredentialProvider' configuration option: ";
16
+
17
+ /** @var StsClient */
18
+ private $client;
19
+
20
+ /** @var array */
21
+ private $assumeRoleParams;
22
+
23
+ /**
24
+ * The constructor requires following configure parameters:
25
+ * - client: a StsClient
26
+ * - assume_role_params: Parameters used to make assumeRole call
27
+ *
28
+ * @param array $config Configuration options
29
+ * @throws \InvalidArgumentException
30
+ */
31
+ public function __construct(array $config = [])
32
+ {
33
+ if (!isset($config['assume_role_params'])) {
34
+ throw new \InvalidArgumentException(self::ERROR_MSG . "'assume_role_params'.");
35
+ }
36
+
37
+ if (!isset($config['client'])) {
38
+ throw new \InvalidArgumentException(self::ERROR_MSG . "'client'.");
39
+ }
40
+
41
+ $this->client = $config['client'];
42
+ $this->assumeRoleParams = $config['assume_role_params'];
43
+ }
44
+
45
+ /**
46
+ * Loads assume role credentials.
47
+ *
48
+ * @return PromiseInterface
49
+ */
50
+ public function __invoke()
51
+ {
52
+ $client = $this->client;
53
+ return $client->assumeRoleAsync($this->assumeRoleParams)
54
+ ->then(function (Result $result) {
55
+ return $this->client->createCredentials($result);
56
+ })->otherwise(function (\RuntimeException $exception) {
57
+ throw new CredentialsException(
58
+ "Error in retrieving assume role credentials.",
59
+ 0,
60
+ $exception
61
+ );
62
+ });
63
+ }
64
+ }
lib/Aws/Aws/Credentials/CredentialProvider.php ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Credentials;
3
+
4
+ use Aws;
5
+ use Aws\Api\DateTimeResult;
6
+ use Aws\CacheInterface;
7
+ use Aws\Exception\CredentialsException;
8
+ use GuzzleHttp\Promise;
9
+
10
+ /**
11
+ * Credential providers are functions that accept no arguments and return a
12
+ * promise that is fulfilled with an {@see \Aws\Credentials\CredentialsInterface}
13
+ * or rejected with an {@see \Aws\Exception\CredentialsException}.
14
+ *
15
+ * <code>
16
+ * use Aws\Credentials\CredentialProvider;
17
+ * $provider = CredentialProvider::defaultProvider();
18
+ * // Returns a CredentialsInterface or throws.
19
+ * $creds = $provider()->wait();
20
+ * </code>
21
+ *
22
+ * Credential providers can be composed to create credentials using conditional
23
+ * logic that can create different credentials in different environments. You
24
+ * can compose multiple providers into a single provider using
25
+ * {@see Aws\Credentials\CredentialProvider::chain}. This function accepts
26
+ * providers as variadic arguments and returns a new function that will invoke
27
+ * each provider until a successful set of credentials is returned.
28
+ *
29
+ * <code>
30
+ * // First try an INI file at this location.
31
+ * $a = CredentialProvider::ini(null, '/path/to/file.ini');
32
+ * // Then try an INI file at this location.
33
+ * $b = CredentialProvider::ini(null, '/path/to/other-file.ini');
34
+ * // Then try loading from environment variables.
35
+ * $c = CredentialProvider::env();
36
+ * // Combine the three providers together.
37
+ * $composed = CredentialProvider::chain($a, $b, $c);
38
+ * // Returns a promise that is fulfilled with credentials or throws.
39
+ * $promise = $composed();
40
+ * // Wait on the credentials to resolve.
41
+ * $creds = $promise->wait();
42
+ * </code>
43
+ */
44
+ class CredentialProvider
45
+ {
46
+ const ENV_KEY = 'AWS_ACCESS_KEY_ID';
47
+ const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
48
+ const ENV_SESSION = 'AWS_SESSION_TOKEN';
49
+ const ENV_PROFILE = 'AWS_PROFILE';
50
+
51
+ /**
52
+ * Create a default credential provider that first checks for environment
53
+ * variables, then checks for the "default" profile in ~/.aws/credentials,
54
+ * then checks for "profile default" profile in ~/.aws/config (which is
55
+ * the default profile of AWS CLI), then tries to make a GET Request to
56
+ * fetch credentials if Ecs environment variable is presented, then checks
57
+ * for credential_process in the "default" profile in ~/.aws/credentials,
58
+ * then for credential_process in the "default profile" profile in
59
+ * ~/.aws/config, and finally checks for EC2 instance profile credentials.
60
+ *
61
+ * This provider is automatically wrapped in a memoize function that caches
62
+ * previously provided credentials.
63
+ *
64
+ * @param array $config Optional array of ecs/instance profile credentials
65
+ * provider options.
66
+ *
67
+ * @return callable
68
+ */
69
+ public static function defaultProvider(array $config = [])
70
+ {
71
+ $localCredentialProviders = self::localCredentialProviders();
72
+ $remoteCredentialProviders = self::remoteCredentialProviders($config);
73
+
74
+ return self::memoize(
75
+ call_user_func_array(
76
+ 'self::chain',
77
+ array_merge($localCredentialProviders, $remoteCredentialProviders)
78
+ )
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Create a credential provider function from a set of static credentials.
84
+ *
85
+ * @param CredentialsInterface $creds
86
+ *
87
+ * @return callable
88
+ */
89
+ public static function fromCredentials(CredentialsInterface $creds)
90
+ {
91
+ $promise = Promise\promise_for($creds);
92
+
93
+ return function () use ($promise) {
94
+ return $promise;
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Creates an aggregate credentials provider that invokes the provided
100
+ * variadic providers one after the other until a provider returns
101
+ * credentials.
102
+ *
103
+ * @return callable
104
+ */
105
+ public static function chain()
106
+ {
107
+ $links = func_get_args();
108
+ if (empty($links)) {
109
+ throw new \InvalidArgumentException('No providers in chain');
110
+ }
111
+
112
+ return function () use ($links) {
113
+ /** @var callable $parent */
114
+ $parent = array_shift($links);
115
+ $promise = $parent();
116
+ while ($next = array_shift($links)) {
117
+ $promise = $promise->otherwise($next);
118
+ }
119
+ return $promise;
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Wraps a credential provider and caches previously provided credentials.
125
+ *
126
+ * Ensures that cached credentials are refreshed when they expire.
127
+ *
128
+ * @param callable $provider Credentials provider function to wrap.
129
+ *
130
+ * @return callable
131
+ */
132
+ public static function memoize(callable $provider)
133
+ {
134
+ return function () use ($provider) {
135
+ static $result;
136
+ static $isConstant;
137
+
138
+ // Constant credentials will be returned constantly.
139
+ if ($isConstant) {
140
+ return $result;
141
+ }
142
+
143
+ // Create the initial promise that will be used as the cached value
144
+ // until it expires.
145
+ if (null === $result) {
146
+ $result = $provider();
147
+ }
148
+
149
+ // Return credentials that could expire and refresh when needed.
150
+ return $result
151
+ ->then(function (CredentialsInterface $creds) use ($provider, &$isConstant, &$result) {
152
+ // Determine if these are constant credentials.
153
+ if (!$creds->getExpiration()) {
154
+ $isConstant = true;
155
+ return $creds;
156
+ }
157
+
158
+ // Refresh expired credentials.
159
+ if (!$creds->isExpired()) {
160
+ return $creds;
161
+ }
162
+ // Refresh the result and forward the promise.
163
+ return $result = $provider();
164
+ })
165
+ ->otherwise(function($reason) use (&$result) {
166
+ // Cleanup rejected promise.
167
+ $result = null;
168
+ return new Promise\RejectedPromise($reason);
169
+ });
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Wraps a credential provider and saves provided credentials in an
175
+ * instance of Aws\CacheInterface. Forwards calls when no credentials found
176
+ * in cache and updates cache with the results.
177
+ *
178
+ * Defaults to using a simple file-based cache when none provided.
179
+ *
180
+ * @param callable $provider Credentials provider function to wrap
181
+ * @param CacheInterface $cache Cache to store credentials
182
+ * @param string|null $cacheKey (optional) Cache key to use
183
+ *
184
+ * @return callable
185
+ */
186
+ public static function cache(
187
+ callable $provider,
188
+ CacheInterface $cache,
189
+ $cacheKey = null
190
+ ) {
191
+ $cacheKey = $cacheKey ?: 'aws_cached_credentials';
192
+
193
+ return function () use ($provider, $cache, $cacheKey) {
194
+ $found = $cache->get($cacheKey);
195
+ if ($found instanceof CredentialsInterface && !$found->isExpired()) {
196
+ return Promise\promise_for($found);
197
+ }
198
+
199
+ return $provider()
200
+ ->then(function (CredentialsInterface $creds) use (
201
+ $cache,
202
+ $cacheKey
203
+ ) {
204
+ $cache->set(
205
+ $cacheKey,
206
+ $creds,
207
+ null === $creds->getExpiration() ?
208
+ 0 : $creds->getExpiration() - time()
209
+ );
210
+
211
+ return $creds;
212
+ });
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Provider that creates credentials from environment variables
218
+ * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN.
219
+ *
220
+ * @return callable
221
+ */
222
+ public static function env()
223
+ {
224
+ return function () {
225
+ // Use credentials from environment variables, if available
226
+ $key = getenv(self::ENV_KEY);
227
+ $secret = getenv(self::ENV_SECRET);
228
+ if ($key && $secret) {
229
+ return Promise\promise_for(
230
+ new Credentials($key, $secret, getenv(self::ENV_SESSION) ?: NULL)
231
+ );
232
+ }
233
+
234
+ return self::reject('Could not find environment variable '
235
+ . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET);
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Credential provider that creates credentials using instance profile
241
+ * credentials.
242
+ *
243
+ * @param array $config Array of configuration data.
244
+ *
245
+ * @return InstanceProfileProvider
246
+ * @see Aws\Credentials\InstanceProfileProvider for $config details.
247
+ */
248
+ public static function instanceProfile(array $config = [])
249
+ {
250
+ return new InstanceProfileProvider($config);
251
+ }
252
+
253
+ /**
254
+ * Credential provider that creates credentials using
255
+ * ecs credentials by a GET request, whose uri is specified
256
+ * by environment variable
257
+ *
258
+ * @param array $config Array of configuration data.
259
+ *
260
+ * @return EcsCredentialProvider
261
+ * @see Aws\Credentials\EcsCredentialProvider for $config details.
262
+ */
263
+ public static function ecsCredentials(array $config = [])
264
+ {
265
+ return new EcsCredentialProvider($config);
266
+ }
267
+
268
+ /**
269
+ * Credential provider that creates credentials using assume role
270
+ *
271
+ * @param array $config Array of configuration data
272
+ * @return callable
273
+ * @see Aws\Credentials\AssumeRoleCredentialProvider for $config details.
274
+ */
275
+ public static function assumeRole(array $config=[])
276
+ {
277
+ return new AssumeRoleCredentialProvider($config);
278
+ }
279
+
280
+ /**
281
+ * Credentials provider that creates credentials using an ini file stored
282
+ * in the current user's home directory.
283
+ *
284
+ * @param string|null $profile Profile to use. If not specified will use
285
+ * the "default" profile in "~/.aws/credentials".
286
+ * @param string|null $filename If provided, uses a custom filename rather
287
+ * than looking in the home directory.
288
+ *
289
+ * @return callable
290
+ */
291
+ public static function ini($profile = null, $filename = null)
292
+ {
293
+ $filename = $filename ?: (self::getHomeDir() . '/.aws/credentials');
294
+ $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
295
+
296
+ return function () use ($profile, $filename) {
297
+ if (!is_readable($filename)) {
298
+ return self::reject("Cannot read credentials from $filename");
299
+ }
300
+ $data = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
301
+ if ($data === false) {
302
+ return self::reject("Invalid credentials file: $filename");
303
+ }
304
+ if (!isset($data[$profile])) {
305
+ return self::reject("'$profile' not found in credentials file");
306
+ }
307
+ if (!isset($data[$profile]['aws_access_key_id'])
308
+ || !isset($data[$profile]['aws_secret_access_key'])
309
+ ) {
310
+ return self::reject("No credentials present in INI profile "
311
+ . "'$profile' ($filename)");
312
+ }
313
+
314
+ if (empty($data[$profile]['aws_session_token'])) {
315
+ $data[$profile]['aws_session_token']
316
+ = isset($data[$profile]['aws_security_token'])
317
+ ? $data[$profile]['aws_security_token']
318
+ : null;
319
+ }
320
+
321
+ return Promise\promise_for(
322
+ new Credentials(
323
+ $data[$profile]['aws_access_key_id'],
324
+ $data[$profile]['aws_secret_access_key'],
325
+ $data[$profile]['aws_session_token']
326
+ )
327
+ );
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Credentials provider that creates credentials using a process configured in
333
+ * ini file stored in the current user's home directory.
334
+ *
335
+ * @param string|null $profile Profile to use. If not specified will use
336
+ * the "default" profile in "~/.aws/credentials".
337
+ * @param string|null $filename If provided, uses a custom filename rather
338
+ * than looking in the home directory.
339
+ *
340
+ * @return callable
341
+ */
342
+ public static function process($profile = null, $filename = null)
343
+ {
344
+ $filename = $filename ?: (self::getHomeDir() . '/.aws/credentials');
345
+ $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
346
+
347
+ return function () use ($profile, $filename) {
348
+ if (!is_readable($filename)) {
349
+ return self::reject("Cannot read process credentials from $filename");
350
+ }
351
+ $data = \Aws\parse_ini_file($filename, true, INI_SCANNER_RAW);
352
+ if ($data === false) {
353
+ return self::reject("Invalid credentials file: $filename");
354
+ }
355
+ if (!isset($data[$profile])) {
356
+ return self::reject("'$profile' not found in credentials file");
357
+ }
358
+ if (!isset($data[$profile]['credential_process'])
359
+ ) {
360
+ return self::reject("No credential_process present in INI profile "
361
+ . "'$profile' ($filename)");
362
+ }
363
+
364
+ $credentialProcess = $data[$profile]['credential_process'];
365
+ $json = shell_exec($credentialProcess);
366
+
367
+ $processData = json_decode($json, true);
368
+
369
+ // Only support version 1
370
+ if (isset($processData['Version'])) {
371
+ if ($processData['Version'] !== 1) {
372
+ return self::reject("credential_process does not return Version == 1");
373
+ }
374
+ }
375
+
376
+ if (!isset($processData['AccessKeyId']) || !isset($processData['SecretAccessKey'])) {
377
+ return self::reject("credential_process does not return valid credentials");
378
+ }
379
+
380
+ if (isset($processData['Expiration'])) {
381
+ try {
382
+ $expiration = new DateTimeResult($processData['Expiration']);
383
+ } catch (\Exception $e) {
384
+ return self::reject("credential_process returned invalid expiration");
385
+ }
386
+ $now = new DateTimeResult();
387
+ if ($expiration < $now) {
388
+ return self::reject("credential_process returned expired credentials");
389
+ }
390
+ } else {
391
+ $processData['Expiration'] = null;
392
+ }
393
+
394
+ if (empty($processData['SessionToken'])) {
395
+ $processData['SessionToken'] = null;
396
+ }
397
+
398
+ return Promise\promise_for(
399
+ new Credentials(
400
+ $processData['AccessKeyId'],
401
+ $processData['SecretAccessKey'],
402
+ $processData['SessionToken'],
403
+ $processData['Expiration']
404
+ )
405
+ );
406
+ };
407
+ }
408
+
409
+
410
+ /**
411
+ * Local credential providers returns a list of local credential providers
412
+ * in following order:
413
+ * - credentials from environment variables
414
+ * - 'default' profile in '.aws/credentials' file
415
+ * - 'profile default' profile in '.aws/config' file
416
+ *
417
+ * @return array
418
+ */
419
+ private static function localCredentialProviders()
420
+ {
421
+ return [
422
+ self::env(),
423
+ self::ini(),
424
+ self::ini('profile default', self::getHomeDir() . '/.aws/config')
425
+ ];
426
+ }
427
+
428
+ /**
429
+ * Remote credential providers returns a list of credentials providers
430
+ * for the remote endpoints such as EC2 or ECS Roles.
431
+ *
432
+ * @param array $config Array of configuration data.
433
+ *
434
+ * @return array
435
+ * @see Aws\Credentials\InstanceProfileProvider for $config details.
436
+ * @see Aws\Credentials\EcsCredentialProvider for $config details.
437
+ */
438
+ private static function remoteCredentialProviders(array $config = [])
439
+ {
440
+ if (!empty(getenv(EcsCredentialProvider::ENV_URI))) {
441
+ $providers['ecs'] = self::ecsCredentials($config);
442
+ }
443
+ $providers['process_credentials'] = self::process();
444
+ $providers['process_config'] = self::process(
445
+ 'profile default',
446
+ self::getHomeDir() . '/.aws/config'
447
+ );
448
+ $providers['instance'] = self::instanceProfile($config);
449
+
450
+ if (isset($config['credentials'])
451
+ && $config['credentials'] instanceof CacheInterface
452
+ ) {
453
+ foreach ($providers as $key => $provider) {
454
+ $providers[$key] = self::cache(
455
+ $provider,
456
+ $config['credentials'],
457
+ 'aws_cached_' . $key . '_credentials'
458
+ );
459
+ }
460
+ }
461
+
462
+ return $providers;
463
+ }
464
+
465
+ /**
466
+ * Gets the environment's HOME directory if available.
467
+ *
468
+ * @return null|string
469
+ */
470
+ private static function getHomeDir()
471
+ {
472
+ // On Linux/Unix-like systems, use the HOME environment variable
473
+ if ($homeDir = getenv('HOME')) {
474
+ return $homeDir;
475
+ }
476
+
477
+ // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
478
+ $homeDrive = getenv('HOMEDRIVE');
479
+ $homePath = getenv('HOMEPATH');
480
+
481
+ return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
482
+ }
483
+
484
+ private static function reject($msg)
485
+ {
486
+ return new Promise\RejectedPromise(new CredentialsException($msg));
487
+ }
488
+ }
lib/Aws/Aws/Credentials/Credentials.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Credentials;
3
+
4
+ /**
5
+ * Basic implementation of the AWS Credentials interface that allows callers to
6
+ * pass in the AWS Access Key and AWS Secret Access Key in the constructor.
7
+ */
8
+ class Credentials implements CredentialsInterface, \Serializable
9
+ {
10
+ private $key;
11
+ private $secret;
12
+ private $token;
13
+ private $expires;
14
+
15
+ /**
16
+ * Constructs a new BasicAWSCredentials object, with the specified AWS
17
+ * access key and AWS secret key
18
+ *
19
+ * @param string $key AWS access key ID
20
+ * @param string $secret AWS secret access key
21
+ * @param string $token Security token to use
22
+ * @param int $expires UNIX timestamp for when credentials expire
23
+ */
24
+ public function __construct($key, $secret, $token = null, $expires = null)
25
+ {
26
+ $this->key = trim($key);
27
+ $this->secret = trim($secret);
28
+ $this->token = $token;
29
+ $this->expires = $expires;
30
+ }
31
+
32
+ public static function __set_state(array $state)
33
+ {
34
+ return new self(
35
+ $state['key'],
36
+ $state['secret'],
37
+ $state['token'],
38
+ $state['expires']
39
+ );
40
+ }
41
+
42
+ public function getAccessKeyId()
43
+ {
44
+ return $this->key;
45
+ }
46
+
47
+ public function getSecretKey()
48
+ {
49
+ return $this->secret;
50
+ }
51
+
52
+ public function getSecurityToken()
53
+ {
54
+ return $this->token;
55
+ }
56
+
57
+ public function getExpiration()
58
+ {
59
+ return $this->expires;
60
+ }
61
+
62
+ public function isExpired()
63
+ {
64
+ return $this->expires !== null && time() >= $this->expires;
65
+ }
66
+
67
+ public function toArray()
68
+ {
69
+ return [
70
+ 'key' => $this->key,
71
+ 'secret' => $this->secret,
72
+ 'token' => $this->token,
73
+ 'expires' => $this->expires
74
+ ];
75
+ }
76
+
77
+ public function serialize()
78
+ {
79
+ return json_encode($this->toArray());
80
+ }
81
+
82
+ public function unserialize($serialized)
83
+ {
84
+ $data = json_decode($serialized, true);
85
+
86
+ $this->key = $data['key'];
87
+ $this->secret = $data['secret'];
88
+ $this->token = $data['token'];
89
+ $this->expires = $data['expires'];
90
+ }
91
+ }
lib/Aws/Aws/Credentials/CredentialsInterface.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Credentials;
3
+
4
+ /**
5
+ * Provides access to the AWS credentials used for accessing AWS services: AWS
6
+ * access key ID, secret access key, and security token. These credentials are
7
+ * used to securely sign requests to AWS services.
8
+ */
9
+ interface CredentialsInterface
10
+ {
11
+ /**
12
+ * Returns the AWS access key ID for this credentials object.
13
+ *
14
+ * @return string
15
+ */
16
+ public function getAccessKeyId();
17
+
18
+ /**
19
+ * Returns the AWS secret access key for this credentials object.
20
+ *
21
+ * @return string
22
+ */
23
+ public function getSecretKey();
24
+
25
+ /**
26
+ * Get the associated security token if available
27
+ *
28
+ * @return string|null
29
+ */
30
+ public function getSecurityToken();
31
+
32
+ /**
33
+ * Get the UNIX timestamp in which the credentials will expire
34
+ *
35
+ * @return int|null
36
+ */
37
+ public function getExpiration();
38
+
39
+ /**
40
+ * Check if the credentials are expired
41
+ *
42
+ * @return bool
43
+ */
44
+ public function isExpired();
45
+
46
+ /**
47
+ * Converts the credentials to an associative array.
48
+ *
49
+ * @return array
50
+ */
51
+ public function toArray();
52
+ }
lib/Aws/Aws/Credentials/EcsCredentialProvider.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Credentials;
3
+
4
+ use Aws\Exception\CredentialsException;
5
+ use GuzzleHttp\Psr7\Request;
6
+ use GuzzleHttp\Promise\PromiseInterface;
7
+ use Psr\Http\Message\ResponseInterface;
8
+
9
+ /**
10
+ * Credential provider that fetches credentials with GET request.
11
+ * ECS environment variable is used in constructing request URI.
12
+ */
13
+ class EcsCredentialProvider
14
+ {
15
+ const SERVER_URI = 'http://169.254.170.2';
16
+ const ENV_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";
17
+
18
+ /** @var callable */
19
+ private $client;
20
+
21
+ /**
22
+ * The constructor accepts following options:
23
+ * - timeout: (optional) Connection timeout, in seconds, default 1.0
24
+ * - client: An EcsClient to make request from
25
+ *
26
+ * @param array $config Configuration options
27
+ */
28
+ public function __construct(array $config = [])
29
+ {
30
+ $this->timeout = isset($config['timeout']) ? $config['timeout'] : 1.0;
31
+ $this->client = isset($config['client'])
32
+ ? $config['client']
33
+ : \Aws\default_http_handler();
34
+ }
35
+
36
+ /**
37
+ * Load ECS credentials
38
+ *
39
+ * @return PromiseInterface
40
+ */
41
+ public function __invoke()
42
+ {
43
+ $client = $this->client;
44
+ $request = new Request('GET', self::getEcsUri());
45
+ return $client(
46
+ $request,
47
+ [
48
+ 'timeout' => $this->timeout,
49
+ 'proxy' => '',
50
+ ]
51
+ )->then(function (ResponseInterface $response) {
52
+ $result = $this->decodeResult((string) $response->getBody());
53
+ return new Credentials(
54
+ $result['AccessKeyId'],
55
+ $result['SecretAccessKey'],
56
+ $result['Token'],
57
+ strtotime($result['Expiration'])
58
+ );
59
+ })->otherwise(function ($reason) {
60
+ $reason = is_array($reason) ? $reason['exception'] : $reason;
61
+ $msg = $reason->getMessage();
62
+ throw new CredentialsException(
63
+ "Error retrieving credential from ECS ($msg)"
64
+ );
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Fetch credential URI from ECS environment variable
70
+ *
71
+ * @return string Returns ECS URI
72
+ */
73
+ private function getEcsUri()
74
+ {
75
+ $creds_uri = getenv(self::ENV_URI);
76
+ return self::SERVER_URI . $creds_uri;
77
+ }
78
+
79
+ private function decodeResult($response)
80
+ {
81
+ $result = json_decode($response, true);
82
+
83
+ if (!isset($result['AccessKeyId'])) {
84
+ throw new CredentialsException('Unexpected ECS credential value');
85
+ }
86
+ return $result;
87
+ }
88
+ }
lib/Aws/Aws/Credentials/InstanceProfileProvider.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Credentials;
3
+
4
+ use Aws\Exception\CredentialsException;
5
+ use Aws\Sdk;
6
+ use GuzzleHttp\Promise;
7
+ use GuzzleHttp\Psr7\Request;
8
+ use GuzzleHttp\Promise\PromiseInterface;
9
+ use Psr\Http\Message\ResponseInterface;
10
+
11
+ /**
12
+ * Credential provider that provides credentials from the EC2 metadata server.
13
+ */
14
+ class InstanceProfileProvider
15
+ {
16
+ const SERVER_URI = 'http://169.254.169.254/latest/';
17
+ const CRED_PATH = 'meta-data/iam/security-credentials/';
18
+
19
+ const ENV_DISABLE = 'AWS_EC2_METADATA_DISABLED';
20
+
21
+ /** @var string */
22
+ private $profile;
23
+
24
+ /** @var callable */
25
+ private $client;
26
+
27
+ /**
28
+ * The constructor accepts the following options:
29
+ *
30
+ * - timeout: Connection timeout, in seconds.
31
+ * - profile: Optional EC2 profile name, if known.
32
+ *
33
+ * @param array $config Configuration options.
34
+ */
35
+ public function __construct(array $config = [])
36
+ {
37
+ $this->timeout = isset($config['timeout']) ? $config['timeout'] : 1.0;
38
+ $this->profile = isset($config['profile']) ? $config['profile'] : null;
39
+ $this->client = isset($config['client'])
40
+ ? $config['client'] // internal use only
41
+ : \Aws\default_http_handler();
42
+ }
43
+
44
+ /**
45
+ * Loads instance profile credentials.
46
+ *
47
+ * @return PromiseInterface
48
+ */
49
+ public function __invoke()
50
+ {
51
+ return Promise\coroutine(function () {
52
+ if (!$this->profile) {
53
+ $this->profile = (yield $this->request(self::CRED_PATH));
54
+ }
55
+ $json = (yield $this->request(self::CRED_PATH . $this->profile));
56
+ $result = $this->decodeResult($json);
57
+ yield new Credentials(
58
+ $result['AccessKeyId'],
59
+ $result['SecretAccessKey'],
60
+ $result['Token'],
61
+ strtotime($result['Expiration'])
62
+ );
63
+ });
64
+ }
65
+
66
+ /**
67
+ * @param string $url
68
+ * @return PromiseInterface Returns a promise that is fulfilled with the
69
+ * body of the response as a string.
70
+ */
71
+ private function request($url)
72
+ {
73
+ $disabled = getenv(self::ENV_DISABLE) ?: false;
74
+ if (strcasecmp($disabled, 'true') === 0) {
75
+ throw new CredentialsException(
76
+ $this->createErrorMessage('EC2 metadata server access disabled')
77
+ );
78
+ }
79
+
80
+ $fn = $this->client;
81
+ $request = new Request('GET', self::SERVER_URI . $url);
82
+ $userAgent = 'aws-sdk-php/' . Sdk::VERSION;
83
+ if (defined('HHVM_VERSION')) {
84
+ $userAgent .= ' HHVM/' . HHVM_VERSION;
85
+ }
86
+ $userAgent .= ' ' . \Aws\default_user_agent();
87
+ $request = $request->withHeader('User-Agent', $userAgent);
88
+
89
+ return $fn($request, ['timeout' => $this->timeout])
90
+ ->then(function (ResponseInterface $response) {
91
+ return (string) $response->getBody();
92
+ })->otherwise(function (array $reason) {
93
+ $reason = $reason['exception'];
94
+ $msg = $reason->getMessage();
95
+ throw new CredentialsException(
96
+ $this->createErrorMessage($msg)
97
+ );
98
+ });
99
+ }
100
+
101
+ private function createErrorMessage($previous)
102
+ {
103
+ return "Error retrieving credentials from the instance profile "
104
+ . "metadata server. ({$previous})";
105
+ }
106
+
107
+ private function decodeResult($response)
108
+ {
109
+ $result = json_decode($response, true);
110
+
111
+ if ($result['Code'] !== 'Success') {
112
+ throw new CredentialsException('Unexpected instance profile '
113
+ . 'response code: ' . $result['Code']);
114
+ }
115
+
116
+ return $result;
117
+ }
118
+ }
lib/Aws/Aws/DoctrineCacheAdapter.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Doctrine\Common\Cache\Cache;
5
+
6
+ class DoctrineCacheAdapter implements CacheInterface, Cache
7
+ {
8
+ /** @var Cache */
9
+ private $cache;
10
+
11
+ public function __construct(Cache $cache)
12
+ {
13
+ $this->cache = $cache;
14
+ }
15
+
16
+ public function get($key)
17
+ {
18
+ return $this->cache->fetch($key);
19
+ }
20
+
21
+ public function fetch($key)
22
+ {
23
+ return $this->get($key);
24
+ }
25
+
26
+ public function set($key, $value, $ttl = 0)
27
+ {
28
+ return $this->cache->save($key, $value, $ttl);
29
+ }
30
+
31
+ public function save($key, $value, $ttl = 0)
32
+ {
33
+ return $this->set($key, $value, $ttl);
34
+ }
35
+
36
+ public function remove($key)
37
+ {
38
+ return $this->cache->delete($key);
39
+ }
40
+
41
+ public function delete($key)
42
+ {
43
+ return $this->remove($key);
44
+ }
45
+
46
+ public function contains($key)
47
+ {
48
+ return $this->cache->contains($key);
49
+ }
50
+
51
+ public function getStats()
52
+ {
53
+ return $this->cache->getStats();
54
+ }
55
+ }
lib/Aws/Aws/Endpoint/EndpointProvider.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Endpoint;
3
+
4
+ use Aws\Exception\UnresolvedEndpointException;
5
+
6
+ /**
7
+ * Endpoint providers.
8
+ *
9
+ * An endpoint provider is a function that accepts a hash of endpoint options,
10
+ * including but not limited to "service" and "region" key value pairs. The
11
+ * endpoint provider function returns a hash of endpoint data, which MUST
12
+ * include an "endpoint" key value pair that represents the resolved endpoint
13
+ * or NULL if an endpoint cannot be determined.
14
+ *
15
+ * You can wrap your calls to an endpoint provider with the
16
+ * {@see EndpointProvider::resolve} function to ensure that an endpoint hash is
17
+ * created. If an endpoint hash is not created, then the resolve() function
18
+ * will throw an {@see Aws\Exception\UnresolvedEndpointException}.
19
+ *
20
+ * use Aws\Endpoint\EndpointProvider;
21
+ * $provider = EndpointProvider::defaultProvider();
22
+ * // Returns an array or NULL.
23
+ * $endpoint = $provider(['service' => 'ec2', 'region' => 'us-west-2']);
24
+ * // Returns an endpoint array or throws.
25
+ * $endpoint = EndpointProvider::resolve($provider, [
26
+ * 'service' => 'ec2',
27
+ * 'region' => 'us-west-2'
28
+ * ]);
29
+ *
30
+ * You can compose multiple providers into a single provider using
31
+ * {@see Aws\or_chain}. This function accepts providers as arguments and
32
+ * returns a new function that will invoke each provider until a non-null value
33
+ * is returned.
34
+ *
35
+ * $a = function (array $args) {
36
+ * if ($args['region'] === 'my-test-region') {
37
+ * return ['endpoint' => 'http://localhost:123/api'];
38
+ * }
39
+ * };
40
+ * $b = EndpointProvider::defaultProvider();
41
+ * $c = \Aws\or_chain($a, $b);
42
+ * $config = ['service' => 'ec2', 'region' => 'my-test-region'];
43
+ * $res = $c($config); // $a handles this.
44
+ * $config['region'] = 'us-west-2';
45
+ * $res = $c($config); // $b handles this.
46
+ */
47
+ class EndpointProvider
48
+ {
49
+ /**
50
+ * Resolves and endpoint provider and ensures a non-null return value.
51
+ *
52
+ * @param callable $provider Provider function to invoke.
53
+ * @param array $args Endpoint arguments to pass to the provider.
54
+ *
55
+ * @return array
56
+ * @throws UnresolvedEndpointException
57
+ */
58
+ public static function resolve(callable $provider, array $args = [])
59
+ {
60
+ $result = $provider($args);
61
+ if (is_array($result)) {
62
+ return $result;
63
+ }
64
+
65
+ throw new UnresolvedEndpointException(
66
+ 'Unable to resolve an endpoint using the provider arguments: '
67
+ . json_encode($args) . '. Note: you can provide an "endpoint" '
68
+ . 'option to a client constructor to bypass invoking an endpoint '
69
+ . 'provider.');
70
+ }
71
+
72
+ /**
73
+ * Creates and returns the default SDK endpoint provider.
74
+ *
75
+ * @deprecated Use an instance of \Aws\Endpoint\Partition instead.
76
+ *
77
+ * @return callable
78
+ */
79
+ public static function defaultProvider()
80
+ {
81
+ return PartitionEndpointProvider::defaultProvider();
82
+ }
83
+
84
+ /**
85
+ * Creates and returns an endpoint provider that uses patterns from an
86
+ * array.
87
+ *
88
+ * @param array $patterns Endpoint patterns
89
+ *
90
+ * @return callable
91
+ */
92
+ public static function patterns(array $patterns)
93
+ {
94
+ return new PatternEndpointProvider($patterns);
95
+ }
96
+ }
lib/Aws/Aws/Endpoint/Partition.php ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Endpoint;
3
+
4
+ use ArrayAccess;
5
+ use Aws\HasDataTrait;
6
+ use InvalidArgumentException as Iae;
7
+
8
+ /**
9
+ * Default implementation of an AWS partition.
10
+ */
11
+ final class Partition implements ArrayAccess, PartitionInterface
12
+ {
13
+ use HasDataTrait;
14
+
15
+ /**
16
+ * The partition constructor accepts the following options:
17
+ *
18
+ * - `partition`: (string, required) The partition name as specified in an
19
+ * ARN (e.g., `aws`)
20
+ * - `partitionName`: (string) The human readable name of the partition
21
+ * (e.g., "AWS Standard")
22
+ * - `dnsSuffix`: (string, required) The DNS suffix of the partition. This
23
+ * value is used to determine how endpoints in the partition are resolved.
24
+ * - `regionRegex`: (string) A PCRE regular expression that specifies the
25
+ * pattern that region names in the endpoint adhere to.
26
+ * - `regions`: (array, required) A map of the regions in the partition.
27
+ * Each key is the region as present in a hostname (e.g., `us-east-1`),
28
+ * and each value is a structure containing region information.
29
+ * - `defaults`: (array) A map of default key value pairs to apply to each
30
+ * endpoint of the partition. Any value in an `endpoint` definition will
31
+ * supersede any values specified in `defaults`.
32
+ * - `services`: (array, required) A map of service endpoint prefix name
33
+ * (the value found in a hostname) to information about the service.
34
+ *
35
+ * @param array $definition
36
+ *
37
+ * @throws Iae if any required options are missing
38
+ */
39
+ public function __construct(array $definition)
40
+ {
41
+ foreach (['partition', 'regions', 'services', 'dnsSuffix'] as $key) {
42
+ if (!isset($definition[$key])) {
43
+ throw new Iae("Partition missing required $key field");
44
+ }
45
+ }
46
+
47
+ $this->data = $definition;
48
+ }
49
+
50
+ public function getName()
51
+ {
52
+ return $this->data['partition'];
53
+ }
54
+
55
+ public function isRegionMatch($region, $service)
56
+ {
57
+ if (isset($this->data['regions'][$region])
58
+ || isset($this->data['services'][$service]['endpoints'][$region])
59
+ ) {
60
+ return true;
61
+ }
62
+
63
+ if (isset($this->data['regionRegex'])) {
64
+ return (bool) preg_match(
65
+ "@{$this->data['regionRegex']}@",
66
+ $region
67
+ );
68
+ }
69
+
70
+ return false;
71
+ }
72
+
73
+ public function getAvailableEndpoints(
74
+ $service,
75
+ $allowNonRegionalEndpoints = false
76
+ ) {
77
+ if ($this->isServicePartitionGlobal($service)) {
78
+ return [$this->getPartitionEndpoint($service)];
79
+ }
80
+
81
+ if (isset($this->data['services'][$service]['endpoints'])) {
82
+ $serviceRegions = array_keys(
83
+ $this->data['services'][$service]['endpoints']
84
+ );
85
+
86
+ return $allowNonRegionalEndpoints
87
+ ? $serviceRegions
88
+ : array_intersect($serviceRegions, array_keys(
89
+ $this->data['regions']
90
+ ));
91
+ }
92
+
93
+ return [];
94
+ }
95
+
96
+ public function __invoke(array $args = [])
97
+ {
98
+ $service = isset($args['service']) ? $args['service'] : '';
99
+ $region = isset($args['region']) ? $args['region'] : '';
100
+ $scheme = isset($args['scheme']) ? $args['scheme'] : 'https';
101
+ $data = $this->getEndpointData($service, $region);
102
+
103
+ return [
104
+ 'endpoint' => "{$scheme}://" . $this->formatEndpoint(
105
+ isset($data['hostname']) ? $data['hostname'] : '',
106
+ $service,
107
+ $region
108
+ ),
109
+ 'signatureVersion' => $this->getSignatureVersion($data),
110
+ 'signingRegion' => isset($data['credentialScope']['region'])
111
+ ? $data['credentialScope']['region']
112
+ : $region,
113
+ 'signingName' => isset($data['credentialScope']['service'])
114
+ ? $data['credentialScope']['service']
115
+ : $service,
116
+ ];
117
+ }
118
+
119
+ private function getEndpointData($service, $region)
120
+ {
121
+
122
+ $resolved = $this->resolveRegion($service, $region);
123
+ $data = isset($this->data['services'][$service]['endpoints'][$resolved])
124
+ ? $this->data['services'][$service]['endpoints'][$resolved]
125
+ : [];
126
+ $data += isset($this->data['services'][$service]['defaults'])
127
+ ? $this->data['services'][$service]['defaults']
128
+ : [];
129
+ $data += isset($this->data['defaults'])
130
+ ? $this->data['defaults']
131
+ : [];
132
+
133
+ return $data;
134
+ }
135
+
136
+ private function getSignatureVersion(array $data)
137
+ {
138
+ static $supportedBySdk = [
139
+ 's3v4',
140
+ 'v4',
141
+ 'anonymous',
142
+ ];
143
+
144
+ $possibilities = array_intersect(
145
+ $supportedBySdk,
146
+ isset($data['signatureVersions'])
147
+ ? $data['signatureVersions']
148
+ : ['v4']
149
+ );
150
+
151
+ return array_shift($possibilities);
152
+ }
153
+
154
+ private function resolveRegion($service, $region)
155
+ {
156
+ if ($this->isServicePartitionGlobal($service)) {
157
+ return $this->getPartitionEndpoint($service);
158
+ }
159
+
160
+ return $region;
161
+ }
162
+
163
+ private function isServicePartitionGlobal($service)
164
+ {
165
+ return isset($this->data['services'][$service]['isRegionalized'])
166
+ && false === $this->data['services'][$service]['isRegionalized']
167
+ && isset($this->data['services'][$service]['partitionEndpoint']);
168
+ }
169
+
170
+ private function getPartitionEndpoint($service)
171
+ {
172
+ return $this->data['services'][$service]['partitionEndpoint'];
173
+ }
174
+
175
+ private function formatEndpoint($template, $service, $region)
176
+ {
177
+ return strtr($template, [
178
+ '{service}' => $service,
179
+ '{region}' => $region,
180
+ '{dnsSuffix}' => $this->data['dnsSuffix'],
181
+ ]);
182
+ }
183
+ }
lib/Aws/Aws/Endpoint/PartitionEndpointProvider.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Endpoint;
3
+
4
+ use JmesPath\Env;
5
+
6
+ class PartitionEndpointProvider
7
+ {
8
+ /** @var Partition[] */
9
+ private $partitions;
10
+ /** @var string */
11
+ private $defaultPartition;
12
+
13
+ public function __construct(array $partitions, $defaultPartition = 'aws')
14
+ {
15
+ $this->partitions = array_map(function (array $definition) {
16
+ return new Partition($definition);
17
+ }, array_values($partitions));
18
+ $this->defaultPartition = $defaultPartition;
19
+ }
20
+
21
+ public function __invoke(array $args = [])
22
+ {
23
+ $partition = $this->getPartition(
24
+ isset($args['region']) ? $args['region'] : '',
25
+ isset($args['service']) ? $args['service'] : ''
26
+ );
27
+
28
+ return $partition($args);
29
+ }
30
+
31
+ /**
32
+ * Returns the partition containing the provided region or the default
33
+ * partition if no match is found.
34
+ *
35
+ * @param string $region
36
+ * @param string $service
37
+ *
38
+ * @return Partition
39
+ */
40
+ public function getPartition($region, $service)
41
+ {
42
+ foreach ($this->partitions as $partition) {
43
+ if ($partition->isRegionMatch($region, $service)) {
44
+ return $partition;
45
+ }
46
+ }
47
+
48
+ return $this->getPartitionByName($this->defaultPartition);
49
+ }
50
+
51
+ /**
52
+ * Returns the partition with the provided name or null if no partition with
53
+ * the provided name can be found.
54
+ *
55
+ * @param string $name
56
+ *
57
+ * @return Partition|null
58
+ */
59
+ public function getPartitionByName($name)
60
+ {
61
+ foreach ($this->partitions as $partition) {
62
+ if ($name === $partition->getName()) {
63
+ return $partition;
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Creates and returns the default SDK partition provider.
70
+ *
71
+ * @return PartitionEndpointProvider
72
+ */
73
+ public static function defaultProvider()
74
+ {
75
+ $data = \Aws\load_compiled_json(__DIR__ . '/../data/endpoints.json');
76
+ $prefixData = \Aws\load_compiled_json(__DIR__ . '/../data/endpoints_prefix_history.json');
77
+ $mergedData = self::mergePrefixData($data, $prefixData);
78
+
79
+ return new self($mergedData['partitions']);
80
+ }
81
+
82
+ /**
83
+ * Copy endpoint data for other prefixes used by a given service
84
+ *
85
+ * @param $data
86
+ * @param $prefixData
87
+ * @return array
88
+ */
89
+ public static function mergePrefixData($data, $prefixData)
90
+ {
91
+ $prefixGroups = $prefixData['prefix-groups'];
92
+
93
+ foreach ($data["partitions"] as $index => $partition) {
94
+ foreach ($prefixGroups as $current => $old) {
95
+ $serviceData = Env::search("services.{$current}", $partition);
96
+ if (!empty($serviceData)) {
97
+ foreach ($old as $prefix) {
98
+ if (empty(Env::search("services.{$prefix}", $partition))) {
99
+ $data["partitions"][$index]["services"][$prefix] = $serviceData;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ return $data;
107
+ }
108
+ }
lib/Aws/Aws/Endpoint/PartitionInterface.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Endpoint;
3
+
4
+ /**
5
+ * Represents a section of the AWS cloud.
6
+ */
7
+ interface PartitionInterface
8
+ {
9
+ /**
10
+ * Returns the partition's short name, e.g., 'aws,' 'aws-cn,' or
11
+ * 'aws-us-gov.'
12
+ *
13
+ * @return string
14
+ */
15
+ public function getName();
16
+
17
+ /**
18
+ * Determine if this partition contains the provided region. Include the
19
+ * name of the service to inspect non-regional endpoints
20
+ *
21
+ * @param string $region
22
+ * @param string $service
23
+ *
24
+ * @return bool
25
+ */
26
+ public function isRegionMatch($region, $service);
27
+
28
+ /**
29
+ * Return the endpoints supported by a given service.
30
+ *
31
+ * @param string $service Identifier of the service
32
+ * whose endpoints should be
33
+ * listed (e.g., 's3' or 'ses')
34
+ * @param bool $allowNonRegionalEndpoints Set to `true` to include
35
+ * endpoints that are not AWS
36
+ * regions (e.g., 'local' for
37
+ * DynamoDB or
38
+ * 'fips-us-gov-west-1' for S3)
39
+ *
40
+ * @return string[]
41
+ */
42
+ public function getAvailableEndpoints(
43
+ $service,
44
+ $allowNonRegionalEndpoints = false
45
+ );
46
+
47
+ /**
48
+ * A partition must be invokable as an endpoint provider.
49
+ *
50
+ * @see EndpointProvider
51
+ *
52
+ * @param array $args
53
+ * @return array
54
+ */
55
+ public function __invoke(array $args = []);
56
+ }
lib/Aws/Aws/Endpoint/PatternEndpointProvider.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Endpoint;
3
+
4
+ /**
5
+ * Provides endpoints based on an endpoint pattern configuration array.
6
+ */
7
+ class PatternEndpointProvider
8
+ {
9
+ /** @var array */
10
+ private $patterns;
11
+
12
+ /**
13
+ * @param array $patterns Hash of endpoint patterns mapping to endpoint
14
+ * configurations.
15
+ */
16
+ public function __construct(array $patterns)
17
+ {
18
+ $this->patterns = $patterns;
19
+ }
20
+
21
+ public function __invoke(array $args = [])
22
+ {
23
+ $service = isset($args['service']) ? $args['service'] : '';
24
+ $region = isset($args['region']) ? $args['region'] : '';
25
+ $keys = ["{$region}/{$service}", "{$region}/*", "*/{$service}", "*/*"];
26
+
27
+ foreach ($keys as $key) {
28
+ if (isset($this->patterns[$key])) {
29
+ return $this->expand(
30
+ $this->patterns[$key],
31
+ isset($args['scheme']) ? $args['scheme'] : 'https',
32
+ $service,
33
+ $region
34
+ );
35
+ }
36
+ }
37
+
38
+ return null;
39
+ }
40
+
41
+ private function expand(array $config, $scheme, $service, $region)
42
+ {
43
+ $config['endpoint'] = $scheme . '://'
44
+ . strtr($config['endpoint'], [
45
+ '{service}' => $service,
46
+ '{region}' => $region
47
+ ]);
48
+
49
+ return $config;
50
+ }
51
+ }
lib/Aws/Aws/EndpointDiscovery/Configuration.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\EndpointDiscovery;
3
+
4
+ class Configuration implements ConfigurationInterface
5
+ {
6
+ private $cacheLimit;
7
+ private $enabled;
8
+
9
+ public function __construct($enabled, $cacheLimit = 1000)
10
+ {
11
+ $this->cacheLimit = filter_var($cacheLimit, FILTER_VALIDATE_INT);
12
+ if ($this->cacheLimit == false || $this->cacheLimit < 1) {
13
+ throw new \InvalidArgumentException(
14
+ "'cache_limit' value must be a positive integer."
15
+ );
16
+ }
17
+
18
+ // Unparsable $enabled flag errs on the side of disabling endpoint discovery
19
+ $this->enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN);
20
+ }
21
+
22
+ /**
23
+ * {@inheritdoc}
24
+ */
25
+ public function isEnabled()
26
+ {
27
+ return $this->enabled;
28
+ }
29
+
30
+ /**
31
+ * {@inheritdoc}
32
+ */
33
+ public function getCacheLimit()
34
+ {
35
+ return $this->cacheLimit;
36
+ }
37
+
38
+ /**
39
+ * {@inheritdoc}
40
+ */
41
+ public function toArray()
42
+ {
43
+ return [
44
+ 'enabled' => $this->isEnabled(),
45
+ 'cache_limit' => $this->getCacheLimit()
46
+ ];
47
+ }
48
+ }
lib/Aws/Aws/EndpointDiscovery/ConfigurationInterface.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\EndpointDiscovery;
3
+
4
+ /**
5
+ * Provides access to endpoint discovery configuration options:
6
+ * 'enabled', 'cache_limit'
7
+ */
8
+ interface ConfigurationInterface
9
+ {
10
+ /**
11
+ * Checks whether or not endpoint discovery is enabled.
12
+ *
13
+ * @return bool
14
+ */
15
+ public function isEnabled();
16
+
17
+ /**
18
+ * Returns the cache limit, if available.
19
+ *
20
+ * @return string|null
21
+ */
22
+ public function getCacheLimit();
23
+
24
+ /**
25
+ * Returns the configuration as an associative array
26
+ *
27
+ * @return array
28
+ */
29
+ public function toArray();
30
+ }
lib/Aws/Aws/EndpointDiscovery/ConfigurationProvider.php ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\EndpointDiscovery;
3
+
4
+ use Aws\CacheInterface;
5
+ use Aws\EndpointDiscovery\Exception\ConfigurationException;
6
+ use GuzzleHttp\Promise;
7
+ use GuzzleHttp\Promise\PromiseInterface;
8
+
9
+ /**
10
+ * A configuration provider is a function that returns a promise that is
11
+ * fulfilled with a {@see \Aws\EndpointDiscovery\ConfigurationInterface}
12
+ * or rejected with an {@see \Aws\EndpointDiscovery\Exception\ConfigurationException}.
13
+ *
14
+ * <code>
15
+ * use Aws\EndpointDiscovery\ConfigurationProvider;
16
+ * $provider = ConfigurationProvider::defaultProvider();
17
+ * // Returns a ConfigurationInterface or throws.
18
+ * $config = $provider()->wait();
19
+ * </code>
20
+ *
21
+ * Configuration providers can be composed to create configuration using
22
+ * conditional logic that can create different configurations in different
23
+ * environments. You can compose multiple providers into a single provider using
24
+ * {@see Aws\EndpointDiscovery\ConfigurationProvider::chain}. This function
25
+ * accepts providers as variadic arguments and returns a new function that will
26
+ * invoke each provider until a successful configuration is returned.
27
+ *
28
+ * <code>
29
+ * // First try an INI file at this location.
30
+ * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
31
+ * // Then try an INI file at this location.
32
+ * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
33
+ * // Then try loading from environment variables.
34
+ * $c = ConfigurationProvider::env();
35
+ * // Combine the three providers together.
36
+ * $composed = ConfigurationProvider::chain($a, $b, $c);
37
+ * // Returns a promise that is fulfilled with a configuration or throws.
38
+ * $promise = $composed();
39
+ * // Wait on the configuration to resolve.
40
+ * $config = $promise->wait();
41
+ * </code>
42
+ */
43
+ class ConfigurationProvider
44
+ {
45
+ const CACHE_KEY = 'aws_cached_endpoint_discovery_config';
46
+ const DEFAULT_ENABLED = false;
47
+ const DEFAULT_CACHE_LIMIT = 1000;
48
+ const ENV_ENABLED = 'AWS_ENDPOINT_DISCOVERY_ENABLED';
49
+ const ENV_ENABLED_ALT = 'AWS_ENABLE_ENDPOINT_DISCOVERY';
50
+ const ENV_PROFILE = 'AWS_PROFILE';
51
+
52
+ /**
53
+ * Wraps a config provider and saves provided configuration in an
54
+ * instance of Aws\CacheInterface. Forwards calls when no config found
55
+ * in cache and updates cache with the results.
56
+ *
57
+ * @param callable $provider Configuration provider function to wrap
58
+ * @param CacheInterface $cache Cache to store credentials
59
+ * @param string|null $cacheKey (optional) Cache key to use
60
+ *
61
+ * @return callable
62
+ */
63
+ public static function cache(
64
+ callable $provider,
65
+ CacheInterface $cache,
66
+ $cacheKey = null
67
+ ) {
68
+ $cacheKey = $cacheKey ?: self::CACHE_KEY;
69
+
70
+ return function () use ($provider, $cache, $cacheKey) {
71
+ $found = $cache->get($cacheKey);
72
+ if ($found instanceof ConfigurationInterface) {
73
+ return Promise\promise_for($found);
74
+ }
75
+
76
+ return $provider()
77
+ ->then(function (ConfigurationInterface $config) use (
78
+ $cache,
79
+ $cacheKey
80
+ ) {
81
+ $cache->set($cacheKey, $config);
82
+ return $config;
83
+ });
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Creates an aggregate credentials provider that invokes the provided
89
+ * variadic providers one after the other until a provider returns
90
+ * credentials.
91
+ *
92
+ * @return callable
93
+ */
94
+ public static function chain()
95
+ {
96
+ $links = func_get_args();
97
+ if (empty($links)) {
98
+ throw new \InvalidArgumentException('No providers in chain');
99
+ }
100
+
101
+ return function () use ($links) {
102
+ /** @var callable $parent */
103
+ $parent = array_shift($links);
104
+ $promise = $parent();
105
+ while ($next = array_shift($links)) {
106
+ $promise = $promise->otherwise($next);
107
+ }
108
+ return $promise;
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Create a default config provider that first checks for environment
114
+ * variables, then checks for a specified profile in ~/.aws/config, then
115
+ * checks for the "default" profile in ~/.aws/config, and failing those uses
116
+ * a default fallback set of configuration options.
117
+ *
118
+ * This provider is automatically wrapped in a memoize function that caches
119
+ * previously provided config options.
120
+ *
121
+ * @param array $config Optional array of ecs/instance profile credentials
122
+ * provider options.
123
+ *
124
+ * @return callable
125
+ */
126
+ public static function defaultProvider(array $config = [])
127
+ {
128
+ $configProviders = [
129
+ self::env(),
130
+ self::ini(),
131
+ self::fallback()
132
+ ];
133
+
134
+ $memo = self::memoize(
135
+ call_user_func_array('self::chain', $configProviders)
136
+ );
137
+
138
+ if (isset($config['endpoint_discovery'])
139
+ && $config['endpoint_discovery'] instanceof CacheInterface
140
+ ) {
141
+ return self::cache($memo, $config['endpoint_discovery'], self::CACHE_KEY);
142
+ }
143
+
144
+ return $memo;
145
+ }
146
+
147
+ /**
148
+ * Provider that creates config from environment variables.
149
+ *
150
+ * @param $cacheLimit
151
+ * @return callable
152
+ */
153
+ public static function env($cacheLimit = self::DEFAULT_CACHE_LIMIT)
154
+ {
155
+ return function () use ($cacheLimit) {
156
+ // Use config from environment variables, if available
157
+ $enabled = getenv(self::ENV_ENABLED);
158
+ if ($enabled === false || $enabled === '') {
159
+ $enabled = getenv(self::ENV_ENABLED_ALT);
160
+ }
161
+ if ($enabled !== false && $enabled !== '') {
162
+ return Promise\promise_for(
163
+ new Configuration($enabled, $cacheLimit)
164
+ );
165
+ }
166
+
167
+ return self::reject('Could not find environment variable config'
168
+ . ' in ' . self::ENV_ENABLED);
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Fallback config options when other sources are not set.
174
+ *
175
+ * @return callable
176
+ */
177
+ public static function fallback()
178
+ {
179
+ return function () {
180
+ return Promise\promise_for(
181
+ new Configuration(
182
+ self::DEFAULT_ENABLED,
183
+ self::DEFAULT_CACHE_LIMIT
184
+ )
185
+ );
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Gets the environment's HOME directory if available.
191
+ *
192
+ * @return null|string
193
+ */
194
+ private static function getHomeDir()
195
+ {
196
+ // On Linux/Unix-like systems, use the HOME environment variable
197
+ if ($homeDir = getenv('HOME')) {
198
+ return $homeDir;
199
+ }
200
+
201
+ // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
202
+ $homeDrive = getenv('HOMEDRIVE');
203
+ $homePath = getenv('HOMEPATH');
204
+
205
+ return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
206
+ }
207
+
208
+ /**
209
+ * Config provider that creates config using an ini file stored
210
+ * in the current user's home directory.
211
+ *
212
+ * @param string|null $profile Profile to use. If not specified will use
213
+ * the "default" profile in "~/.aws/config".
214
+ * @param string|null $filename If provided, uses a custom filename rather
215
+ * than looking in the home directory.
216
+ * @param int $cacheLimit
217
+ *
218
+ * @return callable
219
+ */
220
+ public static function ini(
221
+ $profile = null,
222
+ $filename = null,
223
+ $cacheLimit = self::DEFAULT_CACHE_LIMIT
224
+ ) {
225
+ $filename = $filename ?: (self::getHomeDir() . '/.aws/config');
226
+ $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');
227
+
228
+ return function () use ($profile, $filename, $cacheLimit) {
229
+ if (!is_readable($filename)) {
230
+ return self::reject("Cannot read configuration from $filename");
231
+ }
232
+ $data = \Aws\parse_ini_file($filename, true);
233
+ if ($data === false) {
234
+ return self::reject("Invalid config file: $filename");
235
+ }
236
+ if (!isset($data[$profile])) {
237
+ return self::reject("'$profile' not found in config file");
238
+ }
239
+ if (!isset($data[$profile]['endpoint_discovery_enabled'])) {
240
+ return self::reject("Required endpoint discovery config values
241
+ not present in INI profile '{$profile}' ({$filename})");
242
+ }
243
+
244
+ return Promise\promise_for(
245
+ new Configuration(
246
+ $data[$profile]['endpoint_discovery_enabled'],
247
+ $cacheLimit
248
+ )
249
+ );
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Wraps a config provider and caches previously provided configuration.
255
+ *
256
+ * Ensures that cached configuration is refreshed when it expires.
257
+ *
258
+ * @param callable $provider Config provider function to wrap.
259
+ *
260
+ * @return callable
261
+ */
262
+ public static function memoize(callable $provider)
263
+ {
264
+ return function () use ($provider) {
265
+ static $result;
266
+ static $isConstant;
267
+
268
+ // Constant config will be returned constantly.
269
+ if ($isConstant) {
270
+ return $result;
271
+ }
272
+
273
+ // Create the initial promise that will be used as the cached value
274
+ // until it expires.
275
+ if (null === $result) {
276
+ $result = $provider();
277
+ }
278
+
279
+ // Return config and set flag that provider is already set
280
+ return $result
281
+ ->then(function (ConfigurationInterface $config) use (&$isConstant) {
282
+ $isConstant = true;
283
+ return $config;
284
+ });
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Reject promise with standardized exception.
290
+ *
291
+ * @param $msg
292
+ * @return Promise\RejectedPromise
293
+ */
294
+ private static function reject($msg)
295
+ {
296
+ return new Promise\RejectedPromise(new ConfigurationException($msg));
297
+ }
298
+
299
+ /**
300
+ * Unwraps a configuration object in whatever valid form it is in,
301
+ * always returning a ConfigurationInterface object.
302
+ *
303
+ * @param mixed $config
304
+ * @return ConfigurationInterface
305
+ * @throws \InvalidArgumentException
306
+ */
307
+ public static function unwrap($config)
308
+ {
309
+ if (is_callable($config)) {
310
+ $config = $config();
311
+ }
312
+ if ($config instanceof PromiseInterface) {
313
+ $config = $config->wait();
314
+ }
315
+ if ($config instanceof ConfigurationInterface) {
316
+ return $config;
317
+ } elseif (is_array($config) && isset($config['enabled'])) {
318
+ if (isset($config['cache_limit'])) {
319
+ return new Configuration(
320
+ $config['enabled'],
321
+ $config['cache_limit']
322
+ );
323
+ }
324
+ return new Configuration(
325
+ $config['enabled'],
326
+ self::DEFAULT_CACHE_LIMIT
327
+ );
328
+ }
329
+
330
+ throw new \InvalidArgumentException('Not a valid endpoint_discovery '
331
+ . 'configuration argument.');
332
+ }
333
+ }
lib/Aws/Aws/EndpointDiscovery/EndpointDiscoveryMiddleware.php ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\EndpointDiscovery;
3
+
4
+ use Aws\AwsClient;
5
+ use Aws\CacheInterface;
6
+ use Aws\CommandInterface;
7
+ use Aws\Credentials\CredentialsInterface;
8
+ use Aws\Exception\AwsException;
9
+ use Aws\Exception\UnresolvedEndpointException;
10
+ use Aws\LruArrayCache;
11
+ use Aws\Middleware;
12
+ use Psr\Http\Message\RequestInterface;
13
+ use Psr\Http\Message\UriInterface;
14
+
15
+ class EndpointDiscoveryMiddleware
16
+ {
17
+ /**
18
+ * @var CacheInterface
19
+ */
20
+ private static $cache;
21
+ private static $discoveryCooldown = 60;
22
+
23
+ private $args;
24
+ private $client;
25
+ private $config;
26
+ private $discoveryTimes = [];
27
+ private $nextHandler;
28
+ private $service;
29
+
30
+ public static function wrap(
31
+ $client,
32
+ $args,
33
+ $config
34
+ ) {
35
+ return function (callable $handler) use (
36
+ $client,
37
+ $args,
38
+ $config
39
+ ) {
40
+ return new static(
41
+ $handler,
42
+ $client,
43
+ $args,
44
+ $config
45
+ );
46
+ };
47
+ }
48
+
49
+ public function __construct(
50
+ callable $handler,
51
+ AwsClient $client,
52
+ array $args,
53
+ $config
54
+ ) {
55
+ $this->nextHandler = $handler;
56
+ $this->client = $client;
57
+ $this->args = $args;
58
+ $this->service = $client->getApi();
59
+ $this->config = $config;
60
+ }
61
+
62
+ public function __invoke(CommandInterface $cmd, RequestInterface $request)
63
+ {
64
+ $nextHandler = $this->nextHandler;
65
+ $op = $this->service->getOperation($cmd->getName())->toArray();
66
+
67
+ // Continue only if endpointdiscovery trait is set
68
+ if (isset($op['endpointdiscovery'])) {
69
+ $config = ConfigurationProvider::unwrap($this->config);
70
+ $isRequired = !empty($op['endpointdiscovery']['required']);
71
+
72
+ // Continue only if required by operation or enabled by config
73
+ if ($isRequired || $config->isEnabled()) {
74
+ if (isset($op['endpointoperation'])) {
75
+ throw new UnresolvedEndpointException('This operation is '
76
+ . 'contradictorily marked both as using endpoint discovery '
77
+ . 'and being the endpoint discovery operation. Please '
78
+ . 'verify the accuracy of your model files.');
79
+ }
80
+
81
+ // Original endpoint may be used if discovery optional
82
+ $originalUri = $request->getUri();
83
+
84
+ $identifiers = $this->getIdentifiers($op);
85
+
86
+ $cacheKey = $this->getCacheKey(
87
+ $this->client->getCredentials()->wait(),
88
+ $cmd,
89
+ $identifiers
90
+ );
91
+
92
+ // Check/create cache
93
+ if (!isset(self::$cache)) {
94
+ self::$cache = new LruArrayCache($config->getCacheLimit());
95
+ }
96
+
97
+ if (empty($endpointList = self::$cache->get($cacheKey))) {
98
+ $endpointList = new EndpointList([]);
99
+ }
100
+ $endpoint = $endpointList->getActive();
101
+
102
+ // Retrieve endpoints if there is no active endpoint
103
+ if (empty($endpoint)) {
104
+ try {
105
+ $endpoint = $this->discoverEndpoint(
106
+ $cacheKey,
107
+ $cmd,
108
+ $identifiers
109
+ );
110
+ } catch (\Exception $e) {
111
+ // Use cached endpoint, expired or active, if any remain
112
+ $endpoint = $endpointList->getEndpoint();
113
+
114
+ if (empty($endpoint)) {
115
+ return $this->handleDiscoveryException(
116
+ $isRequired,
117
+ $originalUri,
118
+ $e,
119
+ $cmd,
120
+ $request
121
+ );
122
+ }
123
+ }
124
+ }
125
+
126
+ $request = $this->modifyRequest($request, $endpoint);
127
+
128
+ $g = function ($value) use (
129
+ $cacheKey,
130
+ $cmd,
131
+ $identifiers,
132
+ $isRequired,
133
+ $nextHandler,
134
+ $originalUri,
135
+ $request,
136
+ &$endpoint,
137
+ &$g
138
+ ) {
139
+ if ($value instanceof AwsException
140
+ && (
141
+ $value->getAwsErrorCode() == 'InvalidEndpointException'
142
+ || $value->getStatusCode() == 421
143
+ )
144
+ ) {
145
+ return $this->handleInvalidEndpoint(
146
+ $cacheKey,
147
+ $cmd,
148
+ $identifiers,
149
+ $isRequired,
150
+ $originalUri,
151
+ $request,
152
+ $value,
153
+ $endpoint,
154
+ $g
155
+ );
156
+ }
157
+
158
+ return $value;
159
+ };
160
+
161
+ return $nextHandler($cmd, $request)->otherwise($g);
162
+ }
163
+ }
164
+
165
+ return $nextHandler($cmd, $request);
166
+ }
167
+
168
+ private function discoverEndpoint(
169
+ $cacheKey,
170
+ CommandInterface $cmd,
171
+ array $identifiers
172
+ ) {
173
+ $discCmd = $this->getDiscoveryCommand($cmd, $identifiers);
174
+ $this->discoveryTimes[$cacheKey] = time();
175
+ $result = $this->client->execute($discCmd);
176
+
177
+ if (isset($result['Endpoints'])) {
178
+ $endpointData = [];
179
+ foreach ($result['Endpoints'] as $datum) {
180
+ $endpointData[$datum['Address']] = time()
181
+ + ($datum['CachePeriodInMinutes'] * 60);
182
+ }
183
+ $endpointList = new EndpointList($endpointData);
184
+ self::$cache->set($cacheKey, $endpointList);
185
+ return $endpointList->getEndpoint();
186
+ }
187
+
188
+ throw new UnresolvedEndpointException('The endpoint discovery operation '
189
+ . 'yielded a response that did not contain properly formatted '
190
+ . 'endpoint data.');
191
+ }
192
+
193
+ private function getCacheKey(
194
+ CredentialsInterface $creds,
195
+ CommandInterface $cmd,
196
+ array $identifiers
197
+ ) {
198
+ $key = $this->service->getServiceName() . '_' . $creds->getAccessKeyId();
199
+ if (!empty($identifiers)) {
200
+ $key .= '_' . $cmd->getName();
201
+ foreach ($identifiers as $identifier) {
202
+ $key .= "_{$cmd[$identifier]}";
203
+ }
204
+ }
205
+
206
+ return $key;
207
+ }
208
+
209
+ private function getDiscoveryCommand(
210
+ CommandInterface $cmd,
211
+ array $identifiers
212
+ ) {
213
+ foreach ($this->service->getOperations() as $op) {
214
+ if (isset($op['endpointoperation'])) {
215
+ $endpointOperation = $op->toArray()['name'];
216
+ break;
217
+ }
218
+ }
219
+
220
+ if (!isset($endpointOperation)) {
221
+ throw new UnresolvedEndpointException('This command is set to use '
222
+ . 'endpoint discovery, but no endpoint discovery operation was '
223
+ . 'found. Please verify the accuracy of your model files.');
224
+ }
225
+
226
+ $params = [];
227
+ if (!empty($identifiers)) {
228
+ $params['Operation'] = $cmd->getName();
229
+ $params['Identifiers'] = [];
230
+ foreach ($identifiers as $identifier) {
231
+ $params['Identifiers'][$identifier] = $cmd[$identifier];
232
+ }
233
+ }
234
+ $command = $this->client->getCommand($endpointOperation, $params);
235
+ $command->getHandlerList()->appendBuild(
236
+ Middleware::mapRequest(function (RequestInterface $r) {
237
+ return $r->withHeader(
238
+ 'x-amz-api-version',
239
+ $this->service->getApiVersion()
240
+ );
241
+ }),
242
+ 'x-amz-api-version-header'
243
+ );
244
+
245
+ return $command;
246
+ }
247
+
248
+ private function getIdentifiers(array $operation)
249
+ {
250
+ $inputShape = $this->service->getShapeMap()
251
+ ->resolve($operation['input'])
252
+ ->toArray();
253
+ $identifiers = [];
254
+ foreach ($inputShape['members'] as $key => $member) {
255
+ if (!empty($member['endpointdiscoveryid'])) {
256
+ $identifiers[] = $key;
257
+ }
258
+ }
259
+ return $identifiers;
260
+ }
261
+
262
+ private function handleDiscoveryException(
263
+ $isRequired,
264
+ $originalUri,
265
+ \Exception $e,
266
+ CommandInterface $cmd,
267
+ RequestInterface $request
268
+ ) {
269
+ // If no cached endpoints and discovery required,
270
+ // throw exception
271
+ if ($isRequired) {
272
+ $message = 'The endpoint required for this service is currently '
273
+ . 'unable to be retrieved, and your request can not be fulfilled '
274
+ . 'unless you manually specify an endpoint.';
275
+ throw new AwsException(
276
+ $message,
277
+ $cmd,
278
+ [
279
+ 'code' => 'EndpointDiscoveryException',
280
+ 'message' => $message
281
+ ],
282
+ $e
283
+ );
284
+ }
285
+
286
+ // If discovery isn't required, use original endpoint
287
+ return $this->useOriginalUri(
288
+ $originalUri,
289
+ $cmd,
290
+ $request
291
+ );
292
+ }
293
+
294
+ private function handleInvalidEndpoint(
295
+ $cacheKey,
296
+ $cmd,
297
+ $identifiers,
298
+ $isRequired,
299
+ $originalUri,
300
+ $request,
301
+ $value,
302
+ &$endpoint,
303
+ &$g
304
+ ) {
305
+ $nextHandler = $this->nextHandler;
306
+ $endpointList = self::$cache->get($cacheKey);
307
+ if ($endpointList instanceof EndpointList) {
308
+
309
+ // Remove invalid endpoint from cached list
310
+ $endpointList->remove($endpoint);
311
+
312
+ // If possible, get another cached endpoint
313
+ $newEndpoint = $endpointList->getEndpoint();
314
+ }
315
+ if (empty($newEndpoint)) {
316
+
317
+ // If no more cached endpoints, make discovery call
318
+ // if none made within cooldown for given key
319
+ if (time() - $this->discoveryTimes[$cacheKey]
320
+ < self::$discoveryCooldown
321
+ ) {
322
+
323
+ // If no more cached endpoints and it's required,
324
+ // fail with original exception
325
+ if ($isRequired) {
326
+ return $value;
327
+ }
328
+
329
+ // Use original endpoint if not required
330
+ return $this->useOriginalUri(
331
+ $originalUri,
332
+ $cmd,
333
+ $request
334
+ );
335
+ }
336
+
337
+ $newEndpoint = $this->discoverEndpoint(
338
+ $cacheKey,
339
+ $cmd,
340
+ $identifiers
341
+ );
342
+ }
343
+ $endpoint = $newEndpoint;
344
+ $request = $this->modifyRequest($request, $endpoint);
345
+ return $nextHandler($cmd, $request)->otherwise($g);
346
+ }
347
+
348
+ private function modifyRequest(RequestInterface $request, $endpoint)
349
+ {
350
+ $parsed = $this->parseEndpoint($endpoint);
351
+ if (!empty($request->getHeader('User-Agent'))) {
352
+ $userAgent = $request->getHeader('User-Agent')[0];
353
+ if (strpos($userAgent, 'endpoint-discovery') === false) {
354
+ $userAgent = $userAgent . ' endpoint-discovery';
355
+ }
356
+ } else {
357
+ $userAgent = 'endpoint-discovery';
358
+ }
359
+
360
+ return $request
361
+ ->withUri(
362
+ $request->getUri()
363
+ ->withHost($parsed['host'])
364
+ ->withPath($parsed['path'])
365
+ )
366
+ ->withHeader('User-Agent', $userAgent);
367
+ }
368
+
369
+ /**
370
+ * Parses an endpoint returned from the discovery API into an array with
371
+ * 'host' and 'path' keys.
372
+ *
373
+ * @param $endpoint
374
+ * @return array
375
+ */
376
+ private function parseEndpoint($endpoint)
377
+ {
378
+ $parsed = parse_url($endpoint);
379
+
380
+ // parse_url() will correctly parse full URIs with schemes
381
+ if (isset($parsed['host'])) {
382
+ return $parsed;
383
+ }
384
+
385
+ // parse_url() will put host & path in 'path' if scheme is not provided
386
+ if (isset($parsed['path'])) {
387
+ $split = explode('/', $parsed['path'], 2);
388
+ $parsed['host'] = $split[0];
389
+ if (isset($split[1])) {
390
+ $parsed['path'] = $split[1];
391
+ } else {
392
+ $parsed['path'] = '';
393
+ }
394
+ return $parsed;
395
+ }
396
+
397
+ throw new UnresolvedEndpointException("The supplied endpoint '"
398
+ . "{$endpoint}' is invalid.");
399
+ }
400
+
401
+ private function useOriginalUri(
402
+ UriInterface $uri,
403
+ CommandInterface $cmd,
404
+ RequestInterface $request
405
+ ) {
406
+ $nextHandler = $this->nextHandler;
407
+ $endpoint = $uri->getHost() . $uri->getPath();
408
+ $request = $this->modifyRequest(
409
+ $request,
410
+ $endpoint
411
+ );
412
+ return $nextHandler($cmd, $request);
413
+ }
414
+ }
lib/Aws/Aws/EndpointDiscovery/EndpointList.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\EndpointDiscovery;
3
+
4
+ class EndpointList
5
+ {
6
+ private $active;
7
+ private $expired = [];
8
+
9
+ public function __construct(array $endpoints)
10
+ {
11
+ $this->active = $endpoints;
12
+ reset($this->active);
13
+ }
14
+
15
+ /**
16
+ * Gets an active (unexpired) endpoint. Returns null if none found.
17
+ *
18
+ * @return null|string
19
+ */
20
+ public function getActive()
21
+ {
22
+ if (count($this->active) < 1) {
23
+ return null;
24
+ }
25
+ while (time() > current($this->active)) {
26
+ $key = key($this->active);
27
+ $this->expired[$key] = current($this->active);
28
+ $this->increment($this->active);
29
+ unset($this->active[$key]);
30
+ if (count($this->active) < 1) {
31
+ return null;
32
+ }
33
+ }
34
+ $active = key($this->active);
35
+ $this->increment($this->active);
36
+ return $active;
37
+ }
38
+
39
+ /**
40
+ * Gets an active endpoint if possible, then an expired endpoint if possible.
41
+ * Returns null if no endpoints found.
42
+ *
43
+ * @return null|string
44
+ */
45
+ public function getEndpoint()
46
+ {
47
+ if (!empty($active = $this->getActive())) {
48
+ return $active;
49
+ }
50
+ return $this->getExpired();
51
+ }
52
+
53
+ /**
54
+ * Removes an endpoint from both lists.
55
+ *
56
+ * @param string $key
57
+ */
58
+ public function remove($key)
59
+ {
60
+ unset($this->active[$key]);
61
+ unset($this->expired[$key]);
62
+ }
63
+
64
+ /**
65
+ * Get an expired endpoint. Returns null if none found.
66
+ *
67
+ * @return null|string
68
+ */
69
+ private function getExpired()
70
+ {
71
+ if (count($this->expired) < 1) {
72
+ return null;
73
+ }
74
+ $expired = key($this->expired);
75
+ $this->increment($this->expired);
76
+ return $expired;
77
+ }
78
+
79
+ private function increment(&$array)
80
+ {
81
+ if (next($array) === false) {
82
+ reset($array);
83
+ }
84
+ }
85
+ }
lib/Aws/Aws/EndpointDiscovery/Exception/ConfigurationException.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\EndpointDiscovery\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ /**
8
+ * Represents an error interacting with configuration for endpoint discovery
9
+ */
10
+ class ConfigurationException extends \RuntimeException implements
11
+ MonitoringEventsInterface
12
+ {
13
+ use HasMonitoringEventsTrait;
14
+ }
lib/Aws/Aws/EndpointParameterMiddleware.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\Service;
5
+ use Psr\Http\Message\RequestInterface;
6
+ use Psr\Log\InvalidArgumentException;
7
+
8
+ /**
9
+ * Used to update the host based on a modeled endpoint trait
10
+ *
11
+ * IMPORTANT: this middleware must be added after the "build" step.
12
+ *
13
+ * @internal
14
+ */
15
+ class EndpointParameterMiddleware
16
+ {
17
+
18
+ /**
19
+ * Create a middleware wrapper function
20
+ *
21
+ * @param Service $service
22
+ * @param array $args
23
+ * @return \Closure
24
+ */
25
+ public static function wrap(Service $service)
26
+ {
27
+ return function (callable $handler) use ($service) {
28
+ return new self($handler, $service);
29
+ };
30
+ }
31
+
32
+ public function __construct(callable $nextHandler, Service $service)
33
+ {
34
+ $this->nextHandler = $nextHandler;
35
+ $this->service = $service;
36
+ }
37
+
38
+ public function __invoke(CommandInterface $command, RequestInterface $request)
39
+ {
40
+ $nextHandler = $this->nextHandler;
41
+
42
+ $operation = $this->service->getOperation($command->getName());
43
+
44
+ if (!empty($operation['endpoint']['hostPrefix'])) {
45
+ $prefix = $operation['endpoint']['hostPrefix'];
46
+
47
+ // Captures endpoint parameters stored in the modeled host.
48
+ // These are denoted by enclosure in braces, i.e. '{param}'
49
+ preg_match_all("/\{([a-zA-Z0-9]+)}/", $prefix, $parameters);
50
+
51
+ if (!empty($parameters[1])) {
52
+
53
+ // Captured parameters without braces stored in $parameters[1],
54
+ // which should correspond to members in the Command object
55
+ foreach ($parameters[1] as $index => $parameter) {
56
+ if (empty($command[$parameter])) {
57
+ throw new \InvalidArgumentException(
58
+ "The parameter '{$parameter}' must be set and not empty."
59
+ );
60
+ }
61
+
62
+ // Captured parameters with braces stored in $parameters[0],
63
+ // which are replaced by their corresponding Command value
64
+ $prefix = str_replace(
65
+ $parameters[0][$index],
66
+ $command[$parameter],
67
+ $prefix
68
+ );
69
+ }
70
+ }
71
+
72
+ $uri = $request->getUri();
73
+ $host = $prefix . $uri->getHost();
74
+ if (!\Aws\is_valid_hostname($host)) {
75
+ throw new \InvalidArgumentException(
76
+ "The supplied parameters result in an invalid hostname: '{$host}'."
77
+ );
78
+ }
79
+ $request = $request->withUri($uri->withHost($host));
80
+ }
81
+
82
+ return $nextHandler($command, $request);
83
+ }
84
+ }
lib/Aws/Aws/Exception/AwsException.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+ use Aws\ResponseContainerInterface;
7
+ use Psr\Http\Message\ResponseInterface;
8
+ use Psr\Http\Message\RequestInterface;
9
+ use Aws\CommandInterface;
10
+ use Aws\ResultInterface;
11
+
12
+ /**
13
+ * Represents an AWS exception that is thrown when a command fails.
14
+ */
15
+ class AwsException extends \RuntimeException implements
16
+ MonitoringEventsInterface,
17
+ ResponseContainerInterface
18
+ {
19
+ use HasMonitoringEventsTrait;
20
+
21
+ /** @var ResponseInterface */
22
+ private $response;
23
+ private $request;
24
+ private $result;
25
+ private $command;
26
+ private $requestId;
27
+ private $errorType;
28
+ private $errorCode;
29
+ private $connectionError;
30
+ private $transferInfo;
31
+ private $errorMessage;
32
+ private $maxRetriesExceeded;
33
+
34
+
35
+ /**
36
+ * @param string $message Exception message
37
+ * @param CommandInterface $command
38
+ * @param array $context Exception context
39
+ * @param \Exception $previous Previous exception (if any)
40
+ */
41
+ public function __construct(
42
+ $message,
43
+ CommandInterface $command,
44
+ array $context = [],
45
+ \Exception $previous = null
46
+ ) {
47
+ $this->command = $command;
48
+ $this->response = isset($context['response']) ? $context['response'] : null;
49
+ $this->request = isset($context['request']) ? $context['request'] : null;
50
+ $this->requestId = isset($context['request_id'])
51
+ ? $context['request_id']
52
+ : null;
53
+ $this->errorType = isset($context['type']) ? $context['type'] : null;
54
+ $this->errorCode = isset($context['code']) ? $context['code'] : null;
55
+ $this->connectionError = !empty($context['connection_error']);
56
+ $this->result = isset($context['result']) ? $context['result'] : null;
57
+ $this->transferInfo = isset($context['transfer_stats'])
58
+ ? $context['transfer_stats']
59
+ : [];
60
+ $this->errorMessage = isset($context['message'])
61
+ ? $context['message']
62
+ : null;
63
+ $this->monitoringEvents = [];
64
+ $this->maxRetriesExceeded = false;
65
+ parent::__construct($message, 0, $previous);
66
+ }
67
+
68
+ public function __toString()
69
+ {
70
+ if (!$this->getPrevious()) {
71
+ return parent::__toString();
72
+ }
73
+
74
+ // PHP strangely shows the innermost exception first before the outer
75
+ // exception message. It also has a default character limit for
76
+ // exception message strings such that the "next" exception (this one)
77
+ // might not even get shown, causing developers to attempt to catch
78
+ // the inner exception instead of the actual exception because they
79
+ // can't see the outer exception's __toString output.
80
+ return sprintf(
81
+ "exception '%s' with message '%s'\n\n%s",
82
+ get_class($this),
83
+ $this->getMessage(),
84
+ parent::__toString()
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Get the command that was executed.
90
+ *
91
+ * @return CommandInterface
92
+ */
93
+ public function getCommand()
94
+ {
95
+ return $this->command;
96
+ }
97
+
98
+ /**
99
+ * Get the concise error message if any.
100
+ *
101
+ * @return string|null
102
+ */
103
+ public function getAwsErrorMessage()
104
+ {
105
+ return $this->errorMessage;
106
+ }
107
+
108
+ /**
109
+ * Get the sent HTTP request if any.
110
+ *
111
+ * @return RequestInterface|null
112
+ */
113
+ public function getRequest()
114
+ {
115
+ return $this->request;
116
+ }
117
+
118
+ /**
119
+ * Get the received HTTP response if any.
120
+ *
121
+ * @return ResponseInterface|null
122
+ */
123
+ public function getResponse()
124
+ {
125
+ return $this->response;
126
+ }
127
+
128
+ /**
129
+ * Get the result of the exception if available
130
+ *
131
+ * @return ResultInterface|null
132
+ */
133
+ public function getResult()
134
+ {
135
+ return $this->result;
136
+ }
137
+
138
+ /**
139
+ * Returns true if this is a connection error.
140
+ *
141
+ * @return bool
142
+ */
143
+ public function isConnectionError()
144
+ {
145
+ return $this->connectionError;
146
+ }
147
+
148
+ /**
149
+ * If available, gets the HTTP status code of the corresponding response
150
+ *
151
+ * @return int|null
152
+ */
153
+ public function getStatusCode()
154
+ {
155
+ return $this->response ? $this->response->getStatusCode() : null;
156
+ }
157
+
158
+ /**
159
+ * Get the request ID of the error. This value is only present if a
160
+ * response was received and is not present in the event of a networking
161
+ * error.
162
+ *
163
+ * @return string|null Returns null if no response was received
164
+ */
165
+ public function getAwsRequestId()
166
+ {
167
+ return $this->requestId;
168
+ }
169
+
170
+ /**
171
+ * Get the AWS error type.
172
+ *
173
+ * @return string|null Returns null if no response was received
174
+ */
175
+ public function getAwsErrorType()
176
+ {
177
+ return $this->errorType;
178
+ }
179
+
180
+ /**
181
+ * Get the AWS error code.
182
+ *
183
+ * @return string|null Returns null if no response was received
184
+ */
185
+ public function getAwsErrorCode()
186
+ {
187
+ return $this->errorCode;
188
+ }
189
+
190
+ /**
191
+ * Get all transfer information as an associative array if no $name
192
+ * argument is supplied, or gets a specific transfer statistic if
193
+ * a $name attribute is supplied (e.g., 'retries_attempted').
194
+ *
195
+ * @param string $name Name of the transfer stat to retrieve
196
+ *
197
+ * @return mixed|null|array
198
+ */
199
+ public function getTransferInfo($name = null)
200
+ {
201
+ if (!$name) {
202
+ return $this->transferInfo;
203
+ }
204
+
205
+ return isset($this->transferInfo[$name])
206
+ ? $this->transferInfo[$name]
207
+ : null;
208
+ }
209
+
210
+ /**
211
+ * Replace the transfer information associated with an exception.
212
+ *
213
+ * @param array $info
214
+ */
215
+ public function setTransferInfo(array $info)
216
+ {
217
+ $this->transferInfo = $info;
218
+ }
219
+
220
+ /**
221
+ * Returns whether the max number of retries is exceeded.
222
+ *
223
+ * @return bool
224
+ */
225
+ public function isMaxRetriesExceeded()
226
+ {
227
+ return $this->maxRetriesExceeded;
228
+ }
229
+
230
+ /**
231
+ * Sets the flag for max number of retries exceeded.
232
+ */
233
+ public function setMaxRetriesExceeded()
234
+ {
235
+ $this->maxRetriesExceeded = true;
236
+ }
237
+ }
lib/Aws/Aws/Exception/CouldNotCreateChecksumException.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ class CouldNotCreateChecksumException extends \RuntimeException implements
8
+ MonitoringEventsInterface
9
+ {
10
+ use HasMonitoringEventsTrait;
11
+
12
+ public function __construct($algorithm, \Exception $previous = null)
13
+ {
14
+ $prefix = $algorithm === 'md5' ? "An" : "A";
15
+ parent::__construct("{$prefix} {$algorithm} checksum could not be "
16
+ . "calculated for the provided upload body, because it was not "
17
+ . "seekable. To prevent this error you can either 1) include the "
18
+ . "ContentMD5 or ContentSHA256 parameters with your request, 2) "
19
+ . "use a seekable stream for the body, or 3) wrap the non-seekable "
20
+ . "stream in a GuzzleHttp\\Psr7\\CachingStream object. You "
21
+ . "should be careful though and remember that the CachingStream "
22
+ . "utilizes PHP temp streams. This means that the stream will be "
23
+ . "temporarily stored on the local disk.", 0, $previous);
24
+ }
25
+ }
lib/Aws/Aws/Exception/CredentialsException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ class CredentialsException extends \RuntimeException implements
8
+ MonitoringEventsInterface
9
+ {
10
+ use HasMonitoringEventsTrait;
11
+ }
lib/Aws/Aws/Exception/EventStreamDataException.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ /**
5
+ * Represents an exception that was supplied via an EventStream.
6
+ */
7
+ class EventStreamDataException extends \RuntimeException
8
+ {
9
+ private $errorCode;
10
+ private $errorMessage;
11
+
12
+ public function __construct($code, $message)
13
+ {
14
+ $this->errorCode = $code;
15
+ $this->errorMessage = $message;
16
+ parent::__construct($message);
17
+ }
18
+
19
+ /**
20
+ * Get the AWS error code.
21
+ *
22
+ * @return string|null Returns null if no response was received
23
+ */
24
+ public function getAwsErrorCode()
25
+ {
26
+ return $this->errorCode;
27
+ }
28
+
29
+ /**
30
+ * Get the concise error message if any.
31
+ *
32
+ * @return string|null
33
+ */
34
+ public function getAwsErrorMessage()
35
+ {
36
+ return $this->errorMessage;
37
+ }
38
+ }
lib/Aws/Aws/Exception/MultipartUploadException.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+ use Aws\Multipart\UploadState;
7
+
8
+ class MultipartUploadException extends \RuntimeException implements
9
+ MonitoringEventsInterface
10
+ {
11
+ use HasMonitoringEventsTrait;
12
+
13
+ /** @var UploadState State of the erroneous transfer */
14
+ private $state;
15
+
16
+ /**
17
+ * @param UploadState $state Upload state at time of the exception.
18
+ * @param \Exception|array $prev Exception being thrown.
19
+ */
20
+ public function __construct(UploadState $state, $prev = null) {
21
+ $msg = 'An exception occurred while performing a multipart upload';
22
+
23
+ if (is_array($prev)) {
24
+ $msg = strtr($msg, ['performing' => 'uploading parts to']);
25
+ $msg .= ". The following parts had errors:\n";
26
+ /** @var $error AwsException */
27
+ foreach ($prev as $part => $error) {
28
+ $msg .= "- Part {$part}: " . $error->getMessage(). "\n";
29
+ }
30
+ } elseif ($prev instanceof AwsException) {
31
+ switch ($prev->getCommand()->getName()) {
32
+ case 'CreateMultipartUpload':
33
+ case 'InitiateMultipartUpload':
34
+ $action = 'initiating';
35
+ break;
36
+ case 'CompleteMultipartUpload':
37
+ $action = 'completing';
38
+ break;
39
+ }
40
+ if (isset($action)) {
41
+ $msg = strtr($msg, ['performing' => $action]);
42
+ }
43
+ $msg .= ": {$prev->getMessage()}";
44
+ }
45
+
46
+ if (!$prev instanceof \Exception) {
47
+ $prev = null;
48
+ }
49
+
50
+ parent::__construct($msg, 0, $prev);
51
+ $this->state = $state;
52
+ }
53
+
54
+ /**
55
+ * Get the state of the transfer
56
+ *
57
+ * @return UploadState
58
+ */
59
+ public function getState()
60
+ {
61
+ return $this->state;
62
+ }
63
+ }
lib/Aws/Aws/Exception/UnresolvedApiException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ class UnresolvedApiException extends \RuntimeException implements
8
+ MonitoringEventsInterface
9
+ {
10
+ use HasMonitoringEventsTrait;
11
+ }
lib/Aws/Aws/Exception/UnresolvedEndpointException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ class UnresolvedEndpointException extends \RuntimeException implements
8
+ MonitoringEventsInterface
9
+ {
10
+ use HasMonitoringEventsTrait;
11
+ }
lib/Aws/Aws/Exception/UnresolvedSignatureException.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ class UnresolvedSignatureException extends \RuntimeException implements
8
+ MonitoringEventsInterface
9
+ {
10
+ use HasMonitoringEventsTrait;
11
+ }
lib/Aws/Aws/Handler/GuzzleV5/GuzzleHandler.php ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Handler\GuzzleV5;
3
+
4
+ use Exception;
5
+ use GuzzleHttp\Client;
6
+ use GuzzleHttp\ClientInterface;
7
+ use GuzzleHttp\Event\EndEvent;
8
+ use GuzzleHttp\Exception\ConnectException;
9
+ use GuzzleHttp\Exception\RequestException;
10
+ use GuzzleHttp\Message\ResponseInterface as GuzzleResponse;
11
+ use GuzzleHttp\Promise;
12
+ use GuzzleHttp\Psr7\Response as Psr7Response;
13
+ use GuzzleHttp\Stream\Stream;
14
+ use Psr\Http\Message\RequestInterface as Psr7Request;
15
+ use Psr\Http\Message\StreamInterface as Psr7StreamInterface;
16
+
17
+ /**
18
+ * A request handler that sends PSR-7-compatible requests with Guzzle 5.
19
+ *
20
+ * The handler accepts a PSR-7 Request object and an array of transfer options
21
+ * and returns a Guzzle 6 Promise. The promise is either resolved with a
22
+ * PSR-7 Response object or rejected with an array of error data.
23
+ *
24
+ * @codeCoverageIgnore
25
+ */
26
+ class GuzzleHandler
27
+ {
28
+ private static $validOptions = [
29
+ 'proxy' => true,
30
+ 'expect' => true,
31
+ 'verify' => true,
32
+ 'timeout' => true,
33
+ 'debug' => true,
34
+ 'connect_timeout' => true,
35
+ 'stream' => true,
36
+ 'delay' => true,
37
+ 'sink' => true,
38
+ ];
39
+
40
+ /** @var ClientInterface */
41
+ private $client;
42
+
43
+ /**
44
+ * @param ClientInterface $client
45
+ */
46
+ public function __construct(ClientInterface $client = null)
47
+ {
48
+ $this->client = $client ?: new Client();
49
+ }
50
+
51
+ /**
52
+ * @param Psr7Request $request
53
+ * @param array $options
54
+ *
55
+ * @return Promise\Promise
56
+ */
57
+ public function __invoke(Psr7Request $request, array $options = [])
58
+ {
59
+ // Create and send a Guzzle 5 request
60
+ $guzzlePromise = $this->client->send(
61
+ $this->createGuzzleRequest($request, $options)
62
+ );
63
+
64
+ $promise = new Promise\Promise(
65
+ function () use ($guzzlePromise) {
66
+ try {
67
+ $guzzlePromise->wait();
68
+ } catch (\Exception $e) {
69
+ // The promise is already delivered when the exception is
70
+ // thrown, so don't rethrow it.
71
+ }
72
+ },
73
+ [$guzzlePromise, 'cancel']
74
+ );
75
+
76
+ $guzzlePromise->then([$promise, 'resolve'], [$promise, 'reject']);
77
+
78
+ return $promise->then(
79
+ function (GuzzleResponse $response) {
80
+ // Adapt the Guzzle 5 Future to a Guzzle 6 ResponsePromise.
81
+ return $this->createPsr7Response($response);
82
+ },
83
+ function (Exception $exception) use ($options) {
84
+ // If we got a 'sink' that's a path, set the response body to
85
+ // the contents of the file. This will build the resulting
86
+ // exception with more information.
87
+ if ($exception instanceof RequestException) {
88
+ if (isset($options['sink'])) {
89
+ if (!($options['sink'] instanceof Psr7StreamInterface)) {
90
+ $exception->getResponse()->setBody(
91
+ Stream::factory(
92
+ file_get_contents($options['sink'])
93
+ )
94
+ );
95
+ }
96
+ }
97
+ }
98
+ // Reject with information about the error.
99
+ return new Promise\RejectedPromise($this->prepareErrorData($exception));
100
+ }
101
+ );
102
+ }
103
+
104
+ private function createGuzzleRequest(Psr7Request $psrRequest, array $options)
105
+ {
106
+ $ringConfig = [];
107
+ $statsCallback = isset($options['http_stats_receiver'])
108
+ ? $options['http_stats_receiver']
109
+ : null;
110
+ unset($options['http_stats_receiver']);
111
+
112
+ // Remove unsupported options.
113
+ foreach (array_keys($options) as $key) {
114
+ if (!isset(self::$validOptions[$key])) {
115
+ unset($options[$key]);
116
+ }
117
+ }
118
+
119
+ // Handle delay option.
120
+ if (isset($options['delay'])) {
121
+ $ringConfig['delay'] = $options['delay'];
122
+ unset($options['delay']);
123
+ }
124
+
125
+ // Prepare sink option.
126
+ if (isset($options['sink'])) {
127
+ $ringConfig['save_to'] = ($options['sink'] instanceof Psr7StreamInterface)
128
+ ? new GuzzleStream($options['sink'])
129
+ : $options['sink'];
130
+ unset($options['sink']);
131
+ }
132
+
133
+ // Ensure that all requests are async and lazy like Guzzle 6.
134
+ $options['future'] = 'lazy';
135
+
136
+ // Create the Guzzle 5 request from the provided PSR7 request.
137
+ $request = $this->client->createRequest(
138
+ $psrRequest->getMethod(),
139
+ $psrRequest->getUri(),
140
+ $options
141
+ );
142
+
143
+ if (is_callable($statsCallback)) {
144
+ $request->getEmitter()->on(
145
+ 'end',
146
+ function (EndEvent $event) use ($statsCallback) {
147
+ $statsCallback($event->getTransferInfo());
148
+ }
149
+ );
150
+ }
151
+
152
+ // For the request body, adapt the PSR stream to a Guzzle stream.
153
+ $body = $psrRequest->getBody();
154
+ if ($body->getSize() === 0) {
155
+ $request->setBody(null);
156
+ } else {
157
+ $request->setBody(new GuzzleStream($body));
158
+ }
159
+
160
+ $request->setHeaders($psrRequest->getHeaders());
161
+
162
+ $request->setHeader(
163
+ 'User-Agent',
164
+ $request->getHeader('User-Agent')
165
+ . ' ' . Client::getDefaultUserAgent()
166
+ );
167
+
168
+ // Make sure the delay is configured, if provided.
169
+ if ($ringConfig) {
170
+ foreach ($ringConfig as $k => $v) {
171
+ $request->getConfig()->set($k, $v);
172
+ }
173
+ }
174
+
175
+ return $request;
176
+ }
177
+
178
+ private function createPsr7Response(GuzzleResponse $response)
179
+ {
180
+ if ($body = $response->getBody()) {
181
+ $body = new PsrStream($body);
182
+ }
183
+
184
+ return new Psr7Response(
185
+ $response->getStatusCode(),
186
+ $response->getHeaders(),
187
+ $body,
188
+ $response->getReasonPhrase()
189
+ );
190
+ }
191
+
192
+ private function prepareErrorData(Exception $e)
193
+ {
194
+ $error = [
195
+ 'exception' => $e,
196
+ 'connection_error' => false,
197
+ 'response' => null,
198
+ ];
199
+
200
+ if ($e instanceof ConnectException) {
201
+ $error['connection_error'] = true;
202
+ }
203
+
204
+ if ($e instanceof RequestException && $e->getResponse()) {
205
+ $error['response'] = $this->createPsr7Response($e->getResponse());
206
+ }
207
+
208
+ return $error;
209
+ }
210
+ }
lib/Aws/Aws/Handler/GuzzleV5/GuzzleStream.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Handler\GuzzleV5;
3
+
4
+ use GuzzleHttp\Stream\StreamDecoratorTrait;
5
+ use GuzzleHttp\Stream\StreamInterface as GuzzleStreamInterface;
6
+ use Psr\Http\Message\StreamInterface as Psr7StreamInterface;
7
+
8
+ /**
9
+ * Adapts a PSR-7 Stream to a Guzzle 5 Stream.
10
+ *
11
+ * @codeCoverageIgnore
12
+ */
13
+ class GuzzleStream implements GuzzleStreamInterface
14
+ {
15
+ use StreamDecoratorTrait;
16
+
17
+ /** @var Psr7StreamInterface */
18
+ private $stream;
19
+
20
+ public function __construct(Psr7StreamInterface $stream)
21
+ {
22
+ $this->stream = $stream;
23
+ }
24
+ }
lib/Aws/Aws/Handler/GuzzleV5/PsrStream.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Handler\GuzzleV5;
3
+
4
+ use GuzzleHttp\Stream\StreamDecoratorTrait;
5
+ use GuzzleHttp\Stream\StreamInterface as GuzzleStreamInterface;
6
+ use Psr\Http\Message\StreamInterface as Psr7StreamInterface;
7
+
8
+ /**
9
+ * Adapts a Guzzle 5 Stream to a PSR-7 Stream.
10
+ *
11
+ * @codeCoverageIgnore
12
+ */
13
+ class PsrStream implements Psr7StreamInterface
14
+ {
15
+ use StreamDecoratorTrait;
16
+
17
+ /** @var GuzzleStreamInterface */
18
+ private $stream;
19
+
20
+ public function __construct(GuzzleStreamInterface $stream)
21
+ {
22
+ $this->stream = $stream;
23
+ }
24
+
25
+ public function rewind()
26
+ {
27
+ $this->stream->seek(0);
28
+ }
29
+
30
+ public function getContents()
31
+ {
32
+ return $this->stream->getContents();
33
+ }
34
+ }
lib/Aws/Aws/Handler/GuzzleV6/GuzzleHandler.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Handler\GuzzleV6;
3
+
4
+ use Exception;
5
+ use GuzzleHttp\Exception\ConnectException;
6
+ use GuzzleHttp\Exception\RequestException;
7
+ use GuzzleHttp\Promise;
8
+ use GuzzleHttp\Client;
9
+ use GuzzleHttp\ClientInterface;
10
+ use GuzzleHttp\TransferStats;
11
+ use Psr\Http\Message\RequestInterface as Psr7Request;
12
+
13
+ /**
14
+ * A request handler that sends PSR-7-compatible requests with Guzzle 6.
15
+ */
16
+ class GuzzleHandler
17
+ {
18
+ /** @var ClientInterface */
19
+ private $client;
20
+
21
+ /**
22
+ * @param ClientInterface $client
23
+ */
24
+ public function __construct(ClientInterface $client = null)
25
+ {
26
+ $this->client = $client ?: new Client();
27
+ }
28
+
29
+ /**
30
+ * @param Psr7Request $request
31
+ * @param array $options
32
+ *
33
+ * @return Promise\Promise
34
+ */
35
+ public function __invoke(Psr7Request $request, array $options = [])
36
+ {
37
+ $request = $request->withHeader(
38
+ 'User-Agent',
39
+ $request->getHeaderLine('User-Agent')
40
+ . ' ' . \GuzzleHttp\default_user_agent()
41
+ );
42
+
43
+ return $this->client->sendAsync($request, $this->parseOptions($options))
44
+ ->otherwise(
45
+ static function (\Exception $e) {
46
+ $error = [
47
+ 'exception' => $e,
48
+ 'connection_error' => $e instanceof ConnectException,
49
+ 'response' => null,
50
+ ];
51
+
52
+ if ($e instanceof RequestException && $e->getResponse()) {
53
+ $error['response'] = $e->getResponse();
54
+ }
55
+
56
+ return new Promise\RejectedPromise($error);
57
+ }
58
+ );
59
+ }
60
+
61
+ private function parseOptions(array $options)
62
+ {
63
+ if (isset($options['http_stats_receiver'])) {
64
+ $fn = $options['http_stats_receiver'];
65
+ unset($options['http_stats_receiver']);
66
+
67
+ $prev = isset($options['on_stats'])
68
+ ? $options['on_stats']
69
+ : null;
70
+
71
+ $options['on_stats'] = static function (
72
+ TransferStats $stats
73
+ ) use ($fn, $prev) {
74
+ if (is_callable($prev)) {
75
+ $prev($stats);
76
+ }
77
+ $transferStats = ['total_time' => $stats->getTransferTime()];
78
+ $transferStats += $stats->getHandlerStats();
79
+ $fn($transferStats);
80
+ };
81
+ }
82
+
83
+ return $options;
84
+ }
85
+ }
lib/Aws/Aws/HandlerList.php ADDED
@@ -0,0 +1,451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Builds a single handler function from zero or more middleware functions and
6
+ * a handler. The handler function is then used to send command objects and
7
+ * return a promise that is resolved with an AWS result object.
8
+ *
9
+ * The "front" of the list is invoked before the "end" of the list. You can add
10
+ * middleware to the front of the list using one of the "prepend" method, and
11
+ * the end of the list using one of the "append" method. The last function
12
+ * invoked in a handler list is the handler (a function that does not accept a
13
+ * next handler but rather is responsible for returning a promise that is
14
+ * fulfilled with an Aws\ResultInterface object).
15
+ *
16
+ * Handlers are ordered using a "step" that describes the step at which the
17
+ * SDK is when sending a command. The available steps are:
18
+ *
19
+ * - init: The command is being initialized, allowing you to do things like add
20
+ * default options.
21
+ * - validate: The command is being validated before it is serialized
22
+ * - build: The command is being serialized into an HTTP request. A middleware
23
+ * in this step MUST serialize an HTTP request and populate the "@request"
24
+ * parameter of a command with the request such that it is available to
25
+ * subsequent middleware.
26
+ * - sign: The request is being signed and prepared to be sent over the wire.
27
+ *
28
+ * Middleware can be registered with a name to allow you to easily add a
29
+ * middleware before or after another middleware by name. This also allows you
30
+ * to remove a middleware by name (in addition to removing by instance).
31
+ */
32
+ class HandlerList implements \Countable
33
+ {
34
+ const INIT = 'init';
35
+ const VALIDATE = 'validate';
36
+ const BUILD = 'build';
37
+ const SIGN = 'sign';
38
+ const ATTEMPT = 'attempt';
39
+
40
+ /** @var callable */
41
+ private $handler;
42
+
43
+ /** @var array */
44
+ private $named = [];
45
+
46
+ /** @var array */
47
+ private $sorted;
48
+
49
+ /** @var callable|null */
50
+ private $interposeFn;
51
+
52
+ /** @var array Steps (in reverse order) */
53
+ private $steps = [
54
+ self::ATTEMPT => [],
55
+ self::SIGN => [],
56
+ self::BUILD => [],
57
+ self::VALIDATE => [],
58
+ self::INIT => [],
59
+ ];
60
+
61
+ /**
62
+ * @param callable $handler HTTP handler.
63
+ */
64
+ public function __construct(callable $handler = null)
65
+ {
66
+ $this->handler = $handler;
67
+ }
68
+
69
+ /**
70
+ * Dumps a string representation of the list.
71
+ *
72
+ * @return string
73
+ */
74
+ public function __toString()
75
+ {
76
+ $str = '';
77
+ $i = 0;
78
+
79
+ foreach (array_reverse($this->steps) as $k => $step) {
80
+ foreach (array_reverse($step) as $j => $tuple) {
81
+ $str .= "{$i}) Step: {$k}, ";
82
+ if ($tuple[1]) {
83
+ $str .= "Name: {$tuple[1]}, ";
84
+ }
85
+ $str .= "Function: " . $this->debugCallable($tuple[0]) . "\n";
86
+ $i++;
87
+ }
88
+ }
89
+
90
+ if ($this->handler) {
91
+ $str .= "{$i}) Handler: " . $this->debugCallable($this->handler) . "\n";
92
+ }
93
+
94
+ return $str;
95
+ }
96
+
97
+ /**
98
+ * Set the HTTP handler that actually returns a response.
99
+ *
100
+ * @param callable $handler Function that accepts a request and array of
101
+ * options and returns a Promise.
102
+ */
103
+ public function setHandler(callable $handler)
104
+ {
105
+ $this->handler = $handler;
106
+ }
107
+
108
+ /**
109
+ * Returns true if the builder has a handler.
110
+ *
111
+ * @return bool
112
+ */
113
+ public function hasHandler()
114
+ {
115
+ return (bool) $this->handler;
116
+ }
117
+
118
+ /**
119
+ * Append a middleware to the init step.
120
+ *
121
+ * @param callable $middleware Middleware function to add.
122
+ * @param string $name Name of the middleware.
123
+ */
124
+ public function appendInit(callable $middleware, $name = null)
125
+ {
126
+ $this->add(self::INIT, $name, $middleware);
127
+ }
128
+
129
+ /**
130
+ * Prepend a middleware to the init step.
131
+ *
132
+ * @param callable $middleware Middleware function to add.
133
+ * @param string $name Name of the middleware.
134
+ */
135
+ public function prependInit(callable $middleware, $name = null)
136
+ {
137
+ $this->add(self::INIT, $name, $middleware, true);
138
+ }
139
+
140
+ /**
141
+ * Append a middleware to the validate step.
142
+ *
143
+ * @param callable $middleware Middleware function to add.
144
+ * @param string $name Name of the middleware.
145
+ */
146
+ public function appendValidate(callable $middleware, $name = null)
147
+ {
148
+ $this->add(self::VALIDATE, $name, $middleware);
149
+ }
150
+
151
+ /**
152
+ * Prepend a middleware to the validate step.
153
+ *
154
+ * @param callable $middleware Middleware function to add.
155
+ * @param string $name Name of the middleware.
156
+ */
157
+ public function prependValidate(callable $middleware, $name = null)
158
+ {
159
+ $this->add(self::VALIDATE, $name, $middleware, true);
160
+ }
161
+
162
+ /**
163
+ * Append a middleware to the build step.
164
+ *
165
+ * @param callable $middleware Middleware function to add.
166
+ * @param string $name Name of the middleware.
167
+ */
168
+ public function appendBuild(callable $middleware, $name = null)
169
+ {
170
+ $this->add(self::BUILD, $name, $middleware);
171
+ }
172
+
173
+ /**
174
+ * Prepend a middleware to the build step.
175
+ *
176
+ * @param callable $middleware Middleware function to add.
177
+ * @param string $name Name of the middleware.
178
+ */
179
+ public function prependBuild(callable $middleware, $name = null)
180
+ {
181
+ $this->add(self::BUILD, $name, $middleware, true);
182
+ }
183
+
184
+ /**
185
+ * Append a middleware to the sign step.
186
+ *
187
+ * @param callable $middleware Middleware function to add.
188
+ * @param string $name Name of the middleware.
189
+ */
190
+ public function appendSign(callable $middleware, $name = null)
191
+ {
192
+ $this->add(self::SIGN, $name, $middleware);
193
+ }
194
+
195
+ /**
196
+ * Prepend a middleware to the sign step.
197
+ *
198
+ * @param callable $middleware Middleware function to add.
199
+ * @param string $name Name of the middleware.
200
+ */
201
+ public function prependSign(callable $middleware, $name = null)
202
+ {
203
+ $this->add(self::SIGN, $name, $middleware, true);
204
+ }
205
+
206
+ /**
207
+ * Append a middleware to the attempt step.
208
+ *
209
+ * @param callable $middleware Middleware function to add.
210
+ * @param string $name Name of the middleware.
211
+ */
212
+ public function appendAttempt(callable $middleware, $name = null)
213
+ {
214
+ $this->add(self::ATTEMPT, $name, $middleware);
215
+ }
216
+
217
+ /**
218
+ * Prepend a middleware to the attempt step.
219
+ *
220
+ * @param callable $middleware Middleware function to add.
221
+ * @param string $name Name of the middleware.
222
+ */
223
+ public function prependAttempt(callable $middleware, $name = null)
224
+ {
225
+ $this->add(self::ATTEMPT, $name, $middleware, true);
226
+ }
227
+
228
+ /**
229
+ * Add a middleware before the given middleware by name.
230
+ *
231
+ * @param string|callable $findName Add before this
232
+ * @param string $withName Optional name to give the middleware
233
+ * @param callable $middleware Middleware to add.
234
+ */
235
+ public function before($findName, $withName, callable $middleware)
236
+ {
237
+ $this->splice($findName, $withName, $middleware, true);
238
+ }
239
+
240
+ /**
241
+ * Add a middleware after the given middleware by name.
242
+ *
243
+ * @param string|callable $findName Add after this
244
+ * @param string $withName Optional name to give the middleware
245
+ * @param callable $middleware Middleware to add.
246
+ */
247
+ public function after($findName, $withName, callable $middleware)
248
+ {
249
+ $this->splice($findName, $withName, $middleware, false);
250
+ }
251
+
252
+ /**
253
+ * Remove a middleware by name or by instance from the list.
254
+ *
255
+ * @param string|callable $nameOrInstance Middleware to remove.
256
+ */
257
+ public function remove($nameOrInstance)
258
+ {
259
+ if (is_callable($nameOrInstance)) {
260
+ $this->removeByInstance($nameOrInstance);
261
+ } elseif (is_string($nameOrInstance)) {
262
+ $this->removeByName($nameOrInstance);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Interpose a function between each middleware (e.g., allowing for a trace
268
+ * through the middleware layers).
269
+ *
270
+ * The interpose function is a function that accepts a "step" argument as a
271
+ * string and a "name" argument string. This function must then return a
272
+ * function that accepts the next handler in the list. This function must
273
+ * then return a function that accepts a CommandInterface and optional
274
+ * RequestInterface and returns a promise that is fulfilled with an
275
+ * Aws\ResultInterface or rejected with an Aws\Exception\AwsException
276
+ * object.
277
+ *
278
+ * @param callable|null $fn Pass null to remove any previously set function
279
+ */
280
+ public function interpose(callable $fn = null)
281
+ {
282
+ $this->sorted = null;
283
+ $this->interposeFn = $fn;
284
+ }
285
+
286
+ /**
287
+ * Compose the middleware and handler into a single callable function.
288
+ *
289
+ * @return callable
290
+ */
291
+ public function resolve()
292
+ {
293
+ if (!($prev = $this->handler)) {
294
+ throw new \LogicException('No handler has been specified');
295
+ }
296
+
297
+ if ($this->sorted === null) {
298
+ $this->sortMiddleware();
299
+ }
300
+
301
+ foreach ($this->sorted as $fn) {
302
+ $prev = $fn($prev);
303
+ }
304
+
305
+ return $prev;
306
+ }
307
+
308
+ public function count()
309
+ {
310
+ return count($this->steps[self::INIT])
311
+ + count($this->steps[self::VALIDATE])
312
+ + count($this->steps[self::BUILD])
313
+ + count($this->steps[self::SIGN])
314
+ + count($this->steps[self::ATTEMPT]);
315
+ }
316
+
317
+ /**
318
+ * Splices a function into the middleware list at a specific position.
319
+ *
320
+ * @param $findName
321
+ * @param $withName
322
+ * @param callable $middleware
323
+ * @param $before
324
+ */
325
+ private function splice($findName, $withName, callable $middleware, $before)
326
+ {
327
+ if (!isset($this->named[$findName])) {
328
+ throw new \InvalidArgumentException("$findName not found");
329
+ }
330
+
331
+ $idx = $this->sorted = null;
332
+ $step = $this->named[$findName];
333
+
334
+ if ($withName) {
335
+ $this->named[$withName] = $step;
336
+ }
337
+
338
+ foreach ($this->steps[$step] as $i => $tuple) {
339
+ if ($tuple[1] === $findName) {
340
+ $idx = $i;
341
+ break;
342
+ }
343
+ }
344
+
345
+ $replacement = $before
346
+ ? [$this->steps[$step][$idx], [$middleware, $withName]]
347
+ : [[$middleware, $withName], $this->steps[$step][$idx]];
348
+ array_splice($this->steps[$step], $idx, 1, $replacement);
349
+ }
350
+
351
+ /**
352
+ * Provides a debug string for a given callable.
353
+ *
354
+ * @param array|callable $fn Function to write as a string.
355
+ *
356
+ * @return string
357
+ */
358
+ private function debugCallable($fn)
359
+ {
360
+ if (is_string($fn)) {
361
+ return "callable({$fn})";
362
+ }
363
+
364
+ if (is_array($fn)) {
365
+ $ele = is_string($fn[0]) ? $fn[0] : get_class($fn[0]);
366
+ return "callable(['{$ele}', '{$fn[1]}'])";
367
+ }
368
+
369
+ return 'callable(' . spl_object_hash($fn) . ')';
370
+ }
371
+
372
+ /**
373
+ * Sort the middleware, and interpose if needed in the sorted list.
374
+ */
375
+ private function sortMiddleware()
376
+ {
377
+ $this->sorted = [];
378
+
379
+ if (!$this->interposeFn) {
380
+ foreach ($this->steps as $step) {
381
+ foreach ($step as $fn) {
382
+ $this->sorted[] = $fn[0];
383
+ }
384
+ }
385
+ return;
386
+ }
387
+
388
+ $ifn = $this->interposeFn;
389
+ // Interpose the interposeFn into the handler stack.
390
+ foreach ($this->steps as $stepName => $step) {
391
+ foreach ($step as $fn) {
392
+ $this->sorted[] = $ifn($stepName, $fn[1]);
393
+ $this->sorted[] = $fn[0];
394
+ }
395
+ }
396
+ }
397
+
398
+ private function removeByName($name)
399
+ {
400
+ if (!isset($this->named[$name])) {
401
+ return;
402
+ }
403
+
404
+ $this->sorted = null;
405
+ $step = $this->named[$name];
406
+ $this->steps[$step] = array_values(
407
+ array_filter(
408
+ $this->steps[$step],
409
+ function ($tuple) use ($name) {
410
+ return $tuple[1] !== $name;
411
+ }
412
+ )
413
+ );
414
+ }
415
+
416
+ private function removeByInstance(callable $fn)
417
+ {
418
+ foreach ($this->steps as $k => $step) {
419
+ foreach ($step as $j => $tuple) {
420
+ if ($tuple[0] === $fn) {
421
+ $this->sorted = null;
422
+ unset($this->named[$this->steps[$k][$j][1]]);
423
+ unset($this->steps[$k][$j]);
424
+ }
425
+ }
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Add a middleware to a step.
431
+ *
432
+ * @param string $step Middleware step.
433
+ * @param string $name Middleware name.
434
+ * @param callable $middleware Middleware function to add.
435
+ * @param bool $prepend Prepend instead of append.
436
+ */
437
+ private function add($step, $name, callable $middleware, $prepend = false)
438
+ {
439
+ $this->sorted = null;
440
+
441
+ if ($prepend) {
442
+ $this->steps[$step][] = [$middleware, $name];
443
+ } else {
444
+ array_unshift($this->steps[$step], [$middleware, $name]);
445
+ }
446
+
447
+ if ($name) {
448
+ $this->named[$name] = $step;
449
+ }
450
+ }
451
+ }
lib/Aws/Aws/HasDataTrait.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Trait implementing ToArrayInterface, \ArrayAccess, \Countable, and
6
+ * \IteratorAggregate
7
+ */
8
+ trait HasDataTrait
9
+ {
10
+ /** @var array */
11
+ private $data = [];
12
+
13
+ public function getIterator()
14
+ {
15
+ return new \ArrayIterator($this->data);
16
+ }
17
+
18
+ /**
19
+ * This method returns a reference to the variable to allow for indirect
20
+ * array modification (e.g., $foo['bar']['baz'] = 'qux').
21
+ *
22
+ * @param $offset
23
+ *
24
+ * @return mixed|null
25
+ */
26
+ public function & offsetGet($offset)
27
+ {
28
+ if (isset($this->data[$offset])) {
29
+ return $this->data[$offset];
30
+ }
31
+
32
+ $value = null;
33
+ return $value;
34
+ }
35
+
36
+ public function offsetSet($offset, $value)
37
+ {
38
+ $this->data[$offset] = $value;
39
+ }
40
+
41
+ public function offsetExists($offset)
42
+ {
43
+ return isset($this->data[$offset]);
44
+ }
45
+
46
+ public function offsetUnset($offset)
47
+ {
48
+ unset($this->data[$offset]);
49
+ }
50
+
51
+ public function toArray()
52
+ {
53
+ return $this->data;
54
+ }
55
+
56
+ public function count()
57
+ {
58
+ return count($this->data);
59
+ }
60
+ }
lib/Aws/Aws/HasMonitoringEventsTrait.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+
5
+ trait HasMonitoringEventsTrait
6
+ {
7
+ private $monitoringEvents = [];
8
+
9
+ /**
10
+ * Get client-side monitoring events attached to this object. Each event is
11
+ * represented as an associative array within the returned array.
12
+ *
13
+ * @return array
14
+ */
15
+ public function getMonitoringEvents()
16
+ {
17
+ return $this->monitoringEvents;
18
+ }
19
+
20
+ /**
21
+ * Prepend a client-side monitoring event to this object's event list
22
+ *
23
+ * @param array $event
24
+ */
25
+ public function prependMonitoringEvent(array $event)
26
+ {
27
+ array_unshift($this->monitoringEvents, $event);
28
+ }
29
+
30
+ /**
31
+ * Append a client-side monitoring event to this object's event list
32
+ *
33
+ * @param array $event
34
+ */
35
+ public function appendMonitoringEvent(array $event)
36
+ {
37
+ $this->monitoringEvents []= $event;
38
+ }
39
+ }
lib/Aws/Aws/HashInterface.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Interface that allows implementing various incremental hashes.
6
+ */
7
+ interface HashInterface
8
+ {
9
+ /**
10
+ * Adds data to the hash.
11
+ *
12
+ * @param string $data Data to add to the hash
13
+ */
14
+ public function update($data);
15
+
16
+ /**
17
+ * Finalizes the incremental hash and returns the resulting digest.
18
+ *
19
+ * @return string
20
+ */
21
+ public function complete();
22
+
23
+ /**
24
+ * Removes all data from the hash, effectively starting a new hash.
25
+ */
26
+ public function reset();
27
+ }
lib/Aws/Aws/HashingStream.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use GuzzleHttp\Psr7\StreamDecoratorTrait;
5
+ use Psr\Http\Message\StreamInterface;
6
+
7
+ /**
8
+ * Stream decorator that calculates a rolling hash of the stream as it is read.
9
+ */
10
+ class HashingStream implements StreamInterface
11
+ {
12
+ use StreamDecoratorTrait;
13
+
14
+ /** @var HashInterface */
15
+ private $hash;
16
+
17
+ /** @var callable|null */
18
+ private $callback;
19
+
20
+ /**
21
+ * @param StreamInterface $stream Stream that is being read.
22
+ * @param HashInterface $hash Hash used to calculate checksum.
23
+ * @param callable $onComplete Optional function invoked when the
24
+ * hash calculation is completed.
25
+ */
26
+ public function __construct(
27
+ StreamInterface $stream,
28
+ HashInterface $hash,
29
+ callable $onComplete = null
30
+ ) {
31
+ $this->stream = $stream;
32
+ $this->hash = $hash;
33
+ $this->callback = $onComplete;
34
+ }
35
+
36
+ public function read($length)
37
+ {
38
+ $data = $this->stream->read($length);
39
+ $this->hash->update($data);
40
+ if ($this->eof()) {
41
+ $result = $this->hash->complete();
42
+ if ($this->callback) {
43
+ call_user_func($this->callback, $result);
44
+ }
45
+ }
46
+
47
+ return $data;
48
+ }
49
+
50
+ public function seek($offset, $whence = SEEK_SET)
51
+ {
52
+ if ($offset === 0) {
53
+ $this->hash->reset();
54
+ return $this->stream->seek($offset);
55
+ }
56
+
57
+ // Seeking arbitrarily is not supported.
58
+ return false;
59
+ }
60
+ }
lib/Aws/Aws/History.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Psr\Http\Message\RequestInterface;
5
+ use Aws\Exception\AwsException;
6
+
7
+ /**
8
+ * Represents a history container that is required when using the history
9
+ * middleware.
10
+ */
11
+ class History implements \Countable, \IteratorAggregate
12
+ {
13
+ private $maxEntries;
14
+ private $entries = array();
15
+
16
+ /**
17
+ * @param int $maxEntries Maximum number of entries to store.
18
+ */
19
+ public function __construct($maxEntries = 10)
20
+ {
21
+ $this->maxEntries = $maxEntries;
22
+ }
23
+
24
+ public function count()
25
+ {
26
+ return count($this->entries);
27
+ }
28
+
29
+ public function getIterator()
30
+ {
31
+ return new \ArrayIterator(array_values($this->entries));
32
+ }
33
+
34
+ /**
35
+ * Get the last finished command seen by the history container.
36
+ *
37
+ * @return CommandInterface
38
+ * @throws \LogicException if no commands have been seen.
39
+ */
40
+ public function getLastCommand()
41
+ {
42
+ if (!$this->entries) {
43
+ throw new \LogicException('No commands received');
44
+ }
45
+
46
+ return end($this->entries)['command'];
47
+ }
48
+
49
+ /**
50
+ * Get the last finished request seen by the history container.
51
+ *
52
+ * @return RequestInterface
53
+ * @throws \LogicException if no requests have been seen.
54
+ */
55
+ public function getLastRequest()
56
+ {
57
+ if (!$this->entries) {
58
+ throw new \LogicException('No requests received');
59
+ }
60
+
61
+ return end($this->entries)['request'];
62
+ }
63
+
64
+ /**
65
+ * Get the last received result or exception.
66
+ *
67
+ * @return ResultInterface|AwsException
68
+ * @throws \LogicException if no return values have been received.
69
+ */
70
+ public function getLastReturn()
71
+ {
72
+ if (!$this->entries) {
73
+ throw new \LogicException('No entries');
74
+ }
75
+
76
+ $last = end($this->entries);
77
+
78
+ if (isset($last['result'])) {
79
+ return $last['result'];
80
+ }
81
+
82
+ if (isset($last['exception'])) {
83
+ return $last['exception'];
84
+ }
85
+
86
+ throw new \LogicException('No return value for last entry.');
87
+ }
88
+
89
+ /**
90
+ * Initiate an entry being added to the history.
91
+ *
92
+ * @param CommandInterface $cmd Command be executed.
93
+ * @param RequestInterface $req Request being sent.
94
+ *
95
+ * @return string Returns the ticket used to finish the entry.
96
+ */
97
+ public function start(CommandInterface $cmd, RequestInterface $req)
98
+ {
99
+ $ticket = uniqid();
100
+ $this->entries[$ticket] = [
101
+ 'command' => $cmd,
102
+ 'request' => $req,
103
+ 'result' => null,
104
+ 'exception' => null,
105
+ ];
106
+
107
+ return $ticket;
108
+ }
109
+
110
+ /**
111
+ * Finish adding an entry to the history container.
112
+ *
113
+ * @param string $ticket Ticket returned from the start call.
114
+ * @param mixed $result The result (an exception or AwsResult).
115
+ */
116
+ public function finish($ticket, $result)
117
+ {
118
+ if (!isset($this->entries[$ticket])) {
119
+ throw new \InvalidArgumentException('Invalid history ticket');
120
+ }
121
+
122
+ if (isset($this->entries[$ticket]['result'])
123
+ || isset($this->entries[$ticket]['exception'])
124
+ ) {
125
+ throw new \LogicException('History entry is already finished');
126
+ }
127
+
128
+ if ($result instanceof \Exception) {
129
+ $this->entries[$ticket]['exception'] = $result;
130
+ } else {
131
+ $this->entries[$ticket]['result'] = $result;
132
+ }
133
+
134
+ if (count($this->entries) >= $this->maxEntries) {
135
+ $this->entries = array_slice($this->entries, -$this->maxEntries, null, true);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Flush the history
141
+ */
142
+ public function clear()
143
+ {
144
+ $this->entries = [];
145
+ }
146
+
147
+ /**
148
+ * Converts the history to an array.
149
+ *
150
+ * @return array
151
+ */
152
+ public function toArray()
153
+ {
154
+ return array_values($this->entries);
155
+ }
156
+ }
lib/Aws/Aws/IdempotencyTokenMiddleware.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\Service;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * @internal Middleware that auto fills parameters with `idempotencyToken` trait
9
+ */
10
+ class IdempotencyTokenMiddleware
11
+ {
12
+ /** @var Service */
13
+ private $service;
14
+ /** @var string */
15
+ private $bytesGenerator;
16
+ /** @var callable */
17
+ private $nextHandler;
18
+
19
+ /**
20
+ * Creates a middleware that populates operation parameter
21
+ * with trait 'idempotencyToken' enabled with a random UUIDv4
22
+ *
23
+ * One of following functions needs to be available
24
+ * in order to generate random bytes used for UUID
25
+ * (SDK will attempt to utilize function in following order):
26
+ * - random_bytes (requires PHP 7.0 or above)
27
+ * - openssl_random_pseudo_bytes (requires 'openssl' module enabled)
28
+ * - mcrypt_create_iv (requires 'mcrypt' module enabled)
29
+ *
30
+ * You may also supply a custom bytes generator as an optional second
31
+ * parameter.
32
+ *
33
+ * @param \Aws\Api\Service $service
34
+ * @param callable|null $bytesGenerator
35
+ *
36
+ * @return callable
37
+ */
38
+ public static function wrap(
39
+ Service $service,
40
+ callable $bytesGenerator = null
41
+ ) {
42
+ return function (callable $handler) use ($service, $bytesGenerator) {
43
+ return new self($handler, $service, $bytesGenerator);
44
+ };
45
+ }
46
+
47
+ public function __construct(
48
+ callable $nextHandler,
49
+ Service $service,
50
+ callable $bytesGenerator = null
51
+ ) {
52
+ $this->bytesGenerator = $bytesGenerator
53
+ ?: $this->findCompatibleRandomSource();
54
+ $this->service = $service;
55
+ $this->nextHandler = $nextHandler;
56
+ }
57
+
58
+ public function __invoke(
59
+ CommandInterface $command,
60
+ RequestInterface $request = null
61
+ ) {
62
+ $handler = $this->nextHandler;
63
+ if ($this->bytesGenerator) {
64
+ $operation = $this->service->getOperation($command->getName());
65
+ $members = $operation->getInput()->getMembers();
66
+ foreach ($members as $member => $value) {
67
+ if ($value['idempotencyToken']) {
68
+ $bytes = call_user_func($this->bytesGenerator, 16);
69
+ // populating UUIDv4 only when the parameter is not set
70
+ $command[$member] = $command[$member]
71
+ ?: $this->getUuidV4($bytes);
72
+ // only one member could have the trait enabled
73
+ break;
74
+ }
75
+ }
76
+ }
77
+ return $handler($command, $request);
78
+ }
79
+
80
+ /**
81
+ * This function generates a random UUID v4 string,
82
+ * which is used as auto filled token value.
83
+ *
84
+ * @param string $bytes 16 bytes of pseudo-random bytes
85
+ * @return string
86
+ * More information about UUID v4, see:
87
+ * https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
88
+ * https://tools.ietf.org/html/rfc4122#page-14
89
+ */
90
+ private static function getUuidV4($bytes)
91
+ {
92
+ // set version to 0100
93
+ $bytes[6] = chr(ord($bytes[6]) & 0x0f | 0x40);
94
+ // set bits 6-7 to 10
95
+ $bytes[8] = chr(ord($bytes[8]) & 0x3f | 0x80);
96
+ return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
97
+ }
98
+
99
+ /**
100
+ * This function decides the PHP function used in generating random bytes.
101
+ *
102
+ * @return callable|null
103
+ */
104
+ private function findCompatibleRandomSource()
105
+ {
106
+ if (function_exists('random_bytes')) {
107
+ return 'random_bytes';
108
+ }
109
+
110
+ if (function_exists('openssl_random_pseudo_bytes')) {
111
+ return 'openssl_random_pseudo_bytes';
112
+ }
113
+
114
+ if (function_exists('mcrypt_create_iv')) {
115
+ return 'mcrypt_create_iv';
116
+ }
117
+ }
118
+ }
lib/Aws/Aws/JsonCompiler.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Loads JSON files and compiles them into PHP arrays.
6
+ *
7
+ * @internal Please use json_decode instead.
8
+ * @deprecated
9
+ */
10
+ class JsonCompiler
11
+ {
12
+ const CACHE_ENV = 'AWS_PHP_CACHE_DIR';
13
+
14
+ /**
15
+ * Loads a JSON file from cache or from the JSON file directly.
16
+ *
17
+ * @param string $path Path to the JSON file to load.
18
+ *
19
+ * @return mixed
20
+ */
21
+ public function load($path)
22
+ {
23
+ return load_compiled_json($path);
24
+ }
25
+ }
lib/Aws/Aws/LruArrayCache.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Simple in-memory LRU cache that limits the number of cached entries.
6
+ *
7
+ * The LRU cache is implemented using PHP's ordered associative array. When
8
+ * accessing an element, the element is removed from the hash and re-added to
9
+ * ensure that recently used items are always at the end of the list while
10
+ * least recently used are at the beginning. When a value is added to the
11
+ * cache, if the number of cached items exceeds the allowed number, the first
12
+ * N number of items are removed from the array.
13
+ */
14
+ class LruArrayCache implements CacheInterface, \Countable
15
+ {
16
+ /** @var int */
17
+ private $maxItems;
18
+
19
+ /** @var array */
20
+ private $items = array();
21
+
22
+ /**
23
+ * @param int $maxItems Maximum number of allowed cache items.
24
+ */
25
+ public function __construct($maxItems = 1000)
26
+ {
27
+ $this->maxItems = $maxItems;
28
+ }
29
+
30
+ public function get($key)
31
+ {
32
+ if (!isset($this->items[$key])) {
33
+ return null;
34
+ }
35
+
36
+ $entry = $this->items[$key];
37
+
38
+ // Ensure the item is not expired.
39
+ if (!$entry[1] || time() < $entry[1]) {
40
+ // LRU: remove the item and push it to the end of the array.
41
+ unset($this->items[$key]);
42
+ $this->items[$key] = $entry;
43
+ return $entry[0];
44
+ }
45
+
46
+ unset($this->items[$key]);
47
+ return null;
48
+ }
49
+
50
+ public function set($key, $value, $ttl = 0)
51
+ {
52
+ // Only call time() if the TTL is not 0/false/null
53
+ $ttl = $ttl ? time() + $ttl : 0;
54
+ $this->items[$key] = [$value, $ttl];
55
+
56
+ // Determine if there are more items in the cache than allowed.
57
+ $diff = count($this->items) - $this->maxItems;
58
+
59
+ // Clear out least recently used items.
60
+ if ($diff > 0) {
61
+ // Reset to the beginning of the array and begin unsetting.
62
+ reset($this->items);
63
+ for ($i = 0; $i < $diff; $i++) {
64
+ unset($this->items[key($this->items)]);
65
+ next($this->items);
66
+ }
67
+ }
68
+ }
69
+
70
+ public function remove($key)
71
+ {
72
+ unset($this->items[$key]);
73
+ }
74
+
75
+ public function count()
76
+ {
77
+ return count($this->items);
78
+ }
79
+ }
lib/Aws/Aws/Middleware.php ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\Service;
5
+ use Aws\Api\Validator;
6
+ use Aws\Credentials\CredentialsInterface;
7
+ use Aws\Exception\AwsException;
8
+ use GuzzleHttp\Promise;
9
+ use GuzzleHttp\Psr7;
10
+ use GuzzleHttp\Psr7\LazyOpenStream;
11
+ use Psr\Http\Message\RequestInterface;
12
+
13
+ final class Middleware
14
+ {
15
+ /**
16
+ * Middleware used to allow a command parameter (e.g., "SourceFile") to
17
+ * be used to specify the source of data for an upload operation.
18
+ *
19
+ * @param Service $api
20
+ * @param string $bodyParameter
21
+ * @param string $sourceParameter
22
+ *
23
+ * @return callable
24
+ */
25
+ public static function sourceFile(
26
+ Service $api,
27
+ $bodyParameter = 'Body',
28
+ $sourceParameter = 'SourceFile'
29
+ ) {
30
+ return function (callable $handler) use (
31
+ $api,
32
+ $bodyParameter,
33
+ $sourceParameter
34
+ ) {
35
+ return function (
36
+ CommandInterface $command,
37
+ RequestInterface $request = null)
38
+ use (
39
+ $handler,
40
+ $api,
41
+ $bodyParameter,
42
+ $sourceParameter
43
+ ) {
44
+ $operation = $api->getOperation($command->getName());
45
+ $source = $command[$sourceParameter];
46
+
47
+ if ($source !== null
48
+ && $operation->getInput()->hasMember($bodyParameter)
49
+ ) {
50
+ $command[$bodyParameter] = new LazyOpenStream($source, 'r');
51
+ unset($command[$sourceParameter]);
52
+ }
53
+
54
+ return $handler($command, $request);
55
+ };
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Adds a middleware that uses client-side validation.
61
+ *
62
+ * @param Service $api API being accessed.
63
+ *
64
+ * @return callable
65
+ */
66
+ public static function validation(Service $api, Validator $validator = null)
67
+ {
68
+ $validator = $validator ?: new Validator();
69
+ return function (callable $handler) use ($api, $validator) {
70
+ return function (
71
+ CommandInterface $command,
72
+ RequestInterface $request = null
73
+ ) use ($api, $validator, $handler) {
74
+ $operation = $api->getOperation($command->getName());
75
+ $validator->validate(
76
+ $command->getName(),
77
+ $operation->getInput(),
78
+ $command->toArray()
79
+ );
80
+ return $handler($command, $request);
81
+ };
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Builds an HTTP request for a command.
87
+ *
88
+ * @param callable $serializer Function used to serialize a request for a
89
+ * command.
90
+ * @return callable
91
+ */
92
+ public static function requestBuilder(callable $serializer)
93
+ {
94
+ return function (callable $handler) use ($serializer) {
95
+ return function (CommandInterface $command) use ($serializer, $handler) {
96
+ return $handler($command, $serializer($command));
97
+ };
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Creates a middleware that signs requests for a command.
103
+ *
104
+ * @param callable $credProvider Credentials provider function that
105
+ * returns a promise that is resolved
106
+ * with a CredentialsInterface object.
107
+ * @param callable $signatureFunction Function that accepts a Command
108
+ * object and returns a
109
+ * SignatureInterface.
110
+ *
111
+ * @return callable
112
+ */
113
+ public static function signer(callable $credProvider, callable $signatureFunction)
114
+ {
115
+ return function (callable $handler) use ($signatureFunction, $credProvider) {
116
+ return function (
117
+ CommandInterface $command,
118
+ RequestInterface $request
119
+ ) use ($handler, $signatureFunction, $credProvider) {
120
+ $signer = $signatureFunction($command);
121
+ return $credProvider()->then(
122
+ function (CredentialsInterface $creds)
123
+ use ($handler, $command, $signer, $request) {
124
+ return $handler(
125
+ $command,
126
+ $signer->signRequest($request, $creds)
127
+ );
128
+ }
129
+ );
130
+ };
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Creates a middleware that invokes a callback at a given step.
136
+ *
137
+ * The tap callback accepts a CommandInterface and RequestInterface as
138
+ * arguments but is not expected to return a new value or proxy to
139
+ * downstream middleware. It's simply a way to "tap" into the handler chain
140
+ * to debug or get an intermediate value.
141
+ *
142
+ * @param callable $fn Tap function
143
+ *
144
+ * @return callable
145
+ */
146
+ public static function tap(callable $fn)
147
+ {
148
+ return function (callable $handler) use ($fn) {
149
+ return function (
150
+ CommandInterface $command,
151
+ RequestInterface $request = null
152
+ ) use ($handler, $fn) {
153
+ $fn($command, $request);
154
+ return $handler($command, $request);
155
+ };
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Middleware wrapper function that retries requests based on the boolean
161
+ * result of invoking the provided "decider" function.
162
+ *
163
+ * If no delay function is provided, a simple implementation of exponential
164
+ * backoff will be utilized.
165
+ *
166
+ * @param callable $decider Function that accepts the number of retries,
167
+ * a request, [result], and [exception] and
168
+ * returns true if the command is to be retried.
169
+ * @param callable $delay Function that accepts the number of retries and
170
+ * returns the number of milliseconds to delay.
171
+ * @param bool $stats Whether to collect statistics on retries and the
172
+ * associated delay.
173
+ *
174
+ * @return callable
175
+ */
176
+ public static function retry(
177
+ callable $decider = null,
178
+ callable $delay = null,
179
+ $stats = false
180
+ ) {
181
+ $decider = $decider ?: RetryMiddleware::createDefaultDecider();
182
+ $delay = $delay ?: [RetryMiddleware::class, 'exponentialDelay'];
183
+
184
+ return function (callable $handler) use ($decider, $delay, $stats) {
185
+ return new RetryMiddleware($decider, $delay, $handler, $stats);
186
+ };
187
+ }
188
+ /**
189
+ * Middleware wrapper function that adds an invocation id header to
190
+ * requests, which is only applied after the build step.
191
+ *
192
+ * This is a uniquely generated UUID to identify initial and subsequent
193
+ * retries as part of a complete request lifecycle.
194
+ *
195
+ * @return callable
196
+ */
197
+ public static function invocationId()
198
+ {
199
+ return function (callable $handler) {
200
+ return function (
201
+ CommandInterface $command,
202
+ RequestInterface $request
203
+ ) use ($handler){
204
+ return $handler($command, $request->withHeader(
205
+ 'aws-sdk-invocation-id',
206
+ md5(uniqid(gethostname(), true))
207
+ ));
208
+ };
209
+ };
210
+ }
211
+ /**
212
+ * Middleware wrapper function that adds a Content-Type header to requests.
213
+ * This is only done when the Content-Type has not already been set, and the
214
+ * request body's URI is available. It then checks the file extension of the
215
+ * URI to determine the mime-type.
216
+ *
217
+ * @param array $operations Operations that Content-Type should be added to.
218
+ *
219
+ * @return callable
220
+ */
221
+ public static function contentType(array $operations)
222
+ {
223
+ return function (callable $handler) use ($operations) {
224
+ return function (
225
+ CommandInterface $command,
226
+ RequestInterface $request = null
227
+ ) use ($handler, $operations) {
228
+ if (!$request->hasHeader('Content-Type')
229
+ && in_array($command->getName(), $operations, true)
230
+ && ($uri = $request->getBody()->getMetadata('uri'))
231
+ ) {
232
+ $request = $request->withHeader(
233
+ 'Content-Type',
234
+ Psr7\mimetype_from_filename($uri) ?: 'application/octet-stream'
235
+ );
236
+ }
237
+
238
+ return $handler($command, $request);
239
+ };
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Tracks command and request history using a history container.
245
+ *
246
+ * This is useful for testing.
247
+ *
248
+ * @param History $history History container to store entries.
249
+ *
250
+ * @return callable
251
+ */
252
+ public static function history(History $history)
253
+ {
254
+ return function (callable $handler) use ($history) {
255
+ return function (
256
+ CommandInterface $command,
257
+ RequestInterface $request = null
258
+ ) use ($handler, $history) {
259
+ $ticket = $history->start($command, $request);
260
+ return $handler($command, $request)
261
+ ->then(
262
+ function ($result) use ($history, $ticket) {
263
+ $history->finish($ticket, $result);
264
+ return $result;
265
+ },
266
+ function ($reason) use ($history, $ticket) {
267
+ $history->finish($ticket, $reason);
268
+ return Promise\rejection_for($reason);
269
+ }
270
+ );
271
+ };
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Creates a middleware that applies a map function to requests as they
277
+ * pass through the middleware.
278
+ *
279
+ * @param callable $f Map function that accepts a RequestInterface and
280
+ * returns a RequestInterface.
281
+ *
282
+ * @return callable
283
+ */
284
+ public static function mapRequest(callable $f)
285
+ {
286
+ return function (callable $handler) use ($f) {
287
+ return function (
288
+ CommandInterface $command,
289
+ RequestInterface $request = null
290
+ ) use ($handler, $f) {
291
+ return $handler($command, $f($request));
292
+ };
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Creates a middleware that applies a map function to commands as they
298
+ * pass through the middleware.
299
+ *
300
+ * @param callable $f Map function that accepts a command and returns a
301
+ * command.
302
+ *
303
+ * @return callable
304
+ */
305
+ public static function mapCommand(callable $f)
306
+ {
307
+ return function (callable $handler) use ($f) {
308
+ return function (
309
+ CommandInterface $command,
310
+ RequestInterface $request = null
311
+ ) use ($handler, $f) {
312
+ return $handler($f($command), $request);
313
+ };
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Creates a middleware that applies a map function to results.
319
+ *
320
+ * @param callable $f Map function that accepts an Aws\ResultInterface and
321
+ * returns an Aws\ResultInterface.
322
+ *
323
+ * @return callable
324
+ */
325
+ public static function mapResult(callable $f)
326
+ {
327
+ return function (callable $handler) use ($f) {
328
+ return function (
329
+ CommandInterface $command,
330
+ RequestInterface $request = null
331
+ ) use ($handler, $f) {
332
+ return $handler($command, $request)->then($f);
333
+ };
334
+ };
335
+ }
336
+
337
+ public static function timer()
338
+ {
339
+ return function (callable $handler) {
340
+ return function (
341
+ CommandInterface $command,
342
+ RequestInterface $request = null
343
+ ) use ($handler) {
344
+ $start = microtime(true);
345
+ return $handler($command, $request)
346
+ ->then(
347
+ function (ResultInterface $res) use ($start) {
348
+ if (!isset($res['@metadata'])) {
349
+ $res['@metadata'] = [];
350
+ }
351
+ if (!isset($res['@metadata']['transferStats'])) {
352
+ $res['@metadata']['transferStats'] = [];
353
+ }
354
+
355
+ $res['@metadata']['transferStats']['total_time']
356
+ = microtime(true) - $start;
357
+
358
+ return $res;
359
+ },
360
+ function ($err) use ($start) {
361
+ if ($err instanceof AwsException) {
362
+ $err->setTransferInfo([
363
+ 'total_time' => microtime(true) - $start,
364
+ ] + $err->getTransferInfo());
365
+ }
366
+ return Promise\rejection_for($err);
367
+ }
368
+ );
369
+ };
370
+ };
371
+ }
372
+ }
lib/Aws/Aws/MockHandler.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Exception\AwsException;
5
+ use GuzzleHttp\Promise;
6
+ use GuzzleHttp\Promise\RejectedPromise;
7
+ use Psr\Http\Message\RequestInterface;
8
+
9
+ /**
10
+ * Returns promises that are rejected or fulfilled using a queue of
11
+ * Aws\ResultInterface and Aws\Exception\AwsException objects.
12
+ */
13
+ class MockHandler implements \Countable
14
+ {
15
+ private $queue;
16
+ private $lastCommand;
17
+ private $lastRequest;
18
+ private $onFulfilled;
19
+ private $onRejected;
20
+
21
+ /**
22
+ * The passed in value must be an array of {@see Aws\ResultInterface} or
23
+ * {@see AwsException} objects that acts as a queue of results or
24
+ * exceptions to return each time the handler is invoked.
25
+ *
26
+ * @param array $resultOrQueue
27
+ * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
28
+ * @param callable $onRejected Callback to invoke when the return value is rejected.
29
+ */
30
+ public function __construct(
31
+ array $resultOrQueue = [],
32
+ callable $onFulfilled = null,
33
+ callable $onRejected = null
34
+ ) {
35
+ $this->onFulfilled = $onFulfilled;
36
+ $this->onRejected = $onRejected;
37
+
38
+ if ($resultOrQueue) {
39
+ call_user_func_array([$this, 'append'], $resultOrQueue);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Adds one or more variadic ResultInterface or AwsException objects to the
45
+ * queue.
46
+ */
47
+ public function append()
48
+ {
49
+ foreach (func_get_args() as $value) {
50
+ if ($value instanceof ResultInterface
51
+ || $value instanceof AwsException
52
+ || is_callable($value)
53
+ ) {
54
+ $this->queue[] = $value;
55
+ } else {
56
+ throw new \InvalidArgumentException('Expected an Aws\ResultInterface or Aws\Exception\AwsException.');
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Adds one or more \Exception or \Throwable to the queue
63
+ */
64
+ public function appendException()
65
+ {
66
+ foreach (func_get_args() as $value) {
67
+ if ($value instanceof \Exception || $value instanceof \Throwable) {
68
+ $this->queue[] = $value;
69
+ } else {
70
+ throw new \InvalidArgumentException('Expected an \Exception or \Throwable.');
71
+ }
72
+ }
73
+ }
74
+
75
+ public function __invoke(
76
+ CommandInterface $command,
77
+ RequestInterface $request
78
+ ) {
79
+ if (!$this->queue) {
80
+ $last = $this->lastCommand
81
+ ? ' The last command sent was ' . $this->lastCommand->getName() . '.'
82
+ : '';
83
+ throw new \RuntimeException('Mock queue is empty. Trying to send a '
84
+ . $command->getName() . ' command failed.' . $last);
85
+ }
86
+
87
+ $this->lastCommand = $command;
88
+ $this->lastRequest = $request;
89
+
90
+ $result = array_shift($this->queue);
91
+
92
+ if (is_callable($result)) {
93
+ $result = $result($command, $request);
94
+ }
95
+
96
+ if ($result instanceof \Exception) {
97
+ $result = new RejectedPromise($result);
98
+ } else {
99
+ // Add an effective URI and statusCode if not present.
100
+ $meta = $result['@metadata'];
101
+ if (!isset($meta['effectiveUri'])) {
102
+ $meta['effectiveUri'] = (string) $request->getUri();
103
+ }
104
+ if (!isset($meta['statusCode'])) {
105
+ $meta['statusCode'] = 200;
106
+ }
107
+ $result['@metadata'] = $meta;
108
+ $result = Promise\promise_for($result);
109
+ }
110
+
111
+ $result->then($this->onFulfilled, $this->onRejected);
112
+
113
+ return $result;
114
+ }
115
+
116
+ /**
117
+ * Get the last received request.
118
+ *
119
+ * @return RequestInterface
120
+ */
121
+ public function getLastRequest()
122
+ {
123
+ return $this->lastRequest;
124
+ }
125
+
126
+ /**
127
+ * Get the last received command.
128
+ *
129
+ * @return CommandInterface
130
+ */
131
+ public function getLastCommand()
132
+ {
133
+ return $this->lastCommand;
134
+ }
135
+
136
+ /**
137
+ * Returns the number of remaining items in the queue.
138
+ *
139
+ * @return int
140
+ */
141
+ public function count()
142
+ {
143
+ return count($this->queue);
144
+ }
145
+ }
lib/Aws/Aws/MonitoringEventsInterface.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Interface for adding and retrieving client-side monitoring events
6
+ */
7
+ interface MonitoringEventsInterface
8
+ {
9
+
10
+ /**
11
+ * Get client-side monitoring events attached to this object. Each event is
12
+ * represented as an associative array within the returned array.
13
+ *
14
+ * @return array
15
+ */
16
+ public function getMonitoringEvents();
17
+
18
+ /**
19
+ * Prepend a client-side monitoring event to this object's event list
20
+ *
21
+ * @param array $event
22
+ */
23
+ public function prependMonitoringEvent(array $event);
24
+
25
+ /**
26
+ * Append a client-side monitoring event to this object's event list
27
+ *
28
+ * @param array $event
29
+ */
30
+ public function appendMonitoringEvent(array $event);
31
+
32
+ }
lib/Aws/Aws/MultiRegionClient.php ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Endpoint\PartitionEndpointProvider;
5
+ use Aws\Endpoint\PartitionInterface;
6
+
7
+ class MultiRegionClient implements AwsClientInterface
8
+ {
9
+ use AwsClientTrait;
10
+
11
+ /** @var AwsClientInterface[] A pool of clients keyed by region. */
12
+ private $clientPool = [];
13
+ /** @var callable */
14
+ private $factory;
15
+ /** @var PartitionInterface */
16
+ private $partition;
17
+ /** @var array */
18
+ private $args;
19
+ /** @var array */
20
+ private $config;
21
+ /** @var HandlerList */
22
+ private $handlerList;
23
+
24
+ public static function getArguments()
25
+ {
26
+ $args = array_intersect_key(
27
+ ClientResolver::getDefaultArguments(),
28
+ ['service' => true, 'region' => true]
29
+ );
30
+ $args['region']['required'] = false;
31
+
32
+ return $args + [
33
+ 'client_factory' => [
34
+ 'type' => 'config',
35
+ 'valid' => ['callable'],
36
+ 'doc' => 'A callable that takes an array of client'
37
+ . ' configuration arguments and returns a regionalized'
38
+ . ' client.',
39
+ 'required' => true,
40
+ 'internal' => true,
41
+ 'default' => function (array $args) {
42
+ $namespace = manifest($args['service'])['namespace'];
43
+ $klass = "Aws\\{$namespace}\\{$namespace}Client";
44
+ $region = isset($args['region']) ? $args['region'] : null;
45
+
46
+ return function (array $args) use ($klass, $region) {
47
+ if ($region && empty($args['region'])) {
48
+ $args['region'] = $region;
49
+ }
50
+
51
+ return new $klass($args);
52
+ };
53
+ },
54
+ ],
55
+ 'partition' => [
56
+ 'type' => 'config',
57
+ 'valid' => ['string', PartitionInterface::class],
58
+ 'doc' => 'AWS partition to connect to. Valid partitions'
59
+ . ' include "aws," "aws-cn," and "aws-us-gov." Used to'
60
+ . ' restrict the scope of the mapRegions method.',
61
+ 'default' => function (array $args) {
62
+ $region = isset($args['region']) ? $args['region'] : '';
63
+ return PartitionEndpointProvider::defaultProvider()
64
+ ->getPartition($region, $args['service']);
65
+ },
66
+ 'fn' => function ($value, array &$args) {
67
+ if (is_string($value)) {
68
+ $value = PartitionEndpointProvider::defaultProvider()
69
+ ->getPartitionByName($value);
70
+ }
71
+
72
+ if (!$value instanceof PartitionInterface) {
73
+ throw new \InvalidArgumentException('No valid partition'
74
+ . ' was provided. Provide a concrete partition or'
75
+ . ' the name of a partition (e.g., "aws," "aws-cn,"'
76
+ . ' or "aws-us-gov").'
77
+ );
78
+ }
79
+
80
+ $args['partition'] = $value;
81
+ $args['endpoint_provider'] = $value;
82
+ }
83
+ ],
84
+ ];
85
+ }
86
+
87
+ /**
88
+ * The multi-region client constructor accepts the following options:
89
+ *
90
+ * - client_factory: (callable) An optional callable that takes an array of
91
+ * client configuration arguments and returns a regionalized client.
92
+ * - partition: (Aws\Endpoint\Partition|string) AWS partition to connect to.
93
+ * Valid partitions include "aws," "aws-cn," and "aws-us-gov." Used to
94
+ * restrict the scope of the mapRegions method.
95
+ * - region: (string) Region to connect to when no override is provided.
96
+ * Used to create the default client factory and determine the appropriate
97
+ * AWS partition when present.
98
+ *
99
+ * @param array $args Client configuration arguments.
100
+ */
101
+ public function __construct(array $args = [])
102
+ {
103
+ if (!isset($args['service'])) {
104
+ $args['service'] = $this->parseClass();
105
+ }
106
+
107
+ $this->handlerList = new HandlerList(function (
108
+ CommandInterface $command
109
+ ) {
110
+ list($region, $args) = $this->getRegionFromArgs($command->toArray());
111
+ $command = $this->getClientFromPool($region)
112
+ ->getCommand($command->getName(), $args);
113
+ return $this->executeAsync($command);
114
+ });
115
+
116
+ $argDefinitions = static::getArguments();
117
+ $resolver = new ClientResolver($argDefinitions);
118
+ $args = $resolver->resolve($args, $this->handlerList);
119
+ $this->config = $args['config'];
120
+ $this->factory = $args['client_factory'];
121
+ $this->partition = $args['partition'];
122
+ $this->args = array_diff_key($args, $args['config']);
123
+ }
124
+
125
+ /**
126
+ * Get the region to which the client is configured to send requests by
127
+ * default.
128
+ *
129
+ * @return string
130
+ */
131
+ public function getRegion()
132
+ {
133
+ return $this->getClientFromPool()->getRegion();
134
+ }
135
+
136
+ /**
137
+ * Create a command for an operation name.
138
+ *
139
+ * Special keys may be set on the command to control how it behaves,
140
+ * including:
141
+ *
142
+ * - @http: Associative array of transfer specific options to apply to the
143
+ * request that is serialized for this command. Available keys include
144
+ * "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and
145
+ * "headers".
146
+ * - @region: The region to which the command should be sent.
147
+ *
148
+ * @param string $name Name of the operation to use in the command
149
+ * @param array $args Arguments to pass to the command
150
+ *
151
+ * @return CommandInterface
152
+ * @throws \InvalidArgumentException if no command can be found by name
153
+ */
154
+ public function getCommand($name, array $args = [])
155
+ {
156
+ return new Command($name, $args, clone $this->getHandlerList());
157
+ }
158
+
159
+ public function getConfig($option = null)
160
+ {
161
+ if (null === $option) {
162
+ return $this->config;
163
+ }
164
+
165
+ if (isset($this->config[$option])) {
166
+ return $this->config[$option];
167
+ }
168
+
169
+ return $this->getClientFromPool()->getConfig($option);
170
+ }
171
+
172
+ public function getCredentials()
173
+ {
174
+ return $this->getClientFromPool()->getCredentials();
175
+ }
176
+
177
+ public function getHandlerList()
178
+ {
179
+ return $this->handlerList;
180
+ }
181
+
182
+ public function getApi()
183
+ {
184
+ return $this->getClientFromPool()->getApi();
185
+ }
186
+
187
+ public function getEndpoint()
188
+ {
189
+ return $this->getClientFromPool()->getEndpoint();
190
+ }
191
+
192
+ /**
193
+ * @param string $region Omit this argument or pass in an empty string to
194
+ * allow the configured client factory to apply the
195
+ * region.
196
+ *
197
+ * @return AwsClientInterface
198
+ */
199
+ protected function getClientFromPool($region = '')
200
+ {
201
+ if (empty($this->clientPool[$region])) {
202
+ $factory = $this->factory;
203
+ $this->clientPool[$region] = $factory(
204
+ array_replace($this->args, array_filter(['region' => $region]))
205
+ );
206
+ }
207
+
208
+ return $this->clientPool[$region];
209
+ }
210
+
211
+ /**
212
+ * Parse the class name and return the "service" name of the client.
213
+ *
214
+ * @return string
215
+ */
216
+ private function parseClass()
217
+ {
218
+ $klass = get_class($this);
219
+
220
+ if ($klass === __CLASS__) {
221
+ return '';
222
+ }
223
+
224
+ return strtolower(substr($klass, strrpos($klass, '\\') + 1, -17));
225
+ }
226
+
227
+ private function getRegionFromArgs(array $args)
228
+ {
229
+ $region = isset($args['@region'])
230
+ ? $args['@region']
231
+ : $this->getRegion();
232
+ unset($args['@region']);
233
+
234
+ return [$region, $args];
235
+ }
236
+ }
lib/Aws/Aws/PhpHash.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Incremental hashing using PHP's hash functions.
6
+ */
7
+ class PhpHash implements HashInterface
8
+ {
9
+ /** @var resource|\HashContext */
10
+ private $context;
11
+
12
+ /** @var string */
13
+ private $algo;
14
+
15
+ /** @var array */
16
+ private $options;
17
+
18
+ /** @var string */
19
+ private $hash;
20
+
21
+ /**
22
+ * @param string $algo Hashing algorithm. One of PHP's hash_algos()
23
+ * return values (e.g. md5, sha1, etc...).
24
+ * @param array $options Associative array of hashing options:
25
+ * - key: Secret key used with the hashing algorithm.
26
+ * - base64: Set to true to base64 encode the value when complete.
27
+ */
28
+ public function __construct($algo, array $options = [])
29
+ {
30
+ $this->algo = $algo;
31
+ $this->options = $options;
32
+ }
33
+
34
+ public function update($data)
35
+ {
36
+ if ($this->hash !== null) {
37
+ $this->reset();
38
+ }
39
+
40
+ hash_update($this->getContext(), $data);
41
+ }
42
+
43
+ public function complete()
44
+ {
45
+ if ($this->hash) {
46
+ return $this->hash;
47
+ }
48
+
49
+ $this->hash = hash_final($this->getContext(), true);
50
+
51
+ if (isset($this->options['base64']) && $this->options['base64']) {
52
+ $this->hash = base64_encode($this->hash);
53
+ }
54
+
55
+ return $this->hash;
56
+ }
57
+
58
+ public function reset()
59
+ {
60
+ $this->context = $this->hash = null;
61
+ }
62
+
63
+ /**
64
+ * Get a hash context or create one if needed
65
+ *
66
+ * @return resource|\HashContext
67
+ */
68
+ private function getContext()
69
+ {
70
+ if (!$this->context) {
71
+ $key = isset($this->options['key']) ? $this->options['key'] : null;
72
+ $this->context = hash_init(
73
+ $this->algo,
74
+ $key ? HASH_HMAC : 0,
75
+ $key
76
+ );
77
+ }
78
+
79
+ return $this->context;
80
+ }
81
+ }
lib/Aws/Aws/PresignUrlMiddleware.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Signature\SignatureV4;
5
+ use Aws\Endpoint\EndpointProvider;
6
+ use GuzzleHttp\Psr7\Uri;
7
+ use Psr\Http\Message\RequestInterface;
8
+
9
+ /**
10
+ * @internal Adds computed values to service operations that need presigned url.
11
+ */
12
+ class PresignUrlMiddleware
13
+ {
14
+ private $client;
15
+ private $endpointProvider;
16
+ private $nextHandler;
17
+ /** @var array names of operations that require presign url */
18
+ private $commandPool;
19
+ /** @var string */
20
+ private $serviceName;
21
+ /** @var string */
22
+ private $presignParam;
23
+ /** @var bool */
24
+ private $requireDifferentRegion;
25
+
26
+ public function __construct(
27
+ array $options,
28
+ callable $endpointProvider,
29
+ AwsClientInterface $client,
30
+ callable $nextHandler
31
+ ) {
32
+ $this->endpointProvider = $endpointProvider;
33
+ $this->client = $client;
34
+ $this->nextHandler = $nextHandler;
35
+ $this->commandPool = $options['operations'];
36
+ $this->serviceName = $options['service'];
37
+ $this->presignParam = $options['presign_param'];
38
+ $this->requireDifferentRegion = !empty($options['require_different_region']);
39
+ }
40
+
41
+ public static function wrap(
42
+ AwsClientInterface $client,
43
+ callable $endpointProvider,
44
+ array $options = []
45
+ ) {
46
+ return function (callable $handler) use ($endpointProvider, $client, $options) {
47
+ $f = new PresignUrlMiddleware($options, $endpointProvider, $client, $handler);
48
+ return $f;
49
+ };
50
+ }
51
+
52
+ public function __invoke(CommandInterface $cmd, RequestInterface $request = null)
53
+ {
54
+ if (in_array($cmd->getName(), $this->commandPool)
55
+ && (!isset($cmd->{'__skip' . $cmd->getName()}))
56
+ ) {
57
+ $cmd['DestinationRegion'] = $this->client->getRegion();
58
+ if (!$this->requireDifferentRegion
59
+ || (!empty($cmd['SourceRegion'])
60
+ && $cmd['SourceRegion'] !== $cmd['DestinationRegion'])
61
+ ) {
62
+ $cmd[$this->presignParam] = $this->createPresignedUrl($this->client, $cmd);
63
+ }
64
+ }
65
+
66
+ $f = $this->nextHandler;
67
+ return $f($cmd, $request);
68
+ }
69
+
70
+ private function createPresignedUrl(
71
+ AwsClientInterface $client,
72
+ CommandInterface $cmd
73
+ ) {
74
+ $cmdName = $cmd->getName();
75
+ $newCmd = $client->getCommand($cmdName, $cmd->toArray());
76
+ // Avoid infinite recursion by flagging the new command.
77
+ $newCmd->{'__skip' . $cmdName} = true;
78
+
79
+ // Serialize a request for the operation.
80
+ $request = \Aws\serialize($newCmd);
81
+ // Create the new endpoint for the target endpoint.
82
+ $endpoint = EndpointProvider::resolve($this->endpointProvider, [
83
+ 'region' => $cmd['SourceRegion'],
84
+ 'service' => $this->serviceName,
85
+ ])['endpoint'];
86
+
87
+ // Set the request to hit the target endpoint.
88
+ $uri = $request->getUri()->withHost((new Uri($endpoint))->getHost());
89
+ $request = $request->withUri($uri);
90
+ // Create a presigned URL for our generated request.
91
+ $signer = new SignatureV4($this->serviceName, $cmd['SourceRegion']);
92
+
93
+ return (string) $signer->presign(
94
+ SignatureV4::convertPostToGet($request),
95
+ $client->getCredentials()->wait(),
96
+ '+1 hour'
97
+ )->getUri();
98
+ }
99
+ }
lib/Aws/Aws/Psr16CacheAdapter.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
5
+
6
+ class Psr16CacheAdapter implements CacheInterface
7
+ {
8
+ /** @var SimpleCacheInterface */
9
+ private $cache;
10
+
11
+ public function __construct(SimpleCacheInterface $cache)
12
+ {
13
+ $this->cache = $cache;
14
+ }
15
+
16
+ public function get($key)
17
+ {
18
+ return $this->cache->get($key);
19
+ }
20
+
21
+ public function set($key, $value, $ttl = 0)
22
+ {
23
+ $this->cache->set($key, $value, $ttl);
24
+ }
25
+
26
+ public function remove($key)
27
+ {
28
+ $this->cache->delete($key);
29
+ }
30
+ }
lib/Aws/Aws/PsrCacheAdapter.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Psr\Cache\CacheItemPoolInterface;
5
+
6
+ class PsrCacheAdapter implements CacheInterface
7
+ {
8
+ /** @var CacheItemPoolInterface */
9
+ private $pool;
10
+
11
+ public function __construct(CacheItemPoolInterface $pool)
12
+ {
13
+ $this->pool = $pool;
14
+ }
15
+
16
+ public function get($key)
17
+ {
18
+ $item = $this->pool->getItem($key);
19
+
20
+ return $item->isHit() ? $item->get() : null;
21
+ }
22
+
23
+ public function set($key, $value, $ttl = 0)
24
+ {
25
+ $item = $this->pool->getItem($key);
26
+ $item->set($value);
27
+ if ($ttl > 0) {
28
+ $item->expiresAfter($ttl);
29
+ }
30
+
31
+ $this->pool->save($item);
32
+ }
33
+
34
+ public function remove($key)
35
+ {
36
+ $this->pool->deleteItem($key);
37
+ }
38
+ }
lib/Aws/Aws/ResponseContainerInterface.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aws;
4
+
5
+ interface ResponseContainerInterface
6
+ {
7
+ /**
8
+ * Get the received HTTP response if any.
9
+ *
10
+ * @return ResponseInterface|null
11
+ */
12
+ public function getResponse();
13
+ }
lib/Aws/Aws/Result.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use JmesPath\Env as JmesPath;
5
+
6
+ /**
7
+ * AWS result.
8
+ */
9
+ class Result implements ResultInterface, MonitoringEventsInterface
10
+ {
11
+ use HasDataTrait;
12
+ use HasMonitoringEventsTrait;
13
+
14
+ public function __construct(array $data = [])
15
+ {
16
+ $this->data = $data;
17
+ }
18
+
19
+ public function hasKey($name)
20
+ {
21
+ return isset($this->data[$name]);
22
+ }
23
+
24
+ public function get($key)
25
+ {
26
+ return $this[$key];
27
+ }
28
+
29
+ public function search($expression)
30
+ {
31
+ return JmesPath::search($expression, $this->toArray());
32
+ }
33
+
34
+ public function __toString()
35
+ {
36
+ $jsonData = json_encode($this->toArray(), JSON_PRETTY_PRINT);
37
+ return <<<EOT
38
+ Model Data
39
+ ----------
40
+ Data can be retrieved from the model object using the get() method of the
41
+ model (e.g., `\$result->get(\$key)`) or "accessing the result like an
42
+ associative array (e.g. `\$result['key']`). You can also execute JMESPath
43
+ expressions on the result data using the search() method.
44
+
45
+ {$jsonData}
46
+
47
+ EOT;
48
+ }
49
+
50
+ /**
51
+ * @deprecated
52
+ */
53
+ public function getPath($path)
54
+ {
55
+ return $this->search(str_replace('/', '.', $path));
56
+ }
57
+ }
lib/Aws/Aws/ResultInterface.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Represents an AWS result object that is returned from executing an operation.
6
+ */
7
+ interface ResultInterface extends \ArrayAccess, \IteratorAggregate, \Countable
8
+ {
9
+ /**
10
+ * Provides debug information about the result object
11
+ *
12
+ * @return string
13
+ */
14
+ public function __toString();
15
+
16
+ /**
17
+ * Convert the result to an array.
18
+ *
19
+ * @return array
20
+ */
21
+ public function toArray();
22
+
23
+ /**
24
+ * Check if the model contains a key by name
25
+ *
26
+ * @param string $name Name of the key to retrieve
27
+ *
28
+ * @return bool
29
+ */
30
+ public function hasKey($name);
31
+
32
+ /**
33
+ * Get a specific key value from the result model.
34
+ *
35
+ * @param string $key Key to retrieve.
36
+ *
37
+ * @return mixed|null Value of the key or NULL if not found.
38
+ */
39
+ public function get($key);
40
+
41
+ /**
42
+ * Returns the result of executing a JMESPath expression on the contents
43
+ * of the Result model.
44
+ *
45
+ * $result = $client->execute($command);
46
+ * $jpResult = $result->search('foo.*.bar[?baz > `10`]');
47
+ *
48
+ * @param string $expression JMESPath expression to execute
49
+ *
50
+ * @return mixed Returns the result of the JMESPath expression.
51
+ * @link http://jmespath.readthedocs.org/en/latest/ JMESPath documentation
52
+ */
53
+ public function search($expression);
54
+ };
lib/Aws/Aws/ResultPaginator.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use GuzzleHttp\Promise;
5
+
6
+ /**
7
+ * Iterator that yields each page of results of a pageable operation.
8
+ */
9
+ class ResultPaginator implements \Iterator
10
+ {
11
+ /** @var AwsClientInterface Client performing operations. */
12
+ private $client;
13
+
14
+ /** @var string Name of the operation being paginated. */
15
+ private $operation;
16
+
17
+ /** @var array Args for the operation. */
18
+ private $args;
19
+
20
+ /** @var array Configuration for the paginator. */
21
+ private $config;
22
+
23
+ /** @var Result Most recent result from the client. */
24
+ private $result;
25
+
26
+ /** @var string|array Next token to use for pagination. */
27
+ private $nextToken;
28
+
29
+ /** @var int Number of operations/requests performed. */
30
+ private $requestCount = 0;
31
+
32
+ /**
33
+ * @param AwsClientInterface $client
34
+ * @param string $operation
35
+ * @param array $args
36
+ * @param array $config
37
+ */
38
+ public function __construct(
39
+ AwsClientInterface $client,
40
+ $operation,
41
+ array $args,
42
+ array $config
43
+ ) {
44
+ $this->client = $client;
45
+ $this->operation = $operation;
46
+ $this->args = $args;
47
+ $this->config = $config;
48
+ }
49
+
50
+ /**
51
+ * Runs a paginator asynchronously and uses a callback to handle results.
52
+ *
53
+ * The callback should have the signature: function (Aws\Result $result).
54
+ * A non-null return value from the callback will be yielded by the
55
+ * promise. This means that you can return promises from the callback that
56
+ * will need to be resolved before continuing iteration over the remaining
57
+ * items, essentially merging in other promises to the iteration. The last
58
+ * non-null value returned by the callback will be the result that fulfills
59
+ * the promise to any downstream promises.
60
+ *
61
+ * @param callable $handleResult Callback for handling each page of results.
62
+ * The callback accepts the result that was
63
+ * yielded as a single argument. If the
64
+ * callback returns a promise, the promise
65
+ * will be merged into the coroutine.
66
+ *
67
+ * @return Promise\Promise
68
+ */
69
+ public function each(callable $handleResult)
70
+ {
71
+ return Promise\coroutine(function () use ($handleResult) {
72
+ $nextToken = null;
73
+ do {
74
+ $command = $this->createNextCommand($this->args, $nextToken);
75
+ $result = (yield $this->client->executeAsync($command));
76
+ $nextToken = $this->determineNextToken($result);
77
+ $retVal = $handleResult($result);
78
+ if ($retVal !== null) {
79
+ yield Promise\promise_for($retVal);
80
+ }
81
+ } while ($nextToken);
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Returns an iterator that iterates over the values of applying a JMESPath
87
+ * search to each result yielded by the iterator as a flat sequence.
88
+ *
89
+ * @param string $expression JMESPath expression to apply to each result.
90
+ *
91
+ * @return \Iterator
92
+ */
93
+ public function search($expression)
94
+ {
95
+ // Apply JMESPath expression on each result, but as a flat sequence.
96
+ return flatmap($this, function (Result $result) use ($expression) {
97
+ return (array) $result->search($expression);
98
+ });
99
+ }
100
+
101
+ /**
102
+ * @return Result
103
+ */
104
+ public function current()
105
+ {
106
+ return $this->valid() ? $this->result : false;
107
+ }
108
+
109
+ public function key()
110
+ {
111
+ return $this->valid() ? $this->requestCount - 1 : null;
112
+ }
113
+
114
+ public function next()
115
+ {
116
+ $this->result = null;
117
+ }
118
+
119
+ public function valid()
120
+ {
121
+ if ($this->result) {
122
+ return true;
123
+ }
124
+
125
+ if ($this->nextToken || !$this->requestCount) {
126
+ $this->result = $this->client->execute(
127
+ $this->createNextCommand($this->args, $this->nextToken)
128
+ );
129
+ $this->nextToken = $this->determineNextToken($this->result);
130
+ $this->requestCount++;
131
+ return true;
132
+ }
133
+
134
+ return false;
135
+ }
136
+
137
+ public function rewind()
138
+ {
139
+ $this->requestCount = 0;
140
+ $this->nextToken = null;
141
+ $this->result = null;
142
+ }
143
+
144
+ private function createNextCommand(array $args, array $nextToken = null)
145
+ {
146
+ return $this->client->getCommand($this->operation, array_merge($args, ($nextToken ?: [])));
147
+ }
148
+
149
+ private function determineNextToken(Result $result)
150
+ {
151
+ if (!$this->config['output_token']) {
152
+ return null;
153
+ }
154
+
155
+ if ($this->config['more_results']
156
+ && !$result->search($this->config['more_results'])
157
+ ) {
158
+ return null;
159
+ }
160
+
161
+ $nextToken = is_scalar($this->config['output_token'])
162
+ ? [$this->config['input_token'] => $this->config['output_token']]
163
+ : array_combine($this->config['input_token'], $this->config['output_token']);
164
+
165
+ return array_filter(array_map(function ($outputToken) use ($result) {
166
+ return $result->search($outputToken);
167
+ }, $nextToken));
168
+ }
169
+ }
lib/Aws/Aws/RetryMiddleware.php ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Exception\AwsException;
5
+ use GuzzleHttp\Exception\RequestException;
6
+ use Psr\Http\Message\RequestInterface;
7
+ use GuzzleHttp\Promise\PromiseInterface;
8
+ use GuzzleHttp\Promise;
9
+
10
+ /**
11
+ * @internal Middleware that retries failures.
12
+ */
13
+ class RetryMiddleware
14
+ {
15
+ private static $retryStatusCodes = [
16
+ 500 => true,
17
+ 502 => true,
18
+ 503 => true,
19
+ 504 => true
20
+ ];
21
+
22
+ private static $retryCodes = [
23
+ // Throttling error
24
+ 'RequestLimitExceeded' => true,
25
+ 'Throttling' => true,
26
+ 'ThrottlingException' => true,
27
+ 'ThrottledException' => true,
28
+ 'ProvisionedThroughputExceededException' => true,
29
+ 'RequestThrottled' => true,
30
+ 'BandwidthLimitExceeded' => true,
31
+ 'RequestThrottledException' => true,
32
+ ];
33
+
34
+ private $decider;
35
+ private $delay;
36
+ private $nextHandler;
37
+ private $collectStats;
38
+
39
+ public function __construct(
40
+ callable $decider,
41
+ callable $delay,
42
+ callable $nextHandler,
43
+ $collectStats = false
44
+ ) {
45
+ $this->decider = $decider;
46
+ $this->delay = $delay;
47
+ $this->nextHandler = $nextHandler;
48
+ $this->collectStats = (bool) $collectStats;
49
+ }
50
+
51
+ /**
52
+ * Creates a default AWS retry decider function.
53
+ *
54
+ * The optional $additionalRetryConfig parameter is an associative array
55
+ * that specifies additional retry conditions on top of the ones specified
56
+ * by default by the Aws\RetryMiddleware class, with the following keys:
57
+ *
58
+ * - errorCodes: (string[]) An indexed array of AWS exception codes to retry.
59
+ * Optional.
60
+ * - statusCodes: (int[]) An indexed array of HTTP status codes to retry.
61
+ * Optional.
62
+ * - curlErrors: (int[]) An indexed array of Curl error codes to retry. Note
63
+ * these should be valid Curl constants. Optional.
64
+ *
65
+ * @param int $maxRetries
66
+ * @param array $additionalRetryConfig
67
+ * @return callable
68
+ */
69
+ public static function createDefaultDecider(
70
+ $maxRetries = 3,
71
+ $additionalRetryConfig = []
72
+ ) {
73
+ $retryCurlErrors = [];
74
+ if (extension_loaded('curl')) {
75
+ $retryCurlErrors[CURLE_RECV_ERROR] = true;
76
+ }
77
+
78
+ return function (
79
+ $retries,
80
+ CommandInterface $command,
81
+ RequestInterface $request,
82
+ ResultInterface $result = null,
83
+ $error = null
84
+ ) use ($maxRetries, $retryCurlErrors, $additionalRetryConfig) {
85
+ // Allow command-level options to override this value
86
+ $maxRetries = null !== $command['@retries'] ?
87
+ $command['@retries']
88
+ : $maxRetries;
89
+
90
+ $isRetryable = self::isRetryable(
91
+ $result,
92
+ $error,
93
+ $retryCurlErrors,
94
+ $additionalRetryConfig
95
+ );
96
+
97
+ if ($retries >= $maxRetries) {
98
+ if (!empty($error)
99
+ && $error instanceof AwsException
100
+ && $isRetryable
101
+ ) {
102
+ $error->setMaxRetriesExceeded();
103
+ }
104
+ return false;
105
+ }
106
+
107
+ return $isRetryable;
108
+ };
109
+ }
110
+
111
+ private static function isRetryable(
112
+ $result,
113
+ $error,
114
+ $retryCurlErrors,
115
+ $additionalRetryConfig = []
116
+ ) {
117
+ $errorCodes = self::$retryCodes;
118
+ if (!empty($additionalRetryConfig['errorCodes'])
119
+ && is_array($additionalRetryConfig['errorCodes'])
120
+ ) {
121
+ foreach($additionalRetryConfig['errorCodes'] as $code) {
122
+ $errorCodes[$code] = true;
123
+ }
124
+ }
125
+
126
+ $statusCodes = self::$retryStatusCodes;
127
+ if (!empty($additionalRetryConfig['statusCodes'])
128
+ && is_array($additionalRetryConfig['statusCodes'])
129
+ ) {
130
+ foreach($additionalRetryConfig['statusCodes'] as $code) {
131
+ $statusCodes[$code] = true;
132
+ }
133
+ }
134
+
135
+ if (!empty($additionalRetryConfig['curlErrors'])
136
+ && is_array($additionalRetryConfig['curlErrors'])
137
+ ) {
138
+ foreach($additionalRetryConfig['curlErrors'] as $code) {
139
+ $retryCurlErrors[$code] = true;
140
+ }
141
+ }
142
+
143
+ if (!$error) {
144
+ return isset($statusCodes[$result['@metadata']['statusCode']]);
145
+ }
146
+
147
+ if (!($error instanceof AwsException)) {
148
+ return false;
149
+ }
150
+
151
+ if ($error->isConnectionError()) {
152
+ return true;
153
+ }
154
+
155
+ if (isset($errorCodes[$error->getAwsErrorCode()])) {
156
+ return true;
157
+ }
158
+
159
+ if (isset($statusCodes[$error->getStatusCode()])) {
160
+ return true;
161
+ }
162
+
163
+ if (count($retryCurlErrors)
164
+ && ($previous = $error->getPrevious())
165
+ && $previous instanceof RequestException
166
+ ) {
167
+ if (method_exists($previous, 'getHandlerContext')) {
168
+ $context = $previous->getHandlerContext();
169
+ return !empty($context['errno'])
170
+ && isset($retryCurlErrors[$context['errno']]);
171
+ }
172
+
173
+ $message = $previous->getMessage();
174
+ foreach (array_keys($retryCurlErrors) as $curlError) {
175
+ if (strpos($message, 'cURL error ' . $curlError . ':') === 0) {
176
+ return true;
177
+ }
178
+ }
179
+ }
180
+
181
+ return false;
182
+ }
183
+
184
+ /**
185
+ * Delay function that calculates an exponential delay.
186
+ *
187
+ * Exponential backoff with jitter, 100ms base, 20 sec ceiling
188
+ *
189
+ * @param $retries - The number of retries that have already been attempted
190
+ *
191
+ * @return int
192
+ *
193
+ * @link https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
194
+ */
195
+ public static function exponentialDelay($retries)
196
+ {
197
+ return mt_rand(0, (int) min(20000, (int) pow(2, $retries) * 100));
198
+ }
199
+
200
+ /**
201
+ * @param CommandInterface $command
202
+ * @param RequestInterface $request
203
+ *
204
+ * @return PromiseInterface
205
+ */
206
+ public function __invoke(
207
+ CommandInterface $command,
208
+ RequestInterface $request = null
209
+ ) {
210
+ $retries = 0;
211
+ $requestStats = [];
212
+ $monitoringEvents = [];
213
+ $handler = $this->nextHandler;
214
+ $decider = $this->decider;
215
+ $delay = $this->delay;
216
+
217
+ $request = $this->addRetryHeader($request, 0, 0);
218
+
219
+ $g = function ($value) use (
220
+ $handler,
221
+ $decider,
222
+ $delay,
223
+ $command,
224
+ $request,
225
+ &$retries,
226
+ &$requestStats,
227
+ &$monitoringEvents,
228
+ &$g
229
+ ) {
230
+ $this->updateHttpStats($value, $requestStats);
231
+
232
+ if ($value instanceof MonitoringEventsInterface) {
233
+ $reversedEvents = array_reverse($monitoringEvents);
234
+ $monitoringEvents = array_merge($monitoringEvents, $value->getMonitoringEvents());
235
+ foreach ($reversedEvents as $event) {
236
+ $value->prependMonitoringEvent($event);
237
+ }
238
+ }
239
+ if ($value instanceof \Exception || $value instanceof \Throwable) {
240
+ if (!$decider($retries, $command, $request, null, $value)) {
241
+ return Promise\rejection_for(
242
+ $this->bindStatsToReturn($value, $requestStats)
243
+ );
244
+ }
245
+ } elseif ($value instanceof ResultInterface
246
+ && !$decider($retries, $command, $request, $value, null)
247
+ ) {
248
+ return $this->bindStatsToReturn($value, $requestStats);
249
+ }
250
+
251
+ // Delay fn is called with 0, 1, ... so increment after the call.
252
+ $delayBy = $delay($retries++);
253
+ $command['@http']['delay'] = $delayBy;
254
+ if ($this->collectStats) {
255
+ $this->updateStats($retries, $delayBy, $requestStats);
256
+ }
257
+
258
+ // Update retry header with retry count and delayBy
259
+ $request = $this->addRetryHeader($request, $retries, $delayBy);
260
+
261
+ return $handler($command, $request)->then($g, $g);
262
+ };
263
+
264
+ return $handler($command, $request)->then($g, $g);
265
+ }
266
+
267
+ private function addRetryHeader($request, $retries, $delayBy)
268
+ {
269
+ return $request->withHeader('aws-sdk-retry', "{$retries}/{$delayBy}");
270
+ }
271
+
272
+ private function updateStats($retries, $delay, array &$stats)
273
+ {
274
+ if (!isset($stats['total_retry_delay'])) {
275
+ $stats['total_retry_delay'] = 0;
276
+ }
277
+
278
+ $stats['total_retry_delay'] += $delay;
279
+ $stats['retries_attempted'] = $retries;
280
+ }
281
+
282
+ private function updateHttpStats($value, array &$stats)
283
+ {
284
+ if (empty($stats['http'])) {
285
+ $stats['http'] = [];
286
+ }
287
+
288
+ if ($value instanceof AwsException) {
289
+ $resultStats = isset($value->getTransferInfo('http')[0])
290
+ ? $value->getTransferInfo('http')[0]
291
+ : [];
292
+ $stats['http'] []= $resultStats;
293
+ } elseif ($value instanceof ResultInterface) {
294
+ $resultStats = isset($value['@metadata']['transferStats']['http'][0])
295
+ ? $value['@metadata']['transferStats']['http'][0]
296
+ : [];
297
+ $stats['http'] []= $resultStats;
298
+ }
299
+ }
300
+
301
+ private function bindStatsToReturn($return, array $stats)
302
+ {
303
+ if ($return instanceof ResultInterface) {
304
+ if (!isset($return['@metadata'])) {
305
+ $return['@metadata'] = [];
306
+ }
307
+
308
+ $return['@metadata']['transferStats'] = $stats;
309
+ } elseif ($return instanceof AwsException) {
310
+ $return->setTransferInfo($stats);
311
+ }
312
+
313
+ return $return;
314
+ }
315
+ }
lib/Aws/Aws/S3/AmbiguousSuccessParser.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Api\Parser\AbstractParser;
5
+ use Aws\Api\StructureShape;
6
+ use Aws\CommandInterface;
7
+ use Aws\Exception\AwsException;
8
+ use Psr\Http\Message\ResponseInterface;
9
+ use Psr\Http\Message\StreamInterface;
10
+
11
+ /**
12
+ * Converts errors returned with a status code of 200 to a retryable error type.
13
+ *
14
+ * @internal
15
+ */
16
+ class AmbiguousSuccessParser extends AbstractParser
17
+ {
18
+ private static $ambiguousSuccesses = [
19
+ 'UploadPartCopy' => true,
20
+ 'CopyObject' => true,
21
+ 'CompleteMultipartUpload' => true,
22
+ ];
23
+
24
+ /** @var callable */
25
+ private $errorParser;
26
+ /** @var string */
27
+ private $exceptionClass;
28
+
29
+ public function __construct(
30
+ callable $parser,
31
+ callable $errorParser,
32
+ $exceptionClass = AwsException::class
33
+ ) {
34
+ $this->parser = $parser;
35
+ $this->errorParser = $errorParser;
36
+ $this->exceptionClass = $exceptionClass;
37
+ }
38
+
39
+ public function __invoke(
40
+ CommandInterface $command,
41
+ ResponseInterface $response
42
+ ) {
43
+ if (200 === $response->getStatusCode()
44
+ && isset(self::$ambiguousSuccesses[$command->getName()])
45
+ ) {
46
+ $errorParser = $this->errorParser;
47
+ $parsed = $errorParser($response);
48
+ if (isset($parsed['code']) && isset($parsed['message'])) {
49
+ throw new $this->exceptionClass(
50
+ $parsed['message'],
51
+ $command,
52
+ ['connection_error' => true]
53
+ );
54
+ }
55
+ }
56
+
57
+ $fn = $this->parser;
58
+ return $fn($command, $response);
59
+ }
60
+
61
+ public function parseMemberFromStream(
62
+ StreamInterface $stream,
63
+ StructureShape $member,
64
+ $response
65
+ ) {
66
+ return $this->parser->parseMemberFromStream($stream, $member, $response);
67
+ }
68
+ }
lib/Aws/Aws/S3/ApplyChecksumMiddleware.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use GuzzleHttp\Psr7;
6
+ use Psr\Http\Message\RequestInterface;
7
+
8
+ /**
9
+ * Apply required or optional MD5s to requests before sending.
10
+ *
11
+ * IMPORTANT: This middleware must be added after the "build" step.
12
+ *
13
+ * @internal
14
+ */
15
+ class ApplyChecksumMiddleware
16
+ {
17
+ private static $md5 = [
18
+ 'DeleteObjects',
19
+ 'PutBucketCors',
20
+ 'PutBucketLifecycle',
21
+ 'PutBucketLifecycleConfiguration',
22
+ 'PutBucketPolicy',
23
+ 'PutBucketTagging',
24
+ 'PutBucketReplication',
25
+ 'PutObjectLegalHold',
26
+ 'PutObjectRetention',
27
+ 'PutObjectLockConfiguration',
28
+ ];
29
+
30
+ private static $sha256 = [
31
+ 'PutObject',
32
+ 'UploadPart',
33
+ ];
34
+
35
+ private $nextHandler;
36
+
37
+ /**
38
+ * Create a middleware wrapper function.
39
+ *
40
+ * @return callable
41
+ */
42
+ public static function wrap()
43
+ {
44
+ return function (callable $handler) {
45
+ return new self($handler);
46
+ };
47
+ }
48
+
49
+ public function __construct(callable $nextHandler)
50
+ {
51
+ $this->nextHandler = $nextHandler;
52
+ }
53
+
54
+ public function __invoke(
55
+ CommandInterface $command,
56
+ RequestInterface $request
57
+ ) {
58
+ $next = $this->nextHandler;
59
+ $name = $command->getName();
60
+ $body = $request->getBody();
61
+
62
+ if (in_array($name, self::$md5) && !$request->hasHeader('Content-MD5')) {
63
+ // Set the content MD5 header for operations that require it.
64
+ $request = $request->withHeader(
65
+ 'Content-MD5',
66
+ base64_encode(Psr7\hash($body, 'md5', true))
67
+ );
68
+ } elseif (in_array($name, self::$sha256) && $command['ContentSHA256']) {
69
+ // Set the content hash header if provided in the parameters.
70
+ $request = $request->withHeader(
71
+ 'X-Amz-Content-Sha256',
72
+ $command['ContentSHA256']
73
+ );
74
+ }
75
+
76
+ return $next($command, $request);
77
+ }
78
+ }
lib/Aws/Aws/S3/BatchDelete.php ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\AwsClientInterface;
5
+ use Aws\S3\Exception\DeleteMultipleObjectsException;
6
+ use GuzzleHttp\Promise;
7
+ use GuzzleHttp\Promise\PromisorInterface;
8
+ use GuzzleHttp\Promise\PromiseInterface;
9
+
10
+ /**
11
+ * Efficiently deletes many objects from a single Amazon S3 bucket using an
12
+ * iterator that yields keys. Deletes are made using the DeleteObjects API
13
+ * operation.
14
+ *
15
+ * $s3 = new Aws\S3\Client([
16
+ * 'region' => 'us-west-2',
17
+ * 'version' => 'latest'
18
+ * ]);
19
+ *
20
+ * $listObjectsParams = ['Bucket' => 'foo', 'Prefix' => 'starts/with/'];
21
+ * $delete = Aws\S3\BatchDelete::fromListObjects($s3, $listObjectsParams);
22
+ * // Asynchronously delete
23
+ * $promise = $delete->promise();
24
+ * // Force synchronous completion
25
+ * $delete->delete();
26
+ *
27
+ * When using one of the batch delete creational static methods, you can supply
28
+ * an associative array of options:
29
+ *
30
+ * - before: Function invoked before executing a command. The function is
31
+ * passed the command that is about to be executed. This can be useful
32
+ * for logging, adding custom request headers, etc.
33
+ * - batch_size: The size of each delete batch. Defaults to 1000.
34
+ *
35
+ * @link http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
36
+ */
37
+ class BatchDelete implements PromisorInterface
38
+ {
39
+ private $bucket;
40
+ /** @var AwsClientInterface */
41
+ private $client;
42
+ /** @var callable */
43
+ private $before;
44
+ /** @var PromiseInterface */
45
+ private $cachedPromise;
46
+ /** @var callable */
47
+ private $promiseCreator;
48
+ private $batchSize = 1000;
49
+ private $queue = [];
50
+
51
+ /**
52
+ * Creates a BatchDelete object from all of the paginated results of a
53
+ * ListObjects operation. Each result that is returned by the ListObjects
54
+ * operation will be deleted.
55
+ *
56
+ * @param AwsClientInterface $client AWS Client to use.
57
+ * @param array $listObjectsParams ListObjects API parameters
58
+ * @param array $options BatchDelete options.
59
+ *
60
+ * @return BatchDelete
61
+ */
62
+ public static function fromListObjects(
63
+ AwsClientInterface $client,
64
+ array $listObjectsParams,
65
+ array $options = []
66
+ ) {
67
+ $iter = $client->getPaginator('ListObjects', $listObjectsParams);
68
+ $bucket = $listObjectsParams['Bucket'];
69
+ $fn = function (BatchDelete $that) use ($iter) {
70
+ return $iter->each(function ($result) use ($that) {
71
+ $promises = [];
72
+ if (is_array($result['Contents'])) {
73
+ foreach ($result['Contents'] as $object) {
74
+ if ($promise = $that->enqueue($object)) {
75
+ $promises[] = $promise;
76
+ }
77
+ }
78
+ }
79
+ return $promises ? Promise\all($promises) : null;
80
+ });
81
+ };
82
+
83
+ return new self($client, $bucket, $fn, $options);
84
+ }
85
+
86
+ /**
87
+ * Creates a BatchDelete object from an iterator that yields results.
88
+ *
89
+ * @param AwsClientInterface $client AWS Client to use to execute commands
90
+ * @param string $bucket Bucket where the objects are stored
91
+ * @param \Iterator $iter Iterator that yields assoc arrays
92
+ * @param array $options BatchDelete options
93
+ *
94
+ * @return BatchDelete
95
+ */
96
+ public static function fromIterator(
97
+ AwsClientInterface $client,
98
+ $bucket,
99
+ \Iterator $iter,
100
+ array $options = []
101
+ ) {
102
+ $fn = function (BatchDelete $that) use ($iter) {
103
+ return Promise\coroutine(function () use ($that, $iter) {
104
+ foreach ($iter as $obj) {
105
+ if ($promise = $that->enqueue($obj)) {
106
+ yield $promise;
107
+ }
108
+ }
109
+ });
110
+ };
111
+
112
+ return new self($client, $bucket, $fn, $options);
113
+ }
114
+
115
+ public function promise()
116
+ {
117
+ if (!$this->cachedPromise) {
118
+ $this->cachedPromise = $this->createPromise();
119
+ }
120
+
121
+ return $this->cachedPromise;
122
+ }
123
+
124
+ /**
125
+ * Synchronously deletes all of the objects.
126
+ *
127
+ * @throws DeleteMultipleObjectsException on error.
128
+ */
129
+ public function delete()
130
+ {
131
+ $this->promise()->wait();
132
+ }
133
+
134
+ /**
135
+ * @param AwsClientInterface $client Client used to transfer the requests
136
+ * @param string $bucket Bucket to delete from.
137
+ * @param callable $promiseFn Creates a promise.
138
+ * @param array $options Hash of options used with the batch
139
+ *
140
+ * @throws \InvalidArgumentException if the provided batch_size is <= 0
141
+ */
142
+ private function __construct(
143
+ AwsClientInterface $client,
144
+ $bucket,
145
+ callable $promiseFn,
146
+ array $options = []
147
+ ) {
148
+ $this->client = $client;
149
+ $this->bucket = $bucket;
150
+ $this->promiseCreator = $promiseFn;
151
+
152
+ if (isset($options['before'])) {
153
+ if (!is_callable($options['before'])) {
154
+ throw new \InvalidArgumentException('before must be callable');
155
+ }
156
+ $this->before = $options['before'];
157
+ }
158
+
159
+ if (isset($options['batch_size'])) {
160
+ if ($options['batch_size'] <= 0) {
161
+ throw new \InvalidArgumentException('batch_size is not > 0');
162
+ }
163
+ $this->batchSize = min($options['batch_size'], 1000);
164
+ }
165
+ }
166
+
167
+ private function enqueue(array $obj)
168
+ {
169
+ $this->queue[] = $obj;
170
+ return count($this->queue) >= $this->batchSize
171
+ ? $this->flushQueue()
172
+ : null;
173
+ }
174
+
175
+ private function flushQueue()
176
+ {
177
+ static $validKeys = ['Key' => true, 'VersionId' => true];
178
+
179
+ if (count($this->queue) === 0) {
180
+ return null;
181
+ }
182
+
183
+ $batch = [];
184
+ while ($obj = array_shift($this->queue)) {
185
+ $batch[] = array_intersect_key($obj, $validKeys);
186
+ }
187
+
188
+ $command = $this->client->getCommand('DeleteObjects', [
189
+ 'Bucket' => $this->bucket,
190
+ 'Delete' => ['Objects' => $batch]
191
+ ]);
192
+
193
+ if ($this->before) {
194
+ call_user_func($this->before, $command);
195
+ }
196
+
197
+ return $this->client->executeAsync($command)
198
+ ->then(function ($result) {
199
+ if (!empty($result['Errors'])) {
200
+ throw new DeleteMultipleObjectsException(
201
+ $result['Deleted'] ?: [],
202
+ $result['Errors']
203
+ );
204
+ }
205
+ return $result;
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Returns a promise that will clean up any references when it completes.
211
+ *
212
+ * @return PromiseInterface
213
+ */
214
+ private function createPromise()
215
+ {
216
+ // Create the promise
217
+ $promise = call_user_func($this->promiseCreator, $this);
218
+ $this->promiseCreator = null;
219
+
220
+ // Cleans up the promise state and references.
221
+ $cleanup = function () {
222
+ $this->before = $this->client = $this->queue = null;
223
+ };
224
+
225
+ // When done, ensure cleanup and that any remaining are processed.
226
+ return $promise->then(
227
+ function () use ($cleanup) {
228
+ return Promise\promise_for($this->flushQueue())
229
+ ->then($cleanup);
230
+ },
231
+ function ($reason) use ($cleanup) {
232
+ $cleanup();
233
+ return Promise\rejection_for($reason);
234
+ }
235
+ );
236
+ }
237
+ }
lib/Aws/Aws/S3/BucketEndpointMiddleware.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * Used to update the host used for S3 requests in the case of using a
9
+ * "bucket endpoint" or CNAME bucket.
10
+ *
11
+ * IMPORTANT: this middleware must be added after the "build" step.
12
+ *
13
+ * @internal
14
+ */
15
+ class BucketEndpointMiddleware
16
+ {
17
+ private static $exclusions = ['GetBucketLocation' => true];
18
+ private $nextHandler;
19
+
20
+ /**
21
+ * Create a middleware wrapper function.
22
+ *
23
+ * @return callable
24
+ */
25
+ public static function wrap()
26
+ {
27
+ return function (callable $handler) {
28
+ return new self($handler);
29
+ };
30
+ }
31
+
32
+ public function __construct(callable $nextHandler)
33
+ {
34
+ $this->nextHandler = $nextHandler;
35
+ }
36
+
37
+ public function __invoke(CommandInterface $command, RequestInterface $request)
38
+ {
39
+ $nextHandler = $this->nextHandler;
40
+ $bucket = $command['Bucket'];
41
+
42
+ if ($bucket && !isset(self::$exclusions[$command->getName()])) {
43
+ $request = $this->modifyRequest($request, $command);
44
+ }
45
+
46
+ return $nextHandler($command, $request);
47
+ }
48
+
49
+ private function removeBucketFromPath($path, $bucket)
50
+ {
51
+ $len = strlen($bucket) + 1;
52
+ if (substr($path, 0, $len) === "/{$bucket}") {
53
+ $path = substr($path, $len);
54
+ }
55
+
56
+ return $path ?: '/';
57
+ }
58
+
59
+ private function modifyRequest(
60
+ RequestInterface $request,
61
+ CommandInterface $command
62
+ ) {
63
+ $uri = $request->getUri();
64
+ $path = $uri->getPath();
65
+ $bucket = $command['Bucket'];
66
+ $path = $this->removeBucketFromPath($path, $bucket);
67
+
68
+ // Modify the Key to make sure the key is encoded, but slashes are not.
69
+ if ($command['Key']) {
70
+ $path = S3Client::encodeKey(rawurldecode($path));
71
+ }
72
+
73
+ return $request->withUri($uri->withPath($path));
74
+ }
75
+ }
lib/Aws/Aws/S3/Crypto/CryptoParamsTrait.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Crypto;
3
+
4
+ use Aws\Crypto\MaterialsProvider;
5
+ use Aws\Crypto\MetadataEnvelope;
6
+ use Aws\Crypto\MetadataStrategyInterface;
7
+
8
+ trait CryptoParamsTrait
9
+ {
10
+ protected function getMaterialsProvider(array $args)
11
+ {
12
+ if ($args['@MaterialsProvider'] instanceof MaterialsProvider) {
13
+ return $args['@MaterialsProvider'];
14
+ }
15
+
16
+ throw new \InvalidArgumentException('An instance of MaterialsProvider'
17
+ . ' must be passed in the "MaterialsProvider" field.');
18
+ }
19
+
20
+ protected function getInstructionFileSuffix(array $args)
21
+ {
22
+ return !empty($args['@InstructionFileSuffix'])
23
+ ? $args['@InstructionFileSuffix']
24
+ : $this->instructionFileSuffix;
25
+ }
26
+
27
+ protected function determineGetObjectStrategy(
28
+ $result,
29
+ $instructionFileSuffix
30
+ ) {
31
+ if (isset($result['Metadata'][MetadataEnvelope::CONTENT_KEY_V2_HEADER])) {
32
+ return new HeadersMetadataStrategy();
33
+ }
34
+
35
+ return new InstructionFileMetadataStrategy(
36
+ $this->client,
37
+ $instructionFileSuffix
38
+ );
39
+ }
40
+
41
+ protected function getMetadataStrategy(array $args, $instructionFileSuffix)
42
+ {
43
+ if (!empty($args['@MetadataStrategy'])) {
44
+ if ($args['@MetadataStrategy'] instanceof MetadataStrategyInterface) {
45
+ return $args['@MetadataStrategy'];
46
+ }
47
+
48
+ if (is_string($args['@MetadataStrategy'])) {
49
+ switch ($args['@MetadataStrategy']) {
50
+ case HeadersMetadataStrategy::class:
51
+ return new HeadersMetadataStrategy();
52
+ case InstructionFileMetadataStrategy::class:
53
+ return new InstructionFileMetadataStrategy(
54
+ $this->client,
55
+ $instructionFileSuffix
56
+ );
57
+ default:
58
+ throw new \InvalidArgumentException('Could not match the'
59
+ . ' specified string in "MetadataStrategy" to a'
60
+ . ' predefined strategy.');
61
+ }
62
+ } else {
63
+ throw new \InvalidArgumentException('The metadata strategy that'
64
+ . ' was passed to "MetadataStrategy" was unrecognized.');
65
+ }
66
+ } elseif ($instructionFileSuffix) {
67
+ return new InstructionFileMetadataStrategy(
68
+ $this->client,
69
+ $instructionFileSuffix
70
+ );
71
+ }
72
+
73
+ return null;
74
+ }
75
+ }
lib/Aws/Aws/S3/Crypto/HeadersMetadataStrategy.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Crypto;
3
+
4
+ use \Aws\Crypto\MetadataStrategyInterface;
5
+ use \Aws\Crypto\MetadataEnvelope;
6
+
7
+ class HeadersMetadataStrategy implements MetadataStrategyInterface
8
+ {
9
+ /**
10
+ * Places the information in the MetadataEnvelope in to the Meatadata for
11
+ * the PutObject request of the encrypted object.
12
+ *
13
+ * @param MetadataEnvelope $envelope Encryption data to save according to
14
+ * the strategy.
15
+ * @param array $args Arguments for PutObject that can be manipulated to
16
+ * store strategy related information.
17
+ *
18
+ * @return array Updated arguments for PutObject.
19
+ */
20
+ public function save(MetadataEnvelope $envelope, array $args)
21
+ {
22
+ foreach ($envelope as $header=>$value) {
23
+ $args['Metadata'][$header] = $value;
24
+ }
25
+
26
+ return $args;
27
+ }
28
+
29
+ /**
30
+ * Generates a MetadataEnvelope according to the Metadata headers from the
31
+ * GetObject result.
32
+ *
33
+ * @param array $args Arguments from Command and Result that contains
34
+ * S3 Object information, relevant headers, and command
35
+ * configuration.
36
+ *
37
+ * @return MetadataEnvelope
38
+ */
39
+ public function load(array $args)
40
+ {
41
+ $envelope = new MetadataEnvelope();
42
+ $constantValues = MetadataEnvelope::getConstantValues();
43
+
44
+ foreach ($constantValues as $constant) {
45
+ if (!empty($args['Metadata'][$constant])) {
46
+ $envelope[$constant] = $args['Metadata'][$constant];
47
+ }
48
+ }
49
+
50
+ return $envelope;
51
+ }
52
+ }
lib/Aws/Aws/S3/Crypto/InstructionFileMetadataStrategy.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Crypto;
3
+
4
+ use \Aws\Crypto\MetadataStrategyInterface;
5
+ use \Aws\Crypto\MetadataEnvelope;
6
+ use \Aws\S3\S3Client;
7
+
8
+ /**
9
+ * Stores and reads encryption MetadataEnvelope information in a file on Amazon
10
+ * S3.
11
+ *
12
+ * A file with the contents of a MetadataEnvelope will be created or read from
13
+ * alongside the base file on Amazon S3. The provided client will be used for
14
+ * reading or writing this object. A specified suffix (default of '.instruction'
15
+ * will be applied to each of the operations involved with the instruction file.
16
+ *
17
+ * If there is a failure after an instruction file has been uploaded, it will
18
+ * not be automatically deleted.
19
+ */
20
+ class InstructionFileMetadataStrategy implements MetadataStrategyInterface
21
+ {
22
+ const DEFAULT_FILE_SUFFIX = '.instruction';
23
+
24
+ private $client;
25
+ private $suffix;
26
+
27
+ /**
28
+ * @param S3Client $client Client for use in uploading the instruction file.
29
+ * @param string|null $suffix Optional override suffix for instruction file
30
+ * object keys.
31
+ */
32
+ public function __construct(S3Client $client, $suffix = null)
33
+ {
34
+ $this->suffix = empty($suffix)
35
+ ? self::DEFAULT_FILE_SUFFIX
36
+ : $suffix;
37
+ $this->client = $client;
38
+ }
39
+
40
+ /**
41
+ * Places the information in the MetadataEnvelope to a location on S3.
42
+ *
43
+ * @param MetadataEnvelope $envelope Encryption data to save according to
44
+ * the strategy.
45
+ * @param array $args Starting arguments for PutObject, used for saving
46
+ * extra the instruction file.
47
+ *
48
+ * @return array Updated arguments for PutObject.
49
+ */
50
+ public function save(MetadataEnvelope $envelope, array $args)
51
+ {
52
+ $this->client->putObject([
53
+ 'Bucket' => $args['Bucket'],
54
+ 'Key' => $args['Key'] . $this->suffix,
55
+ 'Body' => json_encode($envelope)
56
+ ]);
57
+
58
+ return $args;
59
+ }
60
+
61
+ /**
62
+ * Uses the strategy's client to retrieve the instruction file from S3 and generates
63
+ * a MetadataEnvelope from its contents.
64
+ *
65
+ * @param array $args Arguments from Command and Result that contains
66
+ * S3 Object information, relevant headers, and command
67
+ * configuration.
68
+ *
69
+ * @return MetadataEnvelope
70
+ */
71
+ public function load(array $args)
72
+ {
73
+ $result = $this->client->getObject([
74
+ 'Bucket' => $args['Bucket'],
75
+ 'Key' => $args['Key'] . $this->suffix
76
+ ]);
77
+
78
+ $metadataHeaders = json_decode($result['Body'], true);
79
+ $envelope = new MetadataEnvelope();
80
+ $constantValues = MetadataEnvelope::getConstantValues();
81
+
82
+ foreach ($constantValues as $constant) {
83
+ if (!empty($metadataHeaders[$constant])) {
84
+ $envelope[$constant] = $metadataHeaders[$constant];
85
+ }
86
+ }
87
+
88
+ return $envelope;
89
+ }
90
+ }
lib/Aws/Aws/S3/Crypto/S3EncryptionClient.php ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Crypto;
3
+
4
+ use Aws\HashingStream;
5
+ use Aws\PhpHash;
6
+ use Aws\Crypto\AbstractCryptoClient;
7
+ use Aws\Crypto\EncryptionTrait;
8
+ use Aws\Crypto\DecryptionTrait;
9
+ use Aws\Crypto\MetadataEnvelope;
10
+ use Aws\Crypto\MaterialsProvider;
11
+ use Aws\Crypto\Cipher\CipherBuilderTrait;
12
+ use Aws\S3\S3Client;
13
+ use GuzzleHttp\Promise;
14
+ use GuzzleHttp\Promise\PromiseInterface;
15
+ use GuzzleHttp\Psr7;
16
+
17
+ /**
18
+ * Provides a wrapper for an S3Client that supplies functionality to encrypt
19
+ * data on putObject[Async] calls and decrypt data on getObject[Async] calls.
20
+ */
21
+ class S3EncryptionClient extends AbstractCryptoClient
22
+ {
23
+ use EncryptionTrait, DecryptionTrait, CipherBuilderTrait, CryptoParamsTrait;
24
+
25
+ private $client;
26
+ private $instructionFileSuffix;
27
+
28
+ /**
29
+ * @param S3Client $client The S3Client to be used for true uploading and
30
+ * retrieving objects from S3 when using the
31
+ * encryption client.
32
+ * @param string|null $instructionFileSuffix Suffix for a client wide
33
+ * default when using instruction
34
+ * files for metadata storage.
35
+ */
36
+ public function __construct(
37
+ S3Client $client,
38
+ $instructionFileSuffix = null
39
+ ) {
40
+ $this->client = $client;
41
+ $this->instructionFileSuffix = $instructionFileSuffix;
42
+ }
43
+
44
+ private static function getDefaultStrategy()
45
+ {
46
+ return new HeadersMetadataStrategy();
47
+ }
48
+
49
+ /**
50
+ * Encrypts the data in the 'Body' field of $args and promises to upload it
51
+ * to the specified location on S3.
52
+ *
53
+ * @param array $args Arguments for encrypting an object and uploading it
54
+ * to S3 via PutObject.
55
+ *
56
+ * The required configuration arguments are as follows:
57
+ *
58
+ * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
59
+ * encrypting/decrypting for encryption metadata.
60
+ * - @CipherOptions: (array) Cipher options for encrypting data. Only the
61
+ * Cipher option is required. Accepts the following:
62
+ * - Cipher: (string) cbc|gcm
63
+ * See also: AbstractCryptoClient::$supportedCiphers
64
+ * - KeySize: (int) 128|192|256
65
+ * See also: MaterialsProvider::$supportedKeySizes
66
+ * - Aad: (string) Additional authentication data. This option is
67
+ * passed directly to OpenSSL when using gcm. It is ignored when
68
+ * using cbc.
69
+ *
70
+ * The optional configuration arguments are as follows:
71
+ *
72
+ * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
73
+ * MetadataEnvelope information. Defaults to using a
74
+ * HeadersMetadataStrategy. Can either be a class implementing
75
+ * MetadataStrategy, a class name of a predefined strategy, or empty/null
76
+ * to default.
77
+ * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
78
+ * instruction file if using an InstructionFileMetadataHandler.
79
+ *
80
+ * @return PromiseInterface
81
+ *
82
+ * @throws \InvalidArgumentException Thrown when arguments above are not
83
+ * passed or are passed incorrectly.
84
+ */
85
+ public function putObjectAsync(array $args)
86
+ {
87
+ $provider = $this->getMaterialsProvider($args);
88
+ unset($args['@MaterialsProvider']);
89
+
90
+ $instructionFileSuffix = $this->getInstructionFileSuffix($args);
91
+ unset($args['@InstructionFileSuffix']);
92
+
93
+ $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
94
+ unset($args['@MetadataStrategy']);
95
+
96
+ $envelope = new MetadataEnvelope();
97
+
98
+ return Promise\promise_for($this->encrypt(
99
+ Psr7\stream_for($args['Body']),
100
+ $args['@CipherOptions'] ?: [],
101
+ $provider,
102
+ $envelope
103
+ ))->then(
104
+ function ($encryptedBodyStream) use ($args) {
105
+ $hash = new PhpHash('sha256');
106
+ $hashingEncryptedBodyStream = new HashingStream(
107
+ $encryptedBodyStream,
108
+ $hash,
109
+ self::getContentShaDecorator($args)
110
+ );
111
+ return [$hashingEncryptedBodyStream, $args];
112
+ }
113
+ )->then(
114
+ function ($putObjectContents) use ($strategy, $envelope) {
115
+ list($bodyStream, $args) = $putObjectContents;
116
+ if ($strategy === null) {
117
+ $strategy = self::getDefaultStrategy();
118
+ }
119
+
120
+ $updatedArgs = $strategy->save($envelope, $args);
121
+ $updatedArgs['Body'] = $bodyStream;
122
+ return $updatedArgs;
123
+ }
124
+ )->then(
125
+ function ($args) {
126
+ unset($args['@CipherOptions']);
127
+ return $this->client->putObjectAsync($args);
128
+ }
129
+ );
130
+ }
131
+
132
+ private static function getContentShaDecorator(&$args)
133
+ {
134
+ return function ($hash) use (&$args) {
135
+ $args['ContentSHA256'] = bin2hex($hash);
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Encrypts the data in the 'Body' field of $args and uploads it to the
141
+ * specified location on S3.
142
+ *
143
+ * @param array $args Arguments for encrypting an object and uploading it
144
+ * to S3 via PutObject.
145
+ *
146
+ * The required configuration arguments are as follows:
147
+ *
148
+ * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
149
+ * encrypting/decrypting for encryption metadata.
150
+ * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
151
+ * is required. Accepts the following options:
152
+ * - Cipher: (string) cbc|gcm
153
+ * See also: AbstractCryptoClient::$supportedCiphers
154
+ * - KeySize: (int) 128|192|256
155
+ * See also: MaterialsProvider::$supportedKeySizes
156
+ * - Aad: (string) Additional authentication data. This option is
157
+ * passed directly to OpenSSL when using gcm. It is ignored when
158
+ * using cbc.
159
+ *
160
+ * The optional configuration arguments are as follows:
161
+ *
162
+ * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
163
+ * MetadataEnvelope information. Defaults to using a
164
+ * HeadersMetadataStrategy. Can either be a class implementing
165
+ * MetadataStrategy, a class name of a predefined strategy, or empty/null
166
+ * to default.
167
+ * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
168
+ * instruction file if an using an InstructionFileMetadataHandler was
169
+ * determined.
170
+ *
171
+ * @return \Aws\Result PutObject call result with the details of uploading
172
+ * the encrypted file.
173
+ *
174
+ * @throws \InvalidArgumentException Thrown when arguments above are not
175
+ * passed or are passed incorrectly.
176
+ */
177
+ public function putObject(array $args)
178
+ {
179
+ return $this->putObjectAsync($args)->wait();
180
+ }
181
+
182
+ /**
183
+ * Promises to retrieve an object from S3 and decrypt the data in the
184
+ * 'Body' field.
185
+ *
186
+ * @param array $args Arguments for retrieving an object from S3 via
187
+ * GetObject and decrypting it.
188
+ *
189
+ * The required configuration argument is as follows:
190
+ *
191
+ * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
192
+ * encrypting/decrypting for decryption metadata. May have data loaded
193
+ * from the MetadataEnvelope upon decryption.
194
+ *
195
+ * The optional configuration arguments are as follows:
196
+ *
197
+ * - SaveAs: (string) The path to a file on disk to save the decrypted
198
+ * object data. This will be handled by file_put_contents instead of the
199
+ * Guzzle sink.
200
+ *
201
+ * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for reading
202
+ * MetadataEnvelope information. Defaults to determining based on object
203
+ * response headers. Can either be a class implementing MetadataStrategy,
204
+ * a class name of a predefined strategy, or empty/null to default.
205
+ * - @InstructionFileSuffix: (string) Suffix used when looking for an
206
+ * instruction file if an InstructionFileMetadataHandler is being used.
207
+ * - @CipherOptions: (array) Cipher options for decrypting data. A Cipher
208
+ * is required. Accepts the following options:
209
+ * - Aad: (string) Additional authentication data. This option is
210
+ * passed directly to OpenSSL when using gcm. It is ignored when
211
+ * using cbc.
212
+ *
213
+ * @return PromiseInterface
214
+ *
215
+ * @throws \InvalidArgumentException Thrown when required arguments are not
216
+ * passed or are passed incorrectly.
217
+ */
218
+ public function getObjectAsync(array $args)
219
+ {
220
+ $provider = $this->getMaterialsProvider($args);
221
+ unset($args['@MaterialsProvider']);
222
+
223
+ $instructionFileSuffix = $this->getInstructionFileSuffix($args);
224
+ unset($args['@InstructionFileSuffix']);
225
+
226
+ $strategy = $this->getMetadataStrategy($args, $instructionFileSuffix);
227
+ unset($args['@MetadataStrategy']);
228
+
229
+ $saveAs = null;
230
+ if (!empty($args['SaveAs'])) {
231
+ $saveAs = $args['SaveAs'];
232
+ }
233
+
234
+ $promise = $this->client->getObjectAsync($args)
235
+ ->then(
236
+ function ($result) use (
237
+ $provider,
238
+ $instructionFileSuffix,
239
+ $strategy,
240
+ $args
241
+ ) {
242
+ if ($strategy === null) {
243
+ $strategy = $this->determineGetObjectStrategy(
244
+ $result,
245
+ $instructionFileSuffix
246
+ );
247
+ }
248
+
249
+ $envelope = $strategy->load($args + [
250
+ 'Metadata' => $result['Metadata']
251
+ ]);
252
+
253
+ $provider = $provider->fromDecryptionEnvelope($envelope);
254
+
255
+ $result['Body'] = $this->decrypt(
256
+ $result['Body'],
257
+ $provider,
258
+ $envelope,
259
+ isset($args['@CipherOptions'])
260
+ ? $args['@CipherOptions']
261
+ : []
262
+ );
263
+ return $result;
264
+ }
265
+ )->then(
266
+ function ($result) use ($saveAs) {
267
+ if (!empty($saveAs)) {
268
+ file_put_contents(
269
+ $saveAs,
270
+ (string)$result['Body'],
271
+ LOCK_EX
272
+ );
273
+ }
274
+ return $result;
275
+ }
276
+ );
277
+
278
+ return $promise;
279
+ }
280
+
281
+ /**
282
+ * Retrieves an object from S3 and decrypts the data in the 'Body' field.
283
+ *
284
+ * @param array $args Arguments for retrieving an object from S3 via
285
+ * GetObject and decrypting it.
286
+ *
287
+ * The required configuration argument is as follows:
288
+ *
289
+ * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
290
+ * encrypting/decrypting for decryption metadata. May have data loaded
291
+ * from the MetadataEnvelope upon decryption.
292
+ *
293
+ * The optional configuration arguments are as follows:
294
+ *
295
+ * - SaveAs: (string) The path to a file on disk to save the decrypted
296
+ * object data. This will be handled by file_put_contents instead of the
297
+ * Guzzle sink.
298
+ * - @InstructionFileSuffix: (string|null) Suffix used when looking for an
299
+ * instruction file if an InstructionFileMetadataHandler was detected.
300
+ * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
301
+ * is required. Accepts the following options:
302
+ * - Aad: (string) Additional authentication data. This option is
303
+ * passed directly to OpenSSL when using gcm. It is ignored when
304
+ * using cbc.
305
+ *
306
+ * @return \Aws\Result GetObject call result with the 'Body' field
307
+ * wrapped in a decryption stream with its metadata
308
+ * information.
309
+ *
310
+ * @throws \InvalidArgumentException Thrown when arguments above are not
311
+ * passed or are passed incorrectly.
312
+ */
313
+ public function getObject(array $args)
314
+ {
315
+ return $this->getObjectAsync($args)->wait();
316
+ }
317
+ }
lib/Aws/Aws/S3/Crypto/S3EncryptionMultipartUploader.php ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Crypto;
3
+
4
+ use Aws\Crypto\AbstractCryptoClient;
5
+ use Aws\Crypto\EncryptionTrait;
6
+ use Aws\Crypto\MetadataEnvelope;
7
+ use Aws\Crypto\Cipher\CipherBuilderTrait;
8
+ use Aws\S3\MultipartUploader;
9
+ use Aws\S3\S3ClientInterface;
10
+ use GuzzleHttp\Promise;
11
+
12
+ /**
13
+ * Encapsulates the execution of a multipart upload of an encrypted object to S3.
14
+ */
15
+ class S3EncryptionMultipartUploader extends MultipartUploader
16
+ {
17
+ use EncryptionTrait, CipherBuilderTrait, CryptoParamsTrait;
18
+
19
+ /**
20
+ * Returns if the passed cipher name is supported for encryption by the SDK.
21
+ *
22
+ * @param string $cipherName The name of a cipher to verify is registered.
23
+ *
24
+ * @return bool If the cipher passed is in our supported list.
25
+ */
26
+ public static function isSupportedCipher($cipherName)
27
+ {
28
+ return in_array($cipherName, AbstractCryptoClient::$supportedCiphers);
29
+ }
30
+
31
+ private $provider;
32
+ private $instructionFileSuffix;
33
+ private $strategy;
34
+
35
+ /**
36
+ * Creates a multipart upload for an S3 object after encrypting it.
37
+ *
38
+ * The required configuration options are as follows:
39
+ *
40
+ * - @MaterialsProvider: (MaterialsProvider) Provides Cek, Iv, and Cek
41
+ * encrypting/decrypting for encryption metadata.
42
+ * - @CipherOptions: (array) Cipher options for encrypting data. A Cipher
43
+ * is required. Accepts the following options:
44
+ * - Cipher: (string) cbc|gcm
45
+ * See also: AbstractCryptoClient::$supportedCiphers
46
+ * - KeySize: (int) 128|192|256
47
+ * See also: MaterialsProvider::$supportedKeySizes
48
+ * - Aad: (string) Additional authentication data. This option is
49
+ * passed directly to OpenSSL when using gcm. It is ignored when
50
+ * using cbc.
51
+ * - bucket: (string) Name of the bucket to which the object is
52
+ * being uploaded.
53
+ * - key: (string) Key to use for the object being uploaded.
54
+ *
55
+ * The optional configuration arguments are as follows:
56
+ *
57
+ * - @MetadataStrategy: (MetadataStrategy|string|null) Strategy for storing
58
+ * MetadataEnvelope information. Defaults to using a
59
+ * HeadersMetadataStrategy. Can either be a class implementing
60
+ * MetadataStrategy, a class name of a predefined strategy, or empty/null
61
+ * to default.
62
+ * - @InstructionFileSuffix: (string|null) Suffix used when writing to an
63
+ * instruction file if an using an InstructionFileMetadataHandler was
64
+ * determined.
65
+ * - acl: (string) ACL to set on the object being upload. Objects are
66
+ * private by default.
67
+ * - before_complete: (callable) Callback to invoke before the
68
+ * `CompleteMultipartUpload` operation. The callback should have a
69
+ * function signature like `function (Aws\Command $command) {...}`.
70
+ * - before_initiate: (callable) Callback to invoke before the
71
+ * `CreateMultipartUpload` operation. The callback should have a function
72
+ * signature like `function (Aws\Command $command) {...}`.
73
+ * - before_upload: (callable) Callback to invoke before any `UploadPart`
74
+ * operations. The callback should have a function signature like
75
+ * `function (Aws\Command $command) {...}`.
76
+ * - concurrency: (int, default=int(5)) Maximum number of concurrent
77
+ * `UploadPart` operations allowed during the multipart upload.
78
+ * - params: (array) An array of key/value parameters that will be applied
79
+ * to each of the sub-commands run by the uploader as a base.
80
+ * Auto-calculated options will override these parameters. If you need
81
+ * more granularity over parameters to each sub-command, use the before_*
82
+ * options detailed above to update the commands directly.
83
+ * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
84
+ * doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
85
+ * - state: (Aws\Multipart\UploadState) An object that represents the state
86
+ * of the multipart upload and that is used to resume a previous upload.
87
+ * When this option is provided, the `bucket`, `key`, and `part_size`
88
+ * options are ignored.
89
+ *
90
+ * @param S3ClientInterface $client Client used for the upload.
91
+ * @param mixed $source Source of the data to upload.
92
+ * @param array $config Configuration used to perform the upload.
93
+ */
94
+ public function __construct(
95
+ S3ClientInterface $client,
96
+ $source,
97
+ array $config = []
98
+ ) {
99
+ $this->client = $client;
100
+ $config['params'] = [];
101
+ if (!empty($config['bucket'])) {
102
+ $config['params']['Bucket'] = $config['bucket'];
103
+ }
104
+ if (!empty($config['key'])) {
105
+ $config['params']['Key'] = $config['key'];
106
+ }
107
+
108
+ $this->provider = $this->getMaterialsProvider($config);
109
+ unset($config['@MaterialsProvider']);
110
+
111
+ $this->instructionFileSuffix = $this->getInstructionFileSuffix($config);
112
+ unset($config['@InstructionFileSuffix']);
113
+ $this->strategy = $this->getMetadataStrategy(
114
+ $config,
115
+ $this->instructionFileSuffix
116
+ );
117
+ if ($this->strategy === null) {
118
+ $this->strategy = self::getDefaultStrategy();
119
+ }
120
+ unset($config['@MetadataStrategy']);
121
+
122
+ $config['prepare_data_source'] = $this->getEncryptingDataPreparer();
123
+
124
+ parent::__construct($client, $source, $config);
125
+ }
126
+
127
+ private static function getDefaultStrategy()
128
+ {
129
+ return new HeadersMetadataStrategy();
130
+ }
131
+
132
+ private function getEncryptingDataPreparer()
133
+ {
134
+ return function() {
135
+ // Defer encryption work until promise is executed
136
+ $envelope = new MetadataEnvelope();
137
+
138
+ list($this->source, $params) = Promise\promise_for($this->encrypt(
139
+ $this->source,
140
+ $this->config['@cipheroptions'] ?: [],
141
+ $this->provider,
142
+ $envelope
143
+ ))->then(
144
+ function ($bodyStream) use ($envelope) {
145
+ $params = $this->strategy->save(
146
+ $envelope,
147
+ $this->config['params']
148
+ );
149
+ return [$bodyStream, $params];
150
+ }
151
+ )->wait();
152
+
153
+ $this->source->rewind();
154
+ $this->config['params'] = $params;
155
+ };
156
+ }
157
+ }
lib/Aws/Aws/S3/Exception/DeleteMultipleObjectsException.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Exception;
3
+
4
+ use Aws\HasMonitoringEventsTrait;
5
+ use Aws\MonitoringEventsInterface;
6
+
7
+ /**
8
+ * Exception thrown when errors occur while deleting objects using a
9
+ * {@see S3\BatchDelete} object.
10
+ */
11
+ class DeleteMultipleObjectsException extends \Exception implements
12
+ MonitoringEventsInterface
13
+ {
14
+ use HasMonitoringEventsTrait;
15
+
16
+ private $deleted = [];
17
+ private $errors = [];
18
+
19
+ /**
20
+ * @param array $deleted Array of successfully deleted keys
21
+ * @param array $errors Array of errors that were encountered
22
+ */
23
+ public function __construct(array $deleted, array $errors)
24
+ {
25
+ $this->deleted = array_values($deleted);
26
+ $this->errors = array_values($errors);
27
+ parent::__construct('Unable to delete certain keys when executing a'
28
+ . ' DeleteMultipleObjects request: '
29
+ . self::createMessageFromErrors($errors));
30
+ }
31
+
32
+ /**
33
+ * Create a single error message from multiple errors.
34
+ *
35
+ * @param array $errors Errors encountered
36
+ *
37
+ * @return string
38
+ */
39
+ public static function createMessageFromErrors(array $errors)
40
+ {
41
+ return "\n- " . implode("\n- ", array_map(function ($key) {
42
+ return json_encode($key);
43
+ }, $errors));
44
+ }
45
+
46
+ /**
47
+ * Get the errored objects
48
+ *
49
+ * @return array Returns an array of associative arrays, each containing
50
+ * a 'Code', 'Message', and 'Key' key.
51
+ */
52
+ public function getErrors()
53
+ {
54
+ return $this->errors;
55
+ }
56
+
57
+ /**
58
+ * Get the successfully deleted objects
59
+ *
60
+ * @return array Returns an array of associative arrays, each containing
61
+ * a 'Key' and optionally 'DeleteMarker' and
62
+ * 'DeleterMarkerVersionId'
63
+ */
64
+ public function getDeleted()
65
+ {
66
+ return $this->deleted;
67
+ }
68
+ }
lib/Aws/Aws/S3/Exception/PermanentRedirectException.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Exception;
3
+
4
+ class PermanentRedirectException extends S3Exception {}
lib/Aws/Aws/S3/Exception/S3Exception.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Exception;
3
+
4
+ use Aws\Exception\AwsException;
5
+
6
+ /**
7
+ * Represents an error interacting with the Amazon Simple Storage Service.
8
+ */
9
+ class S3Exception extends AwsException {}
lib/Aws/Aws/S3/Exception/S3MultipartUploadException.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3\Exception;
3
+
4
+ use Aws\CommandInterface;
5
+ use Aws\Exception\AwsException;
6
+ use Aws\Multipart\UploadState;
7
+
8
+ class S3MultipartUploadException extends \Aws\Exception\MultipartUploadException
9
+ {
10
+ /** @var string Bucket of the transfer object */
11
+ private $bucket;
12
+ /** @var string Key of the transfer object */
13
+ private $key;
14
+ /** @var string Source file name of the transfer object */
15
+ private $filename;
16
+
17
+ /**
18
+ * @param UploadState $state Upload state at time of the exception.
19
+ * @param \Exception|array $prev Exception being thrown. Could be an array of
20
+ * AwsExceptions being thrown when uploading parts
21
+ * for one object, or an instance of AwsException
22
+ * for a specific Multipart error being thrown in
23
+ * the MultipartUpload process.
24
+ */
25
+ public function __construct(UploadState $state, $prev = null) {
26
+ if (is_array($prev) && $error = $prev[key($prev)]) {
27
+ $this->collectPathInfo($error->getCommand());
28
+ } elseif ($prev instanceof AwsException) {
29
+ $this->collectPathInfo($prev->getCommand());
30
+ }
31
+ parent::__construct($state, $prev);
32
+ }
33
+
34
+ /**
35
+ * Get the Bucket information of the transfer object
36
+ *
37
+ * @return string|null Returns null when 'Bucket' information
38
+ * is unavailable.
39
+ */
40
+ public function getBucket()
41
+ {
42
+ return $this->bucket;
43
+ }
44
+
45
+ /**
46
+ * Get the Key information of the transfer object
47
+ *
48
+ * @return string|null Returns null when 'Key' information
49
+ * is unavailable.
50
+ */
51
+ public function getKey()
52
+ {
53
+ return $this->key;
54
+ }
55
+
56
+ /**
57
+ * Get the source file name of the transfer object
58
+ *
59
+ * @return string|null Returns null when metadata of the stream
60
+ * wrapped in 'Body' parameter is unavailable.
61
+ */
62
+ public function getSourceFileName()
63
+ {
64
+ return $this->filename;
65
+ }
66
+
67
+ /**
68
+ * Collect file path information when accessible. (Bucket, Key)
69
+ *
70
+ * @param CommandInterface $cmd
71
+ */
72
+ private function collectPathInfo(CommandInterface $cmd)
73
+ {
74
+ if (empty($this->bucket) && isset($cmd['Bucket'])) {
75
+ $this->bucket = $cmd['Bucket'];
76
+ }
77
+ if (empty($this->key) && isset($cmd['Key'])) {
78
+ $this->key = $cmd['Key'];
79
+ }
80
+ if (empty($this->filename) && isset($cmd['Body'])) {
81
+ $this->filename = $cmd['Body']->getMetadata('uri');
82
+ }
83
+ }
84
+ }
lib/Aws/Aws/S3/GetBucketLocationParser.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Api\Parser\AbstractParser;
5
+ use Aws\Api\StructureShape;
6
+ use Aws\CommandInterface;
7
+ use Psr\Http\Message\ResponseInterface;
8
+ use Psr\Http\Message\StreamInterface;
9
+
10
+ /**
11
+ * @internal Decorates a parser for the S3 service to correctly handle the
12
+ * GetBucketLocation operation.
13
+ */
14
+ class GetBucketLocationParser extends AbstractParser
15
+ {
16
+ /**
17
+ * @param callable $parser Parser to wrap.
18
+ */
19
+ public function __construct(callable $parser)
20
+ {
21
+ $this->parser = $parser;
22
+ }
23
+
24
+ public function __invoke(
25
+ CommandInterface $command,
26
+ ResponseInterface $response
27
+ ) {
28
+ $fn = $this->parser;
29
+ $result = $fn($command, $response);
30
+
31
+ if ($command->getName() === 'GetBucketLocation') {
32
+ $location = 'us-east-1';
33
+ if (preg_match('/>(.+?)<\/LocationConstraint>/', $response->getBody(), $matches)) {
34
+ $location = $matches[1] === 'EU' ? 'eu-west-1' : $matches[1];
35
+ }
36
+ $result['LocationConstraint'] = $location;
37
+ }
38
+
39
+ return $result;
40
+ }
41
+
42
+ public function parseMemberFromStream(
43
+ StreamInterface $stream,
44
+ StructureShape $member,
45
+ $response
46
+ ) {
47
+ return $this->parser->parseMemberFromStream($stream, $member, $response);
48
+ }
49
+ }
lib/Aws/Aws/S3/MultipartCopy.php ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Multipart\AbstractUploadManager;
5
+ use Aws\ResultInterface;
6
+ use GuzzleHttp\Psr7;
7
+
8
+ class MultipartCopy extends AbstractUploadManager
9
+ {
10
+ use MultipartUploadingTrait;
11
+
12
+ /** @var string */
13
+ private $source;
14
+ /** @var ResultInterface */
15
+ private $sourceMetadata;
16
+
17
+ /**
18
+ * Creates a multipart upload for copying an S3 object.
19
+ *
20
+ * The valid configuration options are as follows:
21
+ *
22
+ * - acl: (string) ACL to set on the object being upload. Objects are
23
+ * private by default.
24
+ * - before_complete: (callable) Callback to invoke before the
25
+ * `CompleteMultipartUpload` operation. The callback should have a
26
+ * function signature like `function (Aws\Command $command) {...}`.
27
+ * - before_initiate: (callable) Callback to invoke before the
28
+ * `CreateMultipartUpload` operation. The callback should have a function
29
+ * signature like `function (Aws\Command $command) {...}`.
30
+ * - before_upload: (callable) Callback to invoke before `UploadPartCopy`
31
+ * operations. The callback should have a function signature like
32
+ * `function (Aws\Command $command) {...}`.
33
+ * - bucket: (string, required) Name of the bucket to which the object is
34
+ * being uploaded.
35
+ * - concurrency: (int, default=int(5)) Maximum number of concurrent
36
+ * `UploadPart` operations allowed during the multipart upload.
37
+ * - key: (string, required) Key to use for the object being uploaded.
38
+ * - params: (array) An array of key/value parameters that will be applied
39
+ * to each of the sub-commands run by the uploader as a base.
40
+ * Auto-calculated options will override these parameters. If you need
41
+ * more granularity over parameters to each sub-command, use the before_*
42
+ * options detailed above to update the commands directly.
43
+ * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
44
+ * doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
45
+ * - state: (Aws\Multipart\UploadState) An object that represents the state
46
+ * of the multipart upload and that is used to resume a previous upload.
47
+ * When this option is provided, the `bucket`, `key`, and `part_size`
48
+ * options are ignored.
49
+ * - source_metadata: (Aws\ResultInterface) An object that represents the
50
+ * result of executing a HeadObject command on the copy source.
51
+ *
52
+ * @param S3ClientInterface $client Client used for the upload.
53
+ * @param string $source Location of the data to be copied
54
+ * (in the form /<bucket>/<key>).
55
+ * @param array $config Configuration used to perform the upload.
56
+ */
57
+ public function __construct(
58
+ S3ClientInterface $client,
59
+ $source,
60
+ array $config = []
61
+ ) {
62
+ $this->source = '/' . ltrim($source, '/');
63
+ parent::__construct($client, array_change_key_case($config) + [
64
+ 'source_metadata' => null
65
+ ]);
66
+ }
67
+
68
+ /**
69
+ * An alias of the self::upload method.
70
+ *
71
+ * @see self::upload
72
+ */
73
+ public function copy()
74
+ {
75
+ return $this->upload();
76
+ }
77
+
78
+ protected function loadUploadWorkflowInfo()
79
+ {
80
+ return [
81
+ 'command' => [
82
+ 'initiate' => 'CreateMultipartUpload',
83
+ 'upload' => 'UploadPartCopy',
84
+ 'complete' => 'CompleteMultipartUpload',
85
+ ],
86
+ 'id' => [
87
+ 'bucket' => 'Bucket',
88
+ 'key' => 'Key',
89
+ 'upload_id' => 'UploadId',
90
+ ],
91
+ 'part_num' => 'PartNumber',
92
+ ];
93
+ }
94
+
95
+ protected function getUploadCommands(callable $resultHandler)
96
+ {
97
+ $parts = ceil($this->getSourceSize() / $this->determinePartSize());
98
+
99
+ for ($partNumber = 1; $partNumber <= $parts; $partNumber++) {
100
+ // If we haven't already uploaded this part, yield a new part.
101
+ if (!$this->state->hasPartBeenUploaded($partNumber)) {
102
+ $command = $this->client->getCommand(
103
+ $this->info['command']['upload'],
104
+ $this->createPart($partNumber, $parts)
105
+ + $this->getState()->getId()
106
+ );
107
+ $command->getHandlerList()->appendSign($resultHandler, 'mup');
108
+ yield $command;
109
+ }
110
+ }
111
+ }
112
+
113
+ private function createPart($partNumber, $partsCount)
114
+ {
115
+ $data = [];
116
+
117
+ // Apply custom params to UploadPartCopy data
118
+ $config = $this->getConfig();
119
+ $params = isset($config['params']) ? $config['params'] : [];
120
+ foreach ($params as $k => $v) {
121
+ $data[$k] = $v;
122
+ }
123
+
124
+ $data['CopySource'] = $this->source;
125
+ $data['PartNumber'] = $partNumber;
126
+
127
+ $defaultPartSize = $this->determinePartSize();
128
+ $startByte = $defaultPartSize * ($partNumber - 1);
129
+ $data['ContentLength'] = $partNumber < $partsCount
130
+ ? $defaultPartSize
131
+ : $this->getSourceSize() - ($defaultPartSize * ($partsCount - 1));
132
+ $endByte = $startByte + $data['ContentLength'] - 1;
133
+ $data['CopySourceRange'] = "bytes=$startByte-$endByte";
134
+
135
+ return $data;
136
+ }
137
+
138
+ protected function extractETag(ResultInterface $result)
139
+ {
140
+ return $result->search('CopyPartResult.ETag');
141
+ }
142
+
143
+ protected function getSourceMimeType()
144
+ {
145
+ return $this->getSourceMetadata()['ContentType'];
146
+ }
147
+
148
+ protected function getSourceSize()
149
+ {
150
+ return $this->getSourceMetadata()['ContentLength'];
151
+ }
152
+
153
+ private function getSourceMetadata()
154
+ {
155
+ if (empty($this->sourceMetadata)) {
156
+ $this->sourceMetadata = $this->fetchSourceMetadata();
157
+ }
158
+
159
+ return $this->sourceMetadata;
160
+ }
161
+
162
+ private function fetchSourceMetadata()
163
+ {
164
+ if ($this->config['source_metadata'] instanceof ResultInterface) {
165
+ return $this->config['source_metadata'];
166
+ }
167
+
168
+ list($bucket, $key) = explode('/', ltrim($this->source, '/'), 2);
169
+ $headParams = [
170
+ 'Bucket' => $bucket,
171
+ 'Key' => $key,
172
+ ];
173
+ if (strpos($key, '?')) {
174
+ list($key, $query) = explode('?', $key, 2);
175
+ $headParams['Key'] = $key;
176
+ $query = Psr7\parse_query($query, false);
177
+ if (isset($query['versionId'])) {
178
+ $headParams['VersionId'] = $query['versionId'];
179
+ }
180
+ }
181
+ return $this->client->headObject($headParams);
182
+ }
183
+ }
lib/Aws/Aws/S3/MultipartUploader.php ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\HashingStream;
5
+ use Aws\Multipart\AbstractUploader;
6
+ use Aws\PhpHash;
7
+ use Aws\ResultInterface;
8
+ use GuzzleHttp\Psr7;
9
+ use Psr\Http\Message\StreamInterface as Stream;
10
+ use Aws\S3\Exception\S3MultipartUploadException;
11
+
12
+ /**
13
+ * Encapsulates the execution of a multipart upload to S3 or Glacier.
14
+ */
15
+ class MultipartUploader extends AbstractUploader
16
+ {
17
+ use MultipartUploadingTrait;
18
+
19
+ const PART_MIN_SIZE = 5242880;
20
+ const PART_MAX_SIZE = 5368709120;
21
+ const PART_MAX_NUM = 10000;
22
+
23
+ /**
24
+ * Creates a multipart upload for an S3 object.
25
+ *
26
+ * The valid configuration options are as follows:
27
+ *
28
+ * - acl: (string) ACL to set on the object being upload. Objects are
29
+ * private by default.
30
+ * - before_complete: (callable) Callback to invoke before the
31
+ * `CompleteMultipartUpload` operation. The callback should have a
32
+ * function signature like `function (Aws\Command $command) {...}`.
33
+ * - before_initiate: (callable) Callback to invoke before the
34
+ * `CreateMultipartUpload` operation. The callback should have a function
35
+ * signature like `function (Aws\Command $command) {...}`.
36
+ * - before_upload: (callable) Callback to invoke before any `UploadPart`
37
+ * operations. The callback should have a function signature like
38
+ * `function (Aws\Command $command) {...}`.
39
+ * - bucket: (string, required) Name of the bucket to which the object is
40
+ * being uploaded.
41
+ * - concurrency: (int, default=int(5)) Maximum number of concurrent
42
+ * `UploadPart` operations allowed during the multipart upload.
43
+ * - key: (string, required) Key to use for the object being uploaded.
44
+ * - params: (array) An array of key/value parameters that will be applied
45
+ * to each of the sub-commands run by the uploader as a base.
46
+ * Auto-calculated options will override these parameters. If you need
47
+ * more granularity over parameters to each sub-command, use the before_*
48
+ * options detailed above to update the commands directly.
49
+ * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
50
+ * doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
51
+ * - prepare_data_source: (callable) Callback to invoke before starting the
52
+ * multipart upload workflow. The callback should have a function
53
+ * signature like `function () {...}`.
54
+ * - state: (Aws\Multipart\UploadState) An object that represents the state
55
+ * of the multipart upload and that is used to resume a previous upload.
56
+ * When this option is provided, the `bucket`, `key`, and `part_size`
57
+ * options are ignored.
58
+ *
59
+ * @param S3ClientInterface $client Client used for the upload.
60
+ * @param mixed $source Source of the data to upload.
61
+ * @param array $config Configuration used to perform the upload.
62
+ */
63
+ public function __construct(
64
+ S3ClientInterface $client,
65
+ $source,
66
+ array $config = []
67
+ ) {
68
+ parent::__construct($client, $source, array_change_key_case($config) + [
69
+ 'bucket' => null,
70
+ 'key' => null,
71
+ 'exception_class' => S3MultipartUploadException::class,
72
+ ]);
73
+ }
74
+
75
+ protected function loadUploadWorkflowInfo()
76
+ {
77
+ return [
78
+ 'command' => [
79
+ 'initiate' => 'CreateMultipartUpload',
80
+ 'upload' => 'UploadPart',
81
+ 'complete' => 'CompleteMultipartUpload',
82
+ ],
83
+ 'id' => [
84
+ 'bucket' => 'Bucket',
85
+ 'key' => 'Key',
86
+ 'upload_id' => 'UploadId',
87
+ ],
88
+ 'part_num' => 'PartNumber',
89
+ ];
90
+ }
91
+
92
+ protected function createPart($seekable, $number)
93
+ {
94
+ // Initialize the array of part data that will be returned.
95
+ $data = [];
96
+
97
+ // Apply custom params to UploadPart data
98
+ $config = $this->getConfig();
99
+ $params = isset($config['params']) ? $config['params'] : [];
100
+ foreach ($params as $k => $v) {
101
+ $data[$k] = $v;
102
+ }
103
+
104
+ $data['PartNumber'] = $number;
105
+
106
+ // Read from the source to create the body stream.
107
+ if ($seekable) {
108
+ // Case 1: Source is seekable, use lazy stream to defer work.
109
+ $body = $this->limitPartStream(
110
+ new Psr7\LazyOpenStream($this->source->getMetadata('uri'), 'r')
111
+ );
112
+ } else {
113
+ // Case 2: Stream is not seekable; must store in temp stream.
114
+ $source = $this->limitPartStream($this->source);
115
+ $source = $this->decorateWithHashes($source, $data);
116
+ $body = Psr7\stream_for();
117
+ Psr7\copy_to_stream($source, $body);
118
+ }
119
+
120
+ $contentLength = $body->getSize();
121
+
122
+ // Do not create a part if the body size is zero.
123
+ if ($contentLength === 0) {
124
+ return false;
125
+ }
126
+
127
+ $body->seek(0);
128
+ $data['Body'] = $body;
129
+ $data['ContentLength'] = $contentLength;
130
+
131
+ return $data;
132
+ }
133
+
134
+ protected function extractETag(ResultInterface $result)
135
+ {
136
+ return $result['ETag'];
137
+ }
138
+
139
+ protected function getSourceMimeType()
140
+ {
141
+ if ($uri = $this->source->getMetadata('uri')) {
142
+ return Psr7\mimetype_from_filename($uri)
143
+ ?: 'application/octet-stream';
144
+ }
145
+ }
146
+
147
+ protected function getSourceSize()
148
+ {
149
+ return $this->source->getSize();
150
+ }
151
+
152
+ /**
153
+ * Decorates a stream with a sha256 linear hashing stream.
154
+ *
155
+ * @param Stream $stream Stream to decorate.
156
+ * @param array $data Part data to augment with the hash result.
157
+ *
158
+ * @return Stream
159
+ */
160
+ private function decorateWithHashes(Stream $stream, array &$data)
161
+ {
162
+ // Decorate source with a hashing stream
163
+ $hash = new PhpHash('sha256');
164
+ return new HashingStream($stream, $hash, function ($result) use (&$data) {
165
+ $data['ContentSHA256'] = bin2hex($result);
166
+ });
167
+ }
168
+ }
lib/Aws/Aws/S3/MultipartUploadingTrait.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use Aws\Multipart\UploadState;
6
+ use Aws\ResultInterface;
7
+
8
+ trait MultipartUploadingTrait
9
+ {
10
+ /**
11
+ * Creates an UploadState object for a multipart upload by querying the
12
+ * service for the specified upload's information.
13
+ *
14
+ * @param S3ClientInterface $client S3Client used for the upload.
15
+ * @param string $bucket Bucket for the multipart upload.
16
+ * @param string $key Object key for the multipart upload.
17
+ * @param string $uploadId Upload ID for the multipart upload.
18
+ *
19
+ * @return UploadState
20
+ */
21
+ public static function getStateFromService(
22
+ S3ClientInterface $client,
23
+ $bucket,
24
+ $key,
25
+ $uploadId
26
+ ) {
27
+ $state = new UploadState([
28
+ 'Bucket' => $bucket,
29
+ 'Key' => $key,
30
+ 'UploadId' => $uploadId,
31
+ ]);
32
+
33
+ foreach ($client->getPaginator('ListParts', $state->getId()) as $result) {
34
+ // Get the part size from the first part in the first result.
35
+ if (!$state->getPartSize()) {
36
+ $state->setPartSize($result->search('Parts[0].Size'));
37
+ }
38
+ // Mark all the parts returned by ListParts as uploaded.
39
+ foreach ($result['Parts'] as $part) {
40
+ $state->markPartAsUploaded($part['PartNumber'], [
41
+ 'PartNumber' => $part['PartNumber'],
42
+ 'ETag' => $part['ETag']
43
+ ]);
44
+ }
45
+ }
46
+
47
+ $state->setStatus(UploadState::INITIATED);
48
+
49
+ return $state;
50
+ }
51
+
52
+ protected function handleResult(CommandInterface $command, ResultInterface $result)
53
+ {
54
+ $this->getState()->markPartAsUploaded($command['PartNumber'], [
55
+ 'PartNumber' => $command['PartNumber'],
56
+ 'ETag' => $this->extractETag($result),
57
+ ]);
58
+ }
59
+
60
+ abstract protected function extractETag(ResultInterface $result);
61
+
62
+ protected function getCompleteParams()
63
+ {
64
+ $config = $this->getConfig();
65
+ $params = isset($config['params']) ? $config['params'] : [];
66
+
67
+ $params['MultipartUpload'] = [
68
+ 'Parts' => $this->getState()->getUploadedParts()
69
+ ];
70
+
71
+ return $params;
72
+ }
73
+
74
+ protected function determinePartSize()
75
+ {
76
+ // Make sure the part size is set.
77
+ $partSize = $this->getConfig()['part_size'] ?: MultipartUploader::PART_MIN_SIZE;
78
+
79
+ // Adjust the part size to be larger for known, x-large uploads.
80
+ if ($sourceSize = $this->getSourceSize()) {
81
+ $partSize = (int) max(
82
+ $partSize,
83
+ ceil($sourceSize / MultipartUploader::PART_MAX_NUM)
84
+ );
85
+ }
86
+
87
+ // Ensure that the part size follows the rules: 5 MB <= size <= 5 GB.
88
+ if ($partSize < MultipartUploader::PART_MIN_SIZE || $partSize > MultipartUploader::PART_MAX_SIZE) {
89
+ throw new \InvalidArgumentException('The part size must be no less '
90
+ . 'than 5 MB and no greater than 5 GB.');
91
+ }
92
+
93
+ return $partSize;
94
+ }
95
+
96
+ protected function getInitiateParams()
97
+ {
98
+ $config = $this->getConfig();
99
+ $params = isset($config['params']) ? $config['params'] : [];
100
+
101
+ if (isset($config['acl'])) {
102
+ $params['ACL'] = $config['acl'];
103
+ }
104
+
105
+ // Set the ContentType if not already present
106
+ if (empty($params['ContentType']) && $type = $this->getSourceMimeType()) {
107
+ $params['ContentType'] = $type;
108
+ }
109
+
110
+ return $params;
111
+ }
112
+
113
+ /**
114
+ * @return UploadState
115
+ */
116
+ abstract protected function getState();
117
+
118
+ /**
119
+ * @return array
120
+ */
121
+ abstract protected function getConfig();
122
+
123
+ /**
124
+ * @return int
125
+ */
126
+ abstract protected function getSourceSize();
127
+
128
+ /**
129
+ * @return string|null
130
+ */
131
+ abstract protected function getSourceMimeType();
132
+ }
lib/Aws/Aws/S3/ObjectCopier.php ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Exception\MultipartUploadException;
5
+ use Aws\Result;
6
+ use Aws\S3\Exception\S3Exception;
7
+ use GuzzleHttp\Promise\PromisorInterface;
8
+ use InvalidArgumentException;
9
+
10
+ /**
11
+ * Copies objects from one S3 location to another, utilizing a multipart copy
12
+ * when appropriate.
13
+ */
14
+ class ObjectCopier implements PromisorInterface
15
+ {
16
+ const DEFAULT_MULTIPART_THRESHOLD = MultipartUploader::PART_MAX_SIZE;
17
+
18
+ private $client;
19
+ private $source;
20
+ private $destination;
21
+ private $acl;
22
+ private $options;
23
+
24
+ private static $defaults = [
25
+ 'before_lookup' => null,
26
+ 'before_upload' => null,
27
+ 'concurrency' => 5,
28
+ 'mup_threshold' => self::DEFAULT_MULTIPART_THRESHOLD,
29
+ 'params' => [],
30
+ 'part_size' => null,
31
+ 'version_id' => null,
32
+ ];
33
+
34
+ /**
35
+ * @param S3ClientInterface $client The S3 Client used to execute
36
+ * the copy command(s).
37
+ * @param array $source The object to copy, specified as
38
+ * an array with a 'Bucket' and
39
+ * 'Key' keys. Provide a
40
+ * 'VersionID' key to copy a
41
+ * specified version of an object.
42
+ * @param array $destination The bucket and key to which to
43
+ * copy the $source, specified as
44
+ * an array with a 'Bucket' and
45
+ * 'Key' keys.
46
+ * @param string $acl ACL to apply to the copy
47
+ * (default: private).
48
+ * @param array $options Options used to configure the
49
+ * copy process. Options passed in
50
+ * through 'params' are added to
51
+ * the sub commands.
52
+ *
53
+ * @throws InvalidArgumentException
54
+ */
55
+ public function __construct(
56
+ S3ClientInterface $client,
57
+ array $source,
58
+ array $destination,
59
+ $acl = 'private',
60
+ array $options = []
61
+ ) {
62
+ $this->validateLocation($source);
63
+ $this->validateLocation($destination);
64
+
65
+ $this->client = $client;
66
+ $this->source = $source;
67
+ $this->destination = $destination;
68
+ $this->acl = $acl;
69
+ $this->options = $options + self::$defaults;
70
+ }
71
+
72
+ /**
73
+ * Perform the configured copy asynchronously. Returns a promise that is
74
+ * fulfilled with the result of the CompleteMultipartUpload or CopyObject
75
+ * operation or rejected with an exception.
76
+ */
77
+ public function promise()
78
+ {
79
+ return \GuzzleHttp\Promise\coroutine(function () {
80
+ $headObjectCommand = $this->client->getCommand(
81
+ 'HeadObject',
82
+ $this->options['params'] + $this->source
83
+ );
84
+ if (is_callable($this->options['before_lookup'])) {
85
+ $this->options['before_lookup']($headObjectCommand);
86
+ }
87
+ $objectStats = (yield $this->client->executeAsync(
88
+ $headObjectCommand
89
+ ));
90
+
91
+ if ($objectStats['ContentLength'] > $this->options['mup_threshold']) {
92
+ $mup = new MultipartCopy(
93
+ $this->client,
94
+ $this->getSourcePath(),
95
+ ['source_metadata' => $objectStats, 'acl' => $this->acl]
96
+ + $this->destination
97
+ + $this->options
98
+ );
99
+
100
+ yield $mup->promise();
101
+ } else {
102
+ $defaults = [
103
+ 'ACL' => $this->acl,
104
+ 'MetadataDirective' => 'COPY',
105
+ 'CopySource' => $this->getSourcePath(),
106
+ ];
107
+
108
+ $params = array_diff_key($this->options, self::$defaults)
109
+ + $this->destination + $defaults + $this->options['params'];
110
+
111
+ yield $this->client->executeAsync(
112
+ $this->client->getCommand('CopyObject', $params)
113
+ );
114
+ }
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Perform the configured copy synchronously. Returns the result of the
120
+ * CompleteMultipartUpload or CopyObject operation.
121
+ *
122
+ * @return Result
123
+ *
124
+ * @throws S3Exception
125
+ * @throws MultipartUploadException
126
+ */
127
+ public function copy()
128
+ {
129
+ return $this->promise()->wait();
130
+ }
131
+
132
+ private function validateLocation(array $location)
133
+ {
134
+ if (empty($location['Bucket']) || empty($location['Key'])) {
135
+ throw new \InvalidArgumentException('Locations provided to an'
136
+ . ' Aws\S3\ObjectCopier must have a non-empty Bucket and Key');
137
+ }
138
+ }
139
+
140
+ private function getSourcePath()
141
+ {
142
+ $sourcePath = "/{$this->source['Bucket']}/"
143
+ . rawurlencode($this->source['Key']);
144
+ if (isset($this->source['VersionId'])) {
145
+ $sourcePath .= "?versionId={$this->source['VersionId']}";
146
+ }
147
+
148
+ return $sourcePath;
149
+ }
150
+ }
lib/Aws/Aws/S3/ObjectUploader.php ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use GuzzleHttp\Promise\PromisorInterface;
5
+ use GuzzleHttp\Psr7;
6
+ use Psr\Http\Message\StreamInterface;
7
+
8
+ /**
9
+ * Uploads an object to S3, using a PutObject command or a multipart upload as
10
+ * appropriate.
11
+ */
12
+ class ObjectUploader implements PromisorInterface
13
+ {
14
+ const DEFAULT_MULTIPART_THRESHOLD = 16777216;
15
+
16
+ private $client;
17
+ private $bucket;
18
+ private $key;
19
+ private $body;
20
+ private $acl;
21
+ private $options;
22
+ private static $defaults = [
23
+ 'before_upload' => null,
24
+ 'concurrency' => 3,
25
+ 'mup_threshold' => self::DEFAULT_MULTIPART_THRESHOLD,
26
+ 'params' => [],
27
+ 'part_size' => null,
28
+ ];
29
+
30
+ /**
31
+ * @param S3ClientInterface $client The S3 Client used to execute
32
+ * the upload command(s).
33
+ * @param string $bucket Bucket to upload the object.
34
+ * @param string $key Key of the object.
35
+ * @param mixed $body Object data to upload. Can be a
36
+ * StreamInterface, PHP stream
37
+ * resource, or a string of data to
38
+ * upload.
39
+ * @param string $acl ACL to apply to the copy
40
+ * (default: private).
41
+ * @param array $options Options used to configure the
42
+ * copy process. Options passed in
43
+ * through 'params' are added to
44
+ * the sub command(s).
45
+ */
46
+ public function __construct(
47
+ S3ClientInterface $client,
48
+ $bucket,
49
+ $key,
50
+ $body,
51
+ $acl = 'private',
52
+ array $options = []
53
+ ) {
54
+ $this->client = $client;
55
+ $this->bucket = $bucket;
56
+ $this->key = $key;
57
+ $this->body = Psr7\stream_for($body);
58
+ $this->acl = $acl;
59
+ $this->options = $options + self::$defaults;
60
+ }
61
+
62
+ public function promise()
63
+ {
64
+ /** @var int $mup_threshold */
65
+ $mup_threshold = $this->options['mup_threshold'];
66
+ if ($this->requiresMultipart($this->body, $mup_threshold)) {
67
+ // Perform a multipart upload.
68
+ return (new MultipartUploader($this->client, $this->body, [
69
+ 'bucket' => $this->bucket,
70
+ 'key' => $this->key,
71
+ 'acl' => $this->acl
72
+ ] + $this->options))->promise();
73
+ }
74
+
75
+ // Perform a regular PutObject operation.
76
+ $command = $this->client->getCommand('PutObject', [
77
+ 'Bucket' => $this->bucket,
78
+ 'Key' => $this->key,
79
+ 'Body' => $this->body,
80
+ 'ACL' => $this->acl,
81
+ ] + $this->options['params']);
82
+ if (is_callable($this->options['before_upload'])) {
83
+ $this->options['before_upload']($command);
84
+ }
85
+ return $this->client->executeAsync($command);
86
+ }
87
+
88
+ public function upload()
89
+ {
90
+ return $this->promise()->wait();
91
+ }
92
+
93
+ /**
94
+ * Determines if the body should be uploaded using PutObject or the
95
+ * Multipart Upload System. It also modifies the passed-in $body as needed
96
+ * to support the upload.
97
+ *
98
+ * @param StreamInterface $body Stream representing the body.
99
+ * @param integer $threshold Minimum bytes before using Multipart.
100
+ *
101
+ * @return bool
102
+ */
103
+ private function requiresMultipart(StreamInterface &$body, $threshold)
104
+ {
105
+ // If body size known, compare to threshold to determine if Multipart.
106
+ if ($body->getSize() !== null) {
107
+ return $body->getSize() >= $threshold;
108
+ }
109
+
110
+ /**
111
+ * Handle the situation where the body size is unknown.
112
+ * Read up to 5MB into a buffer to determine how to upload the body.
113
+ * @var StreamInterface $buffer
114
+ */
115
+ $buffer = Psr7\stream_for();
116
+ Psr7\copy_to_stream($body, $buffer, MultipartUploader::PART_MIN_SIZE);
117
+
118
+ // If body < 5MB, use PutObject with the buffer.
119
+ if ($buffer->getSize() < MultipartUploader::PART_MIN_SIZE) {
120
+ $buffer->seek(0);
121
+ $body = $buffer;
122
+ return false;
123
+ }
124
+
125
+ // If body >= 5 MB, then use multipart. [YES]
126
+ if ($body->isSeekable() && $body->getMetadata('uri') !== 'php://input') {
127
+ // If the body is seekable, just rewind the body.
128
+ $body->seek(0);
129
+ } else {
130
+ // If the body is non-seekable, stitch the rewind the buffer and
131
+ // the partially read body together into one stream. This avoids
132
+ // unnecessary disc usage and does not require seeking on the
133
+ // original stream.
134
+ $buffer->seek(0);
135
+ $body = new Psr7\AppendStream([$buffer, $body]);
136
+ }
137
+
138
+ return true;
139
+ }
140
+ }
lib/Aws/Aws/S3/PermanentRedirectMiddleware.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use Aws\ResultInterface;
6
+ use Aws\S3\Exception\PermanentRedirectException;
7
+ use Psr\Http\Message\RequestInterface;
8
+
9
+ /**
10
+ * Throws a PermanentRedirectException exception when a 301 redirect is
11
+ * encountered.
12
+ *
13
+ * @internal
14
+ */
15
+ class PermanentRedirectMiddleware
16
+ {
17
+ /** @var callable */
18
+ private $nextHandler;
19
+
20
+ /**
21
+ * Create a middleware wrapper function.
22
+ *
23
+ * @return callable
24
+ */
25
+ public static function wrap()
26
+ {
27
+ return function (callable $handler) {
28
+ return new self($handler);
29
+ };
30
+ }
31
+
32
+ /**
33
+ * @param callable $nextHandler Next handler to invoke.
34
+ */
35
+ public function __construct(callable $nextHandler)
36
+ {
37
+ $this->nextHandler = $nextHandler;
38
+ }
39
+
40
+ public function __invoke(CommandInterface $command, RequestInterface $request = null)
41
+ {
42
+ $next = $this->nextHandler;
43
+ return $next($command, $request)->then(
44
+ function (ResultInterface $result) use ($command) {
45
+ $status = isset($result['@metadata']['statusCode'])
46
+ ? $result['@metadata']['statusCode']
47
+ : null;
48
+ if ($status == 301) {
49
+ throw new PermanentRedirectException(
50
+ 'Encountered a permanent redirect while requesting '
51
+ . $result->search('"@metadata".effectiveUri') . '. '
52
+ . 'Are you sure you are using the correct region for '
53
+ . 'this bucket?',
54
+ $command,
55
+ ['result' => $result]
56
+ );
57
+ }
58
+ return $result;
59
+ }
60
+ );
61
+ }
62
+ }
lib/Aws/Aws/S3/PostObject.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Credentials\CredentialsInterface;
5
+ use GuzzleHttp\Psr7\Uri;
6
+
7
+ /**
8
+ * @deprecated
9
+ */
10
+ class PostObject
11
+ {
12
+ private $client;
13
+ private $bucket;
14
+ private $formAttributes;
15
+ private $formInputs;
16
+ private $jsonPolicy;
17
+
18
+ /**
19
+ * Constructs the PostObject.
20
+ *
21
+ * @param S3ClientInterface $client Client used with the POST object
22
+ * @param string $bucket Bucket to use
23
+ * @param array $formInputs Associative array of form input
24
+ * fields.
25
+ * @param string|array $jsonPolicy JSON encoded POST policy document.
26
+ * The policy will be base64 encoded
27
+ * and applied to the form on your
28
+ * behalf.
29
+ */
30
+ public function __construct(
31
+ S3ClientInterface $client,
32
+ $bucket,
33
+ array $formInputs,
34
+ $jsonPolicy
35
+ ) {
36
+ $this->client = $client;
37
+ $this->bucket = $bucket;
38
+
39
+ if (is_array($jsonPolicy)) {
40
+ $jsonPolicy = json_encode($jsonPolicy);
41
+ }
42
+
43
+ $this->jsonPolicy = $jsonPolicy;
44
+ $this->formAttributes = [
45
+ 'action' => $this->generateUri(),
46
+ 'method' => 'POST',
47
+ 'enctype' => 'multipart/form-data'
48
+ ];
49
+
50
+ $this->formInputs = $formInputs + ['key' => '${filename}'];
51
+ $credentials = $client->getCredentials()->wait();
52
+ $this->formInputs += $this->getPolicyAndSignature($credentials);
53
+ }
54
+
55
+ /**
56
+ * Gets the S3 client.
57
+ *
58
+ * @return S3ClientInterface
59
+ */
60
+ public function getClient()
61
+ {
62
+ return $this->client;
63
+ }
64
+
65
+ /**
66
+ * Gets the bucket name.
67
+ *
68
+ * @return string
69
+ */
70
+ public function getBucket()
71
+ {
72
+ return $this->bucket;
73
+ }
74
+
75
+ /**
76
+ * Gets the form attributes as an array.
77
+ *
78
+ * @return array
79
+ */
80
+ public function getFormAttributes()
81
+ {
82
+ return $this->formAttributes;
83
+ }
84
+
85
+ /**
86
+ * Set a form attribute.
87
+ *
88
+ * @param string $attribute Form attribute to set.
89
+ * @param string $value Value to set.
90
+ */
91
+ public function setFormAttribute($attribute, $value)
92
+ {
93
+ $this->formAttributes[$attribute] = $value;
94
+ }
95
+
96
+ /**
97
+ * Gets the form inputs as an array.
98
+ *
99
+ * @return array
100
+ */
101
+ public function getFormInputs()
102
+ {
103
+ return $this->formInputs;
104
+ }
105
+
106
+ /**
107
+ * Set a form input.
108
+ *
109
+ * @param string $field Field name to set
110
+ * @param string $value Value to set.
111
+ */
112
+ public function setFormInput($field, $value)
113
+ {
114
+ $this->formInputs[$field] = $value;
115
+ }
116
+
117
+ /**
118
+ * Gets the raw JSON policy.
119
+ *
120
+ * @return string
121
+ */
122
+ public function getJsonPolicy()
123
+ {
124
+ return $this->jsonPolicy;
125
+ }
126
+
127
+ private function generateUri()
128
+ {
129
+ $uri = new Uri($this->client->getEndpoint());
130
+
131
+ if ($this->client->getConfig('use_path_style_endpoint') === true
132
+ || ($uri->getScheme() === 'https'
133
+ && strpos($this->bucket, '.') !== false)
134
+ ) {
135
+ // Use path-style URLs
136
+ $uri = $uri->withPath("/{$this->bucket}");
137
+ } else {
138
+ // Use virtual-style URLs
139
+ $uri = $uri->withHost($this->bucket . '.' . $uri->getHost());
140
+ }
141
+
142
+ return (string) $uri;
143
+ }
144
+
145
+ protected function getPolicyAndSignature(CredentialsInterface $creds)
146
+ {
147
+ $jsonPolicy64 = base64_encode($this->jsonPolicy);
148
+
149
+ return [
150
+ 'AWSAccessKeyId' => $creds->getAccessKeyId(),
151
+ 'policy' => $jsonPolicy64,
152
+ 'signature' => base64_encode(hash_hmac(
153
+ 'sha1',
154
+ $jsonPolicy64,
155
+ $creds->getSecretKey(),
156
+ true
157
+ ))
158
+ ];
159
+ }
160
+ }
lib/Aws/Aws/S3/PostObjectV4.php ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Credentials\CredentialsInterface;
5
+ use GuzzleHttp\Psr7\Uri;
6
+ use Aws\Signature\SignatureTrait;
7
+ use Aws\Signature\SignatureV4 as SignatureV4;
8
+ use Aws\Api\TimestampShape as TimestampShape;
9
+
10
+ /**
11
+ * Encapsulates the logic for getting the data for an S3 object POST upload form
12
+ *
13
+ * @link http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html
14
+ * @link http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
15
+ */
16
+ class PostObjectV4
17
+ {
18
+ use SignatureTrait;
19
+
20
+ private $client;
21
+ private $bucket;
22
+ private $formAttributes;
23
+ private $formInputs;
24
+
25
+ /**
26
+ * Constructs the PostObject.
27
+ *
28
+ * The options array accepts the following keys:
29
+ * @link http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
30
+ *
31
+ * @param S3ClientInterface $client Client used with the POST object
32
+ * @param string $bucket Bucket to use
33
+ * @param array $formInputs Associative array of form input
34
+ * fields.
35
+ * @param array $options Policy condition options
36
+ * @param mixed $expiration Upload expiration time value. By
37
+ * default: 1 hour valid period.
38
+ */
39
+ public function __construct(
40
+ S3ClientInterface $client,
41
+ $bucket,
42
+ array $formInputs,
43
+ array $options = [],
44
+ $expiration = '+1 hours'
45
+ ) {
46
+ $this->client = $client;
47
+ $this->bucket = $bucket;
48
+
49
+ // setup form attributes
50
+ $this->formAttributes = [
51
+ 'action' => $this->generateUri(),
52
+ 'method' => 'POST',
53
+ 'enctype' => 'multipart/form-data'
54
+ ];
55
+
56
+ $credentials = $this->client->getCredentials()->wait();
57
+
58
+ if ($securityToken = $credentials->getSecurityToken()) {
59
+ array_push($options, ['x-amz-security-token' => $securityToken]);
60
+ $formInputs['X-Amz-Security-Token'] = $securityToken;
61
+ }
62
+
63
+ // setup basic policy
64
+ $policy = [
65
+ 'expiration' => TimestampShape::format($expiration, 'iso8601'),
66
+ 'conditions' => $options,
67
+ ];
68
+
69
+ // setup basic formInputs
70
+ $this->formInputs = $formInputs + ['key' => '${filename}'];
71
+
72
+ // finalize policy and signature
73
+
74
+ $this->formInputs += $this->getPolicyAndSignature(
75
+ $credentials,
76
+ $policy
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Gets the S3 client.
82
+ *
83
+ * @return S3ClientInterface
84
+ */
85
+ public function getClient()
86
+ {
87
+ return $this->client;
88
+ }
89
+
90
+ /**
91
+ * Gets the bucket name.
92
+ *
93
+ * @return string
94
+ */
95
+ public function getBucket()
96
+ {
97
+ return $this->bucket;
98
+ }
99
+
100
+ /**
101
+ * Gets the form attributes as an array.
102
+ *
103
+ * @return array
104
+ */
105
+ public function getFormAttributes()
106
+ {
107
+ return $this->formAttributes;
108
+ }
109
+
110
+ /**
111
+ * Set a form attribute.
112
+ *
113
+ * @param string $attribute Form attribute to set.
114
+ * @param string $value Value to set.
115
+ */
116
+ public function setFormAttribute($attribute, $value)
117
+ {
118
+ $this->formAttributes[$attribute] = $value;
119
+ }
120
+
121
+ /**
122
+ * Gets the form inputs as an array.
123
+ *
124
+ * @return array
125
+ */
126
+ public function getFormInputs()
127
+ {
128
+ return $this->formInputs;
129
+ }
130
+
131
+ /**
132
+ * Set a form input.
133
+ *
134
+ * @param string $field Field name to set
135
+ * @param string $value Value to set.
136
+ */
137
+ public function setFormInput($field, $value)
138
+ {
139
+ $this->formInputs[$field] = $value;
140
+ }
141
+
142
+ private function generateUri()
143
+ {
144
+ $uri = new Uri($this->client->getEndpoint());
145
+
146
+ if ($this->client->getConfig('use_path_style_endpoint') === true
147
+ || ($uri->getScheme() === 'https'
148
+ && strpos($this->bucket, '.') !== false)
149
+ ) {
150
+ // Use path-style URLs
151
+ $uri = $uri->withPath("/{$this->bucket}");
152
+ } else {
153
+ // Use virtual-style URLs if haven't been set up already
154
+ if (strpos($uri->getHost(), $this->bucket . '.') !== 0) {
155
+ $uri = $uri->withHost($this->bucket . '.' . $uri->getHost());
156
+ }
157
+ }
158
+
159
+ return (string) $uri;
160
+ }
161
+
162
+ protected function getPolicyAndSignature(
163
+ CredentialsInterface $credentials,
164
+ array $policy
165
+ ){
166
+ $ldt = gmdate(SignatureV4::ISO8601_BASIC);
167
+ $sdt = substr($ldt, 0, 8);
168
+ $policy['conditions'][] = ['X-Amz-Date' => $ldt];
169
+
170
+ $region = $this->client->getRegion();
171
+ $scope = $this->createScope($sdt, $region, 's3');
172
+ $creds = "{$credentials->getAccessKeyId()}/$scope";
173
+ $policy['conditions'][] = ['X-Amz-Credential' => $creds];
174
+
175
+ $policy['conditions'][] = ['X-Amz-Algorithm' => "AWS4-HMAC-SHA256"];
176
+
177
+ $jsonPolicy64 = base64_encode(json_encode($policy));
178
+ $key = $this->getSigningKey(
179
+ $sdt,
180
+ $region,
181
+ 's3',
182
+ $credentials->getSecretKey()
183
+ );
184
+
185
+ return [
186
+ 'X-Amz-Credential' => $creds,
187
+ 'X-Amz-Algorithm' => "AWS4-HMAC-SHA256",
188
+ 'X-Amz-Date' => $ldt,
189
+ 'Policy' => $jsonPolicy64,
190
+ 'X-Amz-Signature' => bin2hex(
191
+ hash_hmac('sha256', $jsonPolicy64, $key, true)
192
+ ),
193
+ ];
194
+ }
195
+ }
lib/Aws/Aws/S3/PutObjectUrlMiddleware.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use Aws\ResultInterface;
6
+ use Psr\Http\Message\RequestInterface;
7
+
8
+ /**
9
+ * Injects ObjectURL into the result of the PutObject operation.
10
+ *
11
+ * @internal
12
+ */
13
+ class PutObjectUrlMiddleware
14
+ {
15
+ /** @var callable */
16
+ private $nextHandler;
17
+
18
+ /**
19
+ * Create a middleware wrapper function.
20
+ *
21
+ * @return callable
22
+ */
23
+ public static function wrap()
24
+ {
25
+ return function (callable $handler) {
26
+ return new self($handler);
27
+ };
28
+ }
29
+
30
+ /**
31
+ * @param callable $nextHandler Next handler to invoke.
32
+ */
33
+ public function __construct(callable $nextHandler)
34
+ {
35
+ $this->nextHandler = $nextHandler;
36
+ }
37
+
38
+ public function __invoke(CommandInterface $command, RequestInterface $request = null)
39
+ {
40
+ $next = $this->nextHandler;
41
+ return $next($command, $request)->then(
42
+ function (ResultInterface $result) use ($command) {
43
+ $name = $command->getName();
44
+ switch ($name) {
45
+ case 'PutObject':
46
+ case 'CopyObject':
47
+ $result['ObjectURL'] = $result['@metadata']['effectiveUri'];
48
+ break;
49
+ case 'CompleteMultipartUpload':
50
+ $result['ObjectURL'] = $result['Location'];
51
+ break;
52
+ }
53
+ return $result;
54
+ }
55
+ );
56
+ }
57
+ }
lib/Aws/Aws/S3/RetryableMalformedResponseParser.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Api\Parser\AbstractParser;
5
+ use Aws\Api\StructureShape;
6
+ use Aws\Api\Parser\Exception\ParserException;
7
+ use Aws\CommandInterface;
8
+ use Aws\Exception\AwsException;
9
+ use Psr\Http\Message\ResponseInterface;
10
+ use Psr\Http\Message\StreamInterface;
11
+
12
+ /**
13
+ * Converts malformed responses to a retryable error type.
14
+ *
15
+ * @internal
16
+ */
17
+ class RetryableMalformedResponseParser extends AbstractParser
18
+ {
19
+ /** @var string */
20
+ private $exceptionClass;
21
+
22
+ public function __construct(
23
+ callable $parser,
24
+ $exceptionClass = AwsException::class
25
+ ) {
26
+ $this->parser = $parser;
27
+ $this->exceptionClass = $exceptionClass;
28
+ }
29
+
30
+ public function __invoke(
31
+ CommandInterface $command,
32
+ ResponseInterface $response
33
+ ) {
34
+ $fn = $this->parser;
35
+
36
+ try {
37
+ return $fn($command, $response);
38
+ } catch (ParserException $e) {
39
+ throw new $this->exceptionClass(
40
+ "Error parsing response for {$command->getName()}:"
41
+ . " AWS parsing error: {$e->getMessage()}",
42
+ $command,
43
+ ['connection_error' => true, 'exception' => $e],
44
+ $e
45
+ );
46
+ }
47
+ }
48
+
49
+ public function parseMemberFromStream(
50
+ StreamInterface $stream,
51
+ StructureShape $member,
52
+ $response
53
+ ) {
54
+ return $this->parser->parseMemberFromStream($stream, $member, $response);
55
+ }
56
+ }
lib/Aws/Aws/S3/S3Client.php ADDED
@@ -0,0 +1,633 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Api\ApiProvider;
5
+ use Aws\Api\DocModel;
6
+ use Aws\Api\Service;
7
+ use Aws\AwsClient;
8
+ use Aws\ClientResolver;
9
+ use Aws\Command;
10
+ use Aws\Exception\AwsException;
11
+ use Aws\HandlerList;
12
+ use Aws\Middleware;
13
+ use Aws\RetryMiddleware;
14
+ use Aws\ResultInterface;
15
+ use Aws\CommandInterface;
16
+ use GuzzleHttp\Exception\RequestException;
17
+ use Psr\Http\Message\RequestInterface;
18
+
19
+ /**
20
+ * Client used to interact with **Amazon Simple Storage Service (Amazon S3)**.
21
+ *
22
+ * @method \Aws\Result abortMultipartUpload(array $args = [])
23
+ * @method \GuzzleHttp\Promise\Promise abortMultipartUploadAsync(array $args = [])
24
+ * @method \Aws\Result completeMultipartUpload(array $args = [])
25
+ * @method \GuzzleHttp\Promise\Promise completeMultipartUploadAsync(array $args = [])
26
+ * @method \Aws\Result copyObject(array $args = [])
27
+ * @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = [])
28
+ * @method \Aws\Result createBucket(array $args = [])
29
+ * @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = [])
30
+ * @method \Aws\Result createMultipartUpload(array $args = [])
31
+ * @method \GuzzleHttp\Promise\Promise createMultipartUploadAsync(array $args = [])
32
+ * @method \Aws\Result deleteBucket(array $args = [])
33
+ * @method \GuzzleHttp\Promise\Promise deleteBucketAsync(array $args = [])
34
+ * @method \Aws\Result deleteBucketAnalyticsConfiguration(array $args = [])
35
+ * @method \GuzzleHttp\Promise\Promise deleteBucketAnalyticsConfigurationAsync(array $args = [])
36
+ * @method \Aws\Result deleteBucketCors(array $args = [])
37
+ * @method \GuzzleHttp\Promise\Promise deleteBucketCorsAsync(array $args = [])
38
+ * @method \Aws\Result deleteBucketEncryption(array $args = [])
39
+ * @method \GuzzleHttp\Promise\Promise deleteBucketEncryptionAsync(array $args = [])
40
+ * @method \Aws\Result deleteBucketInventoryConfiguration(array $args = [])
41
+ * @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = [])
42
+ * @method \Aws\Result deleteBucketLifecycle(array $args = [])
43
+ * @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = [])
44
+ * @method \Aws\Result deleteBucketMetricsConfiguration(array $args = [])
45
+ * @method \GuzzleHttp\Promise\Promise deleteBucketMetricsConfigurationAsync(array $args = [])
46
+ * @method \Aws\Result deleteBucketPolicy(array $args = [])
47
+ * @method \GuzzleHttp\Promise\Promise deleteBucketPolicyAsync(array $args = [])
48
+ * @method \Aws\Result deleteBucketReplication(array $args = [])
49
+ * @method \GuzzleHttp\Promise\Promise deleteBucketReplicationAsync(array $args = [])
50
+ * @method \Aws\Result deleteBucketTagging(array $args = [])
51
+ * @method \GuzzleHttp\Promise\Promise deleteBucketTaggingAsync(array $args = [])
52
+ * @method \Aws\Result deleteBucketWebsite(array $args = [])
53
+ * @method \GuzzleHttp\Promise\Promise deleteBucketWebsiteAsync(array $args = [])
54
+ * @method \Aws\Result deleteObject(array $args = [])
55
+ * @method \GuzzleHttp\Promise\Promise deleteObjectAsync(array $args = [])
56
+ * @method \Aws\Result deleteObjectTagging(array $args = [])
57
+ * @method \GuzzleHttp\Promise\Promise deleteObjectTaggingAsync(array $args = [])
58
+ * @method \Aws\Result deleteObjects(array $args = [])
59
+ * @method \GuzzleHttp\Promise\Promise deleteObjectsAsync(array $args = [])
60
+ * @method \Aws\Result deletePublicAccessBlock(array $args = [])
61
+ * @method \GuzzleHttp\Promise\Promise deletePublicAccessBlockAsync(array $args = [])
62
+ * @method \Aws\Result getBucketAccelerateConfiguration(array $args = [])
63
+ * @method \GuzzleHttp\Promise\Promise getBucketAccelerateConfigurationAsync(array $args = [])
64
+ * @method \Aws\Result getBucketAcl(array $args = [])
65
+ * @method \GuzzleHttp\Promise\Promise getBucketAclAsync(array $args = [])
66
+ * @method \Aws\Result getBucketAnalyticsConfiguration(array $args = [])
67
+ * @method \GuzzleHttp\Promise\Promise getBucketAnalyticsConfigurationAsync(array $args = [])
68
+ * @method \Aws\Result getBucketCors(array $args = [])
69
+ * @method \GuzzleHttp\Promise\Promise getBucketCorsAsync(array $args = [])
70
+ * @method \Aws\Result getBucketEncryption(array $args = [])
71
+ * @method \GuzzleHttp\Promise\Promise getBucketEncryptionAsync(array $args = [])
72
+ * @method \Aws\Result getBucketInventoryConfiguration(array $args = [])
73
+ * @method \GuzzleHttp\Promise\Promise getBucketInventoryConfigurationAsync(array $args = [])
74
+ * @method \Aws\Result getBucketLifecycle(array $args = [])
75
+ * @method \GuzzleHttp\Promise\Promise getBucketLifecycleAsync(array $args = [])
76
+ * @method \Aws\Result getBucketLifecycleConfiguration(array $args = [])
77
+ * @method \GuzzleHttp\Promise\Promise getBucketLifecycleConfigurationAsync(array $args = [])
78
+ * @method \Aws\Result getBucketLocation(array $args = [])
79
+ * @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = [])
80
+ * @method \Aws\Result getBucketLogging(array $args = [])
81
+ * @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = [])
82
+ * @method \Aws\Result getBucketMetricsConfiguration(array $args = [])
83
+ * @method \GuzzleHttp\Promise\Promise getBucketMetricsConfigurationAsync(array $args = [])
84
+ * @method \Aws\Result getBucketNotification(array $args = [])
85
+ * @method \GuzzleHttp\Promise\Promise getBucketNotificationAsync(array $args = [])
86
+ * @method \Aws\Result getBucketNotificationConfiguration(array $args = [])
87
+ * @method \GuzzleHttp\Promise\Promise getBucketNotificationConfigurationAsync(array $args = [])
88
+ * @method \Aws\Result getBucketPolicy(array $args = [])
89
+ * @method \GuzzleHttp\Promise\Promise getBucketPolicyAsync(array $args = [])
90
+ * @method \Aws\Result getBucketPolicyStatus(array $args = [])
91
+ * @method \GuzzleHttp\Promise\Promise getBucketPolicyStatusAsync(array $args = [])
92
+ * @method \Aws\Result getBucketReplication(array $args = [])
93
+ * @method \GuzzleHttp\Promise\Promise getBucketReplicationAsync(array $args = [])
94
+ * @method \Aws\Result getBucketRequestPayment(array $args = [])
95
+ * @method \GuzzleHttp\Promise\Promise getBucketRequestPaymentAsync(array $args = [])
96
+ * @method \Aws\Result getBucketTagging(array $args = [])
97
+ * @method \GuzzleHttp\Promise\Promise getBucketTaggingAsync(array $args = [])
98
+ * @method \Aws\Result getBucketVersioning(array $args = [])
99
+ * @method \GuzzleHttp\Promise\Promise getBucketVersioningAsync(array $args = [])
100
+ * @method \Aws\Result getBucketWebsite(array $args = [])
101
+ * @method \GuzzleHttp\Promise\Promise getBucketWebsiteAsync(array $args = [])
102
+ * @method \Aws\Result getObject(array $args = [])
103
+ * @method \GuzzleHttp\Promise\Promise getObjectAsync(array $args = [])
104
+ * @method \Aws\Result getObjectAcl(array $args = [])
105
+ * @method \GuzzleHttp\Promise\Promise getObjectAclAsync(array $args = [])
106
+ * @method \Aws\Result getObjectLegalHold(array $args = [])
107
+ * @method \GuzzleHttp\Promise\Promise getObjectLegalHoldAsync(array $args = [])
108
+ * @method \Aws\Result getObjectLockConfiguration(array $args = [])
109
+ * @method \GuzzleHttp\Promise\Promise getObjectLockConfigurationAsync(array $args = [])
110
+ * @method \Aws\Result getObjectRetention(array $args = [])
111
+ * @method \GuzzleHttp\Promise\Promise getObjectRetentionAsync(array $args = [])
112
+ * @method \Aws\Result getObjectTagging(array $args = [])
113
+ * @method \GuzzleHttp\Promise\Promise getObjectTaggingAsync(array $args = [])
114
+ * @method \Aws\Result getObjectTorrent(array $args = [])
115
+ * @method \GuzzleHttp\Promise\Promise getObjectTorrentAsync(array $args = [])
116
+ * @method \Aws\Result getPublicAccessBlock(array $args = [])
117
+ * @method \GuzzleHttp\Promise\Promise getPublicAccessBlockAsync(array $args = [])
118
+ * @method \Aws\Result headBucket(array $args = [])
119
+ * @method \GuzzleHttp\Promise\Promise headBucketAsync(array $args = [])
120
+ * @method \Aws\Result headObject(array $args = [])
121
+ * @method \GuzzleHttp\Promise\Promise headObjectAsync(array $args = [])
122
+ * @method \Aws\Result listBucketAnalyticsConfigurations(array $args = [])
123
+ * @method \GuzzleHttp\Promise\Promise listBucketAnalyticsConfigurationsAsync(array $args = [])
124
+ * @method \Aws\Result listBucketInventoryConfigurations(array $args = [])
125
+ * @method \GuzzleHttp\Promise\Promise listBucketInventoryConfigurationsAsync(array $args = [])
126
+ * @method \Aws\Result listBucketMetricsConfigurations(array $args = [])
127
+ * @method \GuzzleHttp\Promise\Promise listBucketMetricsConfigurationsAsync(array $args = [])
128
+ * @method \Aws\Result listBuckets(array $args = [])
129
+ * @method \GuzzleHttp\Promise\Promise listBucketsAsync(array $args = [])
130
+ * @method \Aws\Result listMultipartUploads(array $args = [])
131
+ * @method \GuzzleHttp\Promise\Promise listMultipartUploadsAsync(array $args = [])
132
+ * @method \Aws\Result listObjectVersions(array $args = [])
133
+ * @method \GuzzleHttp\Promise\Promise listObjectVersionsAsync(array $args = [])
134
+ * @method \Aws\Result listObjects(array $args = [])
135
+ * @method \GuzzleHttp\Promise\Promise listObjectsAsync(array $args = [])
136
+ * @method \Aws\Result listObjectsV2(array $args = [])
137
+ * @method \GuzzleHttp\Promise\Promise listObjectsV2Async(array $args = [])
138
+ * @method \Aws\Result listParts(array $args = [])
139
+ * @method \GuzzleHttp\Promise\Promise listPartsAsync(array $args = [])
140
+ * @method \Aws\Result putBucketAccelerateConfiguration(array $args = [])
141
+ * @method \GuzzleHttp\Promise\Promise putBucketAccelerateConfigurationAsync(array $args = [])
142
+ * @method \Aws\Result putBucketAcl(array $args = [])
143
+ * @method \GuzzleHttp\Promise\Promise putBucketAclAsync(array $args = [])
144
+ * @method \Aws\Result putBucketAnalyticsConfiguration(array $args = [])
145
+ * @method \GuzzleHttp\Promise\Promise putBucketAnalyticsConfigurationAsync(array $args = [])
146
+ * @method \Aws\Result putBucketCors(array $args = [])
147
+ * @method \GuzzleHttp\Promise\Promise putBucketCorsAsync(array $args = [])
148
+ * @method \Aws\Result putBucketEncryption(array $args = [])
149
+ * @method \GuzzleHttp\Promise\Promise putBucketEncryptionAsync(array $args = [])
150
+ * @method \Aws\Result putBucketInventoryConfiguration(array $args = [])
151
+ * @method \GuzzleHttp\Promise\Promise putBucketInventoryConfigurationAsync(array $args = [])
152
+ * @method \Aws\Result putBucketLifecycle(array $args = [])
153
+ * @method \GuzzleHttp\Promise\Promise putBucketLifecycleAsync(array $args = [])
154
+ * @method \Aws\Result putBucketLifecycleConfiguration(array $args = [])
155
+ * @method \GuzzleHttp\Promise\Promise putBucketLifecycleConfigurationAsync(array $args = [])
156
+ * @method \Aws\Result putBucketLogging(array $args = [])
157
+ * @method \GuzzleHttp\Promise\Promise putBucketLoggingAsync(array $args = [])
158
+ * @method \Aws\Result putBucketMetricsConfiguration(array $args = [])
159
+ * @method \GuzzleHttp\Promise\Promise putBucketMetricsConfigurationAsync(array $args = [])
160
+ * @method \Aws\Result putBucketNotification(array $args = [])
161
+ * @method \GuzzleHttp\Promise\Promise putBucketNotificationAsync(array $args = [])
162
+ * @method \Aws\Result putBucketNotificationConfiguration(array $args = [])
163
+ * @method \GuzzleHttp\Promise\Promise putBucketNotificationConfigurationAsync(array $args = [])
164
+ * @method \Aws\Result putBucketPolicy(array $args = [])
165
+ * @method \GuzzleHttp\Promise\Promise putBucketPolicyAsync(array $args = [])
166
+ * @method \Aws\Result putBucketReplication(array $args = [])
167
+ * @method \GuzzleHttp\Promise\Promise putBucketReplicationAsync(array $args = [])
168
+ * @method \Aws\Result putBucketRequestPayment(array $args = [])
169
+ * @method \GuzzleHttp\Promise\Promise putBucketRequestPaymentAsync(array $args = [])
170
+ * @method \Aws\Result putBucketTagging(array $args = [])
171
+ * @method \GuzzleHttp\Promise\Promise putBucketTaggingAsync(array $args = [])
172
+ * @method \Aws\Result putBucketVersioning(array $args = [])
173
+ * @method \GuzzleHttp\Promise\Promise putBucketVersioningAsync(array $args = [])
174
+ * @method \Aws\Result putBucketWebsite(array $args = [])
175
+ * @method \GuzzleHttp\Promise\Promise putBucketWebsiteAsync(array $args = [])
176
+ * @method \Aws\Result putObject(array $args = [])
177
+ * @method \GuzzleHttp\Promise\Promise putObjectAsync(array $args = [])
178
+ * @method \Aws\Result putObjectAcl(array $args = [])
179
+ * @method \GuzzleHttp\Promise\Promise putObjectAclAsync(array $args = [])
180
+ * @method \Aws\Result putObjectLegalHold(array $args = [])
181
+ * @method \GuzzleHttp\Promise\Promise putObjectLegalHoldAsync(array $args = [])
182
+ * @method \Aws\Result putObjectLockConfiguration(array $args = [])
183
+ * @method \GuzzleHttp\Promise\Promise putObjectLockConfigurationAsync(array $args = [])
184
+ * @method \Aws\Result putObjectRetention(array $args = [])
185
+ * @method \GuzzleHttp\Promise\Promise putObjectRetentionAsync(array $args = [])
186
+ * @method \Aws\Result putObjectTagging(array $args = [])
187
+ * @method \GuzzleHttp\Promise\Promise putObjectTaggingAsync(array $args = [])
188
+ * @method \Aws\Result putPublicAccessBlock(array $args = [])
189
+ * @method \GuzzleHttp\Promise\Promise putPublicAccessBlockAsync(array $args = [])
190
+ * @method \Aws\Result restoreObject(array $args = [])
191
+ * @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = [])
192
+ * @method \Aws\Result selectObjectContent(array $args = [])
193
+ * @method \GuzzleHttp\Promise\Promise selectObjectContentAsync(array $args = [])
194
+ * @method \Aws\Result uploadPart(array $args = [])
195
+ * @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = [])
196
+ * @method \Aws\Result uploadPartCopy(array $args = [])
197
+ * @method \GuzzleHttp\Promise\Promise uploadPartCopyAsync(array $args = [])
198
+ */
199
+ class S3Client extends AwsClient implements S3ClientInterface
200
+ {
201
+ use S3ClientTrait;
202
+
203
+ public static function getArguments()
204
+ {
205
+ $args = parent::getArguments();
206
+ $args['retries']['fn'] = [__CLASS__, '_applyRetryConfig'];
207
+ $args['api_provider']['fn'] = [__CLASS__, '_applyApiProvider'];
208
+
209
+ return $args + [
210
+ 'bucket_endpoint' => [
211
+ 'type' => 'config',
212
+ 'valid' => ['bool'],
213
+ 'doc' => 'Set to true to send requests to a hardcoded '
214
+ . 'bucket endpoint rather than create an endpoint as a '
215
+ . 'result of injecting the bucket into the URL. This '
216
+ . 'option is useful for interacting with CNAME endpoints.',
217
+ ],
218
+ 'use_accelerate_endpoint' => [
219
+ 'type' => 'config',
220
+ 'valid' => ['bool'],
221
+ 'doc' => 'Set to true to send requests to an S3 Accelerate'
222
+ . ' endpoint by default. Can be enabled or disabled on'
223
+ . ' individual operations by setting'
224
+ . ' \'@use_accelerate_endpoint\' to true or false. Note:'
225
+ . ' you must enable S3 Accelerate on a bucket before it can'
226
+ . ' be accessed via an Accelerate endpoint.',
227
+ 'default' => false,
228
+ ],
229
+ 'use_dual_stack_endpoint' => [
230
+ 'type' => 'config',
231
+ 'valid' => ['bool'],
232
+ 'doc' => 'Set to true to send requests to an S3 Dual Stack'
233
+ . ' endpoint by default, which enables IPv6 Protocol.'
234
+ . ' Can be enabled or disabled on individual operations by setting'
235
+ . ' \'@use_dual_stack_endpoint\' to true or false.',
236
+ 'default' => false,
237
+ ],
238
+ 'use_path_style_endpoint' => [
239
+ 'type' => 'config',
240
+ 'valid' => ['bool'],
241
+ 'doc' => 'Set to true to send requests to an S3 path style'
242
+ . ' endpoint by default.'
243
+ . ' Can be enabled or disabled on individual operations by setting'
244
+ . ' \'@use_path_style_endpoint\' to true or false.',
245
+ 'default' => false,
246
+ ],
247
+ ];
248
+ }
249
+
250
+ /**
251
+ * {@inheritdoc}
252
+ *
253
+ * In addition to the options available to
254
+ * {@see Aws\AwsClient::__construct}, S3Client accepts the following
255
+ * options:
256
+ *
257
+ * - bucket_endpoint: (bool) Set to true to send requests to a
258
+ * hardcoded bucket endpoint rather than create an endpoint as a result
259
+ * of injecting the bucket into the URL. This option is useful for
260
+ * interacting with CNAME endpoints.
261
+ * - calculate_md5: (bool) Set to false to disable calculating an MD5
262
+ * for all Amazon S3 signed uploads.
263
+ * - use_accelerate_endpoint: (bool) Set to true to send requests to an S3
264
+ * Accelerate endpoint by default. Can be enabled or disabled on
265
+ * individual operations by setting '@use_accelerate_endpoint' to true or
266
+ * false. Note: you must enable S3 Accelerate on a bucket before it can be
267
+ * accessed via an Accelerate endpoint.
268
+ * - use_dual_stack_endpoint: (bool) Set to true to send requests to an S3
269
+ * Dual Stack endpoint by default, which enables IPv6 Protocol.
270
+ * Can be enabled or disabled on individual operations by setting
271
+ * '@use_dual_stack_endpoint\' to true or false. Note:
272
+ * you cannot use it together with an accelerate endpoint.
273
+ * - use_path_style_endpoint: (bool) Set to true to send requests to an S3
274
+ * path style endpoint by default.
275
+ * Can be enabled or disabled on individual operations by setting
276
+ * '@use_path_style_endpoint\' to true or false. Note:
277
+ * you cannot use it together with an accelerate endpoint.
278
+ *
279
+ * @param array $args
280
+ */
281
+ public function __construct(array $args)
282
+ {
283
+ parent::__construct($args);
284
+ $stack = $this->getHandlerList();
285
+ $stack->appendInit(SSECMiddleware::wrap($this->getEndpoint()->getScheme()), 's3.ssec');
286
+ $stack->appendBuild(ApplyChecksumMiddleware::wrap(), 's3.checksum');
287
+ $stack->appendBuild(
288
+ Middleware::contentType(['PutObject', 'UploadPart']),
289
+ 's3.content_type'
290
+ );
291
+
292
+
293
+ // Use the bucket style middleware when using a "bucket_endpoint" (for cnames)
294
+ if ($this->getConfig('bucket_endpoint')) {
295
+ $stack->appendBuild(BucketEndpointMiddleware::wrap(), 's3.bucket_endpoint');
296
+ } else {
297
+ $stack->appendBuild(
298
+ S3EndpointMiddleware::wrap(
299
+ $this->getRegion(),
300
+ [
301
+ 'dual_stack' => $this->getConfig('use_dual_stack_endpoint'),
302
+ 'accelerate' => $this->getConfig('use_accelerate_endpoint'),
303
+ 'path_style' => $this->getConfig('use_path_style_endpoint')
304
+ ]
305
+ ),
306
+ 's3.endpoint_middleware'
307
+ );
308
+ }
309
+
310
+ $stack->appendSign(PutObjectUrlMiddleware::wrap(), 's3.put_object_url');
311
+ $stack->appendSign(PermanentRedirectMiddleware::wrap(), 's3.permanent_redirect');
312
+ $stack->appendInit(Middleware::sourceFile($this->getApi()), 's3.source_file');
313
+ $stack->appendInit($this->getSaveAsParameter(), 's3.save_as');
314
+ $stack->appendInit($this->getLocationConstraintMiddleware(), 's3.location');
315
+ $stack->appendInit($this->getEncodingTypeMiddleware(), 's3.auto_encode');
316
+ $stack->appendInit($this->getHeadObjectMiddleware(), 's3.head_object');
317
+ }
318
+
319
+ /**
320
+ * Determine if a string is a valid name for a DNS compatible Amazon S3
321
+ * bucket.
322
+ *
323
+ * DNS compatible bucket names can be used as a subdomain in a URL (e.g.,
324
+ * "<bucket>.s3.amazonaws.com").
325
+ *
326
+ * @param string $bucket Bucket name to check.
327
+ *
328
+ * @return bool
329
+ */
330
+ public static function isBucketDnsCompatible($bucket)
331
+ {
332
+ $bucketLen = strlen($bucket);
333
+
334
+ return ($bucketLen >= 3 && $bucketLen <= 63) &&
335
+ // Cannot look like an IP address
336
+ !filter_var($bucket, FILTER_VALIDATE_IP) &&
337
+ preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/', $bucket);
338
+ }
339
+
340
+ public function createPresignedRequest(CommandInterface $command, $expires)
341
+ {
342
+ $command = clone $command;
343
+ $command->getHandlerList()->remove('signer');
344
+
345
+ /** @var \Aws\Signature\SignatureInterface $signer */
346
+ $signer = call_user_func(
347
+ $this->getSignatureProvider(),
348
+ $this->getConfig('signature_version'),
349
+ $this->getConfig('signing_name'),
350
+ $this->getConfig('signing_region')
351
+ );
352
+
353
+ return $signer->presign(
354
+ \Aws\serialize($command),
355
+ $this->getCredentials()->wait(),
356
+ $expires
357
+ );
358
+ }
359
+
360
+ public function getObjectUrl($bucket, $key)
361
+ {
362
+ $command = $this->getCommand('GetObject', [
363
+ 'Bucket' => $bucket,
364
+ 'Key' => $key
365
+ ]);
366
+
367
+ return (string) \Aws\serialize($command)->getUri();
368
+ }
369
+
370
+ /**
371
+ * Raw URL encode a key and allow for '/' characters
372
+ *
373
+ * @param string $key Key to encode
374
+ *
375
+ * @return string Returns the encoded key
376
+ */
377
+ public static function encodeKey($key)
378
+ {
379
+ return str_replace('%2F', '/', rawurlencode($key));
380
+ }
381
+
382
+ /**
383
+ * Provides a middleware that removes the need to specify LocationConstraint on CreateBucket.
384
+ *
385
+ * @return \Closure
386
+ */
387
+ private function getLocationConstraintMiddleware()
388
+ {
389
+ $region = $this->getRegion();
390
+ return static function (callable $handler) use ($region) {
391
+ return function (Command $command, $request = null) use ($handler, $region) {
392
+ if ($command->getName() === 'CreateBucket') {
393
+ $locationConstraint = isset($command['CreateBucketConfiguration']['LocationConstraint'])
394
+ ? $command['CreateBucketConfiguration']['LocationConstraint']
395
+ : null;
396
+
397
+ if ($locationConstraint === 'us-east-1') {
398
+ unset($command['CreateBucketConfiguration']);
399
+ } elseif ('us-east-1' !== $region && empty($locationConstraint)) {
400
+ $command['CreateBucketConfiguration'] = ['LocationConstraint' => $region];
401
+ }
402
+ }
403
+
404
+ return $handler($command, $request);
405
+ };
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Provides a middleware that supports the `SaveAs` parameter.
411
+ *
412
+ * @return \Closure
413
+ */
414
+ private function getSaveAsParameter()
415
+ {
416
+ return static function (callable $handler) {
417
+ return function (Command $command, $request = null) use ($handler) {
418
+ if ($command->getName() === 'GetObject' && isset($command['SaveAs'])) {
419
+ $command['@http']['sink'] = $command['SaveAs'];
420
+ unset($command['SaveAs']);
421
+ }
422
+
423
+ return $handler($command, $request);
424
+ };
425
+ };
426
+ }
427
+
428
+ /**
429
+ * Provides a middleware that disables content decoding on HeadObject
430
+ * commands.
431
+ *
432
+ * @return \Closure
433
+ */
434
+ private function getHeadObjectMiddleware()
435
+ {
436
+ return static function (callable $handler) {
437
+ return function (
438
+ CommandInterface $command,
439
+ RequestInterface $request = null
440
+ ) use ($handler) {
441
+ if ($command->getName() === 'HeadObject'
442
+ && !isset($command['@http']['decode_content'])
443
+ ) {
444
+ $command['@http']['decode_content'] = false;
445
+ }
446
+
447
+ return $handler($command, $request);
448
+ };
449
+ };
450
+ }
451
+
452
+ /**
453
+ * Provides a middleware that autopopulates the EncodingType parameter on
454
+ * ListObjects commands.
455
+ *
456
+ * @return \Closure
457
+ */
458
+ private function getEncodingTypeMiddleware()
459
+ {
460
+ return static function (callable $handler) {
461
+ return function (Command $command, $request = null) use ($handler) {
462
+ $autoSet = false;
463
+ if ($command->getName() === 'ListObjects'
464
+ && empty($command['EncodingType'])
465
+ ) {
466
+ $command['EncodingType'] = 'url';
467
+ $autoSet = true;
468
+ }
469
+
470
+ return $handler($command, $request)
471
+ ->then(function (ResultInterface $result) use ($autoSet) {
472
+ if ($result['EncodingType'] === 'url' && $autoSet) {
473
+ static $topLevel = [
474
+ 'Delimiter',
475
+ 'Marker',
476
+ 'NextMarker',
477
+ 'Prefix',
478
+ ];
479
+ static $nested = [
480
+ ['Contents', 'Key'],
481
+ ['CommonPrefixes', 'Prefix'],
482
+ ];
483
+
484
+ foreach ($topLevel as $key) {
485
+ if (isset($result[$key])) {
486
+ $result[$key] = urldecode($result[$key]);
487
+ }
488
+ }
489
+ foreach ($nested as $steps) {
490
+ if (isset($result[$steps[0]])) {
491
+ foreach ($result[$steps[0]] as $key => $part) {
492
+ if (isset($part[$steps[1]])) {
493
+ $result[$steps[0]][$key][$steps[1]]
494
+ = urldecode($part[$steps[1]]);
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ }
501
+
502
+ return $result;
503
+ });
504
+ };
505
+ };
506
+ }
507
+
508
+ /** @internal */
509
+ public static function _applyRetryConfig($value, $_, HandlerList $list)
510
+ {
511
+ if (!$value) {
512
+ return;
513
+ }
514
+
515
+ $decider = RetryMiddleware::createDefaultDecider($value);
516
+ $decider = function ($retries, $command, $request, $result, $error) use ($decider, $value) {
517
+ $maxRetries = null !== $command['@retries']
518
+ ? $command['@retries']
519
+ : $value;
520
+
521
+ if ($decider($retries, $command, $request, $result, $error)) {
522
+ return true;
523
+ }
524
+
525
+ if ($error instanceof AwsException
526
+ && $retries < $maxRetries
527
+ ) {
528
+ if ($error->getResponse()
529
+ && $error->getResponse()->getStatusCode() >= 400
530
+ ) {
531
+ return strpos(
532
+ $error->getResponse()->getBody(),
533
+ 'Your socket connection to the server'
534
+ ) !== false;
535
+ }
536
+
537
+ if ($error->getPrevious() instanceof RequestException) {
538
+ // All commands except CompleteMultipartUpload are
539
+ // idempotent and may be retried without worry if a
540
+ // networking error has occurred.
541
+ return $command->getName() !== 'CompleteMultipartUpload';
542
+ }
543
+ }
544
+
545
+ return false;
546
+ };
547
+
548
+ $delay = [RetryMiddleware::class, 'exponentialDelay'];
549
+ $list->appendSign(Middleware::retry($decider, $delay), 'retry');
550
+ }
551
+
552
+ /** @internal */
553
+ public static function _applyApiProvider($value, array &$args, HandlerList $list)
554
+ {
555
+ ClientResolver::_apply_api_provider($value, $args, $list);
556
+ $args['parser'] = new GetBucketLocationParser(
557
+ new AmbiguousSuccessParser(
558
+ new RetryableMalformedResponseParser(
559
+ $args['parser'],
560
+ $args['exception_class']
561
+ ),
562
+ $args['error_parser'],
563
+ $args['exception_class']
564
+ )
565
+ );
566
+ }
567
+
568
+ /**
569
+ * @internal
570
+ * @codeCoverageIgnore
571
+ */
572
+ public static function applyDocFilters(array $api, array $docs)
573
+ {
574
+ $b64 = '<div class="alert alert-info">This value will be base64 encoded on your behalf.</div>';
575
+ $opt = '<div class="alert alert-info">This value will be computed for you it is not supplied.</div>';
576
+
577
+ // Add the SourceFile parameter.
578
+ $docs['shapes']['SourceFile']['base'] = 'The path to a file on disk to use instead of the Body parameter.';
579
+ $api['shapes']['SourceFile'] = ['type' => 'string'];
580
+ $api['shapes']['PutObjectRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
581
+ $api['shapes']['UploadPartRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
582
+
583
+ // Add the ContentSHA256 parameter.
584
+ $docs['shapes']['ContentSHA256']['base'] = 'A SHA256 hash of the body content of the request.';
585
+ $api['shapes']['ContentSHA256'] = ['type' => 'string'];
586
+ $api['shapes']['PutObjectRequest']['members']['ContentSHA256'] = ['shape' => 'ContentSHA256'];
587
+ $api['shapes']['UploadPartRequest']['members']['ContentSHA256'] = ['shape' => 'ContentSHA256'];
588
+ unset($api['shapes']['PutObjectRequest']['members']['ContentMD5']);
589
+ unset($api['shapes']['UploadPartRequest']['members']['ContentMD5']);
590
+ $docs['shapes']['ContentSHA256']['append'] = $opt;
591
+
592
+ // Add the SaveAs parameter.
593
+ $docs['shapes']['SaveAs']['base'] = 'The path to a file on disk to save the object data.';
594
+ $api['shapes']['SaveAs'] = ['type' => 'string'];
595
+ $api['shapes']['GetObjectRequest']['members']['SaveAs'] = ['shape' => 'SaveAs'];
596
+
597
+ // Several SSECustomerKey documentation updates.
598
+ $docs['shapes']['SSECustomerKey']['append'] = $b64;
599
+ $docs['shapes']['CopySourceSSECustomerKey']['append'] = $b64;
600
+ $docs['shapes']['SSECustomerKeyMd5']['append'] = $opt;
601
+
602
+ // Add the ObjectURL to various output shapes and documentation.
603
+ $docs['shapes']['ObjectURL']['base'] = 'The URI of the created object.';
604
+ $api['shapes']['ObjectURL'] = ['type' => 'string'];
605
+ $api['shapes']['PutObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
606
+ $api['shapes']['CopyObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
607
+ $api['shapes']['CompleteMultipartUploadOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
608
+
609
+ // Fix references to Location Constraint.
610
+ unset($api['shapes']['CreateBucketRequest']['payload']);
611
+ $api['shapes']['BucketLocationConstraint']['enum'] = [
612
+ "ap-northeast-1",
613
+ "ap-southeast-2",
614
+ "ap-southeast-1",
615
+ "cn-north-1",
616
+ "eu-central-1",
617
+ "eu-west-1",
618
+ "us-east-1",
619
+ "us-west-1",
620
+ "us-west-2",
621
+ "sa-east-1",
622
+ ];
623
+
624
+ // Add a note that the ContentMD5 is optional.
625
+ $docs['shapes']['ContentMD5']['append'] = '<div class="alert alert-info">The value will be computed on '
626
+ . 'your behalf.</div>';
627
+
628
+ return [
629
+ new Service($api, ApiProvider::defaultProvider()),
630
+ new DocModel($docs)
631
+ ];
632
+ }
633
+ }
lib/Aws/Aws/S3/S3ClientInterface.php ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\AwsClientInterface;
5
+ use Aws\CommandInterface;
6
+ use Aws\ResultInterface;
7
+ use GuzzleHttp\Promise\PromiseInterface;
8
+ use Psr\Http\Message\RequestInterface;
9
+
10
+ interface S3ClientInterface extends AwsClientInterface
11
+ {
12
+ /**
13
+ * Create a pre-signed URL for the given S3 command object.
14
+ *
15
+ * @param CommandInterface $command Command to create a pre-signed
16
+ * URL for.
17
+ * @param int|string|\DateTime $expires The time at which the URL should
18
+ * expire. This can be a Unix
19
+ * timestamp, a PHP DateTime object,
20
+ * or a string that can be evaluated
21
+ * by strtotime().
22
+ *
23
+ * @return RequestInterface
24
+ */
25
+ public function createPresignedRequest(CommandInterface $command, $expires);
26
+
27
+ /**
28
+ * Returns the URL to an object identified by its bucket and key.
29
+ *
30
+ * The URL returned by this method is not signed nor does it ensure the the
31
+ * bucket and key given to the method exist. If you need a signed URL, then
32
+ * use the {@see \Aws\S3\S3Client::createPresignedRequest} method and get
33
+ * the URI of the signed request.
34
+ *
35
+ * @param string $bucket The name of the bucket where the object is located
36
+ * @param string $key The key of the object
37
+ *
38
+ * @return string The URL to the object
39
+ */
40
+ public function getObjectUrl($bucket, $key);
41
+
42
+ /**
43
+ * Determines whether or not a bucket exists by name.
44
+ *
45
+ * @param string $bucket The name of the bucket
46
+ *
47
+ * @return bool
48
+ */
49
+ public function doesBucketExist($bucket);
50
+
51
+ /**
52
+ * Determines whether or not an object exists by name.
53
+ *
54
+ * @param string $bucket The name of the bucket
55
+ * @param string $key The key of the object
56
+ * @param array $options Additional options available in the HeadObject
57
+ * operation (e.g., VersionId).
58
+ *
59
+ * @return bool
60
+ */
61
+ public function doesObjectExist($bucket, $key, array $options = []);
62
+
63
+ /**
64
+ * Register the Amazon S3 stream wrapper with this client instance.
65
+ */
66
+ public function registerStreamWrapper();
67
+
68
+ /**
69
+ * Deletes objects from Amazon S3 that match the result of a ListObjects
70
+ * operation. For example, this allows you to do things like delete all
71
+ * objects that match a specific key prefix.
72
+ *
73
+ * @param string $bucket Bucket that contains the object keys
74
+ * @param string $prefix Optionally delete only objects under this key prefix
75
+ * @param string $regex Delete only objects that match this regex
76
+ * @param array $options Aws\S3\BatchDelete options array.
77
+ *
78
+ * @see Aws\S3\S3Client::listObjects
79
+ * @throws \RuntimeException if no prefix and no regex is given
80
+ */
81
+ public function deleteMatchingObjects(
82
+ $bucket,
83
+ $prefix = '',
84
+ $regex = '',
85
+ array $options = []
86
+ );
87
+
88
+ /**
89
+ * Deletes objects from Amazon S3 that match the result of a ListObjects
90
+ * operation. For example, this allows you to do things like delete all
91
+ * objects that match a specific key prefix.
92
+ *
93
+ * @param string $bucket Bucket that contains the object keys
94
+ * @param string $prefix Optionally delete only objects under this key prefix
95
+ * @param string $regex Delete only objects that match this regex
96
+ * @param array $options Aws\S3\BatchDelete options array.
97
+ *
98
+ * @see Aws\S3\S3Client::listObjects
99
+ *
100
+ * @return PromiseInterface A promise that is settled when matching
101
+ * objects are deleted.
102
+ */
103
+ public function deleteMatchingObjectsAsync(
104
+ $bucket,
105
+ $prefix = '',
106
+ $regex = '',
107
+ array $options = []
108
+ );
109
+
110
+ /**
111
+ * Upload a file, stream, or string to a bucket.
112
+ *
113
+ * If the upload size exceeds the specified threshold, the upload will be
114
+ * performed using concurrent multipart uploads.
115
+ *
116
+ * The options array accepts the following options:
117
+ *
118
+ * - before_upload: (callable) Callback to invoke before any upload
119
+ * operations during the upload process. The callback should have a
120
+ * function signature like `function (Aws\Command $command) {...}`.
121
+ * - concurrency: (int, default=int(3)) Maximum number of concurrent
122
+ * `UploadPart` operations allowed during a multipart upload.
123
+ * - mup_threshold: (int, default=int(16777216)) The size, in bytes, allowed
124
+ * before the upload must be sent via a multipart upload. Default: 16 MB.
125
+ * - params: (array, default=array([])) Custom parameters to use with the
126
+ * upload. For single uploads, they must correspond to those used for the
127
+ * `PutObject` operation. For multipart uploads, they correspond to the
128
+ * parameters of the `CreateMultipartUpload` operation.
129
+ * - part_size: (int) Part size to use when doing a multipart upload.
130
+ *
131
+ * @param string $bucket Bucket to upload the object.
132
+ * @param string $key Key of the object.
133
+ * @param mixed $body Object data to upload. Can be a
134
+ * StreamInterface, PHP stream resource, or a
135
+ * string of data to upload.
136
+ * @param string $acl ACL to apply to the object (default: private).
137
+ * @param array $options Options used to configure the upload process.
138
+ *
139
+ * @see Aws\S3\MultipartUploader for more info about multipart uploads.
140
+ * @return ResultInterface Returns the result of the upload.
141
+ */
142
+ public function upload(
143
+ $bucket,
144
+ $key,
145
+ $body,
146
+ $acl = 'private',
147
+ array $options = []
148
+ );
149
+
150
+ /**
151
+ * Upload a file, stream, or string to a bucket asynchronously.
152
+ *
153
+ * @param string $bucket Bucket to upload the object.
154
+ * @param string $key Key of the object.
155
+ * @param mixed $body Object data to upload. Can be a
156
+ * StreamInterface, PHP stream resource, or a
157
+ * string of data to upload.
158
+ * @param string $acl ACL to apply to the object (default: private).
159
+ * @param array $options Options used to configure the upload process.
160
+ *
161
+ * @see self::upload
162
+ * @return PromiseInterface Returns a promise that will be fulfilled
163
+ * with the result of the upload.
164
+ */
165
+ public function uploadAsync(
166
+ $bucket,
167
+ $key,
168
+ $body,
169
+ $acl = 'private',
170
+ array $options = []
171
+ );
172
+
173
+ /**
174
+ * Copy an object of any size to a different location.
175
+ *
176
+ * If the upload size exceeds the maximum allowable size for direct S3
177
+ * copying, a multipart copy will be used.
178
+ *
179
+ * The options array accepts the following options:
180
+ *
181
+ * - before_upload: (callable) Callback to invoke before any upload
182
+ * operations during the upload process. The callback should have a
183
+ * function signature like `function (Aws\Command $command) {...}`.
184
+ * - concurrency: (int, default=int(5)) Maximum number of concurrent
185
+ * `UploadPart` operations allowed during a multipart upload.
186
+ * - params: (array, default=array([])) Custom parameters to use with the
187
+ * upload. For single uploads, they must correspond to those used for the
188
+ * `CopyObject` operation. For multipart uploads, they correspond to the
189
+ * parameters of the `CreateMultipartUpload` operation.
190
+ * - part_size: (int) Part size to use when doing a multipart upload.
191
+ *
192
+ * @param string $fromBucket Bucket where the copy source resides.
193
+ * @param string $fromKey Key of the copy source.
194
+ * @param string $destBucket Bucket to which to copy the object.
195
+ * @param string $destKey Key to which to copy the object.
196
+ * @param string $acl ACL to apply to the copy (default: private).
197
+ * @param array $options Options used to configure the upload process.
198
+ *
199
+ * @see Aws\S3\MultipartCopy for more info about multipart uploads.
200
+ * @return ResultInterface Returns the result of the copy.
201
+ */
202
+ public function copy(
203
+ $fromBucket,
204
+ $fromKey,
205
+ $destBucket,
206
+ $destKey,
207
+ $acl = 'private',
208
+ array $options = []
209
+ );
210
+
211
+ /**
212
+ * Copy an object of any size to a different location asynchronously.
213
+ *
214
+ * @param string $fromBucket Bucket where the copy source resides.
215
+ * @param string $fromKey Key of the copy source.
216
+ * @param string $destBucket Bucket to which to copy the object.
217
+ * @param string $destKey Key to which to copy the object.
218
+ * @param string $acl ACL to apply to the copy (default: private).
219
+ * @param array $options Options used to configure the upload process.
220
+ *
221
+ * @see self::copy for more info about the parameters above.
222
+ * @return PromiseInterface Returns a promise that will be fulfilled
223
+ * with the result of the copy.
224
+ */
225
+ public function copyAsync(
226
+ $fromBucket,
227
+ $fromKey,
228
+ $destBucket,
229
+ $destKey,
230
+ $acl = 'private',
231
+ array $options = []
232
+ );
233
+
234
+ /**
235
+ * Recursively uploads all files in a given directory to a given bucket.
236
+ *
237
+ * @param string $directory Full path to a directory to upload
238
+ * @param string $bucket Name of the bucket
239
+ * @param string $keyPrefix Virtual directory key prefix to add to each upload
240
+ * @param array $options Options available in Aws\S3\Transfer::__construct
241
+ *
242
+ * @see Aws\S3\Transfer for more options and customization
243
+ */
244
+ public function uploadDirectory(
245
+ $directory,
246
+ $bucket,
247
+ $keyPrefix = null,
248
+ array $options = []
249
+ );
250
+
251
+ /**
252
+ * Recursively uploads all files in a given directory to a given bucket.
253
+ *
254
+ * @param string $directory Full path to a directory to upload
255
+ * @param string $bucket Name of the bucket
256
+ * @param string $keyPrefix Virtual directory key prefix to add to each upload
257
+ * @param array $options Options available in Aws\S3\Transfer::__construct
258
+ *
259
+ * @see Aws\S3\Transfer for more options and customization
260
+ *
261
+ * @return PromiseInterface A promise that is settled when the upload is
262
+ * complete.
263
+ */
264
+ public function uploadDirectoryAsync(
265
+ $directory,
266
+ $bucket,
267
+ $keyPrefix = null,
268
+ array $options = []
269
+ );
270
+
271
+ /**
272
+ * Downloads a bucket to the local filesystem
273
+ *
274
+ * @param string $directory Directory to download to
275
+ * @param string $bucket Bucket to download from
276
+ * @param string $keyPrefix Only download objects that use this key prefix
277
+ * @param array $options Options available in Aws\S3\Transfer::__construct
278
+ */
279
+ public function downloadBucket(
280
+ $directory,
281
+ $bucket,
282
+ $keyPrefix = '',
283
+ array $options = []
284
+ );
285
+
286
+ /**
287
+ * Downloads a bucket to the local filesystem
288
+ *
289
+ * @param string $directory Directory to download to
290
+ * @param string $bucket Bucket to download from
291
+ * @param string $keyPrefix Only download objects that use this key prefix
292
+ * @param array $options Options available in Aws\S3\Transfer::__construct
293
+ *
294
+ * @return PromiseInterface A promise that is settled when the download is
295
+ * complete.
296
+ */
297
+ public function downloadBucketAsync(
298
+ $directory,
299
+ $bucket,
300
+ $keyPrefix = '',
301
+ array $options = []
302
+ );
303
+
304
+ /**
305
+ * Returns the region in which a given bucket is located.
306
+ *
307
+ * @param string $bucketName
308
+ *
309
+ * @return string
310
+ */
311
+ public function determineBucketRegion($bucketName);
312
+
313
+ /**
314
+ * Returns a promise fulfilled with the region in which a given bucket is
315
+ * located.
316
+ *
317
+ * @param string $bucketName
318
+ *
319
+ * @return PromiseInterface
320
+ */
321
+ public function determineBucketRegionAsync($bucketName);
322
+ }
lib/Aws/Aws/S3/S3ClientTrait.php ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\Api\Parser\PayloadParserTrait;
5
+ use Aws\CommandInterface;
6
+ use Aws\Exception\AwsException;
7
+ use Aws\HandlerList;
8
+ use Aws\ResultInterface;
9
+ use Aws\S3\Exception\S3Exception;
10
+ use GuzzleHttp\Promise\PromiseInterface;
11
+ use GuzzleHttp\Promise\RejectedPromise;
12
+ use Psr\Http\Message\ResponseInterface;
13
+
14
+ /**
15
+ * A trait providing S3-specific functionality. This is meant to be used in
16
+ * classes implementing \Aws\S3\S3ClientInterface
17
+ */
18
+ trait S3ClientTrait
19
+ {
20
+ use PayloadParserTrait;
21
+
22
+ /**
23
+ * @see S3ClientInterface::upload()
24
+ */
25
+ public function upload(
26
+ $bucket,
27
+ $key,
28
+ $body,
29
+ $acl = 'private',
30
+ array $options = []
31
+ ) {
32
+ return $this
33
+ ->uploadAsync($bucket, $key, $body, $acl, $options)
34
+ ->wait();
35
+ }
36
+
37
+ /**
38
+ * @see S3ClientInterface::uploadAsync()
39
+ */
40
+ public function uploadAsync(
41
+ $bucket,
42
+ $key,
43
+ $body,
44
+ $acl = 'private',
45
+ array $options = []
46
+ ) {
47
+ return (new ObjectUploader($this, $bucket, $key, $body, $acl, $options))
48
+ ->promise();
49
+ }
50
+
51
+ /**
52
+ * @see S3ClientInterface::copy()
53
+ */
54
+ public function copy(
55
+ $fromB,
56
+ $fromK,
57
+ $destB,
58
+ $destK,
59
+ $acl = 'private',
60
+ array $opts = []
61
+ ) {
62
+ return $this->copyAsync($fromB, $fromK, $destB, $destK, $acl, $opts)
63
+ ->wait();
64
+ }
65
+
66
+ /**
67
+ * @see S3ClientInterface::copyAsync()
68
+ */
69
+ public function copyAsync(
70
+ $fromB,
71
+ $fromK,
72
+ $destB,
73
+ $destK,
74
+ $acl = 'private',
75
+ array $opts = []
76
+ ) {
77
+ $source = [
78
+ 'Bucket' => $fromB,
79
+ 'Key' => $fromK,
80
+ ];
81
+ if (isset($opts['version_id'])) {
82
+ $source['VersionId'] = $opts['version_id'];
83
+ }
84
+ $destination = [
85
+ 'Bucket' => $destB,
86
+ 'Key' => $destK
87
+ ];
88
+
89
+ return (new ObjectCopier($this, $source, $destination, $acl, $opts))
90
+ ->promise();
91
+ }
92
+
93
+ /**
94
+ * @see S3ClientInterface::registerStreamWrapper()
95
+ */
96
+ public function registerStreamWrapper()
97
+ {
98
+ StreamWrapper::register($this);
99
+ }
100
+
101
+ /**
102
+ * @see S3ClientInterface::deleteMatchingObjects()
103
+ */
104
+ public function deleteMatchingObjects(
105
+ $bucket,
106
+ $prefix = '',
107
+ $regex = '',
108
+ array $options = []
109
+ ) {
110
+ $this->deleteMatchingObjectsAsync($bucket, $prefix, $regex, $options)
111
+ ->wait();
112
+ }
113
+
114
+ /**
115
+ * @see S3ClientInterface::deleteMatchingObjectsAsync()
116
+ */
117
+ public function deleteMatchingObjectsAsync(
118
+ $bucket,
119
+ $prefix = '',
120
+ $regex = '',
121
+ array $options = []
122
+ ) {
123
+ if (!$prefix && !$regex) {
124
+ return new RejectedPromise(
125
+ new \RuntimeException('A prefix or regex is required.')
126
+ );
127
+ }
128
+
129
+ $params = ['Bucket' => $bucket, 'Prefix' => $prefix];
130
+ $iter = $this->getIterator('ListObjects', $params);
131
+
132
+ if ($regex) {
133
+ $iter = \Aws\filter($iter, function ($c) use ($regex) {
134
+ return preg_match($regex, $c['Key']);
135
+ });
136
+ }
137
+
138
+ return BatchDelete::fromIterator($this, $bucket, $iter, $options)
139
+ ->promise();
140
+ }
141
+
142
+ /**
143
+ * @see S3ClientInterface::uploadDirectory()
144
+ */
145
+ public function uploadDirectory(
146
+ $directory,
147
+ $bucket,
148
+ $keyPrefix = null,
149
+ array $options = []
150
+ ) {
151
+ $this->uploadDirectoryAsync($directory, $bucket, $keyPrefix, $options)
152
+ ->wait();
153
+ }
154
+
155
+ /**
156
+ * @see S3ClientInterface::uploadDirectoryAsync()
157
+ */
158
+ public function uploadDirectoryAsync(
159
+ $directory,
160
+ $bucket,
161
+ $keyPrefix = null,
162
+ array $options = []
163
+ ) {
164
+ $d = "s3://$bucket" . ($keyPrefix ? '/' . ltrim($keyPrefix, '/') : '');
165
+ return (new Transfer($this, $directory, $d, $options))->promise();
166
+ }
167
+
168
+ /**
169
+ * @see S3ClientInterface::downloadBucket()
170
+ */
171
+ public function downloadBucket(
172
+ $directory,
173
+ $bucket,
174
+ $keyPrefix = '',
175
+ array $options = []
176
+ ) {
177
+ $this->downloadBucketAsync($directory, $bucket, $keyPrefix, $options)
178
+ ->wait();
179
+ }
180
+
181
+ /**
182
+ * @see S3ClientInterface::downloadBucketAsync()
183
+ */
184
+ public function downloadBucketAsync(
185
+ $directory,
186
+ $bucket,
187
+ $keyPrefix = '',
188
+ array $options = []
189
+ ) {
190
+ $s = "s3://$bucket" . ($keyPrefix ? '/' . ltrim($keyPrefix, '/') : '');
191
+ return (new Transfer($this, $s, $directory, $options))->promise();
192
+ }
193
+
194
+ /**
195
+ * @see S3ClientInterface::determineBucketRegion()
196
+ */
197
+ public function determineBucketRegion($bucketName)
198
+ {
199
+ return $this->determineBucketRegionAsync($bucketName)->wait();
200
+ }
201
+
202
+ /**
203
+ * @see S3ClientInterface::determineBucketRegionAsync()
204
+ *
205
+ * @param string $bucketName
206
+ *
207
+ * @return PromiseInterface
208
+ */
209
+ public function determineBucketRegionAsync($bucketName)
210
+ {
211
+ $command = $this->getCommand('HeadBucket', ['Bucket' => $bucketName]);
212
+ $handlerList = clone $this->getHandlerList();
213
+ $handlerList->remove('s3.permanent_redirect');
214
+ $handlerList->remove('signer');
215
+ $handler = $handlerList->resolve();
216
+
217
+ return $handler($command)
218
+ ->then(static function (ResultInterface $result) {
219
+ return $result['@metadata']['headers']['x-amz-bucket-region'];
220
+ }, function (AwsException $e) {
221
+ $response = $e->getResponse();
222
+ if ($response === null) {
223
+ throw $e;
224
+ }
225
+
226
+ if ($e->getAwsErrorCode() === 'AuthorizationHeaderMalformed') {
227
+ $region = $this->determineBucketRegionFromExceptionBody(
228
+ $response
229
+ );
230
+ if (!empty($region)) {
231
+ return $region;
232
+ }
233
+ throw $e;
234
+ }
235
+
236
+ return $response->getHeaderLine('x-amz-bucket-region');
237
+ });
238
+ }
239
+
240
+ private function determineBucketRegionFromExceptionBody(ResponseInterface $response)
241
+ {
242
+ try {
243
+ $element = $this->parseXml($response->getBody(), $response);
244
+ if (!empty($element->Region)) {
245
+ return (string)$element->Region;
246
+ }
247
+ } catch (\Exception $e) {
248
+ // Fallthrough on exceptions from parsing
249
+ }
250
+ return false;
251
+ }
252
+
253
+ /**
254
+ * @see S3ClientInterface::doesBucketExist()
255
+ */
256
+ public function doesBucketExist($bucket)
257
+ {
258
+ return $this->checkExistenceWithCommand(
259
+ $this->getCommand('HeadBucket', ['Bucket' => $bucket])
260
+ );
261
+ }
262
+
263
+ /**
264
+ * @see S3ClientInterface::doesObjectExist()
265
+ */
266
+ public function doesObjectExist($bucket, $key, array $options = [])
267
+ {
268
+ return $this->checkExistenceWithCommand(
269
+ $this->getCommand('HeadObject', [
270
+ 'Bucket' => $bucket,
271
+ 'Key' => $key
272
+ ] + $options)
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Determines whether or not a resource exists using a command
278
+ *
279
+ * @param CommandInterface $command Command used to poll for the resource
280
+ *
281
+ * @return bool
282
+ * @throws S3Exception|\Exception if there is an unhandled exception
283
+ */
284
+ private function checkExistenceWithCommand(CommandInterface $command)
285
+ {
286
+ try {
287
+ $this->execute($command);
288
+ return true;
289
+ } catch (S3Exception $e) {
290
+ if ($e->getAwsErrorCode() == 'AccessDenied') {
291
+ return true;
292
+ }
293
+ if ($e->getStatusCode() >= 500) {
294
+ throw $e;
295
+ }
296
+ return false;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * @see S3ClientInterface::execute()
302
+ */
303
+ abstract public function execute(CommandInterface $command);
304
+
305
+ /**
306
+ * @see S3ClientInterface::getCommand()
307
+ */
308
+ abstract public function getCommand($name, array $args = []);
309
+
310
+ /**
311
+ * @see S3ClientInterface::getHandlerList()
312
+ *
313
+ * @return HandlerList
314
+ */
315
+ abstract public function getHandlerList();
316
+
317
+ /**
318
+ * @see S3ClientInterface::getIterator()
319
+ *
320
+ * @return \Iterator
321
+ */
322
+ abstract public function getIterator($name, array $args = []);
323
+ }
lib/Aws/Aws/S3/S3EndpointMiddleware.php ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * Used to update the URL used for S3 requests to support:
9
+ * S3 Accelerate, S3 DualStack or Both. It will build to
10
+ * host style paths unless specified, including for S3
11
+ * DualStack.
12
+ *
13
+ * IMPORTANT: this middleware must be added after the "build" step.
14
+ *
15
+ * @internal
16
+ */
17
+ class S3EndpointMiddleware
18
+ {
19
+ private static $exclusions = [
20
+ 'CreateBucket' => true,
21
+ 'DeleteBucket' => true,
22
+ 'ListBuckets' => true,
23
+ ];
24
+
25
+ const NO_PATTERN = 0;
26
+ const DUALSTACK = 1;
27
+ const ACCELERATE = 2;
28
+ const ACCELERATE_DUALSTACK = 3;
29
+ const PATH_STYLE = 4;
30
+ const HOST_STYLE = 5;
31
+
32
+ /** @var bool */
33
+ private $accelerateByDefault;
34
+ /** @var bool */
35
+ private $dualStackByDefault;
36
+ /** @var bool */
37
+ private $pathStyleByDefault;
38
+ /** @var string */
39
+ private $region;
40
+ /** @var callable */
41
+ private $nextHandler;
42
+
43
+ /**
44
+ * Create a middleware wrapper function
45
+ *
46
+ * @param string $region
47
+ * @param array $options
48
+ *
49
+ * @return callable
50
+ */
51
+ public static function wrap($region, array $options)
52
+ {
53
+ return function (callable $handler) use ($region, $options) {
54
+ return new self($handler, $region, $options);
55
+ };
56
+ }
57
+
58
+ public function __construct(
59
+ callable $nextHandler,
60
+ $region,
61
+ array $options
62
+ ) {
63
+ $this->pathStyleByDefault = isset($options['path_style'])
64
+ ? (bool) $options['path_style'] : false;
65
+ $this->dualStackByDefault = isset($options['dual_stack'])
66
+ ? (bool) $options['dual_stack'] : false;
67
+ $this->accelerateByDefault = isset($options['accelerate'])
68
+ ? (bool) $options['accelerate'] : false;
69
+ $this->region = (string) $region;
70
+ $this->nextHandler = $nextHandler;
71
+ }
72
+
73
+ public function __invoke(CommandInterface $command, RequestInterface $request)
74
+ {
75
+ switch ($this->endpointPatternDecider($command, $request)) {
76
+ case self::HOST_STYLE:
77
+ $request = $this->applyHostStyleEndpoint($command, $request);
78
+ break;
79
+ case self::NO_PATTERN:
80
+ case self::PATH_STYLE:
81
+ break;
82
+ case self::DUALSTACK:
83
+ $request = $this->applyDualStackEndpoint($command, $request);
84
+ break;
85
+ case self::ACCELERATE:
86
+ $request = $this->applyAccelerateEndpoint(
87
+ $command,
88
+ $request,
89
+ 's3-accelerate'
90
+ );
91
+ break;
92
+ case self::ACCELERATE_DUALSTACK:
93
+ $request = $this->applyAccelerateEndpoint(
94
+ $command,
95
+ $request,
96
+ 's3-accelerate.dualstack'
97
+ );
98
+ break;
99
+ }
100
+
101
+ $nextHandler = $this->nextHandler;
102
+ return $nextHandler($command, $request);
103
+ }
104
+
105
+ private static function isRequestHostStyleCompatible(
106
+ CommandInterface $command,
107
+ RequestInterface $request
108
+ ) {
109
+ return S3Client::isBucketDnsCompatible($command['Bucket'])
110
+ && (
111
+ $request->getUri()->getScheme() === 'http'
112
+ || strpos($command['Bucket'], '.') === false
113
+ );
114
+ }
115
+
116
+ private function endpointPatternDecider(
117
+ CommandInterface $command,
118
+ RequestInterface $request
119
+ ) {
120
+ $accelerate = isset($command['@use_accelerate_endpoint'])
121
+ ? $command['@use_accelerate_endpoint'] : $this->accelerateByDefault;
122
+ $dualStack = isset($command['@use_dual_stack_endpoint'])
123
+ ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault;
124
+ $pathStyle = isset($command['@use_path_style_endpoint'])
125
+ ? $command['@use_path_style_endpoint'] : $this->pathStyleByDefault;
126
+
127
+ if ($accelerate && $dualStack) {
128
+ // When try to enable both for operations excluded from s3-accelerate,
129
+ // only dualstack endpoints will be enabled.
130
+ return $this->canAccelerate($command)
131
+ ? self::ACCELERATE_DUALSTACK
132
+ : self::DUALSTACK;
133
+ }
134
+
135
+ if ($accelerate && $this->canAccelerate($command)) {
136
+ return self::ACCELERATE;
137
+ }
138
+
139
+ if ($dualStack) {
140
+ return self::DUALSTACK;
141
+ }
142
+
143
+ if (!$pathStyle
144
+ && self::isRequestHostStyleCompatible($command, $request)
145
+ ) {
146
+ return self::HOST_STYLE;
147
+ }
148
+
149
+ return self::PATH_STYLE;
150
+ }
151
+
152
+ private function canAccelerate(CommandInterface $command)
153
+ {
154
+ return empty(self::$exclusions[$command->getName()])
155
+ && S3Client::isBucketDnsCompatible($command['Bucket']);
156
+ }
157
+
158
+ private function getBucketStyleHost(CommandInterface $command, $host)
159
+ {
160
+ // For operations on the base host (e.g. ListBuckets)
161
+ if (!isset($command['Bucket'])) {
162
+ return $host;
163
+ }
164
+
165
+ return "{$command['Bucket']}.{$host}";
166
+ }
167
+
168
+ private function applyHostStyleEndpoint(
169
+ CommandInterface $command,
170
+ RequestInterface $request
171
+ ) {
172
+ $uri = $request->getUri();
173
+ $request = $request->withUri(
174
+ $uri->withHost($this->getBucketStyleHost(
175
+ $command,
176
+ $uri->getHost()
177
+ ))
178
+ ->withPath($this->getBucketlessPath(
179
+ $uri->getPath(),
180
+ $command
181
+ ))
182
+ );
183
+ return $request;
184
+ }
185
+
186
+ private function applyDualStackEndpoint(
187
+ CommandInterface $command,
188
+ RequestInterface $request
189
+ ) {
190
+ $request = $request->withUri(
191
+ $request->getUri()
192
+ ->withHost($this->getDualStackHost())
193
+ );
194
+ if (empty($command['@use_path_style_endpoint'])
195
+ && !$this->pathStyleByDefault
196
+ && self::isRequestHostStyleCompatible($command, $request)
197
+ ) {
198
+ $request = $this->applyHostStyleEndpoint($command, $request);
199
+ }
200
+ return $request;
201
+ }
202
+
203
+ private function getDualStackHost()
204
+ {
205
+ return "s3.dualstack.{$this->region}.amazonaws.com";
206
+ }
207
+
208
+ private function applyAccelerateEndpoint(
209
+ CommandInterface $command,
210
+ RequestInterface $request,
211
+ $pattern
212
+ ) {
213
+ $request = $request->withUri(
214
+ $request->getUri()
215
+ ->withHost($this->getAccelerateHost($command, $pattern))
216
+ ->withPath($this->getBucketlessPath(
217
+ $request->getUri()->getPath(),
218
+ $command
219
+ ))
220
+ );
221
+ return $request;
222
+ }
223
+
224
+ private function getAccelerateHost(CommandInterface $command, $pattern)
225
+ {
226
+ return "{$command['Bucket']}.{$pattern}.amazonaws.com";
227
+ }
228
+
229
+ private function getBucketlessPath($path, CommandInterface $command)
230
+ {
231
+ $pattern = '/^\\/' . preg_quote($command['Bucket'], '/') . '/';
232
+ return preg_replace($pattern, '', $path) ?: '/';
233
+ }
234
+ }
lib/Aws/Aws/S3/S3MultiRegionClient.php ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CacheInterface;
5
+ use Aws\CommandInterface;
6
+ use Aws\LruArrayCache;
7
+ use Aws\MultiRegionClient as BaseClient;
8
+ use Aws\Exception\AwsException;
9
+ use Aws\S3\Exception\PermanentRedirectException;
10
+ use GuzzleHttp\Promise;
11
+
12
+ /**
13
+ * **Amazon Simple Storage Service** multi-region client.
14
+ *
15
+ * @method \Aws\Result abortMultipartUpload(array $args = [])
16
+ * @method \GuzzleHttp\Promise\Promise abortMultipartUploadAsync(array $args = [])
17
+ * @method \Aws\Result completeMultipartUpload(array $args = [])
18
+ * @method \GuzzleHttp\Promise\Promise completeMultipartUploadAsync(array $args = [])
19
+ * @method \Aws\Result copyObject(array $args = [])
20
+ * @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = [])
21
+ * @method \Aws\Result createBucket(array $args = [])
22
+ * @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = [])
23
+ * @method \Aws\Result createMultipartUpload(array $args = [])
24
+ * @method \GuzzleHttp\Promise\Promise createMultipartUploadAsync(array $args = [])
25
+ * @method \Aws\Result deleteBucket(array $args = [])
26
+ * @method \GuzzleHttp\Promise\Promise deleteBucketAsync(array $args = [])
27
+ * @method \Aws\Result deleteBucketAnalyticsConfiguration(array $args = [])
28
+ * @method \GuzzleHttp\Promise\Promise deleteBucketAnalyticsConfigurationAsync(array $args = [])
29
+ * @method \Aws\Result deleteBucketCors(array $args = [])
30
+ * @method \GuzzleHttp\Promise\Promise deleteBucketCorsAsync(array $args = [])
31
+ * @method \Aws\Result deleteBucketEncryption(array $args = [])
32
+ * @method \GuzzleHttp\Promise\Promise deleteBucketEncryptionAsync(array $args = [])
33
+ * @method \Aws\Result deleteBucketInventoryConfiguration(array $args = [])
34
+ * @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = [])
35
+ * @method \Aws\Result deleteBucketLifecycle(array $args = [])
36
+ * @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = [])
37
+ * @method \Aws\Result deleteBucketMetricsConfiguration(array $args = [])
38
+ * @method \GuzzleHttp\Promise\Promise deleteBucketMetricsConfigurationAsync(array $args = [])
39
+ * @method \Aws\Result deleteBucketPolicy(array $args = [])
40
+ * @method \GuzzleHttp\Promise\Promise deleteBucketPolicyAsync(array $args = [])
41
+ * @method \Aws\Result deleteBucketReplication(array $args = [])
42
+ * @method \GuzzleHttp\Promise\Promise deleteBucketReplicationAsync(array $args = [])
43
+ * @method \Aws\Result deleteBucketTagging(array $args = [])
44
+ * @method \GuzzleHttp\Promise\Promise deleteBucketTaggingAsync(array $args = [])
45
+ * @method \Aws\Result deleteBucketWebsite(array $args = [])
46
+ * @method \GuzzleHttp\Promise\Promise deleteBucketWebsiteAsync(array $args = [])
47
+ * @method \Aws\Result deleteObject(array $args = [])
48
+ * @method \GuzzleHttp\Promise\Promise deleteObjectAsync(array $args = [])
49
+ * @method \Aws\Result deleteObjectTagging(array $args = [])
50
+ * @method \GuzzleHttp\Promise\Promise deleteObjectTaggingAsync(array $args = [])
51
+ * @method \Aws\Result deleteObjects(array $args = [])
52
+ * @method \GuzzleHttp\Promise\Promise deleteObjectsAsync(array $args = [])
53
+ * @method \Aws\Result deletePublicAccessBlock(array $args = [])
54
+ * @method \GuzzleHttp\Promise\Promise deletePublicAccessBlockAsync(array $args = [])
55
+ * @method \Aws\Result getBucketAccelerateConfiguration(array $args = [])
56
+ * @method \GuzzleHttp\Promise\Promise getBucketAccelerateConfigurationAsync(array $args = [])
57
+ * @method \Aws\Result getBucketAcl(array $args = [])
58
+ * @method \GuzzleHttp\Promise\Promise getBucketAclAsync(array $args = [])
59
+ * @method \Aws\Result getBucketAnalyticsConfiguration(array $args = [])
60
+ * @method \GuzzleHttp\Promise\Promise getBucketAnalyticsConfigurationAsync(array $args = [])
61
+ * @method \Aws\Result getBucketCors(array $args = [])
62
+ * @method \GuzzleHttp\Promise\Promise getBucketCorsAsync(array $args = [])
63
+ * @method \Aws\Result getBucketEncryption(array $args = [])
64
+ * @method \GuzzleHttp\Promise\Promise getBucketEncryptionAsync(array $args = [])
65
+ * @method \Aws\Result getBucketInventoryConfiguration(array $args = [])
66
+ * @method \GuzzleHttp\Promise\Promise getBucketInventoryConfigurationAsync(array $args = [])
67
+ * @method \Aws\Result getBucketLifecycle(array $args = [])
68
+ * @method \GuzzleHttp\Promise\Promise getBucketLifecycleAsync(array $args = [])
69
+ * @method \Aws\Result getBucketLifecycleConfiguration(array $args = [])
70
+ * @method \GuzzleHttp\Promise\Promise getBucketLifecycleConfigurationAsync(array $args = [])
71
+ * @method \Aws\Result getBucketLocation(array $args = [])
72
+ * @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = [])
73
+ * @method \Aws\Result getBucketLogging(array $args = [])
74
+ * @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = [])
75
+ * @method \Aws\Result getBucketMetricsConfiguration(array $args = [])
76
+ * @method \GuzzleHttp\Promise\Promise getBucketMetricsConfigurationAsync(array $args = [])
77
+ * @method \Aws\Result getBucketNotification(array $args = [])
78
+ * @method \GuzzleHttp\Promise\Promise getBucketNotificationAsync(array $args = [])
79
+ * @method \Aws\Result getBucketNotificationConfiguration(array $args = [])
80
+ * @method \GuzzleHttp\Promise\Promise getBucketNotificationConfigurationAsync(array $args = [])
81
+ * @method \Aws\Result getBucketPolicy(array $args = [])
82
+ * @method \GuzzleHttp\Promise\Promise getBucketPolicyAsync(array $args = [])
83
+ * @method \Aws\Result getBucketPolicyStatus(array $args = [])
84
+ * @method \GuzzleHttp\Promise\Promise getBucketPolicyStatusAsync(array $args = [])
85
+ * @method \Aws\Result getBucketReplication(array $args = [])
86
+ * @method \GuzzleHttp\Promise\Promise getBucketReplicationAsync(array $args = [])
87
+ * @method \Aws\Result getBucketRequestPayment(array $args = [])
88
+ * @method \GuzzleHttp\Promise\Promise getBucketRequestPaymentAsync(array $args = [])
89
+ * @method \Aws\Result getBucketTagging(array $args = [])
90
+ * @method \GuzzleHttp\Promise\Promise getBucketTaggingAsync(array $args = [])
91
+ * @method \Aws\Result getBucketVersioning(array $args = [])
92
+ * @method \GuzzleHttp\Promise\Promise getBucketVersioningAsync(array $args = [])
93
+ * @method \Aws\Result getBucketWebsite(array $args = [])
94
+ * @method \GuzzleHttp\Promise\Promise getBucketWebsiteAsync(array $args = [])
95
+ * @method \Aws\Result getObject(array $args = [])
96
+ * @method \GuzzleHttp\Promise\Promise getObjectAsync(array $args = [])
97
+ * @method \Aws\Result getObjectAcl(array $args = [])
98
+ * @method \GuzzleHttp\Promise\Promise getObjectAclAsync(array $args = [])
99
+ * @method \Aws\Result getObjectLegalHold(array $args = [])
100
+ * @method \GuzzleHttp\Promise\Promise getObjectLegalHoldAsync(array $args = [])
101
+ * @method \Aws\Result getObjectLockConfiguration(array $args = [])
102
+ * @method \GuzzleHttp\Promise\Promise getObjectLockConfigurationAsync(array $args = [])
103
+ * @method \Aws\Result getObjectRetention(array $args = [])
104
+ * @method \GuzzleHttp\Promise\Promise getObjectRetentionAsync(array $args = [])
105
+ * @method \Aws\Result getObjectTagging(array $args = [])
106
+ * @method \GuzzleHttp\Promise\Promise getObjectTaggingAsync(array $args = [])
107
+ * @method \Aws\Result getObjectTorrent(array $args = [])
108
+ * @method \GuzzleHttp\Promise\Promise getObjectTorrentAsync(array $args = [])
109
+ * @method \Aws\Result getPublicAccessBlock(array $args = [])
110
+ * @method \GuzzleHttp\Promise\Promise getPublicAccessBlockAsync(array $args = [])
111
+ * @method \Aws\Result headBucket(array $args = [])
112
+ * @method \GuzzleHttp\Promise\Promise headBucketAsync(array $args = [])
113
+ * @method \Aws\Result headObject(array $args = [])
114
+ * @method \GuzzleHttp\Promise\Promise headObjectAsync(array $args = [])
115
+ * @method \Aws\Result listBucketAnalyticsConfigurations(array $args = [])
116
+ * @method \GuzzleHttp\Promise\Promise listBucketAnalyticsConfigurationsAsync(array $args = [])
117
+ * @method \Aws\Result listBucketInventoryConfigurations(array $args = [])
118
+ * @method \GuzzleHttp\Promise\Promise listBucketInventoryConfigurationsAsync(array $args = [])
119
+ * @method \Aws\Result listBucketMetricsConfigurations(array $args = [])
120
+ * @method \GuzzleHttp\Promise\Promise listBucketMetricsConfigurationsAsync(array $args = [])
121
+ * @method \Aws\Result listBuckets(array $args = [])
122
+ * @method \GuzzleHttp\Promise\Promise listBucketsAsync(array $args = [])
123
+ * @method \Aws\Result listMultipartUploads(array $args = [])
124
+ * @method \GuzzleHttp\Promise\Promise listMultipartUploadsAsync(array $args = [])
125
+ * @method \Aws\Result listObjectVersions(array $args = [])
126
+ * @method \GuzzleHttp\Promise\Promise listObjectVersionsAsync(array $args = [])
127
+ * @method \Aws\Result listObjects(array $args = [])
128
+ * @method \GuzzleHttp\Promise\Promise listObjectsAsync(array $args = [])
129
+ * @method \Aws\Result listObjectsV2(array $args = [])
130
+ * @method \GuzzleHttp\Promise\Promise listObjectsV2Async(array $args = [])
131
+ * @method \Aws\Result listParts(array $args = [])
132
+ * @method \GuzzleHttp\Promise\Promise listPartsAsync(array $args = [])
133
+ * @method \Aws\Result putBucketAccelerateConfiguration(array $args = [])
134
+ * @method \GuzzleHttp\Promise\Promise putBucketAccelerateConfigurationAsync(array $args = [])
135
+ * @method \Aws\Result putBucketAcl(array $args = [])
136
+ * @method \GuzzleHttp\Promise\Promise putBucketAclAsync(array $args = [])
137
+ * @method \Aws\Result putBucketAnalyticsConfiguration(array $args = [])
138
+ * @method \GuzzleHttp\Promise\Promise putBucketAnalyticsConfigurationAsync(array $args = [])
139
+ * @method \Aws\Result putBucketCors(array $args = [])
140
+ * @method \GuzzleHttp\Promise\Promise putBucketCorsAsync(array $args = [])
141
+ * @method \Aws\Result putBucketEncryption(array $args = [])
142
+ * @method \GuzzleHttp\Promise\Promise putBucketEncryptionAsync(array $args = [])
143
+ * @method \Aws\Result putBucketInventoryConfiguration(array $args = [])
144
+ * @method \GuzzleHttp\Promise\Promise putBucketInventoryConfigurationAsync(array $args = [])
145
+ * @method \Aws\Result putBucketLifecycle(array $args = [])
146
+ * @method \GuzzleHttp\Promise\Promise putBucketLifecycleAsync(array $args = [])
147
+ * @method \Aws\Result putBucketLifecycleConfiguration(array $args = [])
148
+ * @method \GuzzleHttp\Promise\Promise putBucketLifecycleConfigurationAsync(array $args = [])
149
+ * @method \Aws\Result putBucketLogging(array $args = [])
150
+ * @method \GuzzleHttp\Promise\Promise putBucketLoggingAsync(array $args = [])
151
+ * @method \Aws\Result putBucketMetricsConfiguration(array $args = [])
152
+ * @method \GuzzleHttp\Promise\Promise putBucketMetricsConfigurationAsync(array $args = [])
153
+ * @method \Aws\Result putBucketNotification(array $args = [])
154
+ * @method \GuzzleHttp\Promise\Promise putBucketNotificationAsync(array $args = [])
155
+ * @method \Aws\Result putBucketNotificationConfiguration(array $args = [])
156
+ * @method \GuzzleHttp\Promise\Promise putBucketNotificationConfigurationAsync(array $args = [])
157
+ * @method \Aws\Result putBucketPolicy(array $args = [])
158
+ * @method \GuzzleHttp\Promise\Promise putBucketPolicyAsync(array $args = [])
159
+ * @method \Aws\Result putBucketReplication(array $args = [])
160
+ * @method \GuzzleHttp\Promise\Promise putBucketReplicationAsync(array $args = [])
161
+ * @method \Aws\Result putBucketRequestPayment(array $args = [])
162
+ * @method \GuzzleHttp\Promise\Promise putBucketRequestPaymentAsync(array $args = [])
163
+ * @method \Aws\Result putBucketTagging(array $args = [])
164
+ * @method \GuzzleHttp\Promise\Promise putBucketTaggingAsync(array $args = [])
165
+ * @method \Aws\Result putBucketVersioning(array $args = [])
166
+ * @method \GuzzleHttp\Promise\Promise putBucketVersioningAsync(array $args = [])
167
+ * @method \Aws\Result putBucketWebsite(array $args = [])
168
+ * @method \GuzzleHttp\Promise\Promise putBucketWebsiteAsync(array $args = [])
169
+ * @method \Aws\Result putObject(array $args = [])
170
+ * @method \GuzzleHttp\Promise\Promise putObjectAsync(array $args = [])
171
+ * @method \Aws\Result putObjectAcl(array $args = [])
172
+ * @method \GuzzleHttp\Promise\Promise putObjectAclAsync(array $args = [])
173
+ * @method \Aws\Result putObjectLegalHold(array $args = [])
174
+ * @method \GuzzleHttp\Promise\Promise putObjectLegalHoldAsync(array $args = [])
175
+ * @method \Aws\Result putObjectLockConfiguration(array $args = [])
176
+ * @method \GuzzleHttp\Promise\Promise putObjectLockConfigurationAsync(array $args = [])
177
+ * @method \Aws\Result putObjectRetention(array $args = [])
178
+ * @method \GuzzleHttp\Promise\Promise putObjectRetentionAsync(array $args = [])
179
+ * @method \Aws\Result putObjectTagging(array $args = [])
180
+ * @method \GuzzleHttp\Promise\Promise putObjectTaggingAsync(array $args = [])
181
+ * @method \Aws\Result putPublicAccessBlock(array $args = [])
182
+ * @method \GuzzleHttp\Promise\Promise putPublicAccessBlockAsync(array $args = [])
183
+ * @method \Aws\Result restoreObject(array $args = [])
184
+ * @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = [])
185
+ * @method \Aws\Result selectObjectContent(array $args = [])
186
+ * @method \GuzzleHttp\Promise\Promise selectObjectContentAsync(array $args = [])
187
+ * @method \Aws\Result uploadPart(array $args = [])
188
+ * @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = [])
189
+ * @method \Aws\Result uploadPartCopy(array $args = [])
190
+ * @method \GuzzleHttp\Promise\Promise uploadPartCopyAsync(array $args = [])
191
+ */
192
+ class S3MultiRegionClient extends BaseClient implements S3ClientInterface
193
+ {
194
+ use S3ClientTrait;
195
+
196
+ /** @var CacheInterface */
197
+ private $cache;
198
+
199
+ public static function getArguments()
200
+ {
201
+ $args = parent::getArguments();
202
+ $regionDef = $args['region'] + ['default' => function (array &$args) {
203
+ $availableRegions = array_keys($args['partition']['regions']);
204
+ return end($availableRegions);
205
+ }];
206
+ unset($args['region']);
207
+
208
+ return $args + [
209
+ 'bucket_region_cache' => [
210
+ 'type' => 'config',
211
+ 'valid' => [CacheInterface::class],
212
+ 'doc' => 'Cache of regions in which given buckets are located.',
213
+ 'default' => function () { return new LruArrayCache; },
214
+ ],
215
+ 'region' => $regionDef,
216
+ ];
217
+ }
218
+
219
+ public function __construct(array $args)
220
+ {
221
+ parent::__construct($args);
222
+ $this->cache = $this->getConfig('bucket_region_cache');
223
+
224
+ $this->getHandlerList()->prependInit(
225
+ $this->determineRegionMiddleware(),
226
+ 'determine_region'
227
+ );
228
+ }
229
+
230
+ private function determineRegionMiddleware()
231
+ {
232
+ return function (callable $handler) {
233
+ return function (CommandInterface $command) use ($handler) {
234
+ $cacheKey = $this->getCacheKey($command['Bucket']);
235
+ if (
236
+ empty($command['@region']) &&
237
+ $region = $this->cache->get($cacheKey)
238
+ ) {
239
+ $command['@region'] = $region;
240
+ }
241
+
242
+ return Promise\coroutine(function () use (
243
+ $handler,
244
+ $command,
245
+ $cacheKey
246
+ ) {
247
+ try {
248
+ yield $handler($command);
249
+ } catch (PermanentRedirectException $e) {
250
+ if (empty($command['Bucket'])) {
251
+ throw $e;
252
+ }
253
+ $result = $e->getResult();
254
+ $region = null;
255
+ if (isset($result['@metadata']['headers']['x-amz-bucket-region'])) {
256
+ $region = $result['@metadata']['headers']['x-amz-bucket-region'];
257
+ $this->cache->set($cacheKey, $region);
258
+ } else {
259
+ $region = (yield $this->determineBucketRegionAsync(
260
+ $command['Bucket']
261
+ ));
262
+ }
263
+
264
+ $command['@region'] = $region;
265
+ yield $handler($command);
266
+ } catch (AwsException $e) {
267
+ if ($e->getAwsErrorCode() === 'AuthorizationHeaderMalformed') {
268
+ $region = $this->determineBucketRegionFromExceptionBody(
269
+ $e->getResponse()
270
+ );
271
+ if (!empty($region)) {
272
+ $this->cache->set($cacheKey, $region);
273
+
274
+ $command['@region'] = $region;
275
+ yield $handler($command);
276
+ } else {
277
+ throw $e;
278
+ }
279
+ } else {
280
+ throw $e;
281
+ }
282
+ }
283
+ });
284
+ };
285
+ };
286
+ }
287
+
288
+ public function createPresignedRequest(CommandInterface $command, $expires)
289
+ {
290
+ if (empty($command['Bucket'])) {
291
+ throw new \InvalidArgumentException('The S3\\MultiRegionClient'
292
+ . ' cannot create presigned requests for commands without a'
293
+ . ' specified bucket.');
294
+ }
295
+
296
+ /** @var S3ClientInterface $client */
297
+ $client = $this->getClientFromPool(
298
+ $this->determineBucketRegion($command['Bucket'])
299
+ );
300
+ return $client->createPresignedRequest(
301
+ $client->getCommand($command->getName(), $command->toArray()),
302
+ $expires
303
+ );
304
+ }
305
+
306
+ public function getObjectUrl($bucket, $key)
307
+ {
308
+ /** @var S3Client $regionalClient */
309
+ $regionalClient = $this->getClientFromPool(
310
+ $this->determineBucketRegion($bucket)
311
+ );
312
+
313
+ return $regionalClient->getObjectUrl($bucket, $key);
314
+ }
315
+
316
+ public function determineBucketRegionAsync($bucketName)
317
+ {
318
+ $cacheKey = $this->getCacheKey($bucketName);
319
+ if ($cached = $this->cache->get($cacheKey)) {
320
+ return Promise\promise_for($cached);
321
+ }
322
+
323
+ /** @var S3ClientInterface $regionalClient */
324
+ $regionalClient = $this->getClientFromPool();
325
+ return $regionalClient->determineBucketRegionAsync($bucketName)
326
+ ->then(
327
+ function ($region) use ($cacheKey) {
328
+ $this->cache->set($cacheKey, $region);
329
+
330
+ return $region;
331
+ }
332
+ );
333
+ }
334
+
335
+ private function getCacheKey($bucketName)
336
+ {
337
+ return "aws:s3:{$bucketName}:location";
338
+ }
339
+ }
lib/Aws/Aws/S3/S3UriParser.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use GuzzleHttp\Psr7;
5
+ use Psr\Http\Message\UriInterface;
6
+
7
+ /**
8
+ * Extracts a region, bucket, key, and and if a URI is in path-style
9
+ */
10
+ class S3UriParser
11
+ {
12
+ private $pattern = '/^(.+\\.)?s3[.-]([A-Za-z0-9-]+)\\./';
13
+ private $streamWrapperScheme = 's3';
14
+
15
+ private static $defaultResult = [
16
+ 'path_style' => true,
17
+ 'bucket' => null,
18
+ 'key' => null,
19
+ 'region' => null
20
+ ];
21
+
22
+ /**
23
+ * Parses a URL or S3 StreamWrapper Uri (s3://) into an associative array
24
+ * of Amazon S3 data including:
25
+ *
26
+ * - bucket: The Amazon S3 bucket (null if none)
27
+ * - key: The Amazon S3 key (null if none)
28
+ * - path_style: Set to true if using path style, or false if not
29
+ * - region: Set to a string if a non-class endpoint is used or null.
30
+ *
31
+ * @param string|UriInterface $uri
32
+ *
33
+ * @return array
34
+ * @throws \InvalidArgumentException
35
+ */
36
+ public function parse($uri)
37
+ {
38
+ $url = Psr7\uri_for($uri);
39
+
40
+ if ($url->getScheme() == $this->streamWrapperScheme) {
41
+ return $this->parseStreamWrapper($url);
42
+ }
43
+
44
+ if (!$url->getHost()) {
45
+ throw new \InvalidArgumentException('No hostname found in URI: '
46
+ . $uri);
47
+ }
48
+
49
+ if (!preg_match($this->pattern, $url->getHost(), $matches)) {
50
+ return $this->parseCustomEndpoint($url);
51
+ }
52
+
53
+ // Parse the URI based on the matched format (path / virtual)
54
+ $result = empty($matches[1])
55
+ ? $this->parsePathStyle($url)
56
+ : $this->parseVirtualHosted($url, $matches);
57
+
58
+ // Add the region if one was found and not the classic endpoint
59
+ $result['region'] = $matches[2] == 'amazonaws' ? null : $matches[2];
60
+
61
+ return $result;
62
+ }
63
+
64
+ private function parseStreamWrapper(UriInterface $url)
65
+ {
66
+ $result = self::$defaultResult;
67
+ $result['path_style'] = false;
68
+
69
+ $result['bucket'] = $url->getHost();
70
+ if ($url->getPath()) {
71
+ $key = ltrim($url->getPath(), '/ ');
72
+ if (!empty($key)) {
73
+ $result['key'] = $key;
74
+ }
75
+ }
76
+
77
+ return $result;
78
+ }
79
+
80
+ private function parseCustomEndpoint(UriInterface $url)
81
+ {
82
+ $result = self::$defaultResult;
83
+ $path = ltrim($url->getPath(), '/ ');
84
+ $segments = explode('/', $path, 2);
85
+
86
+ if (isset($segments[0])) {
87
+ $result['bucket'] = $segments[0];
88
+ if (isset($segments[1])) {
89
+ $result['key'] = $segments[1];
90
+ }
91
+ }
92
+
93
+ return $result;
94
+ }
95
+
96
+ private function parsePathStyle(UriInterface $url)
97
+ {
98
+ $result = self::$defaultResult;
99
+
100
+ if ($url->getPath() != '/') {
101
+ $path = ltrim($url->getPath(), '/');
102
+ if ($path) {
103
+ $pathPos = strpos($path, '/');
104
+ if ($pathPos === false) {
105
+ // https://s3.amazonaws.com/bucket
106
+ $result['bucket'] = $path;
107
+ } elseif ($pathPos == strlen($path) - 1) {
108
+ // https://s3.amazonaws.com/bucket/
109
+ $result['bucket'] = substr($path, 0, -1);
110
+ } else {
111
+ // https://s3.amazonaws.com/bucket/key
112
+ $result['bucket'] = substr($path, 0, $pathPos);
113
+ $result['key'] = substr($path, $pathPos + 1) ?: null;
114
+ }
115
+ }
116
+ }
117
+
118
+ return $result;
119
+ }
120
+
121
+ private function parseVirtualHosted(UriInterface $url, array $matches)
122
+ {
123
+ $result = self::$defaultResult;
124
+ $result['path_style'] = false;
125
+ // Remove trailing "." from the prefix to get the bucket
126
+ $result['bucket'] = substr($matches[1], 0, -1);
127
+ $path = $url->getPath();
128
+ // Check if a key was present, and if so, removing the leading "/"
129
+ $result['key'] = !$path || $path == '/' ? null : substr($path, 1);
130
+
131
+ return $result;
132
+ }
133
+ }
lib/Aws/Aws/S3/SSECMiddleware.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CommandInterface;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * Simplifies the SSE-C process by encoding and hashing the key.
9
+ * @internal
10
+ */
11
+ class SSECMiddleware
12
+ {
13
+ private $endpointScheme;
14
+ private $nextHandler;
15
+
16
+ /**
17
+ * Provide the URI scheme of the client sending requests.
18
+ *
19
+ * @param string $endpointScheme URI scheme (http/https).
20
+ *
21
+ * @return callable
22
+ */
23
+ public static function wrap($endpointScheme)
24
+ {
25
+ return function (callable $handler) use ($endpointScheme) {
26
+ return new self($endpointScheme, $handler);
27
+ };
28
+ }
29
+
30
+ public function __construct($endpointScheme, callable $nextHandler)
31
+ {
32
+ $this->nextHandler = $nextHandler;
33
+ $this->endpointScheme = $endpointScheme;
34
+ }
35
+
36
+ public function __invoke(
37
+ CommandInterface $command,
38
+ RequestInterface $request = null
39
+ ) {
40
+ // Allows only HTTPS connections when using SSE-C
41
+ if (($command['SSECustomerKey'] || $command['CopySourceSSECustomerKey'])
42
+ && $this->endpointScheme !== 'https'
43
+ ) {
44
+ throw new \RuntimeException('You must configure your S3 client to '
45
+ . 'use HTTPS in order to use the SSE-C features.');
46
+ }
47
+
48
+ // Prepare the normal SSE-CPK headers
49
+ if ($command['SSECustomerKey']) {
50
+ $this->prepareSseParams($command);
51
+ }
52
+
53
+ // If it's a copy operation, prepare the SSE-CPK headers for the source.
54
+ if ($command['CopySourceSSECustomerKey']) {
55
+ $this->prepareSseParams($command, 'CopySource');
56
+ }
57
+
58
+ $f = $this->nextHandler;
59
+ return $f($command, $request);
60
+ }
61
+
62
+ private function prepareSseParams(CommandInterface $command, $prefix = '')
63
+ {
64
+ // Base64 encode the provided key
65
+ $key = $command[$prefix . 'SSECustomerKey'];
66
+ $command[$prefix . 'SSECustomerKey'] = base64_encode($key);
67
+
68
+ // Base64 the provided MD5 or, generate an MD5 if not provided
69
+ if ($md5 = $command[$prefix . 'SSECustomerKeyMD5']) {
70
+ $command[$prefix . 'SSECustomerKeyMD5'] = base64_encode($md5);
71
+ } else {
72
+ $command[$prefix . 'SSECustomerKeyMD5'] = base64_encode(md5($key, true));
73
+ }
74
+ }
75
+ }
lib/Aws/Aws/S3/StreamWrapper.php ADDED
@@ -0,0 +1,958 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws\CacheInterface;
5
+ use Aws\LruArrayCache;
6
+ use Aws\Result;
7
+ use Aws\S3\Exception\S3Exception;
8
+ use GuzzleHttp\Psr7;
9
+ use GuzzleHttp\Psr7\Stream;
10
+ use GuzzleHttp\Psr7\CachingStream;
11
+ use Psr\Http\Message\StreamInterface;
12
+
13
+ /**
14
+ * Amazon S3 stream wrapper to use "s3://<bucket>/<key>" files with PHP
15
+ * streams, supporting "r", "w", "a", "x".
16
+ *
17
+ * # Opening "r" (read only) streams:
18
+ *
19
+ * Read only streams are truly streaming by default and will not allow you to
20
+ * seek. This is because data read from the stream is not kept in memory or on
21
+ * the local filesystem. You can force a "r" stream to be seekable by setting
22
+ * the "seekable" stream context option true. This will allow true streaming of
23
+ * data from Amazon S3, but will maintain a buffer of previously read bytes in
24
+ * a 'php://temp' stream to allow seeking to previously read bytes from the
25
+ * stream.
26
+ *
27
+ * You may pass any GetObject parameters as 's3' stream context options. These
28
+ * options will affect how the data is downloaded from Amazon S3.
29
+ *
30
+ * # Opening "w" and "x" (write only) streams:
31
+ *
32
+ * Because Amazon S3 requires a Content-Length header, write only streams will
33
+ * maintain a 'php://temp' stream to buffer data written to the stream until
34
+ * the stream is flushed (usually by closing the stream with fclose).
35
+ *
36
+ * You may pass any PutObject parameters as 's3' stream context options. These
37
+ * options will affect how the data is uploaded to Amazon S3.
38
+ *
39
+ * When opening an "x" stream, the file must exist on Amazon S3 for the stream
40
+ * to open successfully.
41
+ *
42
+ * # Opening "a" (write only append) streams:
43
+ *
44
+ * Similar to "w" streams, opening append streams requires that the data be
45
+ * buffered in a "php://temp" stream. Append streams will attempt to download
46
+ * the contents of an object in Amazon S3, seek to the end of the object, then
47
+ * allow you to append to the contents of the object. The data will then be
48
+ * uploaded using a PutObject operation when the stream is flushed (usually
49
+ * with fclose).
50
+ *
51
+ * You may pass any GetObject and/or PutObject parameters as 's3' stream
52
+ * context options. These options will affect how the data is downloaded and
53
+ * uploaded from Amazon S3.
54
+ *
55
+ * Stream context options:
56
+ *
57
+ * - "seekable": Set to true to create a seekable "r" (read only) stream by
58
+ * using a php://temp stream buffer
59
+ * - For "unlink" only: Any option that can be passed to the DeleteObject
60
+ * operation
61
+ */
62
+ class StreamWrapper
63
+ {
64
+ /** @var resource|null Stream context (this is set by PHP) */
65
+ public $context;
66
+
67
+ /** @var StreamInterface Underlying stream resource */
68
+ private $body;
69
+
70
+ /** @var int Size of the body that is opened */
71
+ private $size;
72
+
73
+ /** @var array Hash of opened stream parameters */
74
+ private $params = [];
75
+
76
+ /** @var string Mode in which the stream was opened */
77
+ private $mode;
78
+
79
+ /** @var \Iterator Iterator used with opendir() related calls */
80
+ private $objectIterator;
81
+
82
+ /** @var string The bucket that was opened when opendir() was called */
83
+ private $openedBucket;
84
+
85
+ /** @var string The prefix of the bucket that was opened with opendir() */
86
+ private $openedBucketPrefix;
87
+
88
+ /** @var string Opened bucket path */
89
+ private $openedPath;
90
+
91
+ /** @var CacheInterface Cache for object and dir lookups */
92
+ private $cache;
93
+
94
+ /** @var string The opened protocol (e.g., "s3") */
95
+ private $protocol = 's3';
96
+
97
+ /** @var bool Keeps track of whether stream has been flushed since opening */
98
+ private $isFlushed = false;
99
+
100
+ /**
101
+ * Register the 's3://' stream wrapper
102
+ *
103
+ * @param S3ClientInterface $client Client to use with the stream wrapper
104
+ * @param string $protocol Protocol to register as.
105
+ * @param CacheInterface $cache Default cache for the protocol.
106
+ */
107
+ public static function register(
108
+ S3ClientInterface $client,
109
+ $protocol = 's3',
110
+ CacheInterface $cache = null
111
+ ) {
112
+ if (in_array($protocol, stream_get_wrappers())) {
113
+ stream_wrapper_unregister($protocol);
114
+ }
115
+
116
+ // Set the client passed in as the default stream context client
117
+ stream_wrapper_register($protocol, get_called_class(), STREAM_IS_URL);
118
+ $default = stream_context_get_options(stream_context_get_default());
119
+ $default[$protocol]['client'] = $client;
120
+
121
+ if ($cache) {
122
+ $default[$protocol]['cache'] = $cache;
123
+ } elseif (!isset($default[$protocol]['cache'])) {
124
+ // Set a default cache adapter.
125
+ $default[$protocol]['cache'] = new LruArrayCache();
126
+ }
127
+
128
+ stream_context_set_default($default);
129
+ }
130
+
131
+ public function stream_close()
132
+ {
133
+ if ($this->body->getSize() === 0 && !($this->isFlushed)) {
134
+ $this->stream_flush();
135
+ }
136
+ $this->body = $this->cache = null;
137
+ }
138
+
139
+ public function stream_open($path, $mode, $options, &$opened_path)
140
+ {
141
+ $this->initProtocol($path);
142
+ $this->isFlushed = false;
143
+ $this->params = $this->getBucketKey($path);
144
+ $this->mode = rtrim($mode, 'bt');
145
+
146
+ if ($errors = $this->validate($path, $this->mode)) {
147
+ return $this->triggerError($errors);
148
+ }
149
+
150
+ return $this->boolCall(function() use ($path) {
151
+ switch ($this->mode) {
152
+ case 'r': return $this->openReadStream($path);
153
+ case 'a': return $this->openAppendStream($path);
154
+ default: return $this->openWriteStream($path);
155
+ }
156
+ });
157
+ }
158
+
159
+ public function stream_eof()
160
+ {
161
+ return $this->body->eof();
162
+ }
163
+
164
+ public function stream_flush()
165
+ {
166
+ $this->isFlushed = true;
167
+ if ($this->mode == 'r') {
168
+ return false;
169
+ }
170
+
171
+ if ($this->body->isSeekable()) {
172
+ $this->body->seek(0);
173
+ }
174
+ $params = $this->getOptions(true);
175
+ $params['Body'] = $this->body;
176
+
177
+ // Attempt to guess the ContentType of the upload based on the
178
+ // file extension of the key
179
+ if (!isset($params['ContentType']) &&
180
+ ($type = Psr7\mimetype_from_filename($params['Key']))
181
+ ) {
182
+ $params['ContentType'] = $type;
183
+ }
184
+
185
+ $this->clearCacheKey("s3://{$params['Bucket']}/{$params['Key']}");
186
+ return $this->boolCall(function () use ($params) {
187
+ return (bool) $this->getClient()->putObject($params);
188
+ });
189
+ }
190
+
191
+ public function stream_read($count)
192
+ {
193
+ return $this->body->read($count);
194
+ }
195
+
196
+ public function stream_seek($offset, $whence = SEEK_SET)
197
+ {
198
+ return !$this->body->isSeekable()
199
+ ? false
200
+ : $this->boolCall(function () use ($offset, $whence) {
201
+ $this->body->seek($offset, $whence);
202
+ return true;
203
+ });
204
+ }
205
+
206
+ public function stream_tell()
207
+ {
208
+ return $this->boolCall(function() { return $this->body->tell(); });
209
+ }
210
+
211
+ public function stream_write($data)
212
+ {
213
+ return $this->body->write($data);
214
+ }
215
+
216
+ public function unlink($path)
217
+ {
218
+ $this->initProtocol($path);
219
+
220
+ return $this->boolCall(function () use ($path) {
221
+ $this->clearCacheKey($path);
222
+ $this->getClient()->deleteObject($this->withPath($path));
223
+ return true;
224
+ });
225
+ }
226
+
227
+ public function stream_stat()
228
+ {
229
+ $stat = $this->getStatTemplate();
230
+ $stat[7] = $stat['size'] = $this->getSize();
231
+ $stat[2] = $stat['mode'] = $this->mode;
232
+
233
+ return $stat;
234
+ }
235
+
236
+ /**
237
+ * Provides information for is_dir, is_file, filesize, etc. Works on
238
+ * buckets, keys, and prefixes.
239
+ * @link http://www.php.net/manual/en/streamwrapper.url-stat.php
240
+ */
241
+ public function url_stat($path, $flags)
242
+ {
243
+ $this->initProtocol($path);
244
+
245
+ // Some paths come through as S3:// for some reason.
246
+ $split = explode('://', $path);
247
+ $path = strtolower($split[0]) . '://' . $split[1];
248
+
249
+ // Check if this path is in the url_stat cache
250
+ if ($value = $this->getCacheStorage()->get($path)) {
251
+ return $value;
252
+ }
253
+
254
+ $stat = $this->createStat($path, $flags);
255
+
256
+ if (is_array($stat)) {
257
+ $this->getCacheStorage()->set($path, $stat);
258
+ }
259
+
260
+ return $stat;
261
+ }
262
+
263
+ /**
264
+ * Parse the protocol out of the given path.
265
+ *
266
+ * @param $path
267
+ */
268
+ private function initProtocol($path)
269
+ {
270
+ $parts = explode('://', $path, 2);
271
+ $this->protocol = $parts[0] ?: 's3';
272
+ }
273
+
274
+ private function createStat($path, $flags)
275
+ {
276
+ $this->initProtocol($path);
277
+ $parts = $this->withPath($path);
278
+
279
+ if (!$parts['Key']) {
280
+ return $this->statDirectory($parts, $path, $flags);
281
+ }
282
+
283
+ return $this->boolCall(function () use ($parts, $path) {
284
+ try {
285
+ $result = $this->getClient()->headObject($parts);
286
+ if (substr($parts['Key'], -1, 1) == '/' &&
287
+ $result['ContentLength'] == 0
288
+ ) {
289
+ // Return as if it is a bucket to account for console
290
+ // bucket objects (e.g., zero-byte object "foo/")
291
+ return $this->formatUrlStat($path);
292
+ }
293
+
294
+ // Attempt to stat and cache regular object
295
+ return $this->formatUrlStat($result->toArray());
296
+ } catch (S3Exception $e) {
297
+ // Maybe this isn't an actual key, but a prefix. Do a prefix
298
+ // listing of objects to determine.
299
+ $result = $this->getClient()->listObjects([
300
+ 'Bucket' => $parts['Bucket'],
301
+ 'Prefix' => rtrim($parts['Key'], '/') . '/',
302
+ 'MaxKeys' => 1
303
+ ]);
304
+ if (!$result['Contents'] && !$result['CommonPrefixes']) {
305
+ throw new \Exception("File or directory not found: $path");
306
+ }
307
+ return $this->formatUrlStat($path);
308
+ }
309
+ }, $flags);
310
+ }
311
+
312
+ private function statDirectory($parts, $path, $flags)
313
+ {
314
+ // Stat "directories": buckets, or "s3://"
315
+ if (!$parts['Bucket'] ||
316
+ $this->getClient()->doesBucketExist($parts['Bucket'])
317
+ ) {
318
+ return $this->formatUrlStat($path);
319
+ }
320
+
321
+ return $this->triggerError("File or directory not found: $path", $flags);
322
+ }
323
+
324
+ /**
325
+ * Support for mkdir().
326
+ *
327
+ * @param string $path Directory which should be created.
328
+ * @param int $mode Permissions. 700-range permissions map to
329
+ * ACL_PUBLIC. 600-range permissions map to
330
+ * ACL_AUTH_READ. All other permissions map to
331
+ * ACL_PRIVATE. Expects octal form.
332
+ * @param int $options A bitwise mask of values, such as
333
+ * STREAM_MKDIR_RECURSIVE.
334
+ *
335
+ * @return bool
336
+ * @link http://www.php.net/manual/en/streamwrapper.mkdir.php
337
+ */
338
+ public function mkdir($path, $mode, $options)
339
+ {
340
+ $this->initProtocol($path);
341
+ $params = $this->withPath($path);
342
+ $this->clearCacheKey($path);
343
+ if (!$params['Bucket']) {
344
+ return false;
345
+ }
346
+
347
+ if (!isset($params['ACL'])) {
348
+ $params['ACL'] = $this->determineAcl($mode);
349
+ }
350
+
351
+ return empty($params['Key'])
352
+ ? $this->createBucket($path, $params)
353
+ : $this->createSubfolder($path, $params);
354
+ }
355
+
356
+ public function rmdir($path, $options)
357
+ {
358
+ $this->initProtocol($path);
359
+ $this->clearCacheKey($path);
360
+ $params = $this->withPath($path);
361
+ $client = $this->getClient();
362
+
363
+ if (!$params['Bucket']) {
364
+ return $this->triggerError('You must specify a bucket');
365
+ }
366
+
367
+ return $this->boolCall(function () use ($params, $path, $client) {
368
+ if (!$params['Key']) {
369
+ $client->deleteBucket(['Bucket' => $params['Bucket']]);
370
+ return true;
371
+ }
372
+ return $this->deleteSubfolder($path, $params);
373
+ });
374
+ }
375
+
376
+ /**
377
+ * Support for opendir().
378
+ *
379
+ * The opendir() method of the Amazon S3 stream wrapper supports a stream
380
+ * context option of "listFilter". listFilter must be a callable that
381
+ * accepts an associative array of object data and returns true if the
382
+ * object should be yielded when iterating the keys in a bucket.
383
+ *
384
+ * @param string $path The path to the directory
385
+ * (e.g. "s3://dir[</prefix>]")
386
+ * @param string $options Unused option variable
387
+ *
388
+ * @return bool true on success
389
+ * @see http://www.php.net/manual/en/function.opendir.php
390
+ */
391
+ public function dir_opendir($path, $options)
392
+ {
393
+ $this->initProtocol($path);
394
+ $this->openedPath = $path;
395
+ $params = $this->withPath($path);
396
+ $delimiter = $this->getOption('delimiter');
397
+ /** @var callable $filterFn */
398
+ $filterFn = $this->getOption('listFilter');
399
+ $op = ['Bucket' => $params['Bucket']];
400
+ $this->openedBucket = $params['Bucket'];
401
+
402
+ if ($delimiter === null) {
403
+ $delimiter = '/';
404
+ }
405
+
406
+ if ($delimiter) {
407
+ $op['Delimiter'] = $delimiter;
408
+ }
409
+
410
+ if ($params['Key']) {
411
+ $params['Key'] = rtrim($params['Key'], $delimiter) . $delimiter;
412
+ $op['Prefix'] = $params['Key'];
413
+ }
414
+
415
+ $this->openedBucketPrefix = $params['Key'];
416
+
417
+ // Filter our "/" keys added by the console as directories, and ensure
418
+ // that if a filter function is provided that it passes the filter.
419
+ $this->objectIterator = \Aws\flatmap(
420
+ $this->getClient()->getPaginator('ListObjects', $op),
421
+ function (Result $result) use ($filterFn) {
422
+ $contentsAndPrefixes = $result->search('[Contents[], CommonPrefixes[]][]');
423
+ // Filter out dir place holder keys and use the filter fn.
424
+ return array_filter(
425
+ $contentsAndPrefixes,
426
+ function ($key) use ($filterFn) {
427
+ return (!$filterFn || call_user_func($filterFn, $key))
428
+ && (!isset($key['Key']) || substr($key['Key'], -1, 1) !== '/');
429
+ }
430
+ );
431
+ }
432
+ );
433
+
434
+ return true;
435
+ }
436
+
437
+ /**
438
+ * Close the directory listing handles
439
+ *
440
+ * @return bool true on success
441
+ */
442
+ public function dir_closedir()
443
+ {
444
+ $this->objectIterator = null;
445
+ gc_collect_cycles();
446
+
447
+ return true;
448
+ }
449
+
450
+ /**
451
+ * This method is called in response to rewinddir()
452
+ *
453
+ * @return boolean true on success
454
+ */
455
+ public function dir_rewinddir()
456
+ {
457
+ return $this->boolCall(function() {
458
+ $this->objectIterator = null;
459
+ $this->dir_opendir($this->openedPath, null);
460
+ return true;
461
+ });
462
+ }
463
+
464
+ /**
465
+ * This method is called in response to readdir()
466
+ *
467
+ * @return string Should return a string representing the next filename, or
468
+ * false if there is no next file.
469
+ * @link http://www.php.net/manual/en/function.readdir.php
470
+ */
471
+ public function dir_readdir()
472
+ {
473
+ // Skip empty result keys
474
+ if (!$this->objectIterator->valid()) {
475
+ return false;
476
+ }
477
+
478
+ // First we need to create a cache key. This key is the full path to
479
+ // then object in s3: protocol://bucket/key.
480
+ // Next we need to create a result value. The result value is the
481
+ // current value of the iterator without the opened bucket prefix to
482
+ // emulate how readdir() works on directories.
483
+ // The cache key and result value will depend on if this is a prefix
484
+ // or a key.
485
+ $cur = $this->objectIterator->current();
486
+ if (isset($cur['Prefix'])) {
487
+ // Include "directories". Be sure to strip a trailing "/"
488
+ // on prefixes.
489
+ $result = rtrim($cur['Prefix'], '/');
490
+ $key = $this->formatKey($result);
491
+ $stat = $this->formatUrlStat($key);
492
+ } else {
493
+ $result = $cur['Key'];
494
+ $key = $this->formatKey($cur['Key']);
495
+ $stat = $this->formatUrlStat($cur);
496
+ }
497
+
498
+ // Cache the object data for quick url_stat lookups used with
499
+ // RecursiveDirectoryIterator.
500
+ $this->getCacheStorage()->set($key, $stat);
501
+ $this->objectIterator->next();
502
+
503
+ // Remove the prefix from the result to emulate other stream wrappers.
504
+ return $this->openedBucketPrefix
505
+ ? substr($result, strlen($this->openedBucketPrefix))
506
+ : $result;
507
+ }
508
+
509
+ private function formatKey($key)
510
+ {
511
+ $protocol = explode('://', $this->openedPath)[0];
512
+ return "{$protocol}://{$this->openedBucket}/{$key}";
513
+ }
514
+
515
+ /**
516
+ * Called in response to rename() to rename a file or directory. Currently
517
+ * only supports renaming objects.
518
+ *
519
+ * @param string $path_from the path to the file to rename
520
+ * @param string $path_to the new path to the file
521
+ *
522
+ * @return bool true if file was successfully renamed
523
+ * @link http://www.php.net/manual/en/function.rename.php
524
+ */
525
+ public function rename($path_from, $path_to)
526
+ {
527
+ // PHP will not allow rename across wrapper types, so we can safely
528
+ // assume $path_from and $path_to have the same protocol
529
+ $this->initProtocol($path_from);
530
+ $partsFrom = $this->withPath($path_from);
531
+ $partsTo = $this->withPath($path_to);
532
+ $this->clearCacheKey($path_from);
533
+ $this->clearCacheKey($path_to);
534
+
535
+ if (!$partsFrom['Key'] || !$partsTo['Key']) {
536
+ return $this->triggerError('The Amazon S3 stream wrapper only '
537
+ . 'supports copying objects');
538
+ }
539
+
540
+ return $this->boolCall(function () use ($partsFrom, $partsTo) {
541
+ $options = $this->getOptions(true);
542
+ // Copy the object and allow overriding default parameters if
543
+ // desired, but by default copy metadata
544
+ $this->getClient()->copy(
545
+ $partsFrom['Bucket'],
546
+ $partsFrom['Key'],
547
+ $partsTo['Bucket'],
548
+ $partsTo['Key'],
549
+ isset($options['acl']) ? $options['acl'] : 'private',
550
+ $options
551
+ );
552
+ // Delete the original object
553
+ $this->getClient()->deleteObject([
554
+ 'Bucket' => $partsFrom['Bucket'],
555
+ 'Key' => $partsFrom['Key']
556
+ ] + $options);
557
+ return true;
558
+ });
559
+ }
560
+
561
+ public function stream_cast($cast_as)
562
+ {
563
+ return false;
564
+ }
565
+
566
+ /**
567
+ * Validates the provided stream arguments for fopen and returns an array
568
+ * of errors.
569
+ */
570
+ private function validate($path, $mode)
571
+ {
572
+ $errors = [];
573
+
574
+ if (!$this->getOption('Key')) {
575
+ $errors[] = 'Cannot open a bucket. You must specify a path in the '
576
+ . 'form of s3://bucket/key';
577
+ }
578
+
579
+ if (!in_array($mode, ['r', 'w', 'a', 'x'])) {
580
+ $errors[] = "Mode not supported: {$mode}. "
581
+ . "Use one 'r', 'w', 'a', or 'x'.";
582
+ }
583
+
584
+ // When using mode "x" validate if the file exists before attempting
585
+ // to read
586
+ if ($mode == 'x' &&
587
+ $this->getClient()->doesObjectExist(
588
+ $this->getOption('Bucket'),
589
+ $this->getOption('Key'),
590
+ $this->getOptions(true)
591
+ )
592
+ ) {
593
+ $errors[] = "{$path} already exists on Amazon S3";
594
+ }
595
+
596
+ return $errors;
597
+ }
598
+
599
+ /**
600
+ * Get the stream context options available to the current stream
601
+ *
602
+ * @param bool $removeContextData Set to true to remove contextual kvp's
603
+ * like 'client' from the result.
604
+ *
605
+ * @return array
606
+ */
607
+ private function getOptions($removeContextData = false)
608
+ {
609
+ // Context is not set when doing things like stat
610
+ if ($this->context === null) {
611
+ $options = [];
612
+ } else {
613
+ $options = stream_context_get_options($this->context);
614
+ $options = isset($options[$this->protocol])
615
+ ? $options[$this->protocol]
616
+ : [];
617
+ }
618
+
619
+ $default = stream_context_get_options(stream_context_get_default());
620
+ $default = isset($default[$this->protocol])
621
+ ? $default[$this->protocol]
622
+ : [];
623
+ $result = $this->params + $options + $default;
624
+
625
+ if ($removeContextData) {
626
+ unset($result['client'], $result['seekable'], $result['cache']);
627
+ }
628
+
629
+ return $result;
630
+ }
631
+
632
+ /**
633
+ * Get a specific stream context option
634
+ *
635
+ * @param string $name Name of the option to retrieve
636
+ *
637
+ * @return mixed|null
638
+ */
639
+ private function getOption($name)
640
+ {
641
+ $options = $this->getOptions();
642
+
643
+ return isset($options[$name]) ? $options[$name] : null;
644
+ }
645
+
646
+ /**
647
+ * Gets the client from the stream context
648
+ *
649
+ * @return S3ClientInterface
650
+ * @throws \RuntimeException if no client has been configured
651
+ */
652
+ private function getClient()
653
+ {
654
+ if (!$client = $this->getOption('client')) {
655
+ throw new \RuntimeException('No client in stream context');
656
+ }
657
+
658
+ return $client;
659
+ }
660
+
661
+ private function getBucketKey($path)
662
+ {
663
+ // Remove the protocol
664
+ $parts = explode('://', $path);
665
+ // Get the bucket, key
666
+ $parts = explode('/', $parts[1], 2);
667
+
668
+ return [
669
+ 'Bucket' => $parts[0],
670
+ 'Key' => isset($parts[1]) ? $parts[1] : null
671
+ ];
672
+ }
673
+
674
+ /**
675
+ * Get the bucket and key from the passed path (e.g. s3://bucket/key)
676
+ *
677
+ * @param string $path Path passed to the stream wrapper
678
+ *
679
+ * @return array Hash of 'Bucket', 'Key', and custom params from the context
680
+ */
681
+ private function withPath($path)
682
+ {
683
+ $params = $this->getOptions(true);
684
+
685
+ return $this->getBucketKey($path) + $params;
686
+ }
687
+
688
+ private function openReadStream()
689
+ {
690
+ $client = $this->getClient();
691
+ $command = $client->getCommand('GetObject', $this->getOptions(true));
692
+ $command['@http']['stream'] = true;
693
+ $result = $client->execute($command);
694
+ $this->size = $result['ContentLength'];
695
+ $this->body = $result['Body'];
696
+
697
+ // Wrap the body in a caching entity body if seeking is allowed
698
+ if ($this->getOption('seekable') && !$this->body->isSeekable()) {
699
+ $this->body = new CachingStream($this->body);
700
+ }
701
+
702
+ return true;
703
+ }
704
+
705
+ private function openWriteStream()
706
+ {
707
+ $this->body = new Stream(fopen('php://temp', 'r+'));
708
+ return true;
709
+ }
710
+
711
+ private function openAppendStream()
712
+ {
713
+ try {
714
+ // Get the body of the object and seek to the end of the stream
715
+ $client = $this->getClient();
716
+ $this->body = $client->getObject($this->getOptions(true))['Body'];
717
+ $this->body->seek(0, SEEK_END);
718
+ return true;
719
+ } catch (S3Exception $e) {
720
+ // The object does not exist, so use a simple write stream
721
+ return $this->openWriteStream();
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Trigger one or more errors
727
+ *
728
+ * @param string|array $errors Errors to trigger
729
+ * @param mixed $flags If set to STREAM_URL_STAT_QUIET, then no
730
+ * error or exception occurs
731
+ *
732
+ * @return bool Returns false
733
+ * @throws \RuntimeException if throw_errors is true
734
+ */
735
+ private function triggerError($errors, $flags = null)
736
+ {
737
+ // This is triggered with things like file_exists()
738
+ if ($flags & STREAM_URL_STAT_QUIET) {
739
+ return $flags & STREAM_URL_STAT_LINK
740
+ // This is triggered for things like is_link()
741
+ ? $this->formatUrlStat(false)
742
+ : false;
743
+ }
744
+
745
+ // This is triggered when doing things like lstat() or stat()
746
+ trigger_error(implode("\n", (array) $errors), E_USER_WARNING);
747
+
748
+ return false;
749
+ }
750
+
751
+ /**
752
+ * Prepare a url_stat result array
753
+ *
754
+ * @param string|array $result Data to add
755
+ *
756
+ * @return array Returns the modified url_stat result
757
+ */
758
+ private function formatUrlStat($result = null)
759
+ {
760
+ $stat = $this->getStatTemplate();
761
+ switch (gettype($result)) {
762
+ case 'NULL':
763
+ case 'string':
764
+ // Directory with 0777 access - see "man 2 stat".
765
+ $stat['mode'] = $stat[2] = 0040777;
766
+ break;
767
+ case 'array':
768
+ // Regular file with 0777 access - see "man 2 stat".
769
+ $stat['mode'] = $stat[2] = 0100777;
770
+ // Pluck the content-length if available.
771
+ if (isset($result['ContentLength'])) {
772
+ $stat['size'] = $stat[7] = $result['ContentLength'];
773
+ } elseif (isset($result['Size'])) {
774
+ $stat['size'] = $stat[7] = $result['Size'];
775
+ }
776
+ if (isset($result['LastModified'])) {
777
+ // ListObjects or HeadObject result
778
+ $stat['mtime'] = $stat[9] = $stat['ctime'] = $stat[10]
779
+ = strtotime($result['LastModified']);
780
+ }
781
+ }
782
+
783
+ return $stat;
784
+ }
785
+
786
+ /**
787
+ * Creates a bucket for the given parameters.
788
+ *
789
+ * @param string $path Stream wrapper path
790
+ * @param array $params A result of StreamWrapper::withPath()
791
+ *
792
+ * @return bool Returns true on success or false on failure
793
+ */
794
+ private function createBucket($path, array $params)
795
+ {
796
+ if ($this->getClient()->doesBucketExist($params['Bucket'])) {
797
+ return $this->triggerError("Bucket already exists: {$path}");
798
+ }
799
+
800
+ return $this->boolCall(function () use ($params, $path) {
801
+ $this->getClient()->createBucket($params);
802
+ $this->clearCacheKey($path);
803
+ return true;
804
+ });
805
+ }
806
+
807
+ /**
808
+ * Creates a pseudo-folder by creating an empty "/" suffixed key
809
+ *
810
+ * @param string $path Stream wrapper path
811
+ * @param array $params A result of StreamWrapper::withPath()
812
+ *
813
+ * @return bool
814
+ */
815
+ private function createSubfolder($path, array $params)
816
+ {
817
+ // Ensure the path ends in "/" and the body is empty.
818
+ $params['Key'] = rtrim($params['Key'], '/') . '/';
819
+ $params['Body'] = '';
820
+
821
+ // Fail if this pseudo directory key already exists
822
+ if ($this->getClient()->doesObjectExist(
823
+ $params['Bucket'],
824
+ $params['Key'])
825
+ ) {
826
+ return $this->triggerError("Subfolder already exists: {$path}");
827
+ }
828
+
829
+ return $this->boolCall(function () use ($params, $path) {
830
+ $this->getClient()->putObject($params);
831
+ $this->clearCacheKey($path);
832
+ return true;
833
+ });
834
+ }
835
+
836
+ /**
837
+ * Deletes a nested subfolder if it is empty.
838
+ *
839
+ * @param string $path Path that is being deleted (e.g., 's3://a/b/c')
840
+ * @param array $params A result of StreamWrapper::withPath()
841
+ *
842
+ * @return bool
843
+ */
844
+ private function deleteSubfolder($path, $params)
845
+ {
846
+ // Use a key that adds a trailing slash if needed.
847
+ $prefix = rtrim($params['Key'], '/') . '/';
848
+ $result = $this->getClient()->listObjects([
849
+ 'Bucket' => $params['Bucket'],
850
+ 'Prefix' => $prefix,
851
+ 'MaxKeys' => 1
852
+ ]);
853
+
854
+ // Check if the bucket contains keys other than the placeholder
855
+ if ($contents = $result['Contents']) {
856
+ return (count($contents) > 1 || $contents[0]['Key'] != $prefix)
857
+ ? $this->triggerError('Subfolder is not empty')
858
+ : $this->unlink(rtrim($path, '/') . '/');
859
+ }
860
+
861
+ return $result['CommonPrefixes']
862
+ ? $this->triggerError('Subfolder contains nested folders')
863
+ : true;
864
+ }
865
+
866
+ /**
867
+ * Determine the most appropriate ACL based on a file mode.
868
+ *
869
+ * @param int $mode File mode
870
+ *
871
+ * @return string
872
+ */
873
+ private function determineAcl($mode)
874
+ {
875
+ switch (substr(decoct($mode), 0, 1)) {
876
+ case '7': return 'public-read';
877
+ case '6': return 'authenticated-read';
878
+ default: return 'private';
879
+ }
880
+ }
881
+
882
+ /**
883
+ * Gets a URL stat template with default values
884
+ *
885
+ * @return array
886
+ */
887
+ private function getStatTemplate()
888
+ {
889
+ return [
890
+ 0 => 0, 'dev' => 0,
891
+ 1 => 0, 'ino' => 0,
892
+ 2 => 0, 'mode' => 0,
893
+ 3 => 0, 'nlink' => 0,
894
+ 4 => 0, 'uid' => 0,
895
+ 5 => 0, 'gid' => 0,
896
+ 6 => -1, 'rdev' => -1,
897
+ 7 => 0, 'size' => 0,
898
+ 8 => 0, 'atime' => 0,
899
+ 9 => 0, 'mtime' => 0,
900
+ 10 => 0, 'ctime' => 0,
901
+ 11 => -1, 'blksize' => -1,
902
+ 12 => -1, 'blocks' => -1,
903
+ ];
904
+ }
905
+
906
+ /**
907
+ * Invokes a callable and triggers an error if an exception occurs while
908
+ * calling the function.
909
+ *
910
+ * @param callable $fn
911
+ * @param int $flags
912
+ *
913
+ * @return bool
914
+ */
915
+ private function boolCall(callable $fn, $flags = null)
916
+ {
917
+ try {
918
+ return $fn();
919
+ } catch (\Exception $e) {
920
+ return $this->triggerError($e->getMessage(), $flags);
921
+ }
922
+ }
923
+
924
+ /**
925
+ * @return LruArrayCache
926
+ */
927
+ private function getCacheStorage()
928
+ {
929
+ if (!$this->cache) {
930
+ $this->cache = $this->getOption('cache') ?: new LruArrayCache();
931
+ }
932
+
933
+ return $this->cache;
934
+ }
935
+
936
+ /**
937
+ * Clears a specific stat cache value from the stat cache and LRU cache.
938
+ *
939
+ * @param string $key S3 path (s3://bucket/key).
940
+ */
941
+ private function clearCacheKey($key)
942
+ {
943
+ clearstatcache(true, $key);
944
+ $this->getCacheStorage()->remove($key);
945
+ }
946
+
947
+ /**
948
+ * Returns the size of the opened object body.
949
+ *
950
+ * @return int|null
951
+ */
952
+ private function getSize()
953
+ {
954
+ $size = $this->body->getSize();
955
+
956
+ return $size !== null ? $size : $this->size;
957
+ }
958
+ }
lib/Aws/Aws/S3/Transfer.php ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\S3;
3
+
4
+ use Aws;
5
+ use Aws\CommandInterface;
6
+ use Aws\Exception\AwsException;
7
+ use GuzzleHttp\Promise;
8
+ use GuzzleHttp\Promise\PromisorInterface;
9
+ use Iterator;
10
+
11
+ /**
12
+ * Transfers files from the local filesystem to S3 or from S3 to the local
13
+ * filesystem.
14
+ *
15
+ * This class does not support copying from the local filesystem to somewhere
16
+ * else on the local filesystem or from one S3 bucket to another.
17
+ */
18
+ class Transfer implements PromisorInterface
19
+ {
20
+ private $client;
21
+ private $promise;
22
+ private $source;
23
+ private $sourceMetadata;
24
+ private $destination;
25
+ private $concurrency;
26
+ private $mupThreshold;
27
+ private $before;
28
+ private $s3Args = [];
29
+
30
+ /**
31
+ * When providing the $source argument, you may provide a string referencing
32
+ * the path to a directory on disk to upload, an s3 scheme URI that contains
33
+ * the bucket and key (e.g., "s3://bucket/key"), or an \Iterator object
34
+ * that yields strings containing filenames that are the path to a file on
35
+ * disk or an s3 scheme URI. The "/key" portion of an s3 URI is optional.
36
+ *
37
+ * When providing an iterator for the $source argument, you must also
38
+ * provide a 'base_dir' key value pair in the $options argument.
39
+ *
40
+ * The $dest argument can be the path to a directory on disk or an s3
41
+ * scheme URI (e.g., "s3://bucket/key").
42
+ *
43
+ * The options array can contain the following key value pairs:
44
+ *
45
+ * - base_dir: (string) Base directory of the source, if $source is an
46
+ * iterator. If the $source option is not an array, then this option is
47
+ * ignored.
48
+ * - before: (callable) A callback to invoke before each transfer. The
49
+ * callback accepts a single argument: Aws\CommandInterface $command.
50
+ * The provided command will be either a GetObject, PutObject,
51
+ * InitiateMultipartUpload, or UploadPart command.
52
+ * - mup_threshold: (int) Size in bytes in which a multipart upload should
53
+ * be used instead of PutObject. Defaults to 20971520 (20 MB).
54
+ * - concurrency: (int, default=5) Number of files to upload concurrently.
55
+ * The ideal concurrency value will vary based on the number of files
56
+ * being uploaded and the average size of each file. Generally speaking,
57
+ * smaller files benefit from a higher concurrency while larger files
58
+ * will not.
59
+ * - debug: (bool) Set to true to print out debug information for
60
+ * transfers. Set to an fopen() resource to write to a specific stream
61
+ * rather than writing to STDOUT.
62
+ *
63
+ * @param S3ClientInterface $client Client used for transfers.
64
+ * @param string|Iterator $source Where the files are transferred from.
65
+ * @param string $dest Where the files are transferred to.
66
+ * @param array $options Hash of options.
67
+ */
68
+ public function __construct(
69
+ S3ClientInterface $client,
70
+ $source,
71
+ $dest,
72
+ array $options = []
73
+ ) {
74
+ $this->client = $client;
75
+
76
+ // Prepare the destination.
77
+ $this->destination = $this->prepareTarget($dest);
78
+ if ($this->destination['scheme'] === 's3') {
79
+ $this->s3Args = $this->getS3Args($this->destination['path']);
80
+ }
81
+
82
+ // Prepare the source.
83
+ if (is_string($source)) {
84
+ $this->sourceMetadata = $this->prepareTarget($source);
85
+ $this->source = $source;
86
+ } elseif ($source instanceof Iterator) {
87
+ if (empty($options['base_dir'])) {
88
+ throw new \InvalidArgumentException('You must provide the source'
89
+ . ' argument as a string or provide the "base_dir" option.');
90
+ }
91
+
92
+ $this->sourceMetadata = $this->prepareTarget($options['base_dir']);
93
+ $this->source = $source;
94
+ } else {
95
+ throw new \InvalidArgumentException('source must be the path to a '
96
+ . 'directory or an iterator that yields file names.');
97
+ }
98
+
99
+ // Validate schemes.
100
+ if ($this->sourceMetadata['scheme'] === $this->destination['scheme']) {
101
+ throw new \InvalidArgumentException("You cannot copy from"
102
+ . " {$this->sourceMetadata['scheme']} to"
103
+ . " {$this->destination['scheme']}."
104
+ );
105
+ }
106
+
107
+ // Handle multipart-related options.
108
+ $this->concurrency = isset($options['concurrency'])
109
+ ? $options['concurrency']
110
+ : MultipartUploader::DEFAULT_CONCURRENCY;
111
+ $this->mupThreshold = isset($options['mup_threshold'])
112
+ ? $options['mup_threshold']
113
+ : 16777216;
114
+ if ($this->mupThreshold < MultipartUploader::PART_MIN_SIZE) {
115
+ throw new \InvalidArgumentException('mup_threshold must be >= 5MB');
116
+ }
117
+
118
+ // Handle "before" callback option.
119
+ if (isset($options['before'])) {
120
+ $this->before = $options['before'];
121
+ if (!is_callable($this->before)) {
122
+ throw new \InvalidArgumentException('before must be a callable.');
123
+ }
124
+ }
125
+
126
+ // Handle "debug" option.
127
+ if (isset($options['debug'])) {
128
+ if ($options['debug'] === true) {
129
+ $options['debug'] = fopen('php://output', 'w');
130
+ }
131
+ $this->addDebugToBefore($options['debug']);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Transfers the files.
137
+ */
138
+ public function promise()
139
+ {
140
+ // If the promise has been created, just return it.
141
+ if (!$this->promise) {
142
+ // Create an upload/download promise for the transfer.
143
+ $this->promise = $this->sourceMetadata['scheme'] === 'file'
144
+ ? $this->createUploadPromise()
145
+ : $this->createDownloadPromise();
146
+ }
147
+
148
+ return $this->promise;
149
+ }
150
+
151
+ /**
152
+ * Transfers the files synchronously.
153
+ */
154
+ public function transfer()
155
+ {
156
+ $this->promise()->wait();
157
+ }
158
+
159
+ private function prepareTarget($targetPath)
160
+ {
161
+ $target = [
162
+ 'path' => $this->normalizePath($targetPath),
163
+ 'scheme' => $this->determineScheme($targetPath),
164
+ ];
165
+
166
+ if ($target['scheme'] !== 's3' && $target['scheme'] !== 'file') {
167
+ throw new \InvalidArgumentException('Scheme must be "s3" or "file".');
168
+ }
169
+
170
+ return $target;
171
+ }
172
+
173
+ /**
174
+ * Creates an array that contains Bucket and Key by parsing the filename.
175
+ *
176
+ * @param string $path Path to parse.
177
+ *
178
+ * @return array
179
+ */
180
+ private function getS3Args($path)
181
+ {
182
+ $parts = explode('/', str_replace('s3://', '', $path), 2);
183
+ $args = ['Bucket' => $parts[0]];
184
+ if (isset($parts[1])) {
185
+ $args['Key'] = $parts[1];
186
+ }
187
+
188
+ return $args;
189
+ }
190
+
191
+ /**
192
+ * Parses the scheme from a filename.
193
+ *
194
+ * @param string $path Path to parse.
195
+ *
196
+ * @return string
197
+ */
198
+ private function determineScheme($path)
199
+ {
200
+ return !strpos($path, '://') ? 'file' : explode('://', $path)[0];
201
+ }
202
+
203
+ /**
204
+ * Normalize a path so that it has UNIX-style directory separators and no trailing /
205
+ *
206
+ * @param string $path
207
+ *
208
+ * @return string
209
+ */
210
+ private function normalizePath($path)
211
+ {
212
+ return rtrim(str_replace('\\', '/', $path), '/');
213
+ }
214
+
215
+ private function resolveUri($uri)
216
+ {
217
+ $resolved = [];
218
+ $sections = explode('/', $uri);
219
+ foreach ($sections as $section) {
220
+ if ($section === '.' || $section === '') {
221
+ continue;
222
+ }
223
+ if ($section === '..') {
224
+ array_pop($resolved);
225
+ } else {
226
+ $resolved []= $section;
227
+ }
228
+ }
229
+
230
+ return ($uri[0] === '/' ? '/' : '')
231
+ . implode('/', $resolved);
232
+ }
233
+
234
+ private function createDownloadPromise()
235
+ {
236
+ $parts = $this->getS3Args($this->sourceMetadata['path']);
237
+ $prefix = "s3://{$parts['Bucket']}/"
238
+ . (isset($parts['Key']) ? $parts['Key'] . '/' : '');
239
+
240
+
241
+ $commands = [];
242
+ foreach ($this->getDownloadsIterator() as $object) {
243
+ // Prepare the sink.
244
+ $objectKey = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $object);
245
+
246
+ $resolveSink = $this->destination['path'] . '/';
247
+ if (isset($parts['Key']) && strpos($objectKey, $parts['Key']) !== 0) {
248
+ $resolveSink .= $parts['Key'] . '/';
249
+ }
250
+ $resolveSink .= $objectKey;
251
+ $sink = $this->destination['path'] . '/' . $objectKey;
252
+
253
+ $command = $this->client->getCommand(
254
+ 'GetObject',
255
+ $this->getS3Args($object) + ['@http' => ['sink' => $sink]]
256
+ );
257
+
258
+ if (strpos(
259
+ $this->resolveUri($resolveSink),
260
+ $this->destination['path']
261
+ ) !== 0
262
+ ) {
263
+ throw new AwsException(
264
+ 'Cannot download key ' . $objectKey
265
+ . ', its relative path resolves outside the'
266
+ . ' parent directory', $command);
267
+ }
268
+
269
+ // Create the directory if needed.
270
+ $dir = dirname($sink);
271
+ if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
272
+ throw new \RuntimeException("Could not create dir: {$dir}");
273
+ }
274
+
275
+ // Create the command.
276
+ $commands []= $command;
277
+ }
278
+
279
+ // Create a GetObject command pool and return the promise.
280
+ return (new Aws\CommandPool($this->client, $commands, [
281
+ 'concurrency' => $this->concurrency,
282
+ 'before' => $this->before,
283
+ 'rejected' => function ($reason, $idx, Promise\PromiseInterface $p) {
284
+ $p->reject($reason);
285
+ }
286
+ ]))->promise();
287
+ }
288
+
289
+ private function createUploadPromise()
290
+ {
291
+ // Map each file into a promise that performs the actual transfer.
292
+ $files = \Aws\map($this->getUploadsIterator(), function ($file) {
293
+ return (filesize($file) >= $this->mupThreshold)
294
+ ? $this->uploadMultipart($file)
295
+ : $this->upload($file);
296
+ });
297
+
298
+ // Create an EachPromise, that will concurrently handle the upload
299
+ // operations' yielded promises from the iterator.
300
+ return Promise\each_limit_all($files, $this->concurrency);
301
+ }
302
+
303
+ /** @return Iterator */
304
+ private function getUploadsIterator()
305
+ {
306
+ if (is_string($this->source)) {
307
+ return Aws\filter(
308
+ Aws\recursive_dir_iterator($this->sourceMetadata['path']),
309
+ function ($file) { return !is_dir($file); }
310
+ );
311
+ }
312
+
313
+ return $this->source;
314
+ }
315
+
316
+ /** @return Iterator */
317
+ private function getDownloadsIterator()
318
+ {
319
+ if (is_string($this->source)) {
320
+ $listArgs = $this->getS3Args($this->sourceMetadata['path']);
321
+ if (isset($listArgs['Key'])) {
322
+ $listArgs['Prefix'] = $listArgs['Key'] . '/';
323
+ unset($listArgs['Key']);
324
+ }
325
+
326
+ $files = $this->client
327
+ ->getPaginator('ListObjects', $listArgs)
328
+ ->search('Contents[].Key');
329
+ $files = Aws\map($files, function ($key) use ($listArgs) {
330
+ return "s3://{$listArgs['Bucket']}/$key";
331
+ });
332
+ return Aws\filter($files, function ($key) {
333
+ return substr($key, -1, 1) !== '/';
334
+ });
335
+ }
336
+
337
+ return $this->source;
338
+ }
339
+
340
+ private function upload($filename)
341
+ {
342
+ $args = $this->s3Args;
343
+ $args['SourceFile'] = $filename;
344
+ $args['Key'] = $this->createS3Key($filename);
345
+ $command = $this->client->getCommand('PutObject', $args);
346
+ $this->before and call_user_func($this->before, $command);
347
+
348
+ return $this->client->executeAsync($command);
349
+ }
350
+
351
+ private function uploadMultipart($filename)
352
+ {
353
+ $args = $this->s3Args;
354
+ $args['Key'] = $this->createS3Key($filename);
355
+
356
+ return (new MultipartUploader($this->client, $filename, [
357
+ 'bucket' => $args['Bucket'],
358
+ 'key' => $args['Key'],
359
+ 'before_initiate' => $this->before,
360
+ 'before_upload' => $this->before,
361
+ 'before_complete' => $this->before,
362
+ 'concurrency' => $this->concurrency,
363
+ ]))->promise();
364
+ }
365
+
366
+ private function createS3Key($filename)
367
+ {
368
+ $filename = $this->normalizePath($filename);
369
+ $relative_file_path = ltrim(
370
+ preg_replace('#^' . preg_quote($this->sourceMetadata['path']) . '#', '', $filename),
371
+ '/\\'
372
+ );
373
+
374
+ if (isset($this->s3Args['Key'])) {
375
+ return rtrim($this->s3Args['Key'], '/').'/'.$relative_file_path;
376
+ }
377
+
378
+ return $relative_file_path;
379
+ }
380
+
381
+ private function addDebugToBefore($debug)
382
+ {
383
+ $before = $this->before;
384
+ $sourcePath = $this->sourceMetadata['path'];
385
+ $s3Args = $this->s3Args;
386
+
387
+ $this->before = static function (
388
+ CommandInterface $command
389
+ ) use ($before, $debug, $sourcePath, $s3Args) {
390
+ // Call the composed before function.
391
+ $before and $before($command);
392
+
393
+ // Determine the source and dest values based on operation.
394
+ switch ($operation = $command->getName()) {
395
+ case 'GetObject':
396
+ $source = "s3://{$command['Bucket']}/{$command['Key']}";
397
+ $dest = $command['@http']['sink'];
398
+ break;
399
+ case 'PutObject':
400
+ $source = $command['SourceFile'];
401
+ $dest = "s3://{$command['Bucket']}/{$command['Key']}";
402
+ break;
403
+ case 'UploadPart':
404
+ $part = $command['PartNumber'];
405
+ case 'CreateMultipartUpload':
406
+ case 'CompleteMultipartUpload':
407
+ $sourceKey = $command['Key'];
408
+ if (isset($s3Args['Key']) && strpos($sourceKey, $s3Args['Key']) === 0) {
409
+ $sourceKey = substr($sourceKey, strlen($s3Args['Key']) + 1);
410
+ }
411
+ $source = "{$sourcePath}/{$sourceKey}";
412
+ $dest = "s3://{$command['Bucket']}/{$command['Key']}";
413
+ break;
414
+ default:
415
+ throw new \UnexpectedValueException(
416
+ "Transfer encountered an unexpected operation: {$operation}."
417
+ );
418
+ }
419
+
420
+ // Print the debugging message.
421
+ $context = sprintf('%s -> %s (%s)', $source, $dest, $operation);
422
+ if (isset($part)) {
423
+ $context .= " : Part={$part}";
424
+ }
425
+ fwrite($debug, "Transferring {$context}\n");
426
+ };
427
+ }
428
+ }
lib/Aws/Aws/Sdk.php ADDED
@@ -0,0 +1,466 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ /**
5
+ * Builds AWS clients based on configuration settings.
6
+ *
7
+ * @method \Aws\ACMPCA\ACMPCAClient createACMPCA(array $args = [])
8
+ * @method \Aws\MultiRegionClient createMultiRegionACMPCA(array $args = [])
9
+ * @method \Aws\Acm\AcmClient createAcm(array $args = [])
10
+ * @method \Aws\MultiRegionClient createMultiRegionAcm(array $args = [])
11
+ * @method \Aws\AlexaForBusiness\AlexaForBusinessClient createAlexaForBusiness(array $args = [])
12
+ * @method \Aws\MultiRegionClient createMultiRegionAlexaForBusiness(array $args = [])
13
+ * @method \Aws\Amplify\AmplifyClient createAmplify(array $args = [])
14
+ * @method \Aws\MultiRegionClient createMultiRegionAmplify(array $args = [])
15
+ * @method \Aws\ApiGateway\ApiGatewayClient createApiGateway(array $args = [])
16
+ * @method \Aws\MultiRegionClient createMultiRegionApiGateway(array $args = [])
17
+ * @method \Aws\ApiGatewayManagementApi\ApiGatewayManagementApiClient createApiGatewayManagementApi(array $args = [])
18
+ * @method \Aws\MultiRegionClient createMultiRegionApiGatewayManagementApi(array $args = [])
19
+ * @method \Aws\ApiGatewayV2\ApiGatewayV2Client createApiGatewayV2(array $args = [])
20
+ * @method \Aws\MultiRegionClient createMultiRegionApiGatewayV2(array $args = [])
21
+ * @method \Aws\AppMesh\AppMeshClient createAppMesh(array $args = [])
22
+ * @method \Aws\MultiRegionClient createMultiRegionAppMesh(array $args = [])
23
+ * @method \Aws\AppSync\AppSyncClient createAppSync(array $args = [])
24
+ * @method \Aws\MultiRegionClient createMultiRegionAppSync(array $args = [])
25
+ * @method \Aws\ApplicationAutoScaling\ApplicationAutoScalingClient createApplicationAutoScaling(array $args = [])
26
+ * @method \Aws\MultiRegionClient createMultiRegionApplicationAutoScaling(array $args = [])
27
+ * @method \Aws\ApplicationDiscoveryService\ApplicationDiscoveryServiceClient createApplicationDiscoveryService(array $args = [])
28
+ * @method \Aws\MultiRegionClient createMultiRegionApplicationDiscoveryService(array $args = [])
29
+ * @method \Aws\Appstream\AppstreamClient createAppstream(array $args = [])
30
+ * @method \Aws\MultiRegionClient createMultiRegionAppstream(array $args = [])
31
+ * @method \Aws\Athena\AthenaClient createAthena(array $args = [])
32
+ * @method \Aws\MultiRegionClient createMultiRegionAthena(array $args = [])
33
+ * @method \Aws\AutoScaling\AutoScalingClient createAutoScaling(array $args = [])
34
+ * @method \Aws\MultiRegionClient createMultiRegionAutoScaling(array $args = [])
35
+ * @method \Aws\AutoScalingPlans\AutoScalingPlansClient createAutoScalingPlans(array $args = [])
36
+ * @method \Aws\MultiRegionClient createMultiRegionAutoScalingPlans(array $args = [])
37
+ * @method \Aws\Backup\BackupClient createBackup(array $args = [])
38
+ * @method \Aws\MultiRegionClient createMultiRegionBackup(array $args = [])
39
+ * @method \Aws\Batch\BatchClient createBatch(array $args = [])
40
+ * @method \Aws\MultiRegionClient createMultiRegionBatch(array $args = [])
41
+ * @method \Aws\Budgets\BudgetsClient createBudgets(array $args = [])
42
+ * @method \Aws\MultiRegionClient createMultiRegionBudgets(array $args = [])
43
+ * @method \Aws\Chime\ChimeClient createChime(array $args = [])
44
+ * @method \Aws\MultiRegionClient createMultiRegionChime(array $args = [])
45
+ * @method \Aws\Cloud9\Cloud9Client createCloud9(array $args = [])
46
+ * @method \Aws\MultiRegionClient createMultiRegionCloud9(array $args = [])
47
+ * @method \Aws\CloudDirectory\CloudDirectoryClient createCloudDirectory(array $args = [])
48
+ * @method \Aws\MultiRegionClient createMultiRegionCloudDirectory(array $args = [])
49
+ * @method \Aws\CloudFormation\CloudFormationClient createCloudFormation(array $args = [])
50
+ * @method \Aws\MultiRegionClient createMultiRegionCloudFormation(array $args = [])
51
+ * @method \Aws\CloudFront\CloudFrontClient createCloudFront(array $args = [])
52
+ * @method \Aws\MultiRegionClient createMultiRegionCloudFront(array $args = [])
53
+ * @method \Aws\CloudHSMV2\CloudHSMV2Client createCloudHSMV2(array $args = [])
54
+ * @method \Aws\MultiRegionClient createMultiRegionCloudHSMV2(array $args = [])
55
+ * @method \Aws\CloudHsm\CloudHsmClient createCloudHsm(array $args = [])
56
+ * @method \Aws\MultiRegionClient createMultiRegionCloudHsm(array $args = [])
57
+ * @method \Aws\CloudSearch\CloudSearchClient createCloudSearch(array $args = [])
58
+ * @method \Aws\MultiRegionClient createMultiRegionCloudSearch(array $args = [])
59
+ * @method \Aws\CloudSearchDomain\CloudSearchDomainClient createCloudSearchDomain(array $args = [])
60
+ * @method \Aws\MultiRegionClient createMultiRegionCloudSearchDomain(array $args = [])
61
+ * @method \Aws\CloudTrail\CloudTrailClient createCloudTrail(array $args = [])
62
+ * @method \Aws\MultiRegionClient createMultiRegionCloudTrail(array $args = [])
63
+ * @method \Aws\CloudWatch\CloudWatchClient createCloudWatch(array $args = [])
64
+ * @method \Aws\MultiRegionClient createMultiRegionCloudWatch(array $args = [])
65
+ * @method \Aws\CloudWatchEvents\CloudWatchEventsClient createCloudWatchEvents(array $args = [])
66
+ * @method \Aws\MultiRegionClient createMultiRegionCloudWatchEvents(array $args = [])
67
+ * @method \Aws\CloudWatchLogs\CloudWatchLogsClient createCloudWatchLogs(array $args = [])
68
+ * @method \Aws\MultiRegionClient createMultiRegionCloudWatchLogs(array $args = [])
69
+ * @method \Aws\CodeBuild\CodeBuildClient createCodeBuild(array $args = [])
70
+ * @method \Aws\MultiRegionClient createMultiRegionCodeBuild(array $args = [])
71
+ * @method \Aws\CodeCommit\CodeCommitClient createCodeCommit(array $args = [])
72
+ * @method \Aws\MultiRegionClient createMultiRegionCodeCommit(array $args = [])
73
+ * @method \Aws\CodeDeploy\CodeDeployClient createCodeDeploy(array $args = [])
74
+ * @method \Aws\MultiRegionClient createMultiRegionCodeDeploy(array $args = [])
75
+ * @method \Aws\CodePipeline\CodePipelineClient createCodePipeline(array $args = [])
76
+ * @method \Aws\MultiRegionClient createMultiRegionCodePipeline(array $args = [])
77
+ * @method \Aws\CodeStar\CodeStarClient createCodeStar(array $args = [])
78
+ * @method \Aws\MultiRegionClient createMultiRegionCodeStar(array $args = [])
79
+ * @method \Aws\CognitoIdentity\CognitoIdentityClient createCognitoIdentity(array $args = [])
80
+ * @method \Aws\MultiRegionClient createMultiRegionCognitoIdentity(array $args = [])
81
+ * @method \Aws\CognitoIdentityProvider\CognitoIdentityProviderClient createCognitoIdentityProvider(array $args = [])
82
+ * @method \Aws\MultiRegionClient createMultiRegionCognitoIdentityProvider(array $args = [])
83
+ * @method \Aws\CognitoSync\CognitoSyncClient createCognitoSync(array $args = [])
84
+ * @method \Aws\MultiRegionClient createMultiRegionCognitoSync(array $args = [])
85
+ * @method \Aws\Comprehend\ComprehendClient createComprehend(array $args = [])
86
+ * @method \Aws\MultiRegionClient createMultiRegionComprehend(array $args = [])
87
+ * @method \Aws\ComprehendMedical\ComprehendMedicalClient createComprehendMedical(array $args = [])
88
+ * @method \Aws\MultiRegionClient createMultiRegionComprehendMedical(array $args = [])
89
+ * @method \Aws\ConfigService\ConfigServiceClient createConfigService(array $args = [])
90
+ * @method \Aws\MultiRegionClient createMultiRegionConfigService(array $args = [])
91
+ * @method \Aws\Connect\ConnectClient createConnect(array $args = [])
92
+ * @method \Aws\MultiRegionClient createMultiRegionConnect(array $args = [])
93
+ * @method \Aws\CostExplorer\CostExplorerClient createCostExplorer(array $args = [])
94
+ * @method \Aws\MultiRegionClient createMultiRegionCostExplorer(array $args = [])
95
+ * @method \Aws\CostandUsageReportService\CostandUsageReportServiceClient createCostandUsageReportService(array $args = [])
96
+ * @method \Aws\MultiRegionClient createMultiRegionCostandUsageReportService(array $args = [])
97
+ * @method \Aws\DAX\DAXClient createDAX(array $args = [])
98
+ * @method \Aws\MultiRegionClient createMultiRegionDAX(array $args = [])
99
+ * @method \Aws\DLM\DLMClient createDLM(array $args = [])
100
+ * @method \Aws\MultiRegionClient createMultiRegionDLM(array $args = [])
101
+ * @method \Aws\DataPipeline\DataPipelineClient createDataPipeline(array $args = [])
102
+ * @method \Aws\MultiRegionClient createMultiRegionDataPipeline(array $args = [])
103
+ * @method \Aws\DataSync\DataSyncClient createDataSync(array $args = [])
104
+ * @method \Aws\MultiRegionClient createMultiRegionDataSync(array $args = [])
105
+ * @method \Aws\DatabaseMigrationService\DatabaseMigrationServiceClient createDatabaseMigrationService(array $args = [])
106
+ * @method \Aws\MultiRegionClient createMultiRegionDatabaseMigrationService(array $args = [])
107
+ * @method \Aws\DeviceFarm\DeviceFarmClient createDeviceFarm(array $args = [])
108
+ * @method \Aws\MultiRegionClient createMultiRegionDeviceFarm(array $args = [])
109
+ * @method \Aws\DirectConnect\DirectConnectClient createDirectConnect(array $args = [])
110
+ * @method \Aws\MultiRegionClient createMultiRegionDirectConnect(array $args = [])
111
+ * @method \Aws\DirectoryService\DirectoryServiceClient createDirectoryService(array $args = [])
112
+ * @method \Aws\MultiRegionClient createMultiRegionDirectoryService(array $args = [])
113
+ * @method \Aws\DocDB\DocDBClient createDocDB(array $args = [])
114
+ * @method \Aws\MultiRegionClient createMultiRegionDocDB(array $args = [])
115
+ * @method \Aws\DynamoDb\DynamoDbClient createDynamoDb(array $args = [])
116
+ * @method \Aws\MultiRegionClient createMultiRegionDynamoDb(array $args = [])
117
+ * @method \Aws\DynamoDbStreams\DynamoDbStreamsClient createDynamoDbStreams(array $args = [])
118
+ * @method \Aws\MultiRegionClient createMultiRegionDynamoDbStreams(array $args = [])
119
+ * @method \Aws\EKS\EKSClient createEKS(array $args = [])
120
+ * @method \Aws\MultiRegionClient createMultiRegionEKS(array $args = [])
121
+ * @method \Aws\Ec2\Ec2Client createEc2(array $args = [])
122
+ * @method \Aws\MultiRegionClient createMultiRegionEc2(array $args = [])
123
+ * @method \Aws\Ecr\EcrClient createEcr(array $args = [])
124
+ * @method \Aws\MultiRegionClient createMultiRegionEcr(array $args = [])
125
+ * @method \Aws\Ecs\EcsClient createEcs(array $args = [])
126
+ * @method \Aws\MultiRegionClient createMultiRegionEcs(array $args = [])
127
+ * @method \Aws\Efs\EfsClient createEfs(array $args = [])
128
+ * @method \Aws\MultiRegionClient createMultiRegionEfs(array $args = [])
129
+ * @method \Aws\ElastiCache\ElastiCacheClient createElastiCache(array $args = [])
130
+ * @method \Aws\MultiRegionClient createMultiRegionElastiCache(array $args = [])
131
+ * @method \Aws\ElasticBeanstalk\ElasticBeanstalkClient createElasticBeanstalk(array $args = [])
132
+ * @method \Aws\MultiRegionClient createMultiRegionElasticBeanstalk(array $args = [])
133
+ * @method \Aws\ElasticLoadBalancing\ElasticLoadBalancingClient createElasticLoadBalancing(array $args = [])
134
+ * @method \Aws\MultiRegionClient createMultiRegionElasticLoadBalancing(array $args = [])
135
+ * @method \Aws\ElasticLoadBalancingV2\ElasticLoadBalancingV2Client createElasticLoadBalancingV2(array $args = [])
136
+ * @method \Aws\MultiRegionClient createMultiRegionElasticLoadBalancingV2(array $args = [])
137
+ * @method \Aws\ElasticTranscoder\ElasticTranscoderClient createElasticTranscoder(array $args = [])
138
+ * @method \Aws\MultiRegionClient createMultiRegionElasticTranscoder(array $args = [])
139
+ * @method \Aws\ElasticsearchService\ElasticsearchServiceClient createElasticsearchService(array $args = [])
140
+ * @method \Aws\MultiRegionClient createMultiRegionElasticsearchService(array $args = [])
141
+ * @method \Aws\Emr\EmrClient createEmr(array $args = [])
142
+ * @method \Aws\MultiRegionClient createMultiRegionEmr(array $args = [])
143
+ * @method \Aws\FMS\FMSClient createFMS(array $args = [])
144
+ * @method \Aws\MultiRegionClient createMultiRegionFMS(array $args = [])
145
+ * @method \Aws\FSx\FSxClient createFSx(array $args = [])
146
+ * @method \Aws\MultiRegionClient createMultiRegionFSx(array $args = [])
147
+ * @method \Aws\Firehose\FirehoseClient createFirehose(array $args = [])
148
+ * @method \Aws\MultiRegionClient createMultiRegionFirehose(array $args = [])
149
+ * @method \Aws\GameLift\GameLiftClient createGameLift(array $args = [])
150
+ * @method \Aws\MultiRegionClient createMultiRegionGameLift(array $args = [])
151
+ * @method \Aws\Glacier\GlacierClient createGlacier(array $args = [])
152
+ * @method \Aws\MultiRegionClient createMultiRegionGlacier(array $args = [])
153
+ * @method \Aws\GlobalAccelerator\GlobalAcceleratorClient createGlobalAccelerator(array $args = [])
154
+ * @method \Aws\MultiRegionClient createMultiRegionGlobalAccelerator(array $args = [])
155
+ * @method \Aws\Glue\GlueClient createGlue(array $args = [])
156
+ * @method \Aws\MultiRegionClient createMultiRegionGlue(array $args = [])
157
+ * @method \Aws\Greengrass\GreengrassClient createGreengrass(array $args = [])
158
+ * @method \Aws\MultiRegionClient createMultiRegionGreengrass(array $args = [])
159
+ * @method \Aws\GuardDuty\GuardDutyClient createGuardDuty(array $args = [])
160
+ * @method \Aws\MultiRegionClient createMultiRegionGuardDuty(array $args = [])
161
+ * @method \Aws\Health\HealthClient createHealth(array $args = [])
162
+ * @method \Aws\MultiRegionClient createMultiRegionHealth(array $args = [])
163
+ * @method \Aws\Iam\IamClient createIam(array $args = [])
164
+ * @method \Aws\MultiRegionClient createMultiRegionIam(array $args = [])
165
+ * @method \Aws\ImportExport\ImportExportClient createImportExport(array $args = [])
166
+ * @method \Aws\MultiRegionClient createMultiRegionImportExport(array $args = [])
167
+ * @method \Aws\Inspector\InspectorClient createInspector(array $args = [])
168
+ * @method \Aws\MultiRegionClient createMultiRegionInspector(array $args = [])
169
+ * @method \Aws\IoT1ClickDevicesService\IoT1ClickDevicesServiceClient createIoT1ClickDevicesService(array $args = [])
170
+ * @method \Aws\MultiRegionClient createMultiRegionIoT1ClickDevicesService(array $args = [])
171
+ * @method \Aws\IoT1ClickProjects\IoT1ClickProjectsClient createIoT1ClickProjects(array $args = [])
172
+ * @method \Aws\MultiRegionClient createMultiRegionIoT1ClickProjects(array $args = [])
173
+ * @method \Aws\IoTAnalytics\IoTAnalyticsClient createIoTAnalytics(array $args = [])
174
+ * @method \Aws\MultiRegionClient createMultiRegionIoTAnalytics(array $args = [])
175
+ * @method \Aws\IoTJobsDataPlane\IoTJobsDataPlaneClient createIoTJobsDataPlane(array $args = [])
176
+ * @method \Aws\MultiRegionClient createMultiRegionIoTJobsDataPlane(array $args = [])
177
+ * @method \Aws\Iot\IotClient createIot(array $args = [])
178
+ * @method \Aws\MultiRegionClient createMultiRegionIot(array $args = [])
179
+ * @method \Aws\IotDataPlane\IotDataPlaneClient createIotDataPlane(array $args = [])
180
+ * @method \Aws\MultiRegionClient createMultiRegionIotDataPlane(array $args = [])
181
+ * @method \Aws\Kafka\KafkaClient createKafka(array $args = [])
182
+ * @method \Aws\MultiRegionClient createMultiRegionKafka(array $args = [])
183
+ * @method \Aws\Kinesis\KinesisClient createKinesis(array $args = [])
184
+ * @method \Aws\MultiRegionClient createMultiRegionKinesis(array $args = [])
185
+ * @method \Aws\KinesisAnalytics\KinesisAnalyticsClient createKinesisAnalytics(array $args = [])
186
+ * @method \Aws\MultiRegionClient createMultiRegionKinesisAnalytics(array $args = [])
187
+ * @method \Aws\KinesisAnalyticsV2\KinesisAnalyticsV2Client createKinesisAnalyticsV2(array $args = [])
188
+ * @method \Aws\MultiRegionClient createMultiRegionKinesisAnalyticsV2(array $args = [])
189
+ * @method \Aws\KinesisVideo\KinesisVideoClient createKinesisVideo(array $args = [])
190
+ * @method \Aws\MultiRegionClient createMultiRegionKinesisVideo(array $args = [])
191
+ * @method \Aws\KinesisVideoArchivedMedia\KinesisVideoArchivedMediaClient createKinesisVideoArchivedMedia(array $args = [])
192
+ * @method \Aws\MultiRegionClient createMultiRegionKinesisVideoArchivedMedia(array $args = [])
193
+ * @method \Aws\KinesisVideoMedia\KinesisVideoMediaClient createKinesisVideoMedia(array $args = [])
194
+ * @method \Aws\MultiRegionClient createMultiRegionKinesisVideoMedia(array $args = [])
195
+ * @method \Aws\Kms\KmsClient createKms(array $args = [])
196
+ * @method \Aws\MultiRegionClient createMultiRegionKms(array $args = [])
197
+ * @method \Aws\Lambda\LambdaClient createLambda(array $args = [])
198
+ * @method \Aws\MultiRegionClient createMultiRegionLambda(array $args = [])
199
+ * @method \Aws\LexModelBuildingService\LexModelBuildingServiceClient createLexModelBuildingService(array $args = [])
200
+ * @method \Aws\MultiRegionClient createMultiRegionLexModelBuildingService(array $args = [])
201
+ * @method \Aws\LexRuntimeService\LexRuntimeServiceClient createLexRuntimeService(array $args = [])
202
+ * @method \Aws\MultiRegionClient createMultiRegionLexRuntimeService(array $args = [])
203
+ * @method \Aws\LicenseManager\LicenseManagerClient createLicenseManager(array $args = [])
204
+ * @method \Aws\MultiRegionClient createMultiRegionLicenseManager(array $args = [])
205
+ * @method \Aws\Lightsail\LightsailClient createLightsail(array $args = [])
206
+ * @method \Aws\MultiRegionClient createMultiRegionLightsail(array $args = [])
207
+ * @method \Aws\MQ\MQClient createMQ(array $args = [])
208
+ * @method \Aws\MultiRegionClient createMultiRegionMQ(array $args = [])
209
+ * @method \Aws\MTurk\MTurkClient createMTurk(array $args = [])
210
+ * @method \Aws\MultiRegionClient createMultiRegionMTurk(array $args = [])
211
+ * @method \Aws\MachineLearning\MachineLearningClient createMachineLearning(array $args = [])
212
+ * @method \Aws\MultiRegionClient createMultiRegionMachineLearning(array $args = [])
213
+ * @method \Aws\Macie\MacieClient createMacie(array $args = [])
214
+ * @method \Aws\MultiRegionClient createMultiRegionMacie(array $args = [])
215
+ * @method \Aws\ManagedBlockchain\ManagedBlockchainClient createManagedBlockchain(array $args = [])
216
+ * @method \Aws\MultiRegionClient createMultiRegionManagedBlockchain(array $args = [])
217
+ * @method \Aws\MarketplaceCommerceAnalytics\MarketplaceCommerceAnalyticsClient createMarketplaceCommerceAnalytics(array $args = [])
218
+ * @method \Aws\MultiRegionClient createMultiRegionMarketplaceCommerceAnalytics(array $args = [])
219
+ * @method \Aws\MarketplaceEntitlementService\MarketplaceEntitlementServiceClient createMarketplaceEntitlementService(array $args = [])
220
+ * @method \Aws\MultiRegionClient createMultiRegionMarketplaceEntitlementService(array $args = [])
221
+ * @method \Aws\MarketplaceMetering\MarketplaceMeteringClient createMarketplaceMetering(array $args = [])
222
+ * @method \Aws\MultiRegionClient createMultiRegionMarketplaceMetering(array $args = [])
223
+ * @method \Aws\MediaConnect\MediaConnectClient createMediaConnect(array $args = [])
224
+ * @method \Aws\MultiRegionClient createMultiRegionMediaConnect(array $args = [])
225
+ * @method \Aws\MediaConvert\MediaConvertClient createMediaConvert(array $args = [])
226
+ * @method \Aws\MultiRegionClient createMultiRegionMediaConvert(array $args = [])
227
+ * @method \Aws\MediaLive\MediaLiveClient createMediaLive(array $args = [])
228
+ * @method \Aws\MultiRegionClient createMultiRegionMediaLive(array $args = [])
229
+ * @method \Aws\MediaPackage\MediaPackageClient createMediaPackage(array $args = [])
230
+ * @method \Aws\MultiRegionClient createMultiRegionMediaPackage(array $args = [])
231
+ * @method \Aws\MediaStore\MediaStoreClient createMediaStore(array $args = [])
232
+ * @method \Aws\MultiRegionClient createMultiRegionMediaStore(array $args = [])
233
+ * @method \Aws\MediaStoreData\MediaStoreDataClient createMediaStoreData(array $args = [])
234
+ * @method \Aws\MultiRegionClient createMultiRegionMediaStoreData(array $args = [])
235
+ * @method \Aws\MediaTailor\MediaTailorClient createMediaTailor(array $args = [])
236
+ * @method \Aws\MultiRegionClient createMultiRegionMediaTailor(array $args = [])
237
+ * @method \Aws\MigrationHub\MigrationHubClient createMigrationHub(array $args = [])
238
+ * @method \Aws\MultiRegionClient createMultiRegionMigrationHub(array $args = [])
239
+ * @method \Aws\Mobile\MobileClient createMobile(array $args = [])
240
+ * @method \Aws\MultiRegionClient createMultiRegionMobile(array $args = [])
241
+ * @method \Aws\Neptune\NeptuneClient createNeptune(array $args = [])
242
+ * @method \Aws\MultiRegionClient createMultiRegionNeptune(array $args = [])
243
+ * @method \Aws\OpsWorks\OpsWorksClient createOpsWorks(array $args = [])
244
+ * @method \Aws\MultiRegionClient createMultiRegionOpsWorks(array $args = [])
245
+ * @method \Aws\OpsWorksCM\OpsWorksCMClient createOpsWorksCM(array $args = [])
246
+ * @method \Aws\MultiRegionClient createMultiRegionOpsWorksCM(array $args = [])
247
+ * @method \Aws\Organizations\OrganizationsClient createOrganizations(array $args = [])
248
+ * @method \Aws\MultiRegionClient createMultiRegionOrganizations(array $args = [])
249
+ * @method \Aws\PI\PIClient createPI(array $args = [])
250
+ * @method \Aws\MultiRegionClient createMultiRegionPI(array $args = [])
251
+ * @method \Aws\Pinpoint\PinpointClient createPinpoint(array $args = [])
252
+ * @method \Aws\MultiRegionClient createMultiRegionPinpoint(array $args = [])
253
+ * @method \Aws\PinpointEmail\PinpointEmailClient createPinpointEmail(array $args = [])
254
+ * @method \Aws\MultiRegionClient createMultiRegionPinpointEmail(array $args = [])
255
+ * @method \Aws\PinpointSMSVoice\PinpointSMSVoiceClient createPinpointSMSVoice(array $args = [])
256
+ * @method \Aws\MultiRegionClient createMultiRegionPinpointSMSVoice(array $args = [])
257
+ * @method \Aws\Polly\PollyClient createPolly(array $args = [])
258
+ * @method \Aws\MultiRegionClient createMultiRegionPolly(array $args = [])
259
+ * @method \Aws\Pricing\PricingClient createPricing(array $args = [])
260
+ * @method \Aws\MultiRegionClient createMultiRegionPricing(array $args = [])
261
+ * @method \Aws\QuickSight\QuickSightClient createQuickSight(array $args = [])
262
+ * @method \Aws\MultiRegionClient createMultiRegionQuickSight(array $args = [])
263
+ * @method \Aws\RAM\RAMClient createRAM(array $args = [])
264
+ * @method \Aws\MultiRegionClient createMultiRegionRAM(array $args = [])
265
+ * @method \Aws\RDSDataService\RDSDataServiceClient createRDSDataService(array $args = [])
266
+ * @method \Aws\MultiRegionClient createMultiRegionRDSDataService(array $args = [])
267
+ * @method \Aws\Rds\RdsClient createRds(array $args = [])
268
+ * @method \Aws\MultiRegionClient createMultiRegionRds(array $args = [])
269
+ * @method \Aws\Redshift\RedshiftClient createRedshift(array $args = [])
270
+ * @method \Aws\MultiRegionClient createMultiRegionRedshift(array $args = [])
271
+ * @method \Aws\Rekognition\RekognitionClient createRekognition(array $args = [])
272
+ * @method \Aws\MultiRegionClient createMultiRegionRekognition(array $args = [])
273
+ * @method \Aws\ResourceGroups\ResourceGroupsClient createResourceGroups(array $args = [])
274
+ * @method \Aws\MultiRegionClient createMultiRegionResourceGroups(array $args = [])
275
+ * @method \Aws\ResourceGroupsTaggingAPI\ResourceGroupsTaggingAPIClient createResourceGroupsTaggingAPI(array $args = [])
276
+ * @method \Aws\MultiRegionClient createMultiRegionResourceGroupsTaggingAPI(array $args = [])
277
+ * @method \Aws\RoboMaker\RoboMakerClient createRoboMaker(array $args = [])
278
+ * @method \Aws\MultiRegionClient createMultiRegionRoboMaker(array $args = [])
279
+ * @method \Aws\Route53\Route53Client createRoute53(array $args = [])
280
+ * @method \Aws\MultiRegionClient createMultiRegionRoute53(array $args = [])
281
+ * @method \Aws\Route53Domains\Route53DomainsClient createRoute53Domains(array $args = [])
282
+ * @method \Aws\MultiRegionClient createMultiRegionRoute53Domains(array $args = [])
283
+ * @method \Aws\Route53Resolver\Route53ResolverClient createRoute53Resolver(array $args = [])
284
+ * @method \Aws\MultiRegionClient createMultiRegionRoute53Resolver(array $args = [])
285
+ * @method \Aws\S3\S3Client createS3(array $args = [])
286
+ * @method \Aws\S3\S3MultiRegionClient createMultiRegionS3(array $args = [])
287
+ * @method \Aws\S3Control\S3ControlClient createS3Control(array $args = [])
288
+ * @method \Aws\MultiRegionClient createMultiRegionS3Control(array $args = [])
289
+ * @method \Aws\SageMaker\SageMakerClient createSageMaker(array $args = [])
290
+ * @method \Aws\MultiRegionClient createMultiRegionSageMaker(array $args = [])
291
+ * @method \Aws\SageMakerRuntime\SageMakerRuntimeClient createSageMakerRuntime(array $args = [])
292
+ * @method \Aws\MultiRegionClient createMultiRegionSageMakerRuntime(array $args = [])
293
+ * @method \Aws\SecretsManager\SecretsManagerClient createSecretsManager(array $args = [])
294
+ * @method \Aws\MultiRegionClient createMultiRegionSecretsManager(array $args = [])
295
+ * @method \Aws\SecurityHub\SecurityHubClient createSecurityHub(array $args = [])
296
+ * @method \Aws\MultiRegionClient createMultiRegionSecurityHub(array $args = [])
297
+ * @method \Aws\ServerlessApplicationRepository\ServerlessApplicationRepositoryClient createServerlessApplicationRepository(array $args = [])
298
+ * @method \Aws\MultiRegionClient createMultiRegionServerlessApplicationRepository(array $args = [])
299
+ * @method \Aws\ServiceCatalog\ServiceCatalogClient createServiceCatalog(array $args = [])
300
+ * @method \Aws\MultiRegionClient createMultiRegionServiceCatalog(array $args = [])
301
+ * @method \Aws\ServiceDiscovery\ServiceDiscoveryClient createServiceDiscovery(array $args = [])
302
+ * @method \Aws\MultiRegionClient createMultiRegionServiceDiscovery(array $args = [])
303
+ * @method \Aws\Ses\SesClient createSes(array $args = [])
304
+ * @method \Aws\MultiRegionClient createMultiRegionSes(array $args = [])
305
+ * @method \Aws\Sfn\SfnClient createSfn(array $args = [])
306
+ * @method \Aws\MultiRegionClient createMultiRegionSfn(array $args = [])
307
+ * @method \Aws\Shield\ShieldClient createShield(array $args = [])
308
+ * @method \Aws\MultiRegionClient createMultiRegionShield(array $args = [])
309
+ * @method \Aws\Sms\SmsClient createSms(array $args = [])
310
+ * @method \Aws\MultiRegionClient createMultiRegionSms(array $args = [])
311
+ * @method \Aws\SnowBall\SnowBallClient createSnowBall(array $args = [])
312
+ * @method \Aws\MultiRegionClient createMultiRegionSnowBall(array $args = [])
313
+ * @method \Aws\Sns\SnsClient createSns(array $args = [])
314
+ * @method \Aws\MultiRegionClient createMultiRegionSns(array $args = [])
315
+ * @method \Aws\Sqs\SqsClient createSqs(array $args = [])
316
+ * @method \Aws\MultiRegionClient createMultiRegionSqs(array $args = [])
317
+ * @method \Aws\Ssm\SsmClient createSsm(array $args = [])
318
+ * @method \Aws\MultiRegionClient createMultiRegionSsm(array $args = [])
319
+ * @method \Aws\StorageGateway\StorageGatewayClient createStorageGateway(array $args = [])
320
+ * @method \Aws\MultiRegionClient createMultiRegionStorageGateway(array $args = [])
321
+ * @method \Aws\Sts\StsClient createSts(array $args = [])
322
+ * @method \Aws\MultiRegionClient createMultiRegionSts(array $args = [])
323
+ * @method \Aws\Support\SupportClient createSupport(array $args = [])
324
+ * @method \Aws\MultiRegionClient createMultiRegionSupport(array $args = [])
325
+ * @method \Aws\Swf\SwfClient createSwf(array $args = [])
326
+ * @method \Aws\MultiRegionClient createMultiRegionSwf(array $args = [])
327
+ * @method \Aws\Textract\TextractClient createTextract(array $args = [])
328
+ * @method \Aws\MultiRegionClient createMultiRegionTextract(array $args = [])
329
+ * @method \Aws\TranscribeService\TranscribeServiceClient createTranscribeService(array $args = [])
330
+ * @method \Aws\MultiRegionClient createMultiRegionTranscribeService(array $args = [])
331
+ * @method \Aws\Transfer\TransferClient createTransfer(array $args = [])
332
+ * @method \Aws\MultiRegionClient createMultiRegionTransfer(array $args = [])
333
+ * @method \Aws\Translate\TranslateClient createTranslate(array $args = [])
334
+ * @method \Aws\MultiRegionClient createMultiRegionTranslate(array $args = [])
335
+ * @method \Aws\Waf\WafClient createWaf(array $args = [])
336
+ * @method \Aws\MultiRegionClient createMultiRegionWaf(array $args = [])
337
+ * @method \Aws\WafRegional\WafRegionalClient createWafRegional(array $args = [])
338
+ * @method \Aws\MultiRegionClient createMultiRegionWafRegional(array $args = [])
339
+ * @method \Aws\WorkDocs\WorkDocsClient createWorkDocs(array $args = [])
340
+ * @method \Aws\MultiRegionClient createMultiRegionWorkDocs(array $args = [])
341
+ * @method \Aws\WorkLink\WorkLinkClient createWorkLink(array $args = [])
342
+ * @method \Aws\MultiRegionClient createMultiRegionWorkLink(array $args = [])
343
+ * @method \Aws\WorkMail\WorkMailClient createWorkMail(array $args = [])
344
+ * @method \Aws\MultiRegionClient createMultiRegionWorkMail(array $args = [])
345
+ * @method \Aws\WorkSpaces\WorkSpacesClient createWorkSpaces(array $args = [])
346
+ * @method \Aws\MultiRegionClient createMultiRegionWorkSpaces(array $args = [])
347
+ * @method \Aws\XRay\XRayClient createXRay(array $args = [])
348
+ * @method \Aws\MultiRegionClient createMultiRegionXRay(array $args = [])
349
+ * @method \Aws\signer\signerClient createsigner(array $args = [])
350
+ * @method \Aws\MultiRegionClient createMultiRegionsigner(array $args = [])
351
+ */
352
+ class Sdk
353
+ {
354
+ const VERSION = '3.93.9';
355
+
356
+ /** @var array Arguments for creating clients */
357
+ private $args;
358
+
359
+ /**
360
+ * Constructs a new SDK object with an associative array of default
361
+ * client settings.
362
+ *
363
+ * @param array $args
364
+ *
365
+ * @throws \InvalidArgumentException
366
+ * @see Aws\AwsClient::__construct for a list of available options.
367
+ */
368
+ public function __construct(array $args = [])
369
+ {
370
+ $this->args = $args;
371
+
372
+ if (!isset($args['handler']) && !isset($args['http_handler'])) {
373
+ $this->args['http_handler'] = default_http_handler();
374
+ }
375
+ }
376
+
377
+ public function __call($name, array $args)
378
+ {
379
+ $args = isset($args[0]) ? $args[0] : [];
380
+ if (strpos($name, 'createMultiRegion') === 0) {
381
+ return $this->createMultiRegionClient(substr($name, 17), $args);
382
+ }
383
+
384
+ if (strpos($name, 'create') === 0) {
385
+ return $this->createClient(substr($name, 6), $args);
386
+ }
387
+
388
+ throw new \BadMethodCallException("Unknown method: {$name}.");
389
+ }
390
+
391
+ /**
392
+ * Get a client by name using an array of constructor options.
393
+ *
394
+ * @param string $name Service name or namespace (e.g., DynamoDb, s3).
395
+ * @param array $args Arguments to configure the client.
396
+ *
397
+ * @return AwsClientInterface
398
+ * @throws \InvalidArgumentException if any required options are missing or
399
+ * the service is not supported.
400
+ * @see Aws\AwsClient::__construct for a list of available options for args.
401
+ */
402
+ public function createClient($name, array $args = [])
403
+ {
404
+ // Get information about the service from the manifest file.
405
+ $service = manifest($name);
406
+ $namespace = $service['namespace'];
407
+
408
+ // Instantiate the client class.
409
+ $client = "Aws\\{$namespace}\\{$namespace}Client";
410
+ return new $client($this->mergeArgs($namespace, $service, $args));
411
+ }
412
+
413
+ public function createMultiRegionClient($name, array $args = [])
414
+ {
415
+ // Get information about the service from the manifest file.
416
+ $service = manifest($name);
417
+ $namespace = $service['namespace'];
418
+
419
+ $klass = "Aws\\{$namespace}\\{$namespace}MultiRegionClient";
420
+ $klass = class_exists($klass) ? $klass : 'Aws\\MultiRegionClient';
421
+
422
+ return new $klass($this->mergeArgs($namespace, $service, $args));
423
+ }
424
+
425
+ /**
426
+ * Clone existing SDK instance with ability to pass an associative array
427
+ * of extra client settings.
428
+ *
429
+ * @param array $args
430
+ *
431
+ * @return self
432
+ */
433
+ public function copy(array $args = [])
434
+ {
435
+ return new self($args + $this->args);
436
+ }
437
+
438
+ private function mergeArgs($namespace, array $manifest, array $args = [])
439
+ {
440
+ // Merge provided args with stored, service-specific args.
441
+ if (isset($this->args[$namespace])) {
442
+ $args += $this->args[$namespace];
443
+ }
444
+
445
+ // Provide the endpoint prefix in the args.
446
+ if (!isset($args['service'])) {
447
+ $args['service'] = $manifest['endpoint'];
448
+ }
449
+
450
+ return $args + $this->args;
451
+ }
452
+
453
+ /**
454
+ * Determine the endpoint prefix from a client namespace.
455
+ *
456
+ * @param string $name Namespace name
457
+ *
458
+ * @return string
459
+ * @internal
460
+ * @deprecated Use the `\Aws\manifest()` function instead.
461
+ */
462
+ public static function getEndpointPrefix($name)
463
+ {
464
+ return manifest($name)['endpoint'];
465
+ }
466
+ }
lib/Aws/Aws/Signature/AnonymousSignature.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Signature;
3
+
4
+ use Aws\Credentials\CredentialsInterface;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * Provides anonymous client access (does not sign requests).
9
+ */
10
+ class AnonymousSignature implements SignatureInterface
11
+ {
12
+ public function signRequest(
13
+ RequestInterface $request,
14
+ CredentialsInterface $credentials
15
+ ) {
16
+ return $request;
17
+ }
18
+
19
+ public function presign(
20
+ RequestInterface $request,
21
+ CredentialsInterface $credentials,
22
+ $expires
23
+ ) {
24
+ return $request;
25
+ }
26
+ }
lib/Aws/Aws/Signature/S3SignatureV4.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Signature;
3
+
4
+ use Aws\Credentials\CredentialsInterface;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * Amazon S3 signature version 4 support.
9
+ */
10
+ class S3SignatureV4 extends SignatureV4
11
+ {
12
+ /**
13
+ * Always add a x-amz-content-sha-256 for data integrity.
14
+ */
15
+ public function signRequest(
16
+ RequestInterface $request,
17
+ CredentialsInterface $credentials
18
+ ) {
19
+ if (!$request->hasHeader('x-amz-content-sha256')) {
20
+ $request = $request->withHeader(
21
+ 'X-Amz-Content-Sha256',
22
+ $this->getPayload($request)
23
+ );
24
+ }
25
+
26
+ return parent::signRequest($request, $credentials);
27
+ }
28
+
29
+ /**
30
+ * Always add a x-amz-content-sha-256 for data integrity.
31
+ */
32
+ public function presign(
33
+ RequestInterface $request,
34
+ CredentialsInterface $credentials,
35
+ $expires,
36
+ array $options = []
37
+ ) {
38
+ if (!$request->hasHeader('x-amz-content-sha256')) {
39
+ $request = $request->withHeader(
40
+ 'X-Amz-Content-Sha256',
41
+ $this->getPresignedPayload($request)
42
+ );
43
+ }
44
+
45
+ return parent::presign($request, $credentials, $expires, $options);
46
+ }
47
+
48
+ /**
49
+ * Override used to allow pre-signed URLs to be created for an
50
+ * in-determinate request payload.
51
+ */
52
+ protected function getPresignedPayload(RequestInterface $request)
53
+ {
54
+ return SignatureV4::UNSIGNED_PAYLOAD;
55
+ }
56
+
57
+ /**
58
+ * Amazon S3 does not double-encode the path component in the canonical request
59
+ */
60
+ protected function createCanonicalizedPath($path)
61
+ {
62
+ // Only remove one slash in case of keys that have a preceding slash
63
+ if (substr($path, 0, 1) === '/') {
64
+ $path = substr($path, 1);
65
+ }
66
+ return '/' . $path;
67
+ }
68
+ }
lib/Aws/Aws/Signature/SignatureInterface.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Signature;
3
+
4
+ use Aws\Credentials\CredentialsInterface;
5
+ use Psr\Http\Message\RequestInterface;
6
+
7
+ /**
8
+ * Interface used to provide interchangeable strategies for signing requests
9
+ * using the various AWS signature protocols.
10
+ */
11
+ interface SignatureInterface
12
+ {
13
+ /**
14
+ * Signs the specified request with an AWS signing protocol by using the
15
+ * provided AWS account credentials and adding the required headers to the
16
+ * request.
17
+ *
18
+ * @param RequestInterface $request Request to sign
19
+ * @param CredentialsInterface $credentials Signing credentials
20
+ *
21
+ * @return RequestInterface Returns the modified request.
22
+ */
23
+ public function signRequest(
24
+ RequestInterface $request,
25
+ CredentialsInterface $credentials
26
+ );
27
+
28
+ /**
29
+ * Create a pre-signed request.
30
+ *
31
+ * @param RequestInterface $request Request to sign
32
+ * @param CredentialsInterface $credentials Credentials used to sign
33
+ * @param int|string|\DateTime $expires The time at which the URL should
34
+ * expire. This can be a Unix timestamp, a PHP DateTime object, or a
35
+ * string that can be evaluated by strtotime.
36
+ *
37
+ * @return RequestInterface
38
+ */
39
+ public function presign(
40
+ RequestInterface $request,
41
+ CredentialsInterface $credentials,
42
+ $expires
43
+ );
44
+ }
lib/Aws/Aws/Signature/SignatureProvider.php ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Signature;
3
+
4
+ use Aws\Exception\UnresolvedSignatureException;
5
+
6
+ /**
7
+ * Signature providers.
8
+ *
9
+ * A signature provider is a function that accepts a version, service, and
10
+ * region and returns a {@see SignatureInterface} object on success or NULL if
11
+ * no signature can be created from the provided arguments.
12
+ *
13
+ * You can wrap your calls to a signature provider with the
14
+ * {@see SignatureProvider::resolve} function to ensure that a signature object
15
+ * is created. If a signature object is not created, then the resolve()
16
+ * function will throw a {@see Aws\Exception\UnresolvedSignatureException}.
17
+ *
18
+ * use Aws\Signature\SignatureProvider;
19
+ * $provider = SignatureProvider::defaultProvider();
20
+ * // Returns a SignatureInterface or NULL.
21
+ * $signer = $provider('v4', 's3', 'us-west-2');
22
+ * // Returns a SignatureInterface or throws.
23
+ * $signer = SignatureProvider::resolve($provider, 'no', 's3', 'foo');
24
+ *
25
+ * You can compose multiple providers into a single provider using
26
+ * {@see Aws\or_chain}. This function accepts providers as arguments and
27
+ * returns a new function that will invoke each provider until a non-null value
28
+ * is returned.
29
+ *
30
+ * $a = SignatureProvider::defaultProvider();
31
+ * $b = function ($version, $service, $region) {
32
+ * if ($version === 'foo') {
33
+ * return new MyFooSignature();
34
+ * }
35
+ * };
36
+ * $c = \Aws\or_chain($a, $b);
37
+ * $signer = $c('v4', 'abc', '123'); // $a handles this.
38
+ * $signer = $c('foo', 'abc', '123'); // $b handles this.
39
+ * $nullValue = $c('???', 'abc', '123'); // Neither can handle this.
40
+ */
41
+ class SignatureProvider
42
+ {
43
+ private static $s3v4SignedServices = [
44
+ 's3' => true,
45
+ 's3control' => true,
46
+ ];
47
+
48
+ /**
49
+ * Resolves and signature provider and ensures a non-null return value.
50
+ *
51
+ * @param callable $provider Provider function to invoke.
52
+ * @param string $version Signature version.
53
+ * @param string $service Service name.
54
+ * @param string $region Region name.
55
+ *
56
+ * @return SignatureInterface
57
+ * @throws UnresolvedSignatureException
58
+ */
59
+ public static function resolve(callable $provider, $version, $service, $region)
60
+ {
61
+ $result = $provider($version, $service, $region);
62
+ if ($result instanceof SignatureInterface) {
63
+ return $result;
64
+ }
65
+
66
+ throw new UnresolvedSignatureException(
67
+ "Unable to resolve a signature for $version/$service/$region.\n"
68
+ . "Valid signature versions include v4 and anonymous."
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Default SDK signature provider.
74
+ *
75
+ * @return callable
76
+ */
77
+ public static function defaultProvider()
78
+ {
79
+ return self::memoize(self::version());
80
+ }
81
+
82
+ /**
83
+ * Creates a signature provider that caches previously created signature
84
+ * objects. The computed cache key is the concatenation of the version,
85
+ * service, and region.
86
+ *
87
+ * @param callable $provider Signature provider to wrap.
88
+ *
89
+ * @return callable
90
+ */
91
+ public static function memoize(callable $provider)
92
+ {
93
+ $cache = [];
94
+ return function ($version, $service, $region) use (&$cache, $provider) {
95
+ $key = "($version)($service)($region)";
96
+ if (!isset($cache[$key])) {
97
+ $cache[$key] = $provider($version, $service, $region);
98
+ }
99
+ return $cache[$key];
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Creates signature objects from known signature versions.
105
+ *
106
+ * This provider currently recognizes the following signature versions:
107
+ *
108
+ * - v4: Signature version 4.
109
+ * - anonymous: Does not sign requests.
110
+ *
111
+ * @return callable
112
+ */
113
+ public static function version()
114
+ {
115
+ return function ($version, $service, $region) {
116
+ switch ($version) {
117
+ case 's3v4':
118
+ case 'v4':
119
+ return !empty(self::$s3v4SignedServices[$service])
120
+ ? new S3SignatureV4($service, $region)
121
+ : new SignatureV4($service, $region);
122
+ case 'v4-unsigned-body':
123
+ return new SignatureV4($service, $region, ['unsigned-body' => 'true']);
124
+ case 'anonymous':
125
+ return new AnonymousSignature();
126
+ default:
127
+ return null;
128
+ }
129
+ };
130
+ }
131
+ }
lib/Aws/Aws/Signature/SignatureTrait.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Signature;
3
+
4
+ /**
5
+ * Provides signature calculation for SignatureV4.
6
+ */
7
+ trait SignatureTrait
8
+ {
9
+ /** @var array Cache of previously signed values */
10
+ private $cache = [];
11
+
12
+ /** @var int Size of the hash cache */
13
+ private $cacheSize = 0;
14
+
15
+ private function createScope($shortDate, $region, $service)
16
+ {
17
+ return "$shortDate/$region/$service/aws4_request";
18
+ }
19
+
20
+ private function getSigningKey($shortDate, $region, $service, $secretKey)
21
+ {
22
+ $k = $shortDate . '_' . $region . '_' . $service . '_' . $secretKey;
23
+
24
+ if (!isset($this->cache[$k])) {
25
+ // Clear the cache when it reaches 50 entries
26
+ if (++$this->cacheSize > 50) {
27
+ $this->cache = [];
28
+ $this->cacheSize = 0;
29
+ }
30
+
31
+ $dateKey = hash_hmac(
32
+ 'sha256',
33
+ $shortDate,
34
+ "AWS4{$secretKey}",
35
+ true
36
+ );
37
+ $regionKey = hash_hmac('sha256', $region, $dateKey, true);
38
+ $serviceKey = hash_hmac('sha256', $service, $regionKey, true);
39
+ $this->cache[$k] = hash_hmac(
40
+ 'sha256',
41
+ 'aws4_request',
42
+ $serviceKey,
43
+ true
44
+ );
45
+ }
46
+
47
+ return $this->cache[$k];
48
+ }
49
+ }
lib/Aws/Aws/Signature/SignatureV4.php ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Signature;
3
+
4
+ use Aws\Credentials\CredentialsInterface;
5
+ use Aws\Exception\CouldNotCreateChecksumException;
6
+ use GuzzleHttp\Psr7;
7
+ use Psr\Http\Message\RequestInterface;
8
+
9
+ /**
10
+ * Signature Version 4
11
+ * @link http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
12
+ */
13
+ class SignatureV4 implements SignatureInterface
14
+ {
15
+ use SignatureTrait;
16
+ const ISO8601_BASIC = 'Ymd\THis\Z';
17
+ const UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD';
18
+ const AMZ_CONTENT_SHA256_HEADER = 'X-Amz-Content-Sha256';
19
+
20
+ /** @var string */
21
+ private $service;
22
+
23
+ /** @var string */
24
+ private $region;
25
+
26
+ /** @var bool */
27
+ private $unsigned;
28
+
29
+ /**
30
+ * The following headers are not signed because signing these headers
31
+ * would potentially cause a signature mismatch when sending a request
32
+ * through a proxy or if modified at the HTTP client level.
33
+ *
34
+ * @return array
35
+ */
36
+ private function getHeaderBlacklist()
37
+ {
38
+ return [
39
+ 'cache-control' => true,
40
+ 'content-type' => true,
41
+ 'content-length' => true,
42
+ 'expect' => true,
43
+ 'max-forwards' => true,
44
+ 'pragma' => true,
45
+ 'range' => true,
46
+ 'te' => true,
47
+ 'if-match' => true,
48
+ 'if-none-match' => true,
49
+ 'if-modified-since' => true,
50
+ 'if-unmodified-since' => true,
51
+ 'if-range' => true,
52
+ 'accept' => true,
53
+ 'authorization' => true,
54
+ 'proxy-authorization' => true,
55
+ 'from' => true,
56
+ 'referer' => true,
57
+ 'user-agent' => true,
58
+ 'x-amzn-trace-id' => true,
59
+ 'aws-sdk-invocation-id' => true,
60
+ 'aws-sdk-retry' => true,
61
+ ];
62
+ }
63
+
64
+ /**
65
+ * @param string $service Service name to use when signing
66
+ * @param string $region Region name to use when signing
67
+ * @param array $options Array of configuration options used when signing
68
+ * - unsigned-body: Flag to make request have unsigned payload.
69
+ * Unsigned body is used primarily for streaming requests.
70
+ */
71
+ public function __construct($service, $region, array $options = [])
72
+ {
73
+ $this->service = $service;
74
+ $this->region = $region;
75
+ $this->unsigned = isset($options['unsigned-body']) ? $options['unsigned-body'] : false;
76
+ }
77
+
78
+ public function signRequest(
79
+ RequestInterface $request,
80
+ CredentialsInterface $credentials
81
+ ) {
82
+ $ldt = gmdate(self::ISO8601_BASIC);
83
+ $sdt = substr($ldt, 0, 8);
84
+ $parsed = $this->parseRequest($request);
85
+ $parsed['headers']['X-Amz-Date'] = [$ldt];
86
+
87
+ if ($token = $credentials->getSecurityToken()) {
88
+ $parsed['headers']['X-Amz-Security-Token'] = [$token];
89
+ }
90
+ $cs = $this->createScope($sdt, $this->region, $this->service);
91
+ $payload = $this->getPayload($request);
92
+
93
+ if ($payload == self::UNSIGNED_PAYLOAD) {
94
+ $parsed['headers'][self::AMZ_CONTENT_SHA256_HEADER] = [$payload];
95
+ }
96
+
97
+ $context = $this->createContext($parsed, $payload);
98
+ $toSign = $this->createStringToSign($ldt, $cs, $context['creq']);
99
+ $signingKey = $this->getSigningKey(
100
+ $sdt,
101
+ $this->region,
102
+ $this->service,
103
+ $credentials->getSecretKey()
104
+ );
105
+ $signature = hash_hmac('sha256', $toSign, $signingKey);
106
+ $parsed['headers']['Authorization'] = [
107
+ "AWS4-HMAC-SHA256 "
108
+ . "Credential={$credentials->getAccessKeyId()}/{$cs}, "
109
+ . "SignedHeaders={$context['headers']}, Signature={$signature}"
110
+ ];
111
+
112
+ return $this->buildRequest($parsed);
113
+ }
114
+
115
+ /**
116
+ * Get the headers that were used to pre-sign the request.
117
+ * Used for the X-Amz-SignedHeaders header.
118
+ *
119
+ * @param array $headers
120
+ * @return array
121
+ */
122
+ private function getPresignHeaders(array $headers)
123
+ {
124
+ $presignHeaders = [];
125
+ $blacklist = $this->getHeaderBlacklist();
126
+ foreach ($headers as $name => $value) {
127
+ $lName = strtolower($name);
128
+ if (!isset($blacklist[$lName])
129
+ && $name !== self::AMZ_CONTENT_SHA256_HEADER
130
+ ) {
131
+ $presignHeaders[] = $lName;
132
+ }
133
+ }
134
+ return $presignHeaders;
135
+ }
136
+
137
+ public function presign(
138
+ RequestInterface $request,
139
+ CredentialsInterface $credentials,
140
+ $expires,
141
+ array $options = []
142
+ ) {
143
+
144
+ $startTimestamp = isset($options['start_time'])
145
+ ? $this->convertToTimestamp($options['start_time'], null)
146
+ : time();
147
+
148
+ $expiresTimestamp = $this->convertToTimestamp($expires, $startTimestamp);
149
+
150
+ $parsed = $this->createPresignedRequest($request, $credentials);
151
+ $payload = $this->getPresignedPayload($request);
152
+ $httpDate = gmdate(self::ISO8601_BASIC, $startTimestamp);
153
+ $shortDate = substr($httpDate, 0, 8);
154
+ $scope = $this->createScope($shortDate, $this->region, $this->service);
155
+ $credential = $credentials->getAccessKeyId() . '/' . $scope;
156
+ if ($credentials->getSecurityToken()) {
157
+ unset($parsed['headers']['X-Amz-Security-Token']);
158
+ }
159
+ $parsed['query']['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
160
+ $parsed['query']['X-Amz-Credential'] = $credential;
161
+ $parsed['query']['X-Amz-Date'] = gmdate('Ymd\THis\Z', $startTimestamp);
162
+ $parsed['query']['X-Amz-SignedHeaders'] = implode(';', $this->getPresignHeaders($parsed['headers']));
163
+ $parsed['query']['X-Amz-Expires'] = $this->convertExpires($expiresTimestamp, $startTimestamp);
164
+ $context = $this->createContext($parsed, $payload);
165
+ $stringToSign = $this->createStringToSign($httpDate, $scope, $context['creq']);
166
+ $key = $this->getSigningKey(
167
+ $shortDate,
168
+ $this->region,
169
+ $this->service,
170
+ $credentials->getSecretKey()
171
+ );
172
+ $parsed['query']['X-Amz-Signature'] = hash_hmac('sha256', $stringToSign, $key);
173
+
174
+ return $this->buildRequest($parsed);
175
+ }
176
+
177
+ /**
178
+ * Converts a POST request to a GET request by moving POST fields into the
179
+ * query string.
180
+ *
181
+ * Useful for pre-signing query protocol requests.
182
+ *
183
+ * @param RequestInterface $request Request to clone
184
+ *
185
+ * @return RequestInterface
186
+ * @throws \InvalidArgumentException if the method is not POST
187
+ */
188
+ public static function convertPostToGet(RequestInterface $request)
189
+ {
190
+ if ($request->getMethod() !== 'POST') {
191
+ throw new \InvalidArgumentException('Expected a POST request but '
192
+ . 'received a ' . $request->getMethod() . ' request.');
193
+ }
194
+
195
+ $sr = $request->withMethod('GET')
196
+ ->withBody(Psr7\stream_for(''))
197
+ ->withoutHeader('Content-Type')
198
+ ->withoutHeader('Content-Length');
199
+
200
+ // Move POST fields to the query if they are present
201
+ if ($request->getHeaderLine('Content-Type') === 'application/x-www-form-urlencoded') {
202
+ $body = (string) $request->getBody();
203
+ $sr = $sr->withUri($sr->getUri()->withQuery($body));
204
+ }
205
+
206
+ return $sr;
207
+ }
208
+
209
+ protected function getPayload(RequestInterface $request)
210
+ {
211
+ if ($this->unsigned && $request->getUri()->getScheme() == 'https') {
212
+ return self::UNSIGNED_PAYLOAD;
213
+ }
214
+ // Calculate the request signature payload
215
+ if ($request->hasHeader(self::AMZ_CONTENT_SHA256_HEADER)) {
216
+ // Handle streaming operations (e.g. Glacier.UploadArchive)
217
+ return $request->getHeaderLine(self::AMZ_CONTENT_SHA256_HEADER);
218
+ }
219
+
220
+ if (!$request->getBody()->isSeekable()) {
221
+ throw new CouldNotCreateChecksumException('sha256');
222
+ }
223
+
224
+ try {
225
+ return Psr7\hash($request->getBody(), 'sha256');
226
+ } catch (\Exception $e) {
227
+ throw new CouldNotCreateChecksumException('sha256', $e);
228
+ }
229
+ }
230
+
231
+ protected function getPresignedPayload(RequestInterface $request)
232
+ {
233
+ return $this->getPayload($request);
234
+ }
235
+
236
+ protected function createCanonicalizedPath($path)
237
+ {
238
+ $doubleEncoded = rawurlencode(ltrim($path, '/'));
239
+
240
+ return '/' . str_replace('%2F', '/', $doubleEncoded);
241
+ }
242
+
243
+ private function createStringToSign($longDate, $credentialScope, $creq)
244
+ {
245
+ $hash = hash('sha256', $creq);
246
+
247
+ return "AWS4-HMAC-SHA256\n{$longDate}\n{$credentialScope}\n{$hash}";
248
+ }
249
+
250
+ private function createPresignedRequest(
251
+ RequestInterface $request,
252
+ CredentialsInterface $credentials
253
+ ) {
254
+ $parsedRequest = $this->parseRequest($request);
255
+
256
+ // Make sure to handle temporary credentials
257
+ if ($token = $credentials->getSecurityToken()) {
258
+ $parsedRequest['headers']['X-Amz-Security-Token'] = [$token];
259
+ }
260
+
261
+ return $this->moveHeadersToQuery($parsedRequest);
262
+ }
263
+
264
+ /**
265
+ * @param array $parsedRequest
266
+ * @param string $payload Hash of the request payload
267
+ * @return array Returns an array of context information
268
+ */
269
+ private function createContext(array $parsedRequest, $payload)
270
+ {
271
+ $blacklist = $this->getHeaderBlacklist();
272
+
273
+ // Normalize the path as required by SigV4
274
+ $canon = $parsedRequest['method'] . "\n"
275
+ . $this->createCanonicalizedPath($parsedRequest['path']) . "\n"
276
+ . $this->getCanonicalizedQuery($parsedRequest['query']) . "\n";
277
+
278
+ // Case-insensitively aggregate all of the headers.
279
+ $aggregate = [];
280
+ foreach ($parsedRequest['headers'] as $key => $values) {
281
+ $key = strtolower($key);
282
+ if (!isset($blacklist[$key])) {
283
+ foreach ($values as $v) {
284
+ $aggregate[$key][] = $v;
285
+ }
286
+ }
287
+ }
288
+
289
+ ksort($aggregate);
290
+ $canonHeaders = [];
291
+ foreach ($aggregate as $k => $v) {
292
+ if (count($v) > 0) {
293
+ sort($v);
294
+ }
295
+ $canonHeaders[] = $k . ':' . preg_replace('/\s+/', ' ', implode(',', $v));
296
+ }
297
+
298
+ $signedHeadersString = implode(';', array_keys($aggregate));
299
+ $canon .= implode("\n", $canonHeaders) . "\n\n"
300
+ . $signedHeadersString . "\n"
301
+ . $payload;
302
+
303
+ return ['creq' => $canon, 'headers' => $signedHeadersString];
304
+ }
305
+
306
+ private function getCanonicalizedQuery(array $query)
307
+ {
308
+ unset($query['X-Amz-Signature']);
309
+
310
+ if (!$query) {
311
+ return '';
312
+ }
313
+
314
+ $qs = '';
315
+ ksort($query);
316
+ foreach ($query as $k => $v) {
317
+ if (!is_array($v)) {
318
+ $qs .= rawurlencode($k) . '=' . rawurlencode($v) . '&';
319
+ } else {
320
+ sort($v);
321
+ foreach ($v as $value) {
322
+ $qs .= rawurlencode($k) . '=' . rawurlencode($value) . '&';
323
+ }
324
+ }
325
+ }
326
+
327
+ return substr($qs, 0, -1);
328
+ }
329
+
330
+ private function convertToTimestamp($dateValue, $relativeTimeBase = null)
331
+ {
332
+ if ($dateValue instanceof \DateTimeInterface) {
333
+ $timestamp = $dateValue->getTimestamp();
334
+ } elseif (!is_numeric($dateValue)) {
335
+ $timestamp = strtotime($dateValue,
336
+ $relativeTimeBase === null ? time() : $relativeTimeBase
337
+ );
338
+ } else {
339
+ $timestamp = $dateValue;
340
+ }
341
+
342
+ return $timestamp;
343
+ }
344
+
345
+ private function convertExpires($expiresTimestamp, $startTimestamp)
346
+ {
347
+ $duration = $expiresTimestamp - $startTimestamp;
348
+
349
+ // Ensure that the duration of the signature is not longer than a week
350
+ if ($duration > 604800) {
351
+ throw new \InvalidArgumentException('The expiration date of a '
352
+ . 'signature version 4 presigned URL must be less than one '
353
+ . 'week');
354
+ }
355
+
356
+ return $duration;
357
+ }
358
+
359
+ private function moveHeadersToQuery(array $parsedRequest)
360
+ {
361
+ foreach ($parsedRequest['headers'] as $name => $header) {
362
+ $lname = strtolower($name);
363
+ if (substr($lname, 0, 5) == 'x-amz') {
364
+ $parsedRequest['query'][$name] = $header;
365
+ }
366
+ $blacklist = $this->getHeaderBlacklist();
367
+ if (isset($blacklist[$lname])
368
+ || $lname === strtolower(self::AMZ_CONTENT_SHA256_HEADER)
369
+ ) {
370
+ unset($parsedRequest['headers'][$name]);
371
+ }
372
+ }
373
+
374
+ return $parsedRequest;
375
+ }
376
+
377
+ private function parseRequest(RequestInterface $request)
378
+ {
379
+ // Clean up any previously set headers.
380
+ /** @var RequestInterface $request */
381
+ $request = $request
382
+ ->withoutHeader('X-Amz-Date')
383
+ ->withoutHeader('Date')
384
+ ->withoutHeader('Authorization');
385
+ $uri = $request->getUri();
386
+
387
+ return [
388
+ 'method' => $request->getMethod(),
389
+ 'path' => $uri->getPath(),
390
+ 'query' => Psr7\parse_query($uri->getQuery()),
391
+ 'uri' => $uri,
392
+ 'headers' => $request->getHeaders(),
393
+ 'body' => $request->getBody(),
394
+ 'version' => $request->getProtocolVersion()
395
+ ];
396
+ }
397
+
398
+ private function buildRequest(array $req)
399
+ {
400
+ if ($req['query']) {
401
+ $req['uri'] = $req['uri']->withQuery(Psr7\build_query($req['query']));
402
+ }
403
+
404
+ return new Psr7\Request(
405
+ $req['method'],
406
+ $req['uri'],
407
+ $req['headers'],
408
+ $req['body'],
409
+ $req['version']
410
+ );
411
+ }
412
+ }
lib/Aws/Aws/Sns/Exception/InvalidSnsMessageException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Sns\Exception;
3
+
4
+ /**
5
+ * Runtime exception thrown by the SNS Message Validator.
6
+ */
7
+ class InvalidSnsMessageException extends \RuntimeException
8
+ {
9
+ }
lib/Aws/Aws/Sns/Exception/SnsException.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Sns\Exception;
3
+
4
+ use Aws\Exception\AwsException;
5
+
6
+ /**
7
+ * Represents an error interacting with the Amazon Simple Notification Service.
8
+ */
9
+ class SnsException extends AwsException {}
lib/Aws/Aws/Sns/Message.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Sns;
3
+
4
+ use Psr\Http\Message\RequestInterface;
5
+
6
+ /**
7
+ * Represents an SNS message received over http(s).
8
+ */
9
+ class Message implements \ArrayAccess, \IteratorAggregate
10
+ {
11
+ private static $requiredKeys = [
12
+ 'Message',
13
+ 'MessageId',
14
+ 'Timestamp',
15
+ 'TopicArn',
16
+ 'Type',
17
+ 'Signature',
18
+ ['SigningCertURL', 'SigningCertUrl'],
19
+ 'SignatureVersion',
20
+ ];
21
+
22
+ private static $subscribeKeys = [
23
+ ['SubscribeURL', 'SubscribeUrl'],
24
+ 'Token'
25
+ ];
26
+
27
+ /** @var array The message data */
28
+ private $data;
29
+
30
+ /**
31
+ * Creates a Message object from the raw POST data
32
+ *
33
+ * @return Message
34
+ * @throws \RuntimeException If the POST data is absent, or not a valid JSON document
35
+ */
36
+ public static function fromRawPostData()
37
+ {
38
+ // Make sure the SNS-provided header exists.
39
+ if (!isset($_SERVER['HTTP_X_AMZ_SNS_MESSAGE_TYPE'])) {
40
+ throw new \RuntimeException('SNS message type header not provided.');
41
+ }
42
+
43
+ // Read the raw POST data and JSON-decode it into a message.
44
+ return self::fromJsonString(file_get_contents('php://input'));
45
+ }
46
+
47
+ /**
48
+ * Creates a Message object from a PSR-7 Request or ServerRequest object.
49
+ *
50
+ * @param RequestInterface $request
51
+ * @return Message
52
+ */
53
+ public static function fromPsrRequest(RequestInterface $request)
54
+ {
55
+ return self::fromJsonString($request->getBody());
56
+ }
57
+
58
+ /**
59
+ * Creates a Message object from a JSON-decodable string.
60
+ *
61
+ * @param string $requestBody
62
+ * @return Message
63
+ */
64
+ private static function fromJsonString($requestBody)
65
+ {
66
+ $data = json_decode($requestBody, true);
67
+ if (JSON_ERROR_NONE !== json_last_error() || !is_array($data)) {
68
+ throw new \RuntimeException('Invalid POST data.');
69
+ }
70
+
71
+ return new Message($data);
72
+ }
73
+
74
+ /**
75
+ * Creates a Message object from an array of raw message data.
76
+ *
77
+ * @param array $data The message data.
78
+ *
79
+ * @throws \InvalidArgumentException If a valid type is not provided or
80
+ * there are other required keys missing.
81
+ */
82
+ public function __construct(array $data)
83
+ {
84
+ // Ensure that all the required keys for the message's type are present.
85
+ $this->validateRequiredKeys($data, self::$requiredKeys);
86
+ if ($data['Type'] === 'SubscriptionConfirmation'
87
+ || $data['Type'] === 'UnsubscribeConfirmation'
88
+ ) {
89
+ $this->validateRequiredKeys($data, self::$subscribeKeys);
90
+ }
91
+
92
+ $this->data = $data;
93
+ }
94
+
95
+ public function getIterator()
96
+ {
97
+ return new \ArrayIterator($this->data);
98
+ }
99
+
100
+ public function offsetExists($key)
101
+ {
102
+ return isset($this->data[$key]);
103
+ }
104
+
105
+ public function offsetGet($key)
106
+ {
107
+ return isset($this->data[$key]) ? $this->data[$key] : null;
108
+ }
109
+
110
+ public function offsetSet($key, $value)
111
+ {
112
+ $this->data[$key] = $value;
113
+ }
114
+
115
+ public function offsetUnset($key)
116
+ {
117
+ unset($this->data[$key]);
118
+ }
119
+
120
+ /**
121
+ * Get all the message data as a plain array.
122
+ *
123
+ * @return array
124
+ */
125
+ public function toArray()
126
+ {
127
+ return $this->data;
128
+ }
129
+
130
+ private function validateRequiredKeys(array $data, array $keys)
131
+ {
132
+ foreach ($keys as $key) {
133
+ $keyIsArray = is_array($key);
134
+ if (!$keyIsArray) {
135
+ $found = isset($data[$key]);
136
+ } else {
137
+ $found = false;
138
+ foreach ($key as $keyOption) {
139
+ if (isset($data[$keyOption])) {
140
+ $found = true;
141
+ break;
142
+ }
143
+ }
144
+ }
145
+
146
+ if (!$found) {
147
+ if ($keyIsArray) {
148
+ $key = $key[0];
149
+ }
150
+ throw new \InvalidArgumentException(
151
+ "\"{$key}\" is required to verify the SNS Message."
152
+ );
153
+ }
154
+ }
155
+ }
156
+ }
lib/Aws/Aws/Sns/MessageValidator.php ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Sns;
3
+
4
+ use Aws\Sns\Exception\InvalidSnsMessageException;
5
+
6
+ /**
7
+ * Uses openssl to verify SNS messages to ensure that they were sent by AWS.
8
+ */
9
+ class MessageValidator
10
+ {
11
+ const SIGNATURE_VERSION_1 = '1';
12
+
13
+ /**
14
+ * @var callable Callable used to download the certificate content.
15
+ */
16
+ private $certClient;
17
+
18
+ /** @var string */
19
+ private $hostPattern;
20
+
21
+ /**
22
+ * @var string A pattern that will match all regional SNS endpoints, e.g.:
23
+ * - sns.<region>.amazonaws.com (AWS)
24
+ * - sns.us-gov-west-1.amazonaws.com (AWS GovCloud)
25
+ * - sns.cn-north-1.amazonaws.com.cn (AWS China)
26
+ */
27
+ private static $defaultHostPattern
28
+ = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';
29
+
30
+ private static function isLambdaStyle(Message $message)
31
+ {
32
+ return isset($message['SigningCertUrl']);
33
+ }
34
+
35
+ private static function convertLambdaMessage(Message $lambdaMessage)
36
+ {
37
+ $keyReplacements = [
38
+ 'SigningCertUrl' => 'SigningCertURL',
39
+ 'SubscribeUrl' => 'SubscribeURL',
40
+ 'UnsubscribeUrl' => 'UnsubscribeURL',
41
+ ];
42
+
43
+ $message = clone $lambdaMessage;
44
+ foreach ($keyReplacements as $lambdaKey => $canonicalKey) {
45
+ if (isset($message[$lambdaKey])) {
46
+ $message[$canonicalKey] = $message[$lambdaKey];
47
+ unset($message[$lambdaKey]);
48
+ }
49
+ }
50
+
51
+ return $message;
52
+ }
53
+
54
+ /**
55
+ * Constructs the Message Validator object and ensures that openssl is
56
+ * installed.
57
+ *
58
+ * @param callable $certClient Callable used to download the certificate.
59
+ * Should have the following function signature:
60
+ * `function (string $certUrl) : string $certContent`
61
+ * @param string $hostNamePattern
62
+ */
63
+ public function __construct(
64
+ callable $certClient = null,
65
+ $hostNamePattern = ''
66
+ ) {
67
+ $this->certClient = $certClient ?: 'file_get_contents';
68
+ $this->hostPattern = $hostNamePattern ?: self::$defaultHostPattern;
69
+ }
70
+
71
+ /**
72
+ * Validates a message from SNS to ensure that it was delivered by AWS.
73
+ *
74
+ * @param Message $message Message to validate.
75
+ *
76
+ * @throws InvalidSnsMessageException If the cert cannot be retrieved or its
77
+ * source verified, or the message
78
+ * signature is invalid.
79
+ */
80
+ public function validate(Message $message)
81
+ {
82
+ if (self::isLambdaStyle($message)) {
83
+ $message = self::convertLambdaMessage($message);
84
+ }
85
+
86
+ // Get the certificate.
87
+ $this->validateUrl($message['SigningCertURL']);
88
+ $certificate = call_user_func($this->certClient, $message['SigningCertURL']);
89
+ if ($certificate === false) {
90
+ throw new InvalidSnsMessageException(
91
+ "Cannot get the certificate from \"{$message['SigningCertURL']}\"."
92
+ );
93
+ }
94
+
95
+ // Extract the public key.
96
+ $key = openssl_get_publickey($certificate);
97
+ if (!$key) {
98
+ throw new InvalidSnsMessageException(
99
+ 'Cannot get the public key from the certificate.'
100
+ );
101
+ }
102
+
103
+ // Verify the signature of the message.
104
+ $content = $this->getStringToSign($message);
105
+ $signature = base64_decode($message['Signature']);
106
+ if (openssl_verify($content, $signature, $key, OPENSSL_ALGO_SHA1) != 1) {
107
+ throw new InvalidSnsMessageException(
108
+ 'The message signature is invalid.'
109
+ );
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Determines if a message is valid and that is was delivered by AWS. This
115
+ * method does not throw exceptions and returns a simple boolean value.
116
+ *
117
+ * @param Message $message The message to validate
118
+ *
119
+ * @return bool
120
+ */
121
+ public function isValid(Message $message)
122
+ {
123
+ try {
124
+ $this->validate($message);
125
+ return true;
126
+ } catch (InvalidSnsMessageException $e) {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Builds string-to-sign according to the SNS message spec.
133
+ *
134
+ * @param Message $message Message for which to build the string-to-sign.
135
+ *
136
+ * @return string
137
+ * @link http://docs.aws.amazon.com/sns/latest/gsg/SendMessageToHttp.verify.signature.html
138
+ */
139
+ public function getStringToSign(Message $message)
140
+ {
141
+ static $signableKeys = [
142
+ 'Message',
143
+ 'MessageId',
144
+ 'Subject',
145
+ 'SubscribeURL',
146
+ 'Timestamp',
147
+ 'Token',
148
+ 'TopicArn',
149
+ 'Type',
150
+ ];
151
+
152
+ if ($message['SignatureVersion'] !== self::SIGNATURE_VERSION_1) {
153
+ throw new InvalidSnsMessageException(
154
+ "The SignatureVersion \"{$message['SignatureVersion']}\" is not supported."
155
+ );
156
+ }
157
+
158
+ $stringToSign = '';
159
+ foreach ($signableKeys as $key) {
160
+ if (isset($message[$key])) {
161
+ $stringToSign .= "{$key}\n{$message[$key]}\n";
162
+ }
163
+ }
164
+
165
+ return $stringToSign;
166
+ }
167
+
168
+ /**
169
+ * Ensures that the URL of the certificate is one belonging to AWS, and not
170
+ * just something from the amazonaws domain, which could include S3 buckets.
171
+ *
172
+ * @param string $url Certificate URL
173
+ *
174
+ * @throws InvalidSnsMessageException if the cert url is invalid.
175
+ */
176
+ private function validateUrl($url)
177
+ {
178
+ $parsed = parse_url($url);
179
+ if (empty($parsed['scheme'])
180
+ || empty($parsed['host'])
181
+ || $parsed['scheme'] !== 'https'
182
+ || substr($url, -4) !== '.pem'
183
+ || !preg_match($this->hostPattern, $parsed['host'])
184
+ ) {
185
+ throw new InvalidSnsMessageException(
186
+ 'The certificate is located on an invalid domain.'
187
+ );
188
+ }
189
+ }
190
+ }
lib/Aws/Aws/Sns/SnsClient.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws\Sns;
3
+
4
+ use Aws\AwsClient;
5
+
6
+ /**
7
+ * This client is used to interact with the **Amazon Simple Notification Service (Amazon SNS)**.
8
+ *
9
+ * @method \Aws\Result addPermission(array $args = [])
10
+ * @method \GuzzleHttp\Promise\Promise addPermissionAsync(array $args = [])
11
+ * @method \Aws\Result checkIfPhoneNumberIsOptedOut(array $args = [])
12
+ * @method \GuzzleHttp\Promise\Promise checkIfPhoneNumberIsOptedOutAsync(array $args = [])
13
+ * @method \Aws\Result confirmSubscription(array $args = [])
14
+ * @method \GuzzleHttp\Promise\Promise confirmSubscriptionAsync(array $args = [])
15
+ * @method \Aws\Result createPlatformApplication(array $args = [])
16
+ * @method \GuzzleHttp\Promise\Promise createPlatformApplicationAsync(array $args = [])
17
+ * @method \Aws\Result createPlatformEndpoint(array $args = [])
18
+ * @method \GuzzleHttp\Promise\Promise createPlatformEndpointAsync(array $args = [])
19
+ * @method \Aws\Result createTopic(array $args = [])
20
+ * @method \GuzzleHttp\Promise\Promise createTopicAsync(array $args = [])
21
+ * @method \Aws\Result deleteEndpoint(array $args = [])
22
+ * @method \GuzzleHttp\Promise\Promise deleteEndpointAsync(array $args = [])
23
+ * @method \Aws\Result deletePlatformApplication(array $args = [])
24
+ * @method \GuzzleHttp\Promise\Promise deletePlatformApplicationAsync(array $args = [])
25
+ * @method \Aws\Result deleteTopic(array $args = [])
26
+ * @method \GuzzleHttp\Promise\Promise deleteTopicAsync(array $args = [])
27
+ * @method \Aws\Result getEndpointAttributes(array $args = [])
28
+ * @method \GuzzleHttp\Promise\Promise getEndpointAttributesAsync(array $args = [])
29
+ * @method \Aws\Result getPlatformApplicationAttributes(array $args = [])
30
+ * @method \GuzzleHttp\Promise\Promise getPlatformApplicationAttributesAsync(array $args = [])
31
+ * @method \Aws\Result getSMSAttributes(array $args = [])
32
+ * @method \GuzzleHttp\Promise\Promise getSMSAttributesAsync(array $args = [])
33
+ * @method \Aws\Result getSubscriptionAttributes(array $args = [])
34
+ * @method \GuzzleHttp\Promise\Promise getSubscriptionAttributesAsync(array $args = [])
35
+ * @method \Aws\Result getTopicAttributes(array $args = [])
36
+ * @method \GuzzleHttp\Promise\Promise getTopicAttributesAsync(array $args = [])
37
+ * @method \Aws\Result listEndpointsByPlatformApplication(array $args = [])
38
+ * @method \GuzzleHttp\Promise\Promise listEndpointsByPlatformApplicationAsync(array $args = [])
39
+ * @method \Aws\Result listPhoneNumbersOptedOut(array $args = [])
40
+ * @method \GuzzleHttp\Promise\Promise listPhoneNumbersOptedOutAsync(array $args = [])
41
+ * @method \Aws\Result listPlatformApplications(array $args = [])
42
+ * @method \GuzzleHttp\Promise\Promise listPlatformApplicationsAsync(array $args = [])
43
+ * @method \Aws\Result listSubscriptions(array $args = [])
44
+ * @method \GuzzleHttp\Promise\Promise listSubscriptionsAsync(array $args = [])
45
+ * @method \Aws\Result listSubscriptionsByTopic(array $args = [])
46
+ * @method \GuzzleHttp\Promise\Promise listSubscriptionsByTopicAsync(array $args = [])
47
+ * @method \Aws\Result listTagsForResource(array $args = [])
48
+ * @method \GuzzleHttp\Promise\Promise listTagsForResourceAsync(array $args = [])
49
+ * @method \Aws\Result listTopics(array $args = [])
50
+ * @method \GuzzleHttp\Promise\Promise listTopicsAsync(array $args = [])
51
+ * @method \Aws\Result optInPhoneNumber(array $args = [])
52
+ * @method \GuzzleHttp\Promise\Promise optInPhoneNumberAsync(array $args = [])
53
+ * @method \Aws\Result publish(array $args = [])
54
+ * @method \GuzzleHttp\Promise\Promise publishAsync(array $args = [])
55
+ * @method \Aws\Result removePermission(array $args = [])
56
+ * @method \GuzzleHttp\Promise\Promise removePermissionAsync(array $args = [])
57
+ * @method \Aws\Result setEndpointAttributes(array $args = [])
58
+ * @method \GuzzleHttp\Promise\Promise setEndpointAttributesAsync(array $args = [])
59
+ * @method \Aws\Result setPlatformApplicationAttributes(array $args = [])
60
+ * @method \GuzzleHttp\Promise\Promise setPlatformApplicationAttributesAsync(array $args = [])
61
+ * @method \Aws\Result setSMSAttributes(array $args = [])
62
+ * @method \GuzzleHttp\Promise\Promise setSMSAttributesAsync(array $args = [])
63
+ * @method \Aws\Result setSubscriptionAttributes(array $args = [])
64
+ * @method \GuzzleHttp\Promise\Promise setSubscriptionAttributesAsync(array $args = [])
65
+ * @method \Aws\Result setTopicAttributes(array $args = [])
66
+ * @method \GuzzleHttp\Promise\Promise setTopicAttributesAsync(array $args = [])
67
+ * @method \Aws\Result subscribe(array $args = [])
68
+ * @method \GuzzleHttp\Promise\Promise subscribeAsync(array $args = [])
69
+ * @method \Aws\Result tagResource(array $args = [])
70
+ * @method \GuzzleHttp\Promise\Promise tagResourceAsync(array $args = [])
71
+ * @method \Aws\Result unsubscribe(array $args = [])
72
+ * @method \GuzzleHttp\Promise\Promise unsubscribeAsync(array $args = [])
73
+ * @method \Aws\Result untagResource(array $args = [])
74
+ * @method \GuzzleHttp\Promise\Promise untagResourceAsync(array $args = [])
75
+ */
76
+ class SnsClient extends AwsClient {}
lib/Aws/Aws/TraceMiddleware.php ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Exception\AwsException;
5
+ use GuzzleHttp\Promise\RejectedPromise;
6
+ use Psr\Http\Message\RequestInterface;
7
+ use Psr\Http\Message\ResponseInterface;
8
+ use Psr\Http\Message\StreamInterface;
9
+
10
+ /**
11
+ * Traces state changes between middlewares.
12
+ */
13
+ class TraceMiddleware
14
+ {
15
+ private $prevOutput;
16
+ private $prevInput;
17
+ private $config;
18
+
19
+ private static $authHeaders = [
20
+ 'X-Amz-Security-Token' => '[TOKEN]',
21
+ ];
22
+
23
+ private static $authStrings = [
24
+ // S3Signature
25
+ '/AWSAccessKeyId=[A-Z0-9]{20}&/i' => 'AWSAccessKeyId=[KEY]&',
26
+ // SignatureV4 Signature and S3Signature
27
+ '/Signature=.+/i' => 'Signature=[SIGNATURE]',
28
+ // SignatureV4 access key ID
29
+ '/Credential=[A-Z0-9]{20}\//i' => 'Credential=[KEY]/',
30
+ // S3 signatures
31
+ '/AWS [A-Z0-9]{20}:.+/' => 'AWS AKI[KEY]:[SIGNATURE]',
32
+ // STS Presigned URLs
33
+ '/X-Amz-Security-Token=[^&]+/i' => 'X-Amz-Security-Token=[TOKEN]',
34
+ // Crypto *Stream Keys
35
+ '/\["key.{27,36}Stream.{9}\]=>\s+.{7}\d{2}\) "\X{16,64}"/U' => '["key":[CONTENT KEY]]',
36
+ ];
37
+
38
+ /**
39
+ * Configuration array can contain the following key value pairs.
40
+ *
41
+ * - logfn: (callable) Function that is invoked with log messages. By
42
+ * default, PHP's "echo" function will be utilized.
43
+ * - stream_size: (int) When the size of a stream is greater than this
44
+ * number, the stream data will not be logged. Set to "0" to not log any
45
+ * stream data.
46
+ * - scrub_auth: (bool) Set to false to disable the scrubbing of auth data
47
+ * from the logged messages.
48
+ * - http: (bool) Set to false to disable the "debug" feature of lower
49
+ * level HTTP adapters (e.g., verbose curl output).
50
+ * - auth_strings: (array) A mapping of authentication string regular
51
+ * expressions to scrubbed strings. These mappings are passed directly to
52
+ * preg_replace (e.g., preg_replace($key, $value, $debugOutput) if
53
+ * "scrub_auth" is set to true.
54
+ * - auth_headers: (array) A mapping of header names known to contain
55
+ * sensitive data to what the scrubbed value should be. The value of any
56
+ * headers contained in this array will be replaced with the if
57
+ * "scrub_auth" is set to true.
58
+ */
59
+ public function __construct(array $config = [])
60
+ {
61
+ $this->config = $config + [
62
+ 'logfn' => function ($value) { echo $value; },
63
+ 'stream_size' => 524288,
64
+ 'scrub_auth' => true,
65
+ 'http' => true,
66
+ 'auth_strings' => [],
67
+ 'auth_headers' => [],
68
+ ];
69
+
70
+ $this->config['auth_strings'] += self::$authStrings;
71
+ $this->config['auth_headers'] += self::$authHeaders;
72
+ }
73
+
74
+ public function __invoke($step, $name)
75
+ {
76
+ $this->prevOutput = $this->prevInput = [];
77
+
78
+ return function (callable $next) use ($step, $name) {
79
+ return function (
80
+ CommandInterface $command,
81
+ RequestInterface $request = null
82
+ ) use ($next, $step, $name) {
83
+ $this->createHttpDebug($command);
84
+ $start = microtime(true);
85
+ $this->stepInput([
86
+ 'step' => $step,
87
+ 'name' => $name,
88
+ 'request' => $this->requestArray($request),
89
+ 'command' => $this->commandArray($command)
90
+ ]);
91
+
92
+ return $next($command, $request)->then(
93
+ function ($value) use ($step, $name, $command, $start) {
94
+ $this->flushHttpDebug($command);
95
+ $this->stepOutput($start, [
96
+ 'step' => $step,
97
+ 'name' => $name,
98
+ 'result' => $this->resultArray($value),
99
+ 'error' => null
100
+ ]);
101
+ return $value;
102
+ },
103
+ function ($reason) use ($step, $name, $start, $command) {
104
+ $this->flushHttpDebug($command);
105
+ $this->stepOutput($start, [
106
+ 'step' => $step,
107
+ 'name' => $name,
108
+ 'result' => null,
109
+ 'error' => $this->exceptionArray($reason)
110
+ ]);
111
+ return new RejectedPromise($reason);
112
+ }
113
+ );
114
+ };
115
+ };
116
+ }
117
+
118
+ private function stepInput($entry)
119
+ {
120
+ static $keys = ['command', 'request'];
121
+ $this->compareStep($this->prevInput, $entry, '-> Entering', $keys);
122
+ $this->write("\n");
123
+ $this->prevInput = $entry;
124
+ }
125
+
126
+ private function stepOutput($start, $entry)
127
+ {
128
+ static $keys = ['result', 'error'];
129
+ $this->compareStep($this->prevOutput, $entry, '<- Leaving', $keys);
130
+ $totalTime = microtime(true) - $start;
131
+ $this->write(" Inclusive step time: " . $totalTime . "\n\n");
132
+ $this->prevOutput = $entry;
133
+ }
134
+
135
+ private function compareStep(array $a, array $b, $title, array $keys)
136
+ {
137
+ $changes = [];
138
+ foreach ($keys as $key) {
139
+ $av = isset($a[$key]) ? $a[$key] : null;
140
+ $bv = isset($b[$key]) ? $b[$key] : null;
141
+ $this->compareArray($av, $bv, $key, $changes);
142
+ }
143
+ $str = "\n{$title} step {$b['step']}, name '{$b['name']}'";
144
+ $str .= "\n" . str_repeat('-', strlen($str) - 1) . "\n\n ";
145
+ $str .= $changes
146
+ ? implode("\n ", str_replace("\n", "\n ", $changes))
147
+ : 'no changes';
148
+ $this->write($str . "\n");
149
+ }
150
+
151
+ private function commandArray(CommandInterface $cmd)
152
+ {
153
+ return [
154
+ 'instance' => spl_object_hash($cmd),
155
+ 'name' => $cmd->getName(),
156
+ 'params' => $cmd->toArray()
157
+ ];
158
+ }
159
+
160
+ private function requestArray(RequestInterface $request = null)
161
+ {
162
+ return !$request ? [] : array_filter([
163
+ 'instance' => spl_object_hash($request),
164
+ 'method' => $request->getMethod(),
165
+ 'headers' => $this->redactHeaders($request->getHeaders()),
166
+ 'body' => $this->streamStr($request->getBody()),
167
+ 'scheme' => $request->getUri()->getScheme(),
168
+ 'port' => $request->getUri()->getPort(),
169
+ 'path' => $request->getUri()->getPath(),
170
+ 'query' => $request->getUri()->getQuery(),
171
+ ]);
172
+ }
173
+
174
+ private function responseArray(ResponseInterface $response = null)
175
+ {
176
+ return !$response ? [] : [
177
+ 'instance' => spl_object_hash($response),
178
+ 'statusCode' => $response->getStatusCode(),
179
+ 'headers' => $this->redactHeaders($response->getHeaders()),
180
+ 'body' => $this->streamStr($response->getBody())
181
+ ];
182
+ }
183
+
184
+ private function resultArray($value)
185
+ {
186
+ return $value instanceof ResultInterface
187
+ ? [
188
+ 'instance' => spl_object_hash($value),
189
+ 'data' => $value->toArray()
190
+ ] : $value;
191
+ }
192
+
193
+ private function exceptionArray($e)
194
+ {
195
+ if (!($e instanceof \Exception)) {
196
+ return $e;
197
+ }
198
+
199
+ $result = [
200
+ 'instance' => spl_object_hash($e),
201
+ 'class' => get_class($e),
202
+ 'message' => $e->getMessage(),
203
+ 'file' => $e->getFile(),
204
+ 'line' => $e->getLine(),
205
+ 'trace' => $e->getTraceAsString(),
206
+ ];
207
+
208
+ if ($e instanceof AwsException) {
209
+ $result += [
210
+ 'type' => $e->getAwsErrorType(),
211
+ 'code' => $e->getAwsErrorCode(),
212
+ 'requestId' => $e->getAwsRequestId(),
213
+ 'statusCode' => $e->getStatusCode(),
214
+ 'result' => $this->resultArray($e->getResult()),
215
+ 'request' => $this->requestArray($e->getRequest()),
216
+ 'response' => $this->responseArray($e->getResponse()),
217
+ ];
218
+ }
219
+
220
+ return $result;
221
+ }
222
+
223
+ private function compareArray($a, $b, $path, array &$diff)
224
+ {
225
+ if ($a === $b) {
226
+ return;
227
+ }
228
+
229
+ if (is_array($a)) {
230
+ $b = (array) $b;
231
+ $keys = array_unique(array_merge(array_keys($a), array_keys($b)));
232
+ foreach ($keys as $k) {
233
+ if (!array_key_exists($k, $a)) {
234
+ $this->compareArray(null, $b[$k], "{$path}.{$k}", $diff);
235
+ } elseif (!array_key_exists($k, $b)) {
236
+ $this->compareArray($a[$k], null, "{$path}.{$k}", $diff);
237
+ } else {
238
+ $this->compareArray($a[$k], $b[$k], "{$path}.{$k}", $diff);
239
+ }
240
+ }
241
+ } elseif ($a !== null && $b === null) {
242
+ $diff[] = "{$path} was unset";
243
+ } elseif ($a === null && $b !== null) {
244
+ $diff[] = sprintf("%s was set to %s", $path, $this->str($b));
245
+ } else {
246
+ $diff[] = sprintf("%s changed from %s to %s", $path, $this->str($a), $this->str($b));
247
+ }
248
+ }
249
+
250
+ private function str($value)
251
+ {
252
+ if (is_scalar($value)) {
253
+ return (string) $value;
254
+ }
255
+
256
+ if ($value instanceof \Exception) {
257
+ $value = $this->exceptionArray($value);
258
+ }
259
+
260
+ ob_start();
261
+ var_dump($value);
262
+ return ob_get_clean();
263
+ }
264
+
265
+ private function streamStr(StreamInterface $body)
266
+ {
267
+ return $body->getSize() < $this->config['stream_size']
268
+ ? (string) $body
269
+ : 'stream(size=' . $body->getSize() . ')';
270
+ }
271
+
272
+ private function createHttpDebug(CommandInterface $command)
273
+ {
274
+ if ($this->config['http'] && !isset($command['@http']['debug'])) {
275
+ $command['@http']['debug'] = fopen('php://temp', 'w+');
276
+ }
277
+ }
278
+
279
+ private function flushHttpDebug(CommandInterface $command)
280
+ {
281
+ if ($res = $command['@http']['debug']) {
282
+ rewind($res);
283
+ $this->write(stream_get_contents($res));
284
+ fclose($res);
285
+ $command['@http']['debug'] = null;
286
+ }
287
+ }
288
+
289
+ private function write($value)
290
+ {
291
+ if ($this->config['scrub_auth']) {
292
+ foreach ($this->config['auth_strings'] as $pattern => $replacement) {
293
+ $value = preg_replace_callback(
294
+ $pattern,
295
+ function ($matches) use ($replacement) {
296
+ return $replacement;
297
+ },
298
+ $value
299
+ );
300
+ }
301
+ }
302
+
303
+ call_user_func($this->config['logfn'], $value);
304
+ }
305
+
306
+ private function redactHeaders(array $headers)
307
+ {
308
+ if ($this->config['scrub_auth']) {
309
+ $headers = $this->config['auth_headers'] + $headers;
310
+ }
311
+
312
+ return $headers;
313
+ }
314
+ }
lib/Aws/Aws/Waiter.php ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Exception\AwsException;
5
+ use GuzzleHttp\Promise;
6
+ use GuzzleHttp\Promise\PromisorInterface;
7
+ use GuzzleHttp\Promise\RejectedPromise;
8
+
9
+ /**
10
+ * "Waiters" are associated with an AWS resource (e.g., EC2 instance), and poll
11
+ * that resource and until it is in a particular state.
12
+
13
+ * The Waiter object produces a promise that is either a.) resolved once the
14
+ * waiting conditions are met, or b.) rejected if the waiting conditions cannot
15
+ * be met or has exceeded the number of allowed attempts at meeting the
16
+ * conditions. You can use waiters in a blocking or non-blocking way, depending
17
+ * on whether you call wait() on the promise.
18
+
19
+ * The configuration for the waiter must include information about the operation
20
+ * and the conditions for wait completion.
21
+ */
22
+ class Waiter implements PromisorInterface
23
+ {
24
+ /** @var AwsClientInterface Client used to execute each attempt. */
25
+ private $client;
26
+
27
+ /** @var string Name of the waiter. */
28
+ private $name;
29
+
30
+ /** @var array Params to use with each attempt operation. */
31
+ private $args;
32
+
33
+ /** @var array Waiter configuration. */
34
+ private $config;
35
+
36
+ /** @var array Default configuration options. */
37
+ private static $defaults = ['initDelay' => 0, 'before' => null];
38
+
39
+ /** @var array Required configuration options. */
40
+ private static $required = [
41
+ 'acceptors',
42
+ 'delay',
43
+ 'maxAttempts',
44
+ 'operation',
45
+ ];
46
+
47
+ /**
48
+ * The array of configuration options include:
49
+ *
50
+ * - acceptors: (array) Array of acceptor options
51
+ * - delay: (int) Number of seconds to delay between attempts
52
+ * - maxAttempts: (int) Maximum number of attempts before failing
53
+ * - operation: (string) Name of the API operation to use for polling
54
+ * - before: (callable) Invoked before attempts. Accepts command and tries.
55
+ *
56
+ * @param AwsClientInterface $client Client used to execute commands.
57
+ * @param string $name Waiter name.
58
+ * @param array $args Command arguments.
59
+ * @param array $config Waiter config that overrides defaults.
60
+ *
61
+ * @throws \InvalidArgumentException if the configuration is incomplete.
62
+ */
63
+ public function __construct(
64
+ AwsClientInterface $client,
65
+ $name,
66
+ array $args = [],
67
+ array $config = []
68
+ ) {
69
+ $this->client = $client;
70
+ $this->name = $name;
71
+ $this->args = $args;
72
+
73
+ // Prepare and validate config.
74
+ $this->config = $config + self::$defaults;
75
+ foreach (self::$required as $key) {
76
+ if (!isset($this->config[$key])) {
77
+ throw new \InvalidArgumentException(
78
+ 'The provided waiter configuration was incomplete.'
79
+ );
80
+ }
81
+ }
82
+ if ($this->config['before'] && !is_callable($this->config['before'])) {
83
+ throw new \InvalidArgumentException(
84
+ 'The provided "before" callback is not callable.'
85
+ );
86
+ }
87
+ }
88
+
89
+ public function promise()
90
+ {
91
+ return Promise\coroutine(function () {
92
+ $name = $this->config['operation'];
93
+ for ($state = 'retry', $attempt = 1; $state === 'retry'; $attempt++) {
94
+ // Execute the operation.
95
+ $args = $this->getArgsForAttempt($attempt);
96
+ $command = $this->client->getCommand($name, $args);
97
+ try {
98
+ if ($this->config['before']) {
99
+ $this->config['before']($command, $attempt);
100
+ }
101
+ $result = (yield $this->client->executeAsync($command));
102
+ } catch (AwsException $e) {
103
+ $result = $e;
104
+ }
105
+
106
+ // Determine the waiter's state and what to do next.
107
+ $state = $this->determineState($result);
108
+ if ($state === 'success') {
109
+ yield $command;
110
+ } elseif ($state === 'failed') {
111
+ $msg = "The {$this->name} waiter entered a failure state.";
112
+ if ($result instanceof \Exception) {
113
+ $msg .= ' Reason: ' . $result->getMessage();
114
+ }
115
+ yield new RejectedPromise(new \RuntimeException($msg));
116
+ } elseif ($state === 'retry'
117
+ && $attempt >= $this->config['maxAttempts']
118
+ ) {
119
+ $state = 'failed';
120
+ yield new RejectedPromise(new \RuntimeException(
121
+ "The {$this->name} waiter failed after attempt #{$attempt}."
122
+ ));
123
+ }
124
+ }
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Gets the operation arguments for the attempt, including the delay.
130
+ *
131
+ * @param $attempt Number of the current attempt.
132
+ *
133
+ * @return mixed integer
134
+ */
135
+ private function getArgsForAttempt($attempt)
136
+ {
137
+ $args = $this->args;
138
+
139
+ // Determine the delay.
140
+ $delay = ($attempt === 1)
141
+ ? $this->config['initDelay']
142
+ : $this->config['delay'];
143
+ if (is_callable($delay)) {
144
+ $delay = $delay($attempt);
145
+ }
146
+
147
+ // Set the delay. (Note: handlers except delay in milliseconds.)
148
+ if (!isset($args['@http'])) {
149
+ $args['@http'] = [];
150
+ }
151
+ $args['@http']['delay'] = $delay * 1000;
152
+
153
+ return $args;
154
+ }
155
+
156
+ /**
157
+ * Determines the state of the waiter attempt, based on the result of
158
+ * polling the resource. A waiter can have the state of "success", "failed",
159
+ * or "retry".
160
+ *
161
+ * @param mixed $result
162
+ *
163
+ * @return string Will be "success", "failed", or "retry"
164
+ */
165
+ private function determineState($result)
166
+ {
167
+ foreach ($this->config['acceptors'] as $acceptor) {
168
+ $matcher = 'matches' . ucfirst($acceptor['matcher']);
169
+ if ($this->{$matcher}($result, $acceptor)) {
170
+ return $acceptor['state'];
171
+ }
172
+ }
173
+
174
+ return $result instanceof \Exception ? 'failed' : 'retry';
175
+ }
176
+
177
+ /**
178
+ * @param Result $result Result or exception.
179
+ * @param array $acceptor Acceptor configuration being checked.
180
+ *
181
+ * @return bool
182
+ */
183
+ private function matchesPath($result, array $acceptor)
184
+ {
185
+ return !($result instanceof ResultInterface)
186
+ ? false
187
+ : $acceptor['expected'] == $result->search($acceptor['argument']);
188
+ }
189
+
190
+ /**
191
+ * @param Result $result Result or exception.
192
+ * @param array $acceptor Acceptor configuration being checked.
193
+ *
194
+ * @return bool
195
+ */
196
+ private function matchesPathAll($result, array $acceptor)
197
+ {
198
+ if (!($result instanceof ResultInterface)) {
199
+ return false;
200
+ }
201
+
202
+ $actuals = $result->search($acceptor['argument']) ?: [];
203
+ foreach ($actuals as $actual) {
204
+ if ($actual != $acceptor['expected']) {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ return true;
210
+ }
211
+
212
+ /**
213
+ * @param Result $result Result or exception.
214
+ * @param array $acceptor Acceptor configuration being checked.
215
+ *
216
+ * @return bool
217
+ */
218
+ private function matchesPathAny($result, array $acceptor)
219
+ {
220
+ if (!($result instanceof ResultInterface)) {
221
+ return false;
222
+ }
223
+
224
+ $actuals = $result->search($acceptor['argument']) ?: [];
225
+ return in_array($acceptor['expected'], $actuals);
226
+ }
227
+
228
+ /**
229
+ * @param Result $result Result or exception.
230
+ * @param array $acceptor Acceptor configuration being checked.
231
+ *
232
+ * @return bool
233
+ */
234
+ private function matchesStatus($result, array $acceptor)
235
+ {
236
+ if ($result instanceof ResultInterface) {
237
+ return $acceptor['expected'] == $result['@metadata']['statusCode'];
238
+ }
239
+
240
+ if ($result instanceof AwsException && $response = $result->getResponse()) {
241
+ return $acceptor['expected'] == $response->getStatusCode();
242
+ }
243
+
244
+ return false;
245
+ }
246
+
247
+ /**
248
+ * @param Result $result Result or exception.
249
+ * @param array $acceptor Acceptor configuration being checked.
250
+ *
251
+ * @return bool
252
+ */
253
+ private function matchesError($result, array $acceptor)
254
+ {
255
+ if ($result instanceof AwsException) {
256
+ return $result->isConnectionError()
257
+ || $result->getAwsErrorCode() == $acceptor['expected'];
258
+ }
259
+
260
+ return false;
261
+ }
262
+ }
lib/Aws/Aws/WrappedHttpHandler.php ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aws;
3
+
4
+ use Aws\Api\Parser\Exception\ParserException;
5
+ use GuzzleHttp\Promise;
6
+ use Psr\Http\Message\RequestInterface;
7
+ use Psr\Http\Message\ResponseInterface;
8
+
9
+ /**
10
+ * Converts an HTTP handler into a Command HTTP handler.
11
+ *
12
+ * HTTP handlers have the following signature:
13
+ * function(RequestInterface $request, array $options) : PromiseInterface
14
+ *
15
+ * The promise returned form an HTTP handler must resolve to a PSR-7 response
16
+ * object when fulfilled or an error array when rejected. The error array
17
+ * can contain the following data:
18
+ *
19
+ * - exception: (required, Exception) Exception that was encountered.
20
+ * - response: (ResponseInterface) PSR-7 response that was received (if a
21
+ * response) was received.
22
+ * - connection_error: (bool) True if the error is the result of failing to
23
+ * connect.
24
+ */
25
+ class WrappedHttpHandler
26
+ {
27
+ private $httpHandler;
28
+ private $parser;
29
+ private $errorParser;
30
+ private $exceptionClass;
31
+ private $collectStats;
32
+
33
+ /**
34
+ * @param callable $httpHandler Function that accepts a request and array
35
+ * of request options and returns a promise
36
+ * that fulfills with a response or rejects
37
+ * with an error array.
38
+ * @param callable $parser Function that accepts a response object
39
+ * and returns an AWS result object.
40
+ * @param callable $errorParser Function that parses a response object
41
+ * into AWS error data.
42
+ * @param string $exceptionClass Exception class to throw.
43
+ * @param bool $collectStats Whether to collect HTTP transfer
44
+ * information.
45
+ */
46
+ public function __construct(
47
+ callable $httpHandler,
48
+ callable $parser,
49
+ callable $errorParser,
50
+ $exceptionClass = 'Aws\Exception\AwsException',
51
+ $collectStats = false
52
+ ) {
53
+ $this->httpHandler = $httpHandler;
54
+ $this->parser = $parser;
55
+ $this->errorParser = $errorParser;
56
+ $this->exceptionClass = $exceptionClass;
57
+ $this->collectStats = $collectStats;
58
+ }
59
+
60
+ /**
61
+ * Calls the simpler HTTP specific handler and wraps the returned promise
62
+ * with AWS specific values (e.g., a result object or AWS exception).
63
+ *
64
+ * @param CommandInterface $command Command being executed.
65
+ * @param RequestInterface $request Request to send.
66
+ *
67
+ * @return Promise\PromiseInterface
68
+ */
69
+ public function __invoke(
70
+ CommandInterface $command,
71
+ RequestInterface $request
72
+ ) {
73
+ $fn = $this->httpHandler;
74
+ $options = $command['@http'] ?: [];
75
+ $stats = [];
76
+ if ($this->collectStats || !empty($options['collect_stats'])) {
77
+ $options['http_stats_receiver'] = static function (
78
+ array $transferStats
79
+ ) use (&$stats) {
80
+ $stats = $transferStats;
81
+ };
82
+ } elseif (isset($options['http_stats_receiver'])) {
83
+ throw new \InvalidArgumentException('Providing a custom HTTP stats'
84
+ . ' receiver to Aws\WrappedHttpHandler is not supported.');
85
+ }
86
+
87
+ return Promise\promise_for($fn($request, $options))
88
+ ->then(
89
+ function (
90
+ ResponseInterface $res
91
+ ) use ($command, $request, &$stats) {
92
+ return $this->parseResponse($command, $request, $res, $stats);
93
+ },
94
+ function ($err) use ($request, $command, &$stats) {
95
+ if (is_array($err)) {
96
+ $err = $this->parseError(
97
+ $err,
98
+ $request,
99
+ $command,
100
+ $stats
101
+ );
102
+ }
103
+ return new Promise\RejectedPromise($err);
104
+ }
105
+ );
106
+ }
107
+
108
+ /**
109
+ * @param CommandInterface $command
110
+ * @param RequestInterface $request
111
+ * @param ResponseInterface $response
112
+ * @param array $stats
113
+ *
114
+ * @return ResultInterface
115
+ */
116
+ private function parseResponse(
117
+ CommandInterface $command,
118
+ RequestInterface $request,
119
+ ResponseInterface $response,
120
+ array $stats
121
+ ) {
122
+ $parser = $this->parser;
123
+ $status = $response->getStatusCode();
124
+ $result = $status < 300
125
+ ? $parser($command, $response)
126
+ : new Result();
127
+
128
+ $metadata = [
129
+ 'statusCode' => $status,
130
+ 'effectiveUri' => (string) $request->getUri(),
131
+ 'headers' => [],
132
+ 'transferStats' => [],
133
+ ];
134
+ if (!empty($stats)) {
135
+ $metadata['transferStats']['http'] = [$stats];
136
+ }
137
+
138
+ // Bring headers into the metadata array.
139
+ foreach ($response->getHeaders() as $name => $values) {
140
+ $metadata['headers'][strtolower($name)] = $values[0];
141
+ }
142
+
143
+ $result['@metadata'] = $metadata;
144
+
145
+ return $result;
146
+ }
147
+
148
+ /**
149
+ * Parses a rejection into an AWS error.
150
+ *
151
+ * @param array $err Rejection error array.
152
+ * @param RequestInterface $request Request that was sent.
153
+ * @param CommandInterface $command Command being sent.
154
+ * @param array $stats Transfer statistics
155
+ *
156
+ * @return \Exception
157
+ */
158
+ private function parseError(
159
+ array $err,
160
+ RequestInterface $request,
161
+ CommandInterface $command,
162
+ array $stats
163
+ ) {
164
+ if (!isset($err['exception'])) {
165
+ throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.');
166
+ }
167
+
168
+ $serviceError = "AWS HTTP error: " . $err['exception']->getMessage();
169
+
170
+ if (!isset($err['response'])) {
171
+ $parts = ['response' => null];
172
+ } else {
173
+ try {
174
+ $parts = call_user_func($this->errorParser, $err['response']);
175
+ $serviceError .= " {$parts['code']} ({$parts['type']}): "
176
+ . "{$parts['message']} - " . $err['response']->getBody();
177
+ } catch (ParserException $e) {
178
+ $parts = [];
179
+ $serviceError .= ' Unable to parse error information from '
180
+ . "response - {$e->getMessage()}";
181
+ }
182
+
183
+ $parts['response'] = $err['response'];
184
+ }
185
+
186
+ $parts['exception'] = $err['exception'];
187
+ $parts['request'] = $request;
188
+ $parts['connection_error'] = !empty($err['connection_error']);
189
+ $parts['transfer_stats'] = $stats;
190
+
191
+ return new $this->exceptionClass(
192
+ sprintf(
193
+ 'Error executing "%s" on "%s"; %s',
194
+ $command->getName(),
195
+ $request->getUri(),
196
+ $serviceError
197
+ ),
198
+ $command,
199
+ $parts,
200
+ $err['exception']
201
+ );
202
+ }
203
+ }
lib/Aws/Aws/data/cloudfront/2015-07-27/api-2.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2015-07-27/api-2.json
3
+ return [ 'version' => '2.0', 'metadata' => [ 'apiVersion' => '2015-07-27', 'endpointPrefix' => 'cloudfront', 'globalEndpoint' => 'cloudfront.amazonaws.com', 'protocol' => 'rest-xml', 'serviceAbbreviation' => 'CloudFront', 'serviceFullName' => 'Amazon CloudFront', 'signatureVersion' => 'v4', ], 'operations' => [ 'CreateCloudFrontOriginAccessIdentity' => [ 'name' => 'CreateCloudFrontOriginAccessIdentity2015_07_27', 'http' => [ 'method' => 'POST', 'requestUri' => '/2015-07-27/origin-access-identity/cloudfront', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'CreateCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'CloudFrontOriginAccessIdentityAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCloudFrontOriginAccessIdentities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'CreateDistribution' => [ 'name' => 'CreateDistribution2015_07_27', 'http' => [ 'method' => 'POST', 'requestUri' => '/2015-07-27/distribution', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateDistributionRequest', ], 'output' => [ 'shape' => 'CreateDistributionResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'DistributionAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'InvalidOrigin', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'TooManyTrustedSigners', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TrustedSignerDoesNotExist', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidViewerCertificate', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidMinimumProtocolVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyDistributionCNAMEs', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyDistributions', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidDefaultRootObject', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidRelativePath', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidErrorCode', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidResponseCode', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidRequiredProtocol', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchOrigin', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'TooManyOrigins', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCacheBehaviors', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCookieNamesInWhiteList', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidForwardCookies', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyHeadersInForwardedValues', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidHeadersForS3Origin', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCertificates', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidLocationCode', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidGeoRestrictionParameter', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidProtocolSettings', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidTTLOrder', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidWebACLId', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'CreateInvalidation' => [ 'name' => 'CreateInvalidation2015_07_27', 'http' => [ 'method' => 'POST', 'requestUri' => '/2015-07-27/distribution/{DistributionId}/invalidation', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateInvalidationRequest', ], 'output' => [ 'shape' => 'CreateInvalidationResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'BatchTooLarge', 'error' => [ 'httpStatusCode' => 413, ], 'exception' => true, ], [ 'shape' => 'TooManyInvalidationsInProgress', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'CreateStreamingDistribution' => [ 'name' => 'CreateStreamingDistribution2015_07_27', 'http' => [ 'method' => 'POST', 'requestUri' => '/2015-07-27/streaming-distribution', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateStreamingDistributionRequest', ], 'output' => [ 'shape' => 'CreateStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'StreamingDistributionAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'InvalidOrigin', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'TooManyTrustedSigners', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TrustedSignerDoesNotExist', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyStreamingDistributions', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'DeleteCloudFrontOriginAccessIdentity' => [ 'name' => 'DeleteCloudFrontOriginAccessIdentity2015_07_27', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2015-07-27/origin-access-identity/cloudfront/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteCloudFrontOriginAccessIdentityRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'InvalidIfMatchVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'PreconditionFailed', 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], [ 'shape' => 'CloudFrontOriginAccessIdentityInUse', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], ], ], 'DeleteDistribution' => [ 'name' => 'DeleteDistribution2015_07_27', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2015-07-27/distribution/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteDistributionRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'DistributionNotDisabled', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'InvalidIfMatchVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'PreconditionFailed', 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], ], ], 'DeleteStreamingDistribution' => [ 'name' => 'DeleteStreamingDistribution2015_07_27', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2015-07-27/streaming-distribution/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteStreamingDistributionRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'StreamingDistributionNotDisabled', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'InvalidIfMatchVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchStreamingDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'PreconditionFailed', 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], ], ], 'GetCloudFrontOriginAccessIdentity' => [ 'name' => 'GetCloudFrontOriginAccessIdentity2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/origin-access-identity/cloudfront/{Id}', ], 'input' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'GetCloudFrontOriginAccessIdentityConfig' => [ 'name' => 'GetCloudFrontOriginAccessIdentityConfig2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/origin-access-identity/cloudfront/{Id}/config', ], 'input' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityConfigRequest', ], 'output' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'GetDistribution' => [ 'name' => 'GetDistribution2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/distribution/{Id}', ], 'input' => [ 'shape' => 'GetDistributionRequest', ], 'output' => [ 'shape' => 'GetDistributionResult', ], 'errors' => [ [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'GetDistributionConfig' => [ 'name' => 'GetDistributionConfig2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/distribution/{Id}/config', ], 'input' => [ 'shape' => 'GetDistributionConfigRequest', ], 'output' => [ 'shape' => 'GetDistributionConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'GetInvalidation' => [ 'name' => 'GetInvalidation2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/distribution/{DistributionId}/invalidation/{Id}', ], 'input' => [ 'shape' => 'GetInvalidationRequest', ], 'output' => [ 'shape' => 'GetInvalidationResult', ], 'errors' => [ [ 'shape' => 'NoSuchInvalidation', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'GetStreamingDistribution' => [ 'name' => 'GetStreamingDistribution2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/streaming-distribution/{Id}', ], 'input' => [ 'shape' => 'GetStreamingDistributionRequest', ], 'output' => [ 'shape' => 'GetStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'NoSuchStreamingDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'GetStreamingDistributionConfig' => [ 'name' => 'GetStreamingDistributionConfig2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/streaming-distribution/{Id}/config', ], 'input' => [ 'shape' => 'GetStreamingDistributionConfigRequest', ], 'output' => [ 'shape' => 'GetStreamingDistributionConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchStreamingDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'ListCloudFrontOriginAccessIdentities' => [ 'name' => 'ListCloudFrontOriginAccessIdentities2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/origin-access-identity/cloudfront', ], 'input' => [ 'shape' => 'ListCloudFrontOriginAccessIdentitiesRequest', ], 'output' => [ 'shape' => 'ListCloudFrontOriginAccessIdentitiesResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'ListDistributions' => [ 'name' => 'ListDistributions2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/distribution', ], 'input' => [ 'shape' => 'ListDistributionsRequest', ], 'output' => [ 'shape' => 'ListDistributionsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'ListDistributionsByWebACLId' => [ 'name' => 'ListDistributionsByWebACLId2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/distributionsByWebACLId/{WebACLId}', ], 'input' => [ 'shape' => 'ListDistributionsByWebACLIdRequest', ], 'output' => [ 'shape' => 'ListDistributionsByWebACLIdResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidWebACLId', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'ListInvalidations' => [ 'name' => 'ListInvalidations2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/distribution/{DistributionId}/invalidation', ], 'input' => [ 'shape' => 'ListInvalidationsRequest', ], 'output' => [ 'shape' => 'ListInvalidationsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], ], ], 'ListStreamingDistributions' => [ 'name' => 'ListStreamingDistributions2015_07_27', 'http' => [ 'method' => 'GET', 'requestUri' => '/2015-07-27/streaming-distribution', ], 'input' => [ 'shape' => 'ListStreamingDistributionsRequest', ], 'output' => [ 'shape' => 'ListStreamingDistributionsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'UpdateCloudFrontOriginAccessIdentity' => [ 'name' => 'UpdateCloudFrontOriginAccessIdentity2015_07_27', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2015-07-27/origin-access-identity/cloudfront/{Id}/config', ], 'input' => [ 'shape' => 'UpdateCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'UpdateCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'IllegalUpdate', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidIfMatchVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'PreconditionFailed', 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'UpdateDistribution' => [ 'name' => 'UpdateDistribution2015_07_27', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2015-07-27/distribution/{Id}/config', ], 'input' => [ 'shape' => 'UpdateDistributionRequest', ], 'output' => [ 'shape' => 'UpdateDistributionResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'CNAMEAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'IllegalUpdate', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidIfMatchVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'PreconditionFailed', 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], [ 'shape' => 'TooManyDistributionCNAMEs', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidDefaultRootObject', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidRelativePath', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidErrorCode', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidResponseCode', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyTrustedSigners', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TrustedSignerDoesNotExist', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidViewerCertificate', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidMinimumProtocolVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidRequiredProtocol', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchOrigin', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'TooManyOrigins', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCacheBehaviors', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCookieNamesInWhiteList', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidForwardCookies', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyHeadersInForwardedValues', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidHeadersForS3Origin', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyCertificates', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidLocationCode', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidGeoRestrictionParameter', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidTTLOrder', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidWebACLId', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], 'UpdateStreamingDistribution' => [ 'name' => 'UpdateStreamingDistribution2015_07_27', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2015-07-27/streaming-distribution/{Id}/config', ], 'input' => [ 'shape' => 'UpdateStreamingDistributionRequest', ], 'output' => [ 'shape' => 'UpdateStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], [ 'shape' => 'CNAMEAlreadyExists', 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], [ 'shape' => 'IllegalUpdate', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidIfMatchVersion', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'MissingBody', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'NoSuchStreamingDistribution', 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], [ 'shape' => 'PreconditionFailed', 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidArgument', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InvalidOriginAccessIdentity', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TooManyTrustedSigners', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'TrustedSignerDoesNotExist', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], [ 'shape' => 'InconsistentQuantities', 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], ], ], ], 'shapes' => [ 'AccessDenied' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], 'ActiveTrustedSigners' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Quantity', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'SignerList', ], ], ], 'AliasList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'CNAME', ], ], 'Aliases' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'AliasList', ], ], ], 'AllowedMethods' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'MethodsList', ], 'CachedMethods' => [ 'shape' => 'CachedMethods', ], ], ], 'AwsAccountNumberList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'AwsAccountNumber', ], ], 'BatchTooLarge' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 413, ], 'exception' => true, ], 'CNAMEAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CacheBehavior' => [ 'type' => 'structure', 'required' => [ 'PathPattern', 'TargetOriginId', 'ForwardedValues', 'TrustedSigners', 'ViewerProtocolPolicy', 'MinTTL', ], 'members' => [ 'PathPattern' => [ 'shape' => 'string', ], 'TargetOriginId' => [ 'shape' => 'string', ], 'ForwardedValues' => [ 'shape' => 'ForwardedValues', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'ViewerProtocolPolicy' => [ 'shape' => 'ViewerProtocolPolicy', ], 'MinTTL' => [ 'shape' => 'long', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', ], 'SmoothStreaming' => [ 'shape' => 'boolean', ], 'DefaultTTL' => [ 'shape' => 'long', ], 'MaxTTL' => [ 'shape' => 'long', ], ], ], 'CacheBehaviorList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CacheBehavior', 'locationName' => 'CacheBehavior', ], ], 'CacheBehaviors' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CacheBehaviorList', ], ], ], 'CachedMethods' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'MethodsList', ], ], ], 'CloudFrontOriginAccessIdentity' => [ 'type' => 'structure', 'required' => [ 'Id', 'S3CanonicalUserId', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'S3CanonicalUserId' => [ 'shape' => 'string', ], 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', ], ], ], 'CloudFrontOriginAccessIdentityAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CloudFrontOriginAccessIdentityConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'Comment', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'Comment' => [ 'shape' => 'string', ], ], ], 'CloudFrontOriginAccessIdentityInUse' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CloudFrontOriginAccessIdentityList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CloudFrontOriginAccessIdentitySummaryList', ], ], ], 'CloudFrontOriginAccessIdentitySummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'S3CanonicalUserId', 'Comment', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'S3CanonicalUserId' => [ 'shape' => 'string', ], 'Comment' => [ 'shape' => 'string', ], ], ], 'CloudFrontOriginAccessIdentitySummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CloudFrontOriginAccessIdentitySummary', 'locationName' => 'CloudFrontOriginAccessIdentitySummary', ], ], 'CookieNameList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Name', ], ], 'CookieNames' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CookieNameList', ], ], ], 'CookiePreference' => [ 'type' => 'structure', 'required' => [ 'Forward', ], 'members' => [ 'Forward' => [ 'shape' => 'ItemSelection', ], 'WhitelistedNames' => [ 'shape' => 'CookieNames', ], ], ], 'CreateCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'CloudFrontOriginAccessIdentityConfig', ], 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', 'locationName' => 'CloudFrontOriginAccessIdentityConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'CreateCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'CreateDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionConfig', ], 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', 'locationName' => 'DistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], ], 'payload' => 'DistributionConfig', ], 'CreateDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'CreateInvalidationRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', 'InvalidationBatch', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'InvalidationBatch' => [ 'shape' => 'InvalidationBatch', 'locationName' => 'InvalidationBatch', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], ], 'payload' => 'InvalidationBatch', ], 'CreateInvalidationResult' => [ 'type' => 'structure', 'members' => [ 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'Invalidation' => [ 'shape' => 'Invalidation', ], ], 'payload' => 'Invalidation', ], 'CreateStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfig', ], 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', 'locationName' => 'StreamingDistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], ], 'payload' => 'StreamingDistributionConfig', ], 'CreateStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'CustomErrorResponse' => [ 'type' => 'structure', 'required' => [ 'ErrorCode', ], 'members' => [ 'ErrorCode' => [ 'shape' => 'integer', ], 'ResponsePagePath' => [ 'shape' => 'string', ], 'ResponseCode' => [ 'shape' => 'string', ], 'ErrorCachingMinTTL' => [ 'shape' => 'long', ], ], ], 'CustomErrorResponseList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CustomErrorResponse', 'locationName' => 'CustomErrorResponse', ], ], 'CustomErrorResponses' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CustomErrorResponseList', ], ], ], 'CustomOriginConfig' => [ 'type' => 'structure', 'required' => [ 'HTTPPort', 'HTTPSPort', 'OriginProtocolPolicy', ], 'members' => [ 'HTTPPort' => [ 'shape' => 'integer', ], 'HTTPSPort' => [ 'shape' => 'integer', ], 'OriginProtocolPolicy' => [ 'shape' => 'OriginProtocolPolicy', ], ], ], 'DefaultCacheBehavior' => [ 'type' => 'structure', 'required' => [ 'TargetOriginId', 'ForwardedValues', 'TrustedSigners', 'ViewerProtocolPolicy', 'MinTTL', ], 'members' => [ 'TargetOriginId' => [ 'shape' => 'string', ], 'ForwardedValues' => [ 'shape' => 'ForwardedValues', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'ViewerProtocolPolicy' => [ 'shape' => 'ViewerProtocolPolicy', ], 'MinTTL' => [ 'shape' => 'long', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', ], 'SmoothStreaming' => [ 'shape' => 'boolean', ], 'DefaultTTL' => [ 'shape' => 'long', ], 'MaxTTL' => [ 'shape' => 'long', ], ], ], 'DeleteCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'DeleteDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'DeleteStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'Distribution' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'LastModifiedTime', 'InProgressInvalidationBatches', 'DomainName', 'ActiveTrustedSigners', 'DistributionConfig', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'InProgressInvalidationBatches' => [ 'shape' => 'integer', ], 'DomainName' => [ 'shape' => 'string', ], 'ActiveTrustedSigners' => [ 'shape' => 'ActiveTrustedSigners', ], 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], ], ], 'DistributionAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'DistributionConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'Origins', 'DefaultCacheBehavior', 'Comment', 'Enabled', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'DefaultRootObject' => [ 'shape' => 'string', ], 'Origins' => [ 'shape' => 'Origins', ], 'DefaultCacheBehavior' => [ 'shape' => 'DefaultCacheBehavior', ], 'CacheBehaviors' => [ 'shape' => 'CacheBehaviors', ], 'CustomErrorResponses' => [ 'shape' => 'CustomErrorResponses', ], 'Comment' => [ 'shape' => 'string', ], 'Logging' => [ 'shape' => 'LoggingConfig', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], 'ViewerCertificate' => [ 'shape' => 'ViewerCertificate', ], 'Restrictions' => [ 'shape' => 'Restrictions', ], 'WebACLId' => [ 'shape' => 'string', ], ], ], 'DistributionList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'DistributionSummaryList', ], ], ], 'DistributionNotDisabled' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'DistributionSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'LastModifiedTime', 'DomainName', 'Aliases', 'Origins', 'DefaultCacheBehavior', 'CacheBehaviors', 'CustomErrorResponses', 'Comment', 'PriceClass', 'Enabled', 'ViewerCertificate', 'Restrictions', 'WebACLId', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'Origins' => [ 'shape' => 'Origins', ], 'DefaultCacheBehavior' => [ 'shape' => 'DefaultCacheBehavior', ], 'CacheBehaviors' => [ 'shape' => 'CacheBehaviors', ], 'CustomErrorResponses' => [ 'shape' => 'CustomErrorResponses', ], 'Comment' => [ 'shape' => 'string', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], 'ViewerCertificate' => [ 'shape' => 'ViewerCertificate', ], 'Restrictions' => [ 'shape' => 'Restrictions', ], 'WebACLId' => [ 'shape' => 'string', ], ], ], 'DistributionSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'DistributionSummary', 'locationName' => 'DistributionSummary', ], ], 'ForwardedValues' => [ 'type' => 'structure', 'required' => [ 'QueryString', 'Cookies', ], 'members' => [ 'QueryString' => [ 'shape' => 'boolean', ], 'Cookies' => [ 'shape' => 'CookiePreference', ], 'Headers' => [ 'shape' => 'Headers', ], ], ], 'GeoRestriction' => [ 'type' => 'structure', 'required' => [ 'RestrictionType', 'Quantity', ], 'members' => [ 'RestrictionType' => [ 'shape' => 'GeoRestrictionType', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'LocationList', ], ], ], 'GeoRestrictionType' => [ 'type' => 'string', 'enum' => [ 'blacklist', 'whitelist', 'none', ], ], 'GetCloudFrontOriginAccessIdentityConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetCloudFrontOriginAccessIdentityConfigResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'GetCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'GetDistributionConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetDistributionConfigResult' => [ 'type' => 'structure', 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'DistributionConfig', ], 'GetDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'GetInvalidationRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', 'Id', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetInvalidationResult' => [ 'type' => 'structure', 'members' => [ 'Invalidation' => [ 'shape' => 'Invalidation', ], ], 'payload' => 'Invalidation', ], 'GetStreamingDistributionConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetStreamingDistributionConfigResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistributionConfig', ], 'GetStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'HeaderList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Name', ], ], 'Headers' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'HeaderList', ], ], ], 'IllegalUpdate' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InconsistentQuantities' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidArgument' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidDefaultRootObject' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidErrorCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidForwardCookies' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidGeoRestrictionParameter' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidHeadersForS3Origin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidIfMatchVersion' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidLocationCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidMinimumProtocolVersion' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidOrigin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidOriginAccessIdentity' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidProtocolSettings' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidRelativePath' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidRequiredProtocol' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidResponseCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidTTLOrder' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidViewerCertificate' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidWebACLId' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'Invalidation' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'CreateTime', 'InvalidationBatch', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'CreateTime' => [ 'shape' => 'timestamp', ], 'InvalidationBatch' => [ 'shape' => 'InvalidationBatch', ], ], ], 'InvalidationBatch' => [ 'type' => 'structure', 'required' => [ 'Paths', 'CallerReference', ], 'members' => [ 'Paths' => [ 'shape' => 'Paths', ], 'CallerReference' => [ 'shape' => 'string', ], ], ], 'InvalidationList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'InvalidationSummaryList', ], ], ], 'InvalidationSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'CreateTime', 'Status', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'CreateTime' => [ 'shape' => 'timestamp', ], 'Status' => [ 'shape' => 'string', ], ], ], 'InvalidationSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'InvalidationSummary', 'locationName' => 'InvalidationSummary', ], ], 'ItemSelection' => [ 'type' => 'string', 'enum' => [ 'none', 'whitelist', 'all', ], ], 'KeyPairIdList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'KeyPairId', ], ], 'KeyPairIds' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'KeyPairIdList', ], ], ], 'ListCloudFrontOriginAccessIdentitiesRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListCloudFrontOriginAccessIdentitiesResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentityList' => [ 'shape' => 'CloudFrontOriginAccessIdentityList', ], ], 'payload' => 'CloudFrontOriginAccessIdentityList', ], 'ListDistributionsByWebACLIdRequest' => [ 'type' => 'structure', 'required' => [ 'WebACLId', ], 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], 'WebACLId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'WebACLId', ], ], ], 'ListDistributionsByWebACLIdResult' => [ 'type' => 'structure', 'members' => [ 'DistributionList' => [ 'shape' => 'DistributionList', ], ], 'payload' => 'DistributionList', ], 'ListDistributionsRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListDistributionsResult' => [ 'type' => 'structure', 'members' => [ 'DistributionList' => [ 'shape' => 'DistributionList', ], ], 'payload' => 'DistributionList', ], 'ListInvalidationsRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListInvalidationsResult' => [ 'type' => 'structure', 'members' => [ 'InvalidationList' => [ 'shape' => 'InvalidationList', ], ], 'payload' => 'InvalidationList', ], 'ListStreamingDistributionsRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListStreamingDistributionsResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistributionList' => [ 'shape' => 'StreamingDistributionList', ], ], 'payload' => 'StreamingDistributionList', ], 'LocationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Location', ], ], 'LoggingConfig' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'IncludeCookies', 'Bucket', 'Prefix', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'IncludeCookies' => [ 'shape' => 'boolean', ], 'Bucket' => [ 'shape' => 'string', ], 'Prefix' => [ 'shape' => 'string', ], ], ], 'Method' => [ 'type' => 'string', 'enum' => [ 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', ], ], 'MethodsList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Method', 'locationName' => 'Method', ], ], 'MinimumProtocolVersion' => [ 'type' => 'string', 'enum' => [ 'SSLv3', 'TLSv1', ], ], 'MissingBody' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'NoSuchCloudFrontOriginAccessIdentity' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchDistribution' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchInvalidation' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchOrigin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchStreamingDistribution' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'Origin' => [ 'type' => 'structure', 'required' => [ 'Id', 'DomainName', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'DomainName' => [ 'shape' => 'string', ], 'OriginPath' => [ 'shape' => 'string', ], 'S3OriginConfig' => [ 'shape' => 'S3OriginConfig', ], 'CustomOriginConfig' => [ 'shape' => 'CustomOriginConfig', ], ], ], 'OriginList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Origin', 'locationName' => 'Origin', ], 'min' => 1, ], 'OriginProtocolPolicy' => [ 'type' => 'string', 'enum' => [ 'http-only', 'match-viewer', ], ], 'Origins' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'OriginList', ], ], ], 'PathList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Path', ], ], 'Paths' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'PathList', ], ], ], 'PreconditionFailed' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], 'PriceClass' => [ 'type' => 'string', 'enum' => [ 'PriceClass_100', 'PriceClass_200', 'PriceClass_All', ], ], 'Restrictions' => [ 'type' => 'structure', 'required' => [ 'GeoRestriction', ], 'members' => [ 'GeoRestriction' => [ 'shape' => 'GeoRestriction', ], ], ], 'S3Origin' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'OriginAccessIdentity', ], 'members' => [ 'DomainName' => [ 'shape' => 'string', ], 'OriginAccessIdentity' => [ 'shape' => 'string', ], ], ], 'S3OriginConfig' => [ 'type' => 'structure', 'required' => [ 'OriginAccessIdentity', ], 'members' => [ 'OriginAccessIdentity' => [ 'shape' => 'string', ], ], ], 'SSLSupportMethod' => [ 'type' => 'string', 'enum' => [ 'sni-only', 'vip', ], ], 'Signer' => [ 'type' => 'structure', 'members' => [ 'AwsAccountNumber' => [ 'shape' => 'string', ], 'KeyPairIds' => [ 'shape' => 'KeyPairIds', ], ], ], 'SignerList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Signer', 'locationName' => 'Signer', ], ], 'StreamingDistribution' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'DomainName', 'ActiveTrustedSigners', 'StreamingDistributionConfig', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'ActiveTrustedSigners' => [ 'shape' => 'ActiveTrustedSigners', ], 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], ], ], 'StreamingDistributionAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'StreamingDistributionConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'S3Origin', 'Comment', 'TrustedSigners', 'Enabled', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'S3Origin' => [ 'shape' => 'S3Origin', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'Comment' => [ 'shape' => 'string', ], 'Logging' => [ 'shape' => 'StreamingLoggingConfig', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], ], ], 'StreamingDistributionList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'StreamingDistributionSummaryList', ], ], ], 'StreamingDistributionNotDisabled' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'StreamingDistributionSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'LastModifiedTime', 'DomainName', 'S3Origin', 'Aliases', 'TrustedSigners', 'Comment', 'PriceClass', 'Enabled', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'S3Origin' => [ 'shape' => 'S3Origin', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'Comment' => [ 'shape' => 'string', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], ], ], 'StreamingDistributionSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'StreamingDistributionSummary', 'locationName' => 'StreamingDistributionSummary', ], ], 'StreamingLoggingConfig' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Bucket', 'Prefix', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Bucket' => [ 'shape' => 'string', ], 'Prefix' => [ 'shape' => 'string', ], ], ], 'TooManyCacheBehaviors' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCertificates' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCloudFrontOriginAccessIdentities' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCookieNamesInWhiteList' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyDistributionCNAMEs' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyDistributions' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyHeadersInForwardedValues' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyInvalidationsInProgress' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyOrigins' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyStreamingDistributionCNAMEs' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyStreamingDistributions' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyTrustedSigners' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TrustedSignerDoesNotExist' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TrustedSigners' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Quantity', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'AwsAccountNumberList', ], ], ], 'UpdateCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'CloudFrontOriginAccessIdentityConfig', 'Id', ], 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', 'locationName' => 'CloudFrontOriginAccessIdentityConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'UpdateCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'UpdateDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionConfig', 'Id', ], 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', 'locationName' => 'DistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], 'payload' => 'DistributionConfig', ], 'UpdateDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'UpdateStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfig', 'Id', ], 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', 'locationName' => 'StreamingDistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2015-07-27/', ], ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], 'payload' => 'StreamingDistributionConfig', ], 'UpdateStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'ViewerCertificate' => [ 'type' => 'structure', 'members' => [ 'IAMCertificateId' => [ 'shape' => 'string', ], 'CloudFrontDefaultCertificate' => [ 'shape' => 'boolean', ], 'SSLSupportMethod' => [ 'shape' => 'SSLSupportMethod', ], 'MinimumProtocolVersion' => [ 'shape' => 'MinimumProtocolVersion', ], ], ], 'ViewerProtocolPolicy' => [ 'type' => 'string', 'enum' => [ 'allow-all', 'https-only', 'redirect-to-https', ], ], 'boolean' => [ 'type' => 'boolean', ], 'integer' => [ 'type' => 'integer', ], 'long' => [ 'type' => 'long', ], 'string' => [ 'type' => 'string', ], 'timestamp' => [ 'type' => 'timestamp', ], ],];
lib/Aws/Aws/data/cloudfront/2015-07-27/paginators-1.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2015-07-27/paginators-1.json
3
+ return [ 'pagination' => [ 'ListCloudFrontOriginAccessIdentities' => [ 'input_token' => 'Marker', 'limit_key' => 'MaxItems', 'more_results' => 'CloudFrontOriginAccessIdentityList.IsTruncated', 'output_token' => 'CloudFrontOriginAccessIdentityList.NextMarker', 'result_key' => 'CloudFrontOriginAccessIdentityList.Items', ], 'ListDistributions' => [ 'input_token' => 'Marker', 'limit_key' => 'MaxItems', 'more_results' => 'DistributionList.IsTruncated', 'output_token' => 'DistributionList.NextMarker', 'result_key' => 'DistributionList.Items', ], 'ListInvalidations' => [ 'input_token' => 'Marker', 'limit_key' => 'MaxItems', 'more_results' => 'InvalidationList.IsTruncated', 'output_token' => 'InvalidationList.NextMarker', 'result_key' => 'InvalidationList.Items', ], 'ListStreamingDistributions' => [ 'input_token' => 'Marker', 'limit_key' => 'MaxItems', 'more_results' => 'StreamingDistributionList.IsTruncated', 'output_token' => 'StreamingDistributionList.NextMarker', 'result_key' => 'StreamingDistributionList.Items', ], ],];
lib/Aws/Aws/data/cloudfront/2015-07-27/waiters-2.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2015-07-27/waiters-2.json
3
+ return [ 'version' => 2, 'waiters' => [ 'DistributionDeployed' => [ 'acceptors' => [ [ 'argument' => 'Distribution.Status', 'expected' => 'Deployed', 'matcher' => 'path', 'state' => 'success', ], ], 'delay' => 60, 'description' => 'Wait until a distribution is deployed.', 'maxAttempts' => 25, 'operation' => 'GetDistribution', ], 'InvalidationCompleted' => [ 'acceptors' => [ [ 'argument' => 'Invalidation.Status', 'expected' => 'Completed', 'matcher' => 'path', 'state' => 'success', ], ], 'delay' => 20, 'description' => 'Wait until an invalidation has completed.', 'maxAttempts' => 30, 'operation' => 'GetInvalidation', ], 'StreamingDistributionDeployed' => [ 'acceptors' => [ [ 'argument' => 'StreamingDistribution.Status', 'expected' => 'Deployed', 'matcher' => 'path', 'state' => 'success', ], ], 'delay' => 60, 'description' => 'Wait until a streaming distribution is deployed.', 'maxAttempts' => 25, 'operation' => 'GetStreamingDistribution', ], ],];
lib/Aws/Aws/data/cloudfront/2016-01-28/api-2.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2016-01-28/api-2.json
3
+ return [ 'version' => '2.0', 'metadata' => [ 'uid' => 'cloudfront-2016-01-28', 'apiVersion' => '2016-01-28', 'endpointPrefix' => 'cloudfront', 'globalEndpoint' => 'cloudfront.amazonaws.com', 'protocol' => 'rest-xml', 'serviceAbbreviation' => 'CloudFront', 'serviceFullName' => 'Amazon CloudFront', 'signatureVersion' => 'v4', ], 'operations' => [ 'CreateCloudFrontOriginAccessIdentity' => [ 'name' => 'CreateCloudFrontOriginAccessIdentity2016_01_28', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-01-28/origin-access-identity/cloudfront', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'CreateCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'CloudFrontOriginAccessIdentityAlreadyExists', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyCloudFrontOriginAccessIdentities', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'CreateDistribution' => [ 'name' => 'CreateDistribution2016_01_28', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-01-28/distribution', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateDistributionRequest', ], 'output' => [ 'shape' => 'CreateDistributionResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'DistributionAlreadyExists', ], [ 'shape' => 'InvalidOrigin', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InvalidViewerCertificate', ], [ 'shape' => 'InvalidMinimumProtocolVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyDistributionCNAMEs', ], [ 'shape' => 'TooManyDistributions', ], [ 'shape' => 'InvalidDefaultRootObject', ], [ 'shape' => 'InvalidRelativePath', ], [ 'shape' => 'InvalidErrorCode', ], [ 'shape' => 'InvalidResponseCode', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidRequiredProtocol', ], [ 'shape' => 'NoSuchOrigin', ], [ 'shape' => 'TooManyOrigins', ], [ 'shape' => 'TooManyCacheBehaviors', ], [ 'shape' => 'TooManyCookieNamesInWhiteList', ], [ 'shape' => 'InvalidForwardCookies', ], [ 'shape' => 'TooManyHeadersInForwardedValues', ], [ 'shape' => 'InvalidHeadersForS3Origin', ], [ 'shape' => 'InconsistentQuantities', ], [ 'shape' => 'TooManyCertificates', ], [ 'shape' => 'InvalidLocationCode', ], [ 'shape' => 'InvalidGeoRestrictionParameter', ], [ 'shape' => 'InvalidProtocolSettings', ], [ 'shape' => 'InvalidTTLOrder', ], [ 'shape' => 'InvalidWebACLId', ], [ 'shape' => 'TooManyOriginCustomHeaders', ], ], ], 'CreateInvalidation' => [ 'name' => 'CreateInvalidation2016_01_28', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-01-28/distribution/{DistributionId}/invalidation', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateInvalidationRequest', ], 'output' => [ 'shape' => 'CreateInvalidationResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'BatchTooLarge', ], [ 'shape' => 'TooManyInvalidationsInProgress', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'CreateStreamingDistribution' => [ 'name' => 'CreateStreamingDistribution2016_01_28', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-01-28/streaming-distribution', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateStreamingDistributionRequest', ], 'output' => [ 'shape' => 'CreateStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'StreamingDistributionAlreadyExists', ], [ 'shape' => 'InvalidOrigin', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', ], [ 'shape' => 'TooManyStreamingDistributions', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'DeleteCloudFrontOriginAccessIdentity' => [ 'name' => 'DeleteCloudFrontOriginAccessIdentity2016_01_28', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2016-01-28/origin-access-identity/cloudfront/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteCloudFrontOriginAccessIdentityRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'CloudFrontOriginAccessIdentityInUse', ], ], ], 'DeleteDistribution' => [ 'name' => 'DeleteDistribution2016_01_28', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2016-01-28/distribution/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteDistributionRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'DistributionNotDisabled', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'PreconditionFailed', ], ], ], 'DeleteStreamingDistribution' => [ 'name' => 'DeleteStreamingDistribution2016_01_28', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2016-01-28/streaming-distribution/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteStreamingDistributionRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'StreamingDistributionNotDisabled', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'PreconditionFailed', ], ], ], 'GetCloudFrontOriginAccessIdentity' => [ 'name' => 'GetCloudFrontOriginAccessIdentity2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/origin-access-identity/cloudfront/{Id}', ], 'input' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetCloudFrontOriginAccessIdentityConfig' => [ 'name' => 'GetCloudFrontOriginAccessIdentityConfig2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/origin-access-identity/cloudfront/{Id}/config', ], 'input' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityConfigRequest', ], 'output' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetDistribution' => [ 'name' => 'GetDistribution2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/distribution/{Id}', ], 'input' => [ 'shape' => 'GetDistributionRequest', ], 'output' => [ 'shape' => 'GetDistributionResult', ], 'errors' => [ [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetDistributionConfig' => [ 'name' => 'GetDistributionConfig2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/distribution/{Id}/config', ], 'input' => [ 'shape' => 'GetDistributionConfigRequest', ], 'output' => [ 'shape' => 'GetDistributionConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetInvalidation' => [ 'name' => 'GetInvalidation2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/distribution/{DistributionId}/invalidation/{Id}', ], 'input' => [ 'shape' => 'GetInvalidationRequest', ], 'output' => [ 'shape' => 'GetInvalidationResult', ], 'errors' => [ [ 'shape' => 'NoSuchInvalidation', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetStreamingDistribution' => [ 'name' => 'GetStreamingDistribution2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/streaming-distribution/{Id}', ], 'input' => [ 'shape' => 'GetStreamingDistributionRequest', ], 'output' => [ 'shape' => 'GetStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetStreamingDistributionConfig' => [ 'name' => 'GetStreamingDistributionConfig2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/streaming-distribution/{Id}/config', ], 'input' => [ 'shape' => 'GetStreamingDistributionConfigRequest', ], 'output' => [ 'shape' => 'GetStreamingDistributionConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'ListCloudFrontOriginAccessIdentities' => [ 'name' => 'ListCloudFrontOriginAccessIdentities2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/origin-access-identity/cloudfront', ], 'input' => [ 'shape' => 'ListCloudFrontOriginAccessIdentitiesRequest', ], 'output' => [ 'shape' => 'ListCloudFrontOriginAccessIdentitiesResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], ], ], 'ListDistributions' => [ 'name' => 'ListDistributions2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/distribution', ], 'input' => [ 'shape' => 'ListDistributionsRequest', ], 'output' => [ 'shape' => 'ListDistributionsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], ], ], 'ListDistributionsByWebACLId' => [ 'name' => 'ListDistributionsByWebACLId2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/distributionsByWebACLId/{WebACLId}', ], 'input' => [ 'shape' => 'ListDistributionsByWebACLIdRequest', ], 'output' => [ 'shape' => 'ListDistributionsByWebACLIdResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidWebACLId', ], ], ], 'ListInvalidations' => [ 'name' => 'ListInvalidations2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/distribution/{DistributionId}/invalidation', ], 'input' => [ 'shape' => 'ListInvalidationsRequest', ], 'output' => [ 'shape' => 'ListInvalidationsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'ListStreamingDistributions' => [ 'name' => 'ListStreamingDistributions2016_01_28', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-01-28/streaming-distribution', ], 'input' => [ 'shape' => 'ListStreamingDistributionsRequest', ], 'output' => [ 'shape' => 'ListStreamingDistributionsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], ], ], 'UpdateCloudFrontOriginAccessIdentity' => [ 'name' => 'UpdateCloudFrontOriginAccessIdentity2016_01_28', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2016-01-28/origin-access-identity/cloudfront/{Id}/config', ], 'input' => [ 'shape' => 'UpdateCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'UpdateCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'IllegalUpdate', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'UpdateDistribution' => [ 'name' => 'UpdateDistribution2016_01_28', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2016-01-28/distribution/{Id}/config', ], 'input' => [ 'shape' => 'UpdateDistributionRequest', ], 'output' => [ 'shape' => 'UpdateDistributionResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'IllegalUpdate', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'TooManyDistributionCNAMEs', ], [ 'shape' => 'InvalidDefaultRootObject', ], [ 'shape' => 'InvalidRelativePath', ], [ 'shape' => 'InvalidErrorCode', ], [ 'shape' => 'InvalidResponseCode', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InvalidViewerCertificate', ], [ 'shape' => 'InvalidMinimumProtocolVersion', ], [ 'shape' => 'InvalidRequiredProtocol', ], [ 'shape' => 'NoSuchOrigin', ], [ 'shape' => 'TooManyOrigins', ], [ 'shape' => 'TooManyCacheBehaviors', ], [ 'shape' => 'TooManyCookieNamesInWhiteList', ], [ 'shape' => 'InvalidForwardCookies', ], [ 'shape' => 'TooManyHeadersInForwardedValues', ], [ 'shape' => 'InvalidHeadersForS3Origin', ], [ 'shape' => 'InconsistentQuantities', ], [ 'shape' => 'TooManyCertificates', ], [ 'shape' => 'InvalidLocationCode', ], [ 'shape' => 'InvalidGeoRestrictionParameter', ], [ 'shape' => 'InvalidTTLOrder', ], [ 'shape' => 'InvalidWebACLId', ], [ 'shape' => 'TooManyOriginCustomHeaders', ], ], ], 'UpdateStreamingDistribution' => [ 'name' => 'UpdateStreamingDistribution2016_01_28', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2016-01-28/streaming-distribution/{Id}/config', ], 'input' => [ 'shape' => 'UpdateStreamingDistributionRequest', ], 'output' => [ 'shape' => 'UpdateStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'IllegalUpdate', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InconsistentQuantities', ], ], ], ], 'shapes' => [ 'AccessDenied' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], 'ActiveTrustedSigners' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Quantity', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'SignerList', ], ], ], 'AliasList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'CNAME', ], ], 'Aliases' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'AliasList', ], ], ], 'AllowedMethods' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'MethodsList', ], 'CachedMethods' => [ 'shape' => 'CachedMethods', ], ], ], 'AwsAccountNumberList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'AwsAccountNumber', ], ], 'BatchTooLarge' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 413, ], 'exception' => true, ], 'CNAMEAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CacheBehavior' => [ 'type' => 'structure', 'required' => [ 'PathPattern', 'TargetOriginId', 'ForwardedValues', 'TrustedSigners', 'ViewerProtocolPolicy', 'MinTTL', ], 'members' => [ 'PathPattern' => [ 'shape' => 'string', ], 'TargetOriginId' => [ 'shape' => 'string', ], 'ForwardedValues' => [ 'shape' => 'ForwardedValues', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'ViewerProtocolPolicy' => [ 'shape' => 'ViewerProtocolPolicy', ], 'MinTTL' => [ 'shape' => 'long', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', ], 'SmoothStreaming' => [ 'shape' => 'boolean', ], 'DefaultTTL' => [ 'shape' => 'long', ], 'MaxTTL' => [ 'shape' => 'long', ], 'Compress' => [ 'shape' => 'boolean', ], ], ], 'CacheBehaviorList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CacheBehavior', 'locationName' => 'CacheBehavior', ], ], 'CacheBehaviors' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CacheBehaviorList', ], ], ], 'CachedMethods' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'MethodsList', ], ], ], 'CertificateSource' => [ 'type' => 'string', 'enum' => [ 'cloudfront', 'iam', 'acm', ], ], 'CloudFrontOriginAccessIdentity' => [ 'type' => 'structure', 'required' => [ 'Id', 'S3CanonicalUserId', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'S3CanonicalUserId' => [ 'shape' => 'string', ], 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', ], ], ], 'CloudFrontOriginAccessIdentityAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CloudFrontOriginAccessIdentityConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'Comment', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'Comment' => [ 'shape' => 'string', ], ], ], 'CloudFrontOriginAccessIdentityInUse' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CloudFrontOriginAccessIdentityList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CloudFrontOriginAccessIdentitySummaryList', ], ], ], 'CloudFrontOriginAccessIdentitySummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'S3CanonicalUserId', 'Comment', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'S3CanonicalUserId' => [ 'shape' => 'string', ], 'Comment' => [ 'shape' => 'string', ], ], ], 'CloudFrontOriginAccessIdentitySummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CloudFrontOriginAccessIdentitySummary', 'locationName' => 'CloudFrontOriginAccessIdentitySummary', ], ], 'CookieNameList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Name', ], ], 'CookieNames' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CookieNameList', ], ], ], 'CookiePreference' => [ 'type' => 'structure', 'required' => [ 'Forward', ], 'members' => [ 'Forward' => [ 'shape' => 'ItemSelection', ], 'WhitelistedNames' => [ 'shape' => 'CookieNames', ], ], ], 'CreateCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'CloudFrontOriginAccessIdentityConfig', ], 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', 'locationName' => 'CloudFrontOriginAccessIdentityConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'CreateCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'CreateDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionConfig', ], 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', 'locationName' => 'DistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], ], 'payload' => 'DistributionConfig', ], 'CreateDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'CreateInvalidationRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', 'InvalidationBatch', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'InvalidationBatch' => [ 'shape' => 'InvalidationBatch', 'locationName' => 'InvalidationBatch', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], ], 'payload' => 'InvalidationBatch', ], 'CreateInvalidationResult' => [ 'type' => 'structure', 'members' => [ 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'Invalidation' => [ 'shape' => 'Invalidation', ], ], 'payload' => 'Invalidation', ], 'CreateStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfig', ], 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', 'locationName' => 'StreamingDistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], ], 'payload' => 'StreamingDistributionConfig', ], 'CreateStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'CustomErrorResponse' => [ 'type' => 'structure', 'required' => [ 'ErrorCode', ], 'members' => [ 'ErrorCode' => [ 'shape' => 'integer', ], 'ResponsePagePath' => [ 'shape' => 'string', ], 'ResponseCode' => [ 'shape' => 'string', ], 'ErrorCachingMinTTL' => [ 'shape' => 'long', ], ], ], 'CustomErrorResponseList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CustomErrorResponse', 'locationName' => 'CustomErrorResponse', ], ], 'CustomErrorResponses' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CustomErrorResponseList', ], ], ], 'CustomHeaders' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'OriginCustomHeadersList', ], ], ], 'CustomOriginConfig' => [ 'type' => 'structure', 'required' => [ 'HTTPPort', 'HTTPSPort', 'OriginProtocolPolicy', ], 'members' => [ 'HTTPPort' => [ 'shape' => 'integer', ], 'HTTPSPort' => [ 'shape' => 'integer', ], 'OriginProtocolPolicy' => [ 'shape' => 'OriginProtocolPolicy', ], 'OriginSslProtocols' => [ 'shape' => 'OriginSslProtocols', ], ], ], 'DefaultCacheBehavior' => [ 'type' => 'structure', 'required' => [ 'TargetOriginId', 'ForwardedValues', 'TrustedSigners', 'ViewerProtocolPolicy', 'MinTTL', ], 'members' => [ 'TargetOriginId' => [ 'shape' => 'string', ], 'ForwardedValues' => [ 'shape' => 'ForwardedValues', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'ViewerProtocolPolicy' => [ 'shape' => 'ViewerProtocolPolicy', ], 'MinTTL' => [ 'shape' => 'long', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', ], 'SmoothStreaming' => [ 'shape' => 'boolean', ], 'DefaultTTL' => [ 'shape' => 'long', ], 'MaxTTL' => [ 'shape' => 'long', ], 'Compress' => [ 'shape' => 'boolean', ], ], ], 'DeleteCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'DeleteDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'DeleteStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'Distribution' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'LastModifiedTime', 'InProgressInvalidationBatches', 'DomainName', 'ActiveTrustedSigners', 'DistributionConfig', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'InProgressInvalidationBatches' => [ 'shape' => 'integer', ], 'DomainName' => [ 'shape' => 'string', ], 'ActiveTrustedSigners' => [ 'shape' => 'ActiveTrustedSigners', ], 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], ], ], 'DistributionAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'DistributionConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'Origins', 'DefaultCacheBehavior', 'Comment', 'Enabled', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'DefaultRootObject' => [ 'shape' => 'string', ], 'Origins' => [ 'shape' => 'Origins', ], 'DefaultCacheBehavior' => [ 'shape' => 'DefaultCacheBehavior', ], 'CacheBehaviors' => [ 'shape' => 'CacheBehaviors', ], 'CustomErrorResponses' => [ 'shape' => 'CustomErrorResponses', ], 'Comment' => [ 'shape' => 'string', ], 'Logging' => [ 'shape' => 'LoggingConfig', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], 'ViewerCertificate' => [ 'shape' => 'ViewerCertificate', ], 'Restrictions' => [ 'shape' => 'Restrictions', ], 'WebACLId' => [ 'shape' => 'string', ], ], ], 'DistributionList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'DistributionSummaryList', ], ], ], 'DistributionNotDisabled' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'DistributionSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'LastModifiedTime', 'DomainName', 'Aliases', 'Origins', 'DefaultCacheBehavior', 'CacheBehaviors', 'CustomErrorResponses', 'Comment', 'PriceClass', 'Enabled', 'ViewerCertificate', 'Restrictions', 'WebACLId', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'Origins' => [ 'shape' => 'Origins', ], 'DefaultCacheBehavior' => [ 'shape' => 'DefaultCacheBehavior', ], 'CacheBehaviors' => [ 'shape' => 'CacheBehaviors', ], 'CustomErrorResponses' => [ 'shape' => 'CustomErrorResponses', ], 'Comment' => [ 'shape' => 'string', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], 'ViewerCertificate' => [ 'shape' => 'ViewerCertificate', ], 'Restrictions' => [ 'shape' => 'Restrictions', ], 'WebACLId' => [ 'shape' => 'string', ], ], ], 'DistributionSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'DistributionSummary', 'locationName' => 'DistributionSummary', ], ], 'ForwardedValues' => [ 'type' => 'structure', 'required' => [ 'QueryString', 'Cookies', ], 'members' => [ 'QueryString' => [ 'shape' => 'boolean', ], 'Cookies' => [ 'shape' => 'CookiePreference', ], 'Headers' => [ 'shape' => 'Headers', ], ], ], 'GeoRestriction' => [ 'type' => 'structure', 'required' => [ 'RestrictionType', 'Quantity', ], 'members' => [ 'RestrictionType' => [ 'shape' => 'GeoRestrictionType', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'LocationList', ], ], ], 'GeoRestrictionType' => [ 'type' => 'string', 'enum' => [ 'blacklist', 'whitelist', 'none', ], ], 'GetCloudFrontOriginAccessIdentityConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetCloudFrontOriginAccessIdentityConfigResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'GetCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'GetDistributionConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetDistributionConfigResult' => [ 'type' => 'structure', 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'DistributionConfig', ], 'GetDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'GetInvalidationRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', 'Id', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetInvalidationResult' => [ 'type' => 'structure', 'members' => [ 'Invalidation' => [ 'shape' => 'Invalidation', ], ], 'payload' => 'Invalidation', ], 'GetStreamingDistributionConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetStreamingDistributionConfigResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistributionConfig', ], 'GetStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'HeaderList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Name', ], ], 'Headers' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'HeaderList', ], ], ], 'IllegalUpdate' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InconsistentQuantities' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidArgument' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidDefaultRootObject' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidErrorCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidForwardCookies' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidGeoRestrictionParameter' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidHeadersForS3Origin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidIfMatchVersion' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidLocationCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidMinimumProtocolVersion' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidOrigin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidOriginAccessIdentity' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidProtocolSettings' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidRelativePath' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidRequiredProtocol' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidResponseCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidTTLOrder' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidViewerCertificate' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidWebACLId' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'Invalidation' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'CreateTime', 'InvalidationBatch', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'CreateTime' => [ 'shape' => 'timestamp', ], 'InvalidationBatch' => [ 'shape' => 'InvalidationBatch', ], ], ], 'InvalidationBatch' => [ 'type' => 'structure', 'required' => [ 'Paths', 'CallerReference', ], 'members' => [ 'Paths' => [ 'shape' => 'Paths', ], 'CallerReference' => [ 'shape' => 'string', ], ], ], 'InvalidationList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'InvalidationSummaryList', ], ], ], 'InvalidationSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'CreateTime', 'Status', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'CreateTime' => [ 'shape' => 'timestamp', ], 'Status' => [ 'shape' => 'string', ], ], ], 'InvalidationSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'InvalidationSummary', 'locationName' => 'InvalidationSummary', ], ], 'ItemSelection' => [ 'type' => 'string', 'enum' => [ 'none', 'whitelist', 'all', ], ], 'KeyPairIdList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'KeyPairId', ], ], 'KeyPairIds' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'KeyPairIdList', ], ], ], 'ListCloudFrontOriginAccessIdentitiesRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListCloudFrontOriginAccessIdentitiesResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentityList' => [ 'shape' => 'CloudFrontOriginAccessIdentityList', ], ], 'payload' => 'CloudFrontOriginAccessIdentityList', ], 'ListDistributionsByWebACLIdRequest' => [ 'type' => 'structure', 'required' => [ 'WebACLId', ], 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], 'WebACLId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'WebACLId', ], ], ], 'ListDistributionsByWebACLIdResult' => [ 'type' => 'structure', 'members' => [ 'DistributionList' => [ 'shape' => 'DistributionList', ], ], 'payload' => 'DistributionList', ], 'ListDistributionsRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListDistributionsResult' => [ 'type' => 'structure', 'members' => [ 'DistributionList' => [ 'shape' => 'DistributionList', ], ], 'payload' => 'DistributionList', ], 'ListInvalidationsRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListInvalidationsResult' => [ 'type' => 'structure', 'members' => [ 'InvalidationList' => [ 'shape' => 'InvalidationList', ], ], 'payload' => 'InvalidationList', ], 'ListStreamingDistributionsRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListStreamingDistributionsResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistributionList' => [ 'shape' => 'StreamingDistributionList', ], ], 'payload' => 'StreamingDistributionList', ], 'LocationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Location', ], ], 'LoggingConfig' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'IncludeCookies', 'Bucket', 'Prefix', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'IncludeCookies' => [ 'shape' => 'boolean', ], 'Bucket' => [ 'shape' => 'string', ], 'Prefix' => [ 'shape' => 'string', ], ], ], 'Method' => [ 'type' => 'string', 'enum' => [ 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', ], ], 'MethodsList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Method', 'locationName' => 'Method', ], ], 'MinimumProtocolVersion' => [ 'type' => 'string', 'enum' => [ 'SSLv3', 'TLSv1', ], ], 'MissingBody' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'NoSuchCloudFrontOriginAccessIdentity' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchDistribution' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchInvalidation' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchOrigin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchStreamingDistribution' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'Origin' => [ 'type' => 'structure', 'required' => [ 'Id', 'DomainName', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'DomainName' => [ 'shape' => 'string', ], 'OriginPath' => [ 'shape' => 'string', ], 'CustomHeaders' => [ 'shape' => 'CustomHeaders', ], 'S3OriginConfig' => [ 'shape' => 'S3OriginConfig', ], 'CustomOriginConfig' => [ 'shape' => 'CustomOriginConfig', ], ], ], 'OriginCustomHeader' => [ 'type' => 'structure', 'required' => [ 'HeaderName', 'HeaderValue', ], 'members' => [ 'HeaderName' => [ 'shape' => 'string', ], 'HeaderValue' => [ 'shape' => 'string', ], ], ], 'OriginCustomHeadersList' => [ 'type' => 'list', 'member' => [ 'shape' => 'OriginCustomHeader', 'locationName' => 'OriginCustomHeader', ], ], 'OriginList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Origin', 'locationName' => 'Origin', ], 'min' => 1, ], 'OriginProtocolPolicy' => [ 'type' => 'string', 'enum' => [ 'http-only', 'match-viewer', 'https-only', ], ], 'OriginSslProtocols' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'SslProtocolsList', ], ], ], 'Origins' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'OriginList', ], ], ], 'PathList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Path', ], ], 'Paths' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'PathList', ], ], ], 'PreconditionFailed' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], 'PriceClass' => [ 'type' => 'string', 'enum' => [ 'PriceClass_100', 'PriceClass_200', 'PriceClass_All', ], ], 'Restrictions' => [ 'type' => 'structure', 'required' => [ 'GeoRestriction', ], 'members' => [ 'GeoRestriction' => [ 'shape' => 'GeoRestriction', ], ], ], 'S3Origin' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'OriginAccessIdentity', ], 'members' => [ 'DomainName' => [ 'shape' => 'string', ], 'OriginAccessIdentity' => [ 'shape' => 'string', ], ], ], 'S3OriginConfig' => [ 'type' => 'structure', 'required' => [ 'OriginAccessIdentity', ], 'members' => [ 'OriginAccessIdentity' => [ 'shape' => 'string', ], ], ], 'SSLSupportMethod' => [ 'type' => 'string', 'enum' => [ 'sni-only', 'vip', ], ], 'Signer' => [ 'type' => 'structure', 'members' => [ 'AwsAccountNumber' => [ 'shape' => 'string', ], 'KeyPairIds' => [ 'shape' => 'KeyPairIds', ], ], ], 'SignerList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Signer', 'locationName' => 'Signer', ], ], 'SslProtocol' => [ 'type' => 'string', 'enum' => [ 'SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2', ], ], 'SslProtocolsList' => [ 'type' => 'list', 'member' => [ 'shape' => 'SslProtocol', 'locationName' => 'SslProtocol', ], ], 'StreamingDistribution' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'DomainName', 'ActiveTrustedSigners', 'StreamingDistributionConfig', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'ActiveTrustedSigners' => [ 'shape' => 'ActiveTrustedSigners', ], 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], ], ], 'StreamingDistributionAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'StreamingDistributionConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'S3Origin', 'Comment', 'TrustedSigners', 'Enabled', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'S3Origin' => [ 'shape' => 'S3Origin', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'Comment' => [ 'shape' => 'string', ], 'Logging' => [ 'shape' => 'StreamingLoggingConfig', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], ], ], 'StreamingDistributionList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'StreamingDistributionSummaryList', ], ], ], 'StreamingDistributionNotDisabled' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'StreamingDistributionSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'LastModifiedTime', 'DomainName', 'S3Origin', 'Aliases', 'TrustedSigners', 'Comment', 'PriceClass', 'Enabled', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'S3Origin' => [ 'shape' => 'S3Origin', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'Comment' => [ 'shape' => 'string', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], ], ], 'StreamingDistributionSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'StreamingDistributionSummary', 'locationName' => 'StreamingDistributionSummary', ], ], 'StreamingLoggingConfig' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Bucket', 'Prefix', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Bucket' => [ 'shape' => 'string', ], 'Prefix' => [ 'shape' => 'string', ], ], ], 'TooManyCacheBehaviors' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCertificates' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCloudFrontOriginAccessIdentities' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCookieNamesInWhiteList' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyDistributionCNAMEs' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyDistributions' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyHeadersInForwardedValues' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyInvalidationsInProgress' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyOriginCustomHeaders' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyOrigins' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyStreamingDistributionCNAMEs' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyStreamingDistributions' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyTrustedSigners' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TrustedSignerDoesNotExist' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TrustedSigners' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Quantity', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'AwsAccountNumberList', ], ], ], 'UpdateCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'CloudFrontOriginAccessIdentityConfig', 'Id', ], 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', 'locationName' => 'CloudFrontOriginAccessIdentityConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'UpdateCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'UpdateDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionConfig', 'Id', ], 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', 'locationName' => 'DistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], 'payload' => 'DistributionConfig', ], 'UpdateDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'UpdateStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfig', 'Id', ], 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', 'locationName' => 'StreamingDistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-01-28/', ], ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], 'payload' => 'StreamingDistributionConfig', ], 'UpdateStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'ViewerCertificate' => [ 'type' => 'structure', 'members' => [ 'CloudFrontDefaultCertificate' => [ 'shape' => 'boolean', ], 'IAMCertificateId' => [ 'shape' => 'string', ], 'ACMCertificateArn' => [ 'shape' => 'string', ], 'SSLSupportMethod' => [ 'shape' => 'SSLSupportMethod', ], 'MinimumProtocolVersion' => [ 'shape' => 'MinimumProtocolVersion', ], 'Certificate' => [ 'shape' => 'string', 'deprecated' => true, ], 'CertificateSource' => [ 'shape' => 'CertificateSource', 'deprecated' => true, ], ], ], 'ViewerProtocolPolicy' => [ 'type' => 'string', 'enum' => [ 'allow-all', 'https-only', 'redirect-to-https', ], ], 'boolean' => [ 'type' => 'boolean', ], 'integer' => [ 'type' => 'integer', ], 'long' => [ 'type' => 'long', ], 'string' => [ 'type' => 'string', ], 'timestamp' => [ 'type' => 'timestamp', ], ],];
lib/Aws/Aws/data/cloudfront/2016-01-28/paginators-1.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2016-01-28/paginators-1.json
3
+ return [ 'pagination' => [ 'ListCloudFrontOriginAccessIdentities' => [ 'input_token' => 'Marker', 'output_token' => 'CloudFrontOriginAccessIdentityList.NextMarker', 'limit_key' => 'MaxItems', 'more_results' => 'CloudFrontOriginAccessIdentityList.IsTruncated', 'result_key' => 'CloudFrontOriginAccessIdentityList.Items', ], 'ListDistributions' => [ 'input_token' => 'Marker', 'output_token' => 'DistributionList.NextMarker', 'limit_key' => 'MaxItems', 'more_results' => 'DistributionList.IsTruncated', 'result_key' => 'DistributionList.Items', ], 'ListInvalidations' => [ 'input_token' => 'Marker', 'output_token' => 'InvalidationList.NextMarker', 'limit_key' => 'MaxItems', 'more_results' => 'InvalidationList.IsTruncated', 'result_key' => 'InvalidationList.Items', ], 'ListStreamingDistributions' => [ 'input_token' => 'Marker', 'output_token' => 'StreamingDistributionList.NextMarker', 'limit_key' => 'MaxItems', 'more_results' => 'StreamingDistributionList.IsTruncated', 'result_key' => 'StreamingDistributionList.Items', ], ],];
lib/Aws/Aws/data/cloudfront/2016-01-28/waiters-2.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2016-01-28/waiters-2.json
3
+ return [ 'version' => 2, 'waiters' => [ 'DistributionDeployed' => [ 'delay' => 60, 'operation' => 'GetDistribution', 'maxAttempts' => 25, 'description' => 'Wait until a distribution is deployed.', 'acceptors' => [ [ 'expected' => 'Deployed', 'matcher' => 'path', 'state' => 'success', 'argument' => 'Distribution.Status', ], ], ], 'InvalidationCompleted' => [ 'delay' => 20, 'operation' => 'GetInvalidation', 'maxAttempts' => 30, 'description' => 'Wait until an invalidation has completed.', 'acceptors' => [ [ 'expected' => 'Completed', 'matcher' => 'path', 'state' => 'success', 'argument' => 'Invalidation.Status', ], ], ], 'StreamingDistributionDeployed' => [ 'delay' => 60, 'operation' => 'GetStreamingDistribution', 'maxAttempts' => 25, 'description' => 'Wait until a streaming distribution is deployed.', 'acceptors' => [ [ 'expected' => 'Deployed', 'matcher' => 'path', 'state' => 'success', 'argument' => 'StreamingDistribution.Status', ], ], ], ],];
lib/Aws/Aws/data/cloudfront/2016-08-01/api-2.json.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+ // This file was auto-generated from sdk-root/src/data/cloudfront/2016-08-01/api-2.json
3
+ return [ 'version' => '2.0', 'metadata' => [ 'uid' => 'cloudfront-2016-08-01', 'apiVersion' => '2016-08-01', 'endpointPrefix' => 'cloudfront', 'globalEndpoint' => 'cloudfront.amazonaws.com', 'protocol' => 'rest-xml', 'serviceAbbreviation' => 'CloudFront', 'serviceFullName' => 'Amazon CloudFront', 'signatureVersion' => 'v4', ], 'operations' => [ 'CreateCloudFrontOriginAccessIdentity' => [ 'name' => 'CreateCloudFrontOriginAccessIdentity2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/origin-access-identity/cloudfront', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'CreateCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'CloudFrontOriginAccessIdentityAlreadyExists', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyCloudFrontOriginAccessIdentities', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'CreateDistribution' => [ 'name' => 'CreateDistribution2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/distribution', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateDistributionRequest', ], 'output' => [ 'shape' => 'CreateDistributionResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'DistributionAlreadyExists', ], [ 'shape' => 'InvalidOrigin', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InvalidViewerCertificate', ], [ 'shape' => 'InvalidMinimumProtocolVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyDistributionCNAMEs', ], [ 'shape' => 'TooManyDistributions', ], [ 'shape' => 'InvalidDefaultRootObject', ], [ 'shape' => 'InvalidRelativePath', ], [ 'shape' => 'InvalidErrorCode', ], [ 'shape' => 'InvalidResponseCode', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidRequiredProtocol', ], [ 'shape' => 'NoSuchOrigin', ], [ 'shape' => 'TooManyOrigins', ], [ 'shape' => 'TooManyCacheBehaviors', ], [ 'shape' => 'TooManyCookieNamesInWhiteList', ], [ 'shape' => 'InvalidForwardCookies', ], [ 'shape' => 'TooManyHeadersInForwardedValues', ], [ 'shape' => 'InvalidHeadersForS3Origin', ], [ 'shape' => 'InconsistentQuantities', ], [ 'shape' => 'TooManyCertificates', ], [ 'shape' => 'InvalidLocationCode', ], [ 'shape' => 'InvalidGeoRestrictionParameter', ], [ 'shape' => 'InvalidProtocolSettings', ], [ 'shape' => 'InvalidTTLOrder', ], [ 'shape' => 'InvalidWebACLId', ], [ 'shape' => 'TooManyOriginCustomHeaders', ], ], ], 'CreateDistributionWithTags' => [ 'name' => 'CreateDistributionWithTags2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/distribution?WithTags', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateDistributionWithTagsRequest', ], 'output' => [ 'shape' => 'CreateDistributionWithTagsResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'DistributionAlreadyExists', ], [ 'shape' => 'InvalidOrigin', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InvalidViewerCertificate', ], [ 'shape' => 'InvalidMinimumProtocolVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyDistributionCNAMEs', ], [ 'shape' => 'TooManyDistributions', ], [ 'shape' => 'InvalidDefaultRootObject', ], [ 'shape' => 'InvalidRelativePath', ], [ 'shape' => 'InvalidErrorCode', ], [ 'shape' => 'InvalidResponseCode', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidRequiredProtocol', ], [ 'shape' => 'NoSuchOrigin', ], [ 'shape' => 'TooManyOrigins', ], [ 'shape' => 'TooManyCacheBehaviors', ], [ 'shape' => 'TooManyCookieNamesInWhiteList', ], [ 'shape' => 'InvalidForwardCookies', ], [ 'shape' => 'TooManyHeadersInForwardedValues', ], [ 'shape' => 'InvalidHeadersForS3Origin', ], [ 'shape' => 'InconsistentQuantities', ], [ 'shape' => 'TooManyCertificates', ], [ 'shape' => 'InvalidLocationCode', ], [ 'shape' => 'InvalidGeoRestrictionParameter', ], [ 'shape' => 'InvalidProtocolSettings', ], [ 'shape' => 'InvalidTTLOrder', ], [ 'shape' => 'InvalidWebACLId', ], [ 'shape' => 'TooManyOriginCustomHeaders', ], [ 'shape' => 'InvalidTagging', ], ], ], 'CreateInvalidation' => [ 'name' => 'CreateInvalidation2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/distribution/{DistributionId}/invalidation', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateInvalidationRequest', ], 'output' => [ 'shape' => 'CreateInvalidationResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'BatchTooLarge', ], [ 'shape' => 'TooManyInvalidationsInProgress', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'CreateStreamingDistribution' => [ 'name' => 'CreateStreamingDistribution2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/streaming-distribution', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateStreamingDistributionRequest', ], 'output' => [ 'shape' => 'CreateStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'StreamingDistributionAlreadyExists', ], [ 'shape' => 'InvalidOrigin', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', ], [ 'shape' => 'TooManyStreamingDistributions', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'CreateStreamingDistributionWithTags' => [ 'name' => 'CreateStreamingDistributionWithTags2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/streaming-distribution?WithTags', 'responseCode' => 201, ], 'input' => [ 'shape' => 'CreateStreamingDistributionWithTagsRequest', ], 'output' => [ 'shape' => 'CreateStreamingDistributionWithTagsResult', ], 'errors' => [ [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'StreamingDistributionAlreadyExists', ], [ 'shape' => 'InvalidOrigin', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', ], [ 'shape' => 'TooManyStreamingDistributions', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], [ 'shape' => 'InvalidTagging', ], ], ], 'DeleteCloudFrontOriginAccessIdentity' => [ 'name' => 'DeleteCloudFrontOriginAccessIdentity2016_08_01', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2016-08-01/origin-access-identity/cloudfront/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteCloudFrontOriginAccessIdentityRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'CloudFrontOriginAccessIdentityInUse', ], ], ], 'DeleteDistribution' => [ 'name' => 'DeleteDistribution2016_08_01', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2016-08-01/distribution/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteDistributionRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'DistributionNotDisabled', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'PreconditionFailed', ], ], ], 'DeleteStreamingDistribution' => [ 'name' => 'DeleteStreamingDistribution2016_08_01', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/2016-08-01/streaming-distribution/{Id}', 'responseCode' => 204, ], 'input' => [ 'shape' => 'DeleteStreamingDistributionRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'StreamingDistributionNotDisabled', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'PreconditionFailed', ], ], ], 'GetCloudFrontOriginAccessIdentity' => [ 'name' => 'GetCloudFrontOriginAccessIdentity2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/origin-access-identity/cloudfront/{Id}', ], 'input' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetCloudFrontOriginAccessIdentityConfig' => [ 'name' => 'GetCloudFrontOriginAccessIdentityConfig2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/origin-access-identity/cloudfront/{Id}/config', ], 'input' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityConfigRequest', ], 'output' => [ 'shape' => 'GetCloudFrontOriginAccessIdentityConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetDistribution' => [ 'name' => 'GetDistribution2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/distribution/{Id}', ], 'input' => [ 'shape' => 'GetDistributionRequest', ], 'output' => [ 'shape' => 'GetDistributionResult', ], 'errors' => [ [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetDistributionConfig' => [ 'name' => 'GetDistributionConfig2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/distribution/{Id}/config', ], 'input' => [ 'shape' => 'GetDistributionConfigRequest', ], 'output' => [ 'shape' => 'GetDistributionConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetInvalidation' => [ 'name' => 'GetInvalidation2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/distribution/{DistributionId}/invalidation/{Id}', ], 'input' => [ 'shape' => 'GetInvalidationRequest', ], 'output' => [ 'shape' => 'GetInvalidationResult', ], 'errors' => [ [ 'shape' => 'NoSuchInvalidation', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetStreamingDistribution' => [ 'name' => 'GetStreamingDistribution2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/streaming-distribution/{Id}', ], 'input' => [ 'shape' => 'GetStreamingDistributionRequest', ], 'output' => [ 'shape' => 'GetStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'GetStreamingDistributionConfig' => [ 'name' => 'GetStreamingDistributionConfig2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/streaming-distribution/{Id}/config', ], 'input' => [ 'shape' => 'GetStreamingDistributionConfigRequest', ], 'output' => [ 'shape' => 'GetStreamingDistributionConfigResult', ], 'errors' => [ [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'ListCloudFrontOriginAccessIdentities' => [ 'name' => 'ListCloudFrontOriginAccessIdentities2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/origin-access-identity/cloudfront', ], 'input' => [ 'shape' => 'ListCloudFrontOriginAccessIdentitiesRequest', ], 'output' => [ 'shape' => 'ListCloudFrontOriginAccessIdentitiesResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], ], ], 'ListDistributions' => [ 'name' => 'ListDistributions2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/distribution', ], 'input' => [ 'shape' => 'ListDistributionsRequest', ], 'output' => [ 'shape' => 'ListDistributionsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], ], ], 'ListDistributionsByWebACLId' => [ 'name' => 'ListDistributionsByWebACLId2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/distributionsByWebACLId/{WebACLId}', ], 'input' => [ 'shape' => 'ListDistributionsByWebACLIdRequest', ], 'output' => [ 'shape' => 'ListDistributionsByWebACLIdResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidWebACLId', ], ], ], 'ListInvalidations' => [ 'name' => 'ListInvalidations2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/distribution/{DistributionId}/invalidation', ], 'input' => [ 'shape' => 'ListInvalidationsRequest', ], 'output' => [ 'shape' => 'ListInvalidationsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'AccessDenied', ], ], ], 'ListStreamingDistributions' => [ 'name' => 'ListStreamingDistributions2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/streaming-distribution', ], 'input' => [ 'shape' => 'ListStreamingDistributionsRequest', ], 'output' => [ 'shape' => 'ListStreamingDistributionsResult', ], 'errors' => [ [ 'shape' => 'InvalidArgument', ], ], ], 'ListTagsForResource' => [ 'name' => 'ListTagsForResource2016_08_01', 'http' => [ 'method' => 'GET', 'requestUri' => '/2016-08-01/tagging', ], 'input' => [ 'shape' => 'ListTagsForResourceRequest', ], 'output' => [ 'shape' => 'ListTagsForResourceResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidTagging', ], [ 'shape' => 'NoSuchResource', ], ], ], 'TagResource' => [ 'name' => 'TagResource2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/tagging?Operation=Tag', 'responseCode' => 204, ], 'input' => [ 'shape' => 'TagResourceRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidTagging', ], [ 'shape' => 'NoSuchResource', ], ], ], 'UntagResource' => [ 'name' => 'UntagResource2016_08_01', 'http' => [ 'method' => 'POST', 'requestUri' => '/2016-08-01/tagging?Operation=Untag', 'responseCode' => 204, ], 'input' => [ 'shape' => 'UntagResourceRequest', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidTagging', ], [ 'shape' => 'NoSuchResource', ], ], ], 'UpdateCloudFrontOriginAccessIdentity' => [ 'name' => 'UpdateCloudFrontOriginAccessIdentity2016_08_01', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2016-08-01/origin-access-identity/cloudfront/{Id}/config', ], 'input' => [ 'shape' => 'UpdateCloudFrontOriginAccessIdentityRequest', ], 'output' => [ 'shape' => 'UpdateCloudFrontOriginAccessIdentityResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'IllegalUpdate', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'NoSuchCloudFrontOriginAccessIdentity', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InconsistentQuantities', ], ], ], 'UpdateDistribution' => [ 'name' => 'UpdateDistribution2016_08_01', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2016-08-01/distribution/{Id}/config', ], 'input' => [ 'shape' => 'UpdateDistributionRequest', ], 'output' => [ 'shape' => 'UpdateDistributionResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'IllegalUpdate', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'NoSuchDistribution', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'TooManyDistributionCNAMEs', ], [ 'shape' => 'InvalidDefaultRootObject', ], [ 'shape' => 'InvalidRelativePath', ], [ 'shape' => 'InvalidErrorCode', ], [ 'shape' => 'InvalidResponseCode', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InvalidViewerCertificate', ], [ 'shape' => 'InvalidMinimumProtocolVersion', ], [ 'shape' => 'InvalidRequiredProtocol', ], [ 'shape' => 'NoSuchOrigin', ], [ 'shape' => 'TooManyOrigins', ], [ 'shape' => 'TooManyCacheBehaviors', ], [ 'shape' => 'TooManyCookieNamesInWhiteList', ], [ 'shape' => 'InvalidForwardCookies', ], [ 'shape' => 'TooManyHeadersInForwardedValues', ], [ 'shape' => 'InvalidHeadersForS3Origin', ], [ 'shape' => 'InconsistentQuantities', ], [ 'shape' => 'TooManyCertificates', ], [ 'shape' => 'InvalidLocationCode', ], [ 'shape' => 'InvalidGeoRestrictionParameter', ], [ 'shape' => 'InvalidTTLOrder', ], [ 'shape' => 'InvalidWebACLId', ], [ 'shape' => 'TooManyOriginCustomHeaders', ], ], ], 'UpdateStreamingDistribution' => [ 'name' => 'UpdateStreamingDistribution2016_08_01', 'http' => [ 'method' => 'PUT', 'requestUri' => '/2016-08-01/streaming-distribution/{Id}/config', ], 'input' => [ 'shape' => 'UpdateStreamingDistributionRequest', ], 'output' => [ 'shape' => 'UpdateStreamingDistributionResult', ], 'errors' => [ [ 'shape' => 'AccessDenied', ], [ 'shape' => 'CNAMEAlreadyExists', ], [ 'shape' => 'IllegalUpdate', ], [ 'shape' => 'InvalidIfMatchVersion', ], [ 'shape' => 'MissingBody', ], [ 'shape' => 'NoSuchStreamingDistribution', ], [ 'shape' => 'PreconditionFailed', ], [ 'shape' => 'TooManyStreamingDistributionCNAMEs', ], [ 'shape' => 'InvalidArgument', ], [ 'shape' => 'InvalidOriginAccessIdentity', ], [ 'shape' => 'TooManyTrustedSigners', ], [ 'shape' => 'TrustedSignerDoesNotExist', ], [ 'shape' => 'InconsistentQuantities', ], ], ], ], 'shapes' => [ 'AccessDenied' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 403, ], 'exception' => true, ], 'ActiveTrustedSigners' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Quantity', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'SignerList', ], ], ], 'AliasList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'CNAME', ], ], 'Aliases' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'AliasList', ], ], ], 'AllowedMethods' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'MethodsList', ], 'CachedMethods' => [ 'shape' => 'CachedMethods', ], ], ], 'AwsAccountNumberList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'AwsAccountNumber', ], ], 'BatchTooLarge' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 413, ], 'exception' => true, ], 'CNAMEAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CacheBehavior' => [ 'type' => 'structure', 'required' => [ 'PathPattern', 'TargetOriginId', 'ForwardedValues', 'TrustedSigners', 'ViewerProtocolPolicy', 'MinTTL', ], 'members' => [ 'PathPattern' => [ 'shape' => 'string', ], 'TargetOriginId' => [ 'shape' => 'string', ], 'ForwardedValues' => [ 'shape' => 'ForwardedValues', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'ViewerProtocolPolicy' => [ 'shape' => 'ViewerProtocolPolicy', ], 'MinTTL' => [ 'shape' => 'long', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', ], 'SmoothStreaming' => [ 'shape' => 'boolean', ], 'DefaultTTL' => [ 'shape' => 'long', ], 'MaxTTL' => [ 'shape' => 'long', ], 'Compress' => [ 'shape' => 'boolean', ], ], ], 'CacheBehaviorList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CacheBehavior', 'locationName' => 'CacheBehavior', ], ], 'CacheBehaviors' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CacheBehaviorList', ], ], ], 'CachedMethods' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'MethodsList', ], ], ], 'CertificateSource' => [ 'type' => 'string', 'enum' => [ 'cloudfront', 'iam', 'acm', ], ], 'CloudFrontOriginAccessIdentity' => [ 'type' => 'structure', 'required' => [ 'Id', 'S3CanonicalUserId', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'S3CanonicalUserId' => [ 'shape' => 'string', ], 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', ], ], ], 'CloudFrontOriginAccessIdentityAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CloudFrontOriginAccessIdentityConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'Comment', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'Comment' => [ 'shape' => 'string', ], ], ], 'CloudFrontOriginAccessIdentityInUse' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'CloudFrontOriginAccessIdentityList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CloudFrontOriginAccessIdentitySummaryList', ], ], ], 'CloudFrontOriginAccessIdentitySummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'S3CanonicalUserId', 'Comment', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'S3CanonicalUserId' => [ 'shape' => 'string', ], 'Comment' => [ 'shape' => 'string', ], ], ], 'CloudFrontOriginAccessIdentitySummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CloudFrontOriginAccessIdentitySummary', 'locationName' => 'CloudFrontOriginAccessIdentitySummary', ], ], 'CookieNameList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Name', ], ], 'CookieNames' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CookieNameList', ], ], ], 'CookiePreference' => [ 'type' => 'structure', 'required' => [ 'Forward', ], 'members' => [ 'Forward' => [ 'shape' => 'ItemSelection', ], 'WhitelistedNames' => [ 'shape' => 'CookieNames', ], ], ], 'CreateCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'CloudFrontOriginAccessIdentityConfig', ], 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', 'locationName' => 'CloudFrontOriginAccessIdentityConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'CreateCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'CreateDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionConfig', ], 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', 'locationName' => 'DistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'DistributionConfig', ], 'CreateDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'CreateDistributionWithTagsRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionConfigWithTags', ], 'members' => [ 'DistributionConfigWithTags' => [ 'shape' => 'DistributionConfigWithTags', 'locationName' => 'DistributionConfigWithTags', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'DistributionConfigWithTags', ], 'CreateDistributionWithTagsResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'CreateInvalidationRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', 'InvalidationBatch', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'InvalidationBatch' => [ 'shape' => 'InvalidationBatch', 'locationName' => 'InvalidationBatch', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'InvalidationBatch', ], 'CreateInvalidationResult' => [ 'type' => 'structure', 'members' => [ 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'Invalidation' => [ 'shape' => 'Invalidation', ], ], 'payload' => 'Invalidation', ], 'CreateStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfig', ], 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', 'locationName' => 'StreamingDistributionConfig', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'StreamingDistributionConfig', ], 'CreateStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'CreateStreamingDistributionWithTagsRequest' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfigWithTags', ], 'members' => [ 'StreamingDistributionConfigWithTags' => [ 'shape' => 'StreamingDistributionConfigWithTags', 'locationName' => 'StreamingDistributionConfigWithTags', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'StreamingDistributionConfigWithTags', ], 'CreateStreamingDistributionWithTagsResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'Location' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'Location', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'CustomErrorResponse' => [ 'type' => 'structure', 'required' => [ 'ErrorCode', ], 'members' => [ 'ErrorCode' => [ 'shape' => 'integer', ], 'ResponsePagePath' => [ 'shape' => 'string', ], 'ResponseCode' => [ 'shape' => 'string', ], 'ErrorCachingMinTTL' => [ 'shape' => 'long', ], ], ], 'CustomErrorResponseList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CustomErrorResponse', 'locationName' => 'CustomErrorResponse', ], ], 'CustomErrorResponses' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'CustomErrorResponseList', ], ], ], 'CustomHeaders' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'OriginCustomHeadersList', ], ], ], 'CustomOriginConfig' => [ 'type' => 'structure', 'required' => [ 'HTTPPort', 'HTTPSPort', 'OriginProtocolPolicy', ], 'members' => [ 'HTTPPort' => [ 'shape' => 'integer', ], 'HTTPSPort' => [ 'shape' => 'integer', ], 'OriginProtocolPolicy' => [ 'shape' => 'OriginProtocolPolicy', ], 'OriginSslProtocols' => [ 'shape' => 'OriginSslProtocols', ], ], ], 'DefaultCacheBehavior' => [ 'type' => 'structure', 'required' => [ 'TargetOriginId', 'ForwardedValues', 'TrustedSigners', 'ViewerProtocolPolicy', 'MinTTL', ], 'members' => [ 'TargetOriginId' => [ 'shape' => 'string', ], 'ForwardedValues' => [ 'shape' => 'ForwardedValues', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'ViewerProtocolPolicy' => [ 'shape' => 'ViewerProtocolPolicy', ], 'MinTTL' => [ 'shape' => 'long', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', ], 'SmoothStreaming' => [ 'shape' => 'boolean', ], 'DefaultTTL' => [ 'shape' => 'long', ], 'MaxTTL' => [ 'shape' => 'long', ], 'Compress' => [ 'shape' => 'boolean', ], ], ], 'DeleteCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'DeleteDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'DeleteStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], 'IfMatch' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'If-Match', ], ], ], 'Distribution' => [ 'type' => 'structure', 'required' => [ 'Id', 'ARN', 'Status', 'LastModifiedTime', 'InProgressInvalidationBatches', 'DomainName', 'ActiveTrustedSigners', 'DistributionConfig', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'ARN' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'InProgressInvalidationBatches' => [ 'shape' => 'integer', ], 'DomainName' => [ 'shape' => 'string', ], 'ActiveTrustedSigners' => [ 'shape' => 'ActiveTrustedSigners', ], 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], ], ], 'DistributionAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'DistributionConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'Origins', 'DefaultCacheBehavior', 'Comment', 'Enabled', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'DefaultRootObject' => [ 'shape' => 'string', ], 'Origins' => [ 'shape' => 'Origins', ], 'DefaultCacheBehavior' => [ 'shape' => 'DefaultCacheBehavior', ], 'CacheBehaviors' => [ 'shape' => 'CacheBehaviors', ], 'CustomErrorResponses' => [ 'shape' => 'CustomErrorResponses', ], 'Comment' => [ 'shape' => 'string', ], 'Logging' => [ 'shape' => 'LoggingConfig', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], 'ViewerCertificate' => [ 'shape' => 'ViewerCertificate', ], 'Restrictions' => [ 'shape' => 'Restrictions', ], 'WebACLId' => [ 'shape' => 'string', ], ], ], 'DistributionConfigWithTags' => [ 'type' => 'structure', 'required' => [ 'DistributionConfig', 'Tags', ], 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], 'Tags' => [ 'shape' => 'Tags', ], ], ], 'DistributionList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'DistributionSummaryList', ], ], ], 'DistributionNotDisabled' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'DistributionSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'ARN', 'Status', 'LastModifiedTime', 'DomainName', 'Aliases', 'Origins', 'DefaultCacheBehavior', 'CacheBehaviors', 'CustomErrorResponses', 'Comment', 'PriceClass', 'Enabled', 'ViewerCertificate', 'Restrictions', 'WebACLId', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'ARN' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'Origins' => [ 'shape' => 'Origins', ], 'DefaultCacheBehavior' => [ 'shape' => 'DefaultCacheBehavior', ], 'CacheBehaviors' => [ 'shape' => 'CacheBehaviors', ], 'CustomErrorResponses' => [ 'shape' => 'CustomErrorResponses', ], 'Comment' => [ 'shape' => 'string', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], 'ViewerCertificate' => [ 'shape' => 'ViewerCertificate', ], 'Restrictions' => [ 'shape' => 'Restrictions', ], 'WebACLId' => [ 'shape' => 'string', ], ], ], 'DistributionSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'DistributionSummary', 'locationName' => 'DistributionSummary', ], ], 'ForwardedValues' => [ 'type' => 'structure', 'required' => [ 'QueryString', 'Cookies', ], 'members' => [ 'QueryString' => [ 'shape' => 'boolean', ], 'Cookies' => [ 'shape' => 'CookiePreference', ], 'Headers' => [ 'shape' => 'Headers', ], ], ], 'GeoRestriction' => [ 'type' => 'structure', 'required' => [ 'RestrictionType', 'Quantity', ], 'members' => [ 'RestrictionType' => [ 'shape' => 'GeoRestrictionType', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'LocationList', ], ], ], 'GeoRestrictionType' => [ 'type' => 'string', 'enum' => [ 'blacklist', 'whitelist', 'none', ], ], 'GetCloudFrontOriginAccessIdentityConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetCloudFrontOriginAccessIdentityConfigResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentityConfig' => [ 'shape' => 'CloudFrontOriginAccessIdentityConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentityConfig', ], 'GetCloudFrontOriginAccessIdentityRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetCloudFrontOriginAccessIdentityResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentity' => [ 'shape' => 'CloudFrontOriginAccessIdentity', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'CloudFrontOriginAccessIdentity', ], 'GetDistributionConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetDistributionConfigResult' => [ 'type' => 'structure', 'members' => [ 'DistributionConfig' => [ 'shape' => 'DistributionConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'DistributionConfig', ], 'GetDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetDistributionResult' => [ 'type' => 'structure', 'members' => [ 'Distribution' => [ 'shape' => 'Distribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'Distribution', ], 'GetInvalidationRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', 'Id', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetInvalidationResult' => [ 'type' => 'structure', 'members' => [ 'Invalidation' => [ 'shape' => 'Invalidation', ], ], 'payload' => 'Invalidation', ], 'GetStreamingDistributionConfigRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetStreamingDistributionConfigResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistributionConfig', ], 'GetStreamingDistributionRequest' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'Id', ], ], ], 'GetStreamingDistributionResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistribution' => [ 'shape' => 'StreamingDistribution', ], 'ETag' => [ 'shape' => 'string', 'location' => 'header', 'locationName' => 'ETag', ], ], 'payload' => 'StreamingDistribution', ], 'HeaderList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Name', ], ], 'Headers' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'HeaderList', ], ], ], 'IllegalUpdate' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InconsistentQuantities' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidArgument' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidDefaultRootObject' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidErrorCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidForwardCookies' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidGeoRestrictionParameter' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidHeadersForS3Origin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidIfMatchVersion' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidLocationCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidMinimumProtocolVersion' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidOrigin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidOriginAccessIdentity' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidProtocolSettings' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidRelativePath' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidRequiredProtocol' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidResponseCode' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidTTLOrder' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidTagging' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidViewerCertificate' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'InvalidWebACLId' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'Invalidation' => [ 'type' => 'structure', 'required' => [ 'Id', 'Status', 'CreateTime', 'InvalidationBatch', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'CreateTime' => [ 'shape' => 'timestamp', ], 'InvalidationBatch' => [ 'shape' => 'InvalidationBatch', ], ], ], 'InvalidationBatch' => [ 'type' => 'structure', 'required' => [ 'Paths', 'CallerReference', ], 'members' => [ 'Paths' => [ 'shape' => 'Paths', ], 'CallerReference' => [ 'shape' => 'string', ], ], ], 'InvalidationList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'InvalidationSummaryList', ], ], ], 'InvalidationSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'CreateTime', 'Status', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'CreateTime' => [ 'shape' => 'timestamp', ], 'Status' => [ 'shape' => 'string', ], ], ], 'InvalidationSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'InvalidationSummary', 'locationName' => 'InvalidationSummary', ], ], 'ItemSelection' => [ 'type' => 'string', 'enum' => [ 'none', 'whitelist', 'all', ], ], 'KeyPairIdList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'KeyPairId', ], ], 'KeyPairIds' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'KeyPairIdList', ], ], ], 'ListCloudFrontOriginAccessIdentitiesRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListCloudFrontOriginAccessIdentitiesResult' => [ 'type' => 'structure', 'members' => [ 'CloudFrontOriginAccessIdentityList' => [ 'shape' => 'CloudFrontOriginAccessIdentityList', ], ], 'payload' => 'CloudFrontOriginAccessIdentityList', ], 'ListDistributionsByWebACLIdRequest' => [ 'type' => 'structure', 'required' => [ 'WebACLId', ], 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], 'WebACLId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'WebACLId', ], ], ], 'ListDistributionsByWebACLIdResult' => [ 'type' => 'structure', 'members' => [ 'DistributionList' => [ 'shape' => 'DistributionList', ], ], 'payload' => 'DistributionList', ], 'ListDistributionsRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListDistributionsResult' => [ 'type' => 'structure', 'members' => [ 'DistributionList' => [ 'shape' => 'DistributionList', ], ], 'payload' => 'DistributionList', ], 'ListInvalidationsRequest' => [ 'type' => 'structure', 'required' => [ 'DistributionId', ], 'members' => [ 'DistributionId' => [ 'shape' => 'string', 'location' => 'uri', 'locationName' => 'DistributionId', ], 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListInvalidationsResult' => [ 'type' => 'structure', 'members' => [ 'InvalidationList' => [ 'shape' => 'InvalidationList', ], ], 'payload' => 'InvalidationList', ], 'ListStreamingDistributionsRequest' => [ 'type' => 'structure', 'members' => [ 'Marker' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'Marker', ], 'MaxItems' => [ 'shape' => 'string', 'location' => 'querystring', 'locationName' => 'MaxItems', ], ], ], 'ListStreamingDistributionsResult' => [ 'type' => 'structure', 'members' => [ 'StreamingDistributionList' => [ 'shape' => 'StreamingDistributionList', ], ], 'payload' => 'StreamingDistributionList', ], 'ListTagsForResourceRequest' => [ 'type' => 'structure', 'required' => [ 'Resource', ], 'members' => [ 'Resource' => [ 'shape' => 'ResourceARN', 'location' => 'querystring', 'locationName' => 'Resource', ], ], ], 'ListTagsForResourceResult' => [ 'type' => 'structure', 'required' => [ 'Tags', ], 'members' => [ 'Tags' => [ 'shape' => 'Tags', ], ], 'payload' => 'Tags', ], 'LocationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Location', ], ], 'LoggingConfig' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'IncludeCookies', 'Bucket', 'Prefix', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'IncludeCookies' => [ 'shape' => 'boolean', ], 'Bucket' => [ 'shape' => 'string', ], 'Prefix' => [ 'shape' => 'string', ], ], ], 'Method' => [ 'type' => 'string', 'enum' => [ 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', ], ], 'MethodsList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Method', 'locationName' => 'Method', ], ], 'MinimumProtocolVersion' => [ 'type' => 'string', 'enum' => [ 'SSLv3', 'TLSv1', ], ], 'MissingBody' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'NoSuchCloudFrontOriginAccessIdentity' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchDistribution' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchInvalidation' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchOrigin' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchResource' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'NoSuchStreamingDistribution' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 404, ], 'exception' => true, ], 'Origin' => [ 'type' => 'structure', 'required' => [ 'Id', 'DomainName', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'DomainName' => [ 'shape' => 'string', ], 'OriginPath' => [ 'shape' => 'string', ], 'CustomHeaders' => [ 'shape' => 'CustomHeaders', ], 'S3OriginConfig' => [ 'shape' => 'S3OriginConfig', ], 'CustomOriginConfig' => [ 'shape' => 'CustomOriginConfig', ], ], ], 'OriginCustomHeader' => [ 'type' => 'structure', 'required' => [ 'HeaderName', 'HeaderValue', ], 'members' => [ 'HeaderName' => [ 'shape' => 'string', ], 'HeaderValue' => [ 'shape' => 'string', ], ], ], 'OriginCustomHeadersList' => [ 'type' => 'list', 'member' => [ 'shape' => 'OriginCustomHeader', 'locationName' => 'OriginCustomHeader', ], ], 'OriginList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Origin', 'locationName' => 'Origin', ], 'min' => 1, ], 'OriginProtocolPolicy' => [ 'type' => 'string', 'enum' => [ 'http-only', 'match-viewer', 'https-only', ], ], 'OriginSslProtocols' => [ 'type' => 'structure', 'required' => [ 'Quantity', 'Items', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'SslProtocolsList', ], ], ], 'Origins' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'OriginList', ], ], ], 'PathList' => [ 'type' => 'list', 'member' => [ 'shape' => 'string', 'locationName' => 'Path', ], ], 'Paths' => [ 'type' => 'structure', 'required' => [ 'Quantity', ], 'members' => [ 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'PathList', ], ], ], 'PreconditionFailed' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 412, ], 'exception' => true, ], 'PriceClass' => [ 'type' => 'string', 'enum' => [ 'PriceClass_100', 'PriceClass_200', 'PriceClass_All', ], ], 'ResourceARN' => [ 'type' => 'string', 'pattern' => 'arn:aws:cloudfront::[0-9]+:.*', ], 'Restrictions' => [ 'type' => 'structure', 'required' => [ 'GeoRestriction', ], 'members' => [ 'GeoRestriction' => [ 'shape' => 'GeoRestriction', ], ], ], 'S3Origin' => [ 'type' => 'structure', 'required' => [ 'DomainName', 'OriginAccessIdentity', ], 'members' => [ 'DomainName' => [ 'shape' => 'string', ], 'OriginAccessIdentity' => [ 'shape' => 'string', ], ], ], 'S3OriginConfig' => [ 'type' => 'structure', 'required' => [ 'OriginAccessIdentity', ], 'members' => [ 'OriginAccessIdentity' => [ 'shape' => 'string', ], ], ], 'SSLSupportMethod' => [ 'type' => 'string', 'enum' => [ 'sni-only', 'vip', ], ], 'Signer' => [ 'type' => 'structure', 'members' => [ 'AwsAccountNumber' => [ 'shape' => 'string', ], 'KeyPairIds' => [ 'shape' => 'KeyPairIds', ], ], ], 'SignerList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Signer', 'locationName' => 'Signer', ], ], 'SslProtocol' => [ 'type' => 'string', 'enum' => [ 'SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2', ], ], 'SslProtocolsList' => [ 'type' => 'list', 'member' => [ 'shape' => 'SslProtocol', 'locationName' => 'SslProtocol', ], ], 'StreamingDistribution' => [ 'type' => 'structure', 'required' => [ 'Id', 'ARN', 'Status', 'DomainName', 'ActiveTrustedSigners', 'StreamingDistributionConfig', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'ARN' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'ActiveTrustedSigners' => [ 'shape' => 'ActiveTrustedSigners', ], 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], ], ], 'StreamingDistributionAlreadyExists' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'StreamingDistributionConfig' => [ 'type' => 'structure', 'required' => [ 'CallerReference', 'S3Origin', 'Comment', 'TrustedSigners', 'Enabled', ], 'members' => [ 'CallerReference' => [ 'shape' => 'string', ], 'S3Origin' => [ 'shape' => 'S3Origin', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'Comment' => [ 'shape' => 'string', ], 'Logging' => [ 'shape' => 'StreamingLoggingConfig', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], ], ], 'StreamingDistributionConfigWithTags' => [ 'type' => 'structure', 'required' => [ 'StreamingDistributionConfig', 'Tags', ], 'members' => [ 'StreamingDistributionConfig' => [ 'shape' => 'StreamingDistributionConfig', ], 'Tags' => [ 'shape' => 'Tags', ], ], ], 'StreamingDistributionList' => [ 'type' => 'structure', 'required' => [ 'Marker', 'MaxItems', 'IsTruncated', 'Quantity', ], 'members' => [ 'Marker' => [ 'shape' => 'string', ], 'NextMarker' => [ 'shape' => 'string', ], 'MaxItems' => [ 'shape' => 'integer', ], 'IsTruncated' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items' => [ 'shape' => 'StreamingDistributionSummaryList', ], ], ], 'StreamingDistributionNotDisabled' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 409, ], 'exception' => true, ], 'StreamingDistributionSummary' => [ 'type' => 'structure', 'required' => [ 'Id', 'ARN', 'Status', 'LastModifiedTime', 'DomainName', 'S3Origin', 'Aliases', 'TrustedSigners', 'Comment', 'PriceClass', 'Enabled', ], 'members' => [ 'Id' => [ 'shape' => 'string', ], 'ARN' => [ 'shape' => 'string', ], 'Status' => [ 'shape' => 'string', ], 'LastModifiedTime' => [ 'shape' => 'timestamp', ], 'DomainName' => [ 'shape' => 'string', ], 'S3Origin' => [ 'shape' => 'S3Origin', ], 'Aliases' => [ 'shape' => 'Aliases', ], 'TrustedSigners' => [ 'shape' => 'TrustedSigners', ], 'Comment' => [ 'shape' => 'string', ], 'PriceClass' => [ 'shape' => 'PriceClass', ], 'Enabled' => [ 'shape' => 'boolean', ], ], ], 'StreamingDistributionSummaryList' => [ 'type' => 'list', 'member' => [ 'shape' => 'StreamingDistributionSummary', 'locationName' => 'StreamingDistributionSummary', ], ], 'StreamingLoggingConfig' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Bucket', 'Prefix', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Bucket' => [ 'shape' => 'string', ], 'Prefix' => [ 'shape' => 'string', ], ], ], 'Tag' => [ 'type' => 'structure', 'required' => [ 'Key', ], 'members' => [ 'Key' => [ 'shape' => 'TagKey', ], 'Value' => [ 'shape' => 'TagValue', ], ], ], 'TagKey' => [ 'type' => 'string', 'max' => 128, 'min' => 1, 'pattern' => '^([\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]*)$', ], 'TagKeyList' => [ 'type' => 'list', 'member' => [ 'shape' => 'TagKey', 'locationName' => 'Key', ], ], 'TagKeys' => [ 'type' => 'structure', 'members' => [ 'Items' => [ 'shape' => 'TagKeyList', ], ], ], 'TagList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Tag', 'locationName' => 'Tag', ], ], 'TagResourceRequest' => [ 'type' => 'structure', 'required' => [ 'Resource', 'Tags', ], 'members' => [ 'Resource' => [ 'shape' => 'ResourceARN', 'location' => 'querystring', 'locationName' => 'Resource', ], 'Tags' => [ 'shape' => 'Tags', 'locationName' => 'Tags', 'xmlNamespace' => [ 'uri' => 'http://cloudfront.amazonaws.com/doc/2016-08-01/', ], ], ], 'payload' => 'Tags', ], 'TagValue' => [ 'type' => 'string', 'max' => 256, 'min' => 0, 'pattern' => '^([\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]*)$', ], 'Tags' => [ 'type' => 'structure', 'members' => [ 'Items' => [ 'shape' => 'TagList', ], ], ], 'TooManyCacheBehaviors' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCertificates' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCloudFrontOriginAccessIdentities' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyCookieNamesInWhiteList' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyDistributionCNAMEs' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyDistributions' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyHeadersInForwardedValues' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyInvalidationsInProgress' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyOriginCustomHeaders' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyOrigins' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyStreamingDistributionCNAMEs' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyStreamingDistributions' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TooManyTrustedSigners' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TrustedSignerDoesNotExist' => [ 'type' => 'structure', 'members' => [ 'Message' => [ 'shape' => 'string', ], ], 'error' => [ 'httpStatusCode' => 400, ], 'exception' => true, ], 'TrustedSigners' => [ 'type' => 'structure', 'required' => [ 'Enabled', 'Quantity', ], 'members' => [ 'Enabled' => [ 'shape' => 'boolean', ], 'Quantity' => [ 'shape' => 'integer', ], 'Items'