s3.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329
  1. var AWS = require('../core');
  2. var v4Credentials = require('../signers/v4_credentials');
  3. var resolveRegionalEndpointsFlag = require('../config_regional_endpoint');
  4. var s3util = require('./s3util');
  5. var regionUtil = require('../region_config');
  6. // Pull in managed upload extension
  7. require('../s3/managed_upload');
  8. /**
  9. * @api private
  10. */
  11. var operationsWith200StatusCodeError = {
  12. 'completeMultipartUpload': true,
  13. 'copyObject': true,
  14. 'uploadPartCopy': true
  15. };
  16. /**
  17. * @api private
  18. */
  19. var regionRedirectErrorCodes = [
  20. 'AuthorizationHeaderMalformed', // non-head operations on virtual-hosted global bucket endpoints
  21. 'BadRequest', // head operations on virtual-hosted global bucket endpoints
  22. 'PermanentRedirect', // non-head operations on path-style or regional endpoints
  23. 301 // head operations on path-style or regional endpoints
  24. ];
  25. var OBJECT_LAMBDA_SERVICE = 's3-object-lambda';
  26. AWS.util.update(AWS.S3.prototype, {
  27. /**
  28. * @api private
  29. */
  30. getSignatureVersion: function getSignatureVersion(request) {
  31. var defaultApiVersion = this.api.signatureVersion;
  32. var userDefinedVersion = this._originalConfig ? this._originalConfig.signatureVersion : null;
  33. var regionDefinedVersion = this.config.signatureVersion;
  34. var isPresigned = request ? request.isPresigned() : false;
  35. /*
  36. 1) User defined version specified:
  37. a) always return user defined version
  38. 2) No user defined version specified:
  39. a) If not using presigned urls, default to V4
  40. b) If using presigned urls, default to lowest version the region supports
  41. */
  42. if (userDefinedVersion) {
  43. userDefinedVersion = userDefinedVersion === 'v2' ? 's3' : userDefinedVersion;
  44. return userDefinedVersion;
  45. }
  46. if (isPresigned !== true) {
  47. defaultApiVersion = 'v4';
  48. } else if (regionDefinedVersion) {
  49. defaultApiVersion = regionDefinedVersion;
  50. }
  51. return defaultApiVersion;
  52. },
  53. /**
  54. * @api private
  55. */
  56. getSigningName: function getSigningName(req) {
  57. if (req && req.operation === 'writeGetObjectResponse') {
  58. return OBJECT_LAMBDA_SERVICE;
  59. }
  60. var _super = AWS.Service.prototype.getSigningName;
  61. return (req && req._parsedArn && req._parsedArn.service)
  62. ? req._parsedArn.service
  63. : _super.call(this);
  64. },
  65. /**
  66. * @api private
  67. */
  68. getSignerClass: function getSignerClass(request) {
  69. var signatureVersion = this.getSignatureVersion(request);
  70. return AWS.Signers.RequestSigner.getVersion(signatureVersion);
  71. },
  72. /**
  73. * @api private
  74. */
  75. validateService: function validateService() {
  76. var msg;
  77. var messages = [];
  78. // default to us-east-1 when no region is provided
  79. if (!this.config.region) this.config.region = 'us-east-1';
  80. if (!this.config.endpoint && this.config.s3BucketEndpoint) {
  81. messages.push('An endpoint must be provided when configuring ' +
  82. '`s3BucketEndpoint` to true.');
  83. }
  84. if (messages.length === 1) {
  85. msg = messages[0];
  86. } else if (messages.length > 1) {
  87. msg = 'Multiple configuration errors:\n' + messages.join('\n');
  88. }
  89. if (msg) {
  90. throw AWS.util.error(new Error(),
  91. {name: 'InvalidEndpoint', message: msg});
  92. }
  93. },
  94. /**
  95. * @api private
  96. */
  97. shouldDisableBodySigning: function shouldDisableBodySigning(request) {
  98. var signerClass = this.getSignerClass();
  99. if (this.config.s3DisableBodySigning === true && signerClass === AWS.Signers.V4
  100. && request.httpRequest.endpoint.protocol === 'https:') {
  101. return true;
  102. }
  103. return false;
  104. },
  105. /**
  106. * @api private
  107. */
  108. setupRequestListeners: function setupRequestListeners(request) {
  109. request.addListener('validateResponse', this.setExpiresString);
  110. var prependListener = true;
  111. request.addListener('validate', this.validateScheme);
  112. request.addListener('validate', this.validateBucketName, prependListener);
  113. request.addListener('validate', this.optInUsEast1RegionalEndpoint, prependListener);
  114. request.removeListener('validate',
  115. AWS.EventListeners.Core.VALIDATE_REGION);
  116. request.addListener('build', this.addContentType);
  117. request.addListener('build', this.computeContentMd5);
  118. request.addListener('build', this.computeSseCustomerKeyMd5);
  119. request.addListener('build', this.populateURI);
  120. request.addListener('afterBuild', this.addExpect100Continue);
  121. request.addListener('extractError', this.extractError);
  122. request.addListener('extractData', AWS.util.hoistPayloadMember);
  123. request.addListener('extractData', this.extractData);
  124. request.addListener('extractData', this.extractErrorFrom200Response);
  125. request.addListener('beforePresign', this.prepareSignedUrl);
  126. if (this.shouldDisableBodySigning(request)) {
  127. request.removeListener('afterBuild', AWS.EventListeners.Core.COMPUTE_SHA256);
  128. request.addListener('afterBuild', this.disableBodySigning);
  129. }
  130. //deal with ARNs supplied to Bucket
  131. if (request.operation !== 'createBucket' && s3util.isArnInParam(request, 'Bucket')) {
  132. // avoid duplicate parsing in the future
  133. request._parsedArn = AWS.util.ARN.parse(request.params.Bucket);
  134. request.removeListener('validate', this.validateBucketName);
  135. request.removeListener('build', this.populateURI);
  136. if (request._parsedArn.service === 's3') {
  137. request.addListener('validate', s3util.validateS3AccessPointArn);
  138. request.addListener('validate', this.validateArnResourceType);
  139. request.addListener('validate', this.validateArnRegion);
  140. } else if (request._parsedArn.service === 's3-outposts') {
  141. request.addListener('validate', s3util.validateOutpostsAccessPointArn);
  142. request.addListener('validate', s3util.validateOutpostsArn);
  143. request.addListener('validate', s3util.validateArnRegion);
  144. }
  145. request.addListener('validate', s3util.validateArnAccount);
  146. request.addListener('validate', s3util.validateArnService);
  147. request.addListener('build', this.populateUriFromAccessPointArn);
  148. request.addListener('build', s3util.validatePopulateUriFromArn);
  149. return;
  150. }
  151. //listeners regarding region inference
  152. request.addListener('validate', this.validateBucketEndpoint);
  153. request.addListener('validate', this.correctBucketRegionFromCache);
  154. request.onAsync('extractError', this.requestBucketRegion);
  155. if (AWS.util.isBrowser()) {
  156. request.onAsync('retry', this.reqRegionForNetworkingError);
  157. }
  158. },
  159. /**
  160. * @api private
  161. */
  162. validateScheme: function(req) {
  163. var params = req.params,
  164. scheme = req.httpRequest.endpoint.protocol,
  165. sensitive = params.SSECustomerKey || params.CopySourceSSECustomerKey;
  166. if (sensitive && scheme !== 'https:') {
  167. var msg = 'Cannot send SSE keys over HTTP. Set \'sslEnabled\'' +
  168. 'to \'true\' in your configuration';
  169. throw AWS.util.error(new Error(),
  170. { code: 'ConfigError', message: msg });
  171. }
  172. },
  173. /**
  174. * @api private
  175. */
  176. validateBucketEndpoint: function(req) {
  177. if (!req.params.Bucket && req.service.config.s3BucketEndpoint) {
  178. var msg = 'Cannot send requests to root API with `s3BucketEndpoint` set.';
  179. throw AWS.util.error(new Error(),
  180. { code: 'ConfigError', message: msg });
  181. }
  182. },
  183. /**
  184. * @api private
  185. */
  186. validateArnRegion: function validateArnRegion(req) {
  187. s3util.validateArnRegion(req, { allowFipsEndpoint: true });
  188. },
  189. /**
  190. * Validate resource-type supplied in S3 ARN
  191. */
  192. validateArnResourceType: function validateArnResourceType(req) {
  193. var resource = req._parsedArn.resource;
  194. if (
  195. resource.indexOf('accesspoint:') !== 0 &&
  196. resource.indexOf('accesspoint/') !== 0
  197. ) {
  198. throw AWS.util.error(new Error(), {
  199. code: 'InvalidARN',
  200. message: 'ARN resource should begin with \'accesspoint/\''
  201. });
  202. }
  203. },
  204. /**
  205. * @api private
  206. */
  207. validateBucketName: function validateBucketName(req) {
  208. var service = req.service;
  209. var signatureVersion = service.getSignatureVersion(req);
  210. var bucket = req.params && req.params.Bucket;
  211. var key = req.params && req.params.Key;
  212. var slashIndex = bucket && bucket.indexOf('/');
  213. if (bucket && slashIndex >= 0) {
  214. if (typeof key === 'string' && slashIndex > 0) {
  215. req.params = AWS.util.copy(req.params);
  216. // Need to include trailing slash to match sigv2 behavior
  217. var prefix = bucket.substr(slashIndex + 1) || '';
  218. req.params.Key = prefix + '/' + key;
  219. req.params.Bucket = bucket.substr(0, slashIndex);
  220. } else if (signatureVersion === 'v4') {
  221. var msg = 'Bucket names cannot contain forward slashes. Bucket: ' + bucket;
  222. throw AWS.util.error(new Error(),
  223. { code: 'InvalidBucket', message: msg });
  224. }
  225. }
  226. },
  227. /**
  228. * @api private
  229. */
  230. isValidAccelerateOperation: function isValidAccelerateOperation(operation) {
  231. var invalidOperations = [
  232. 'createBucket',
  233. 'deleteBucket',
  234. 'listBuckets'
  235. ];
  236. return invalidOperations.indexOf(operation) === -1;
  237. },
  238. /**
  239. * When us-east-1 region endpoint configuration is set, in stead of sending request to
  240. * global endpoint(e.g. 's3.amazonaws.com'), we will send request to
  241. * 's3.us-east-1.amazonaws.com'.
  242. * @api private
  243. */
  244. optInUsEast1RegionalEndpoint: function optInUsEast1RegionalEndpoint(req) {
  245. var service = req.service;
  246. var config = service.config;
  247. config.s3UsEast1RegionalEndpoint = resolveRegionalEndpointsFlag(service._originalConfig, {
  248. env: 'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT',
  249. sharedConfig: 's3_us_east_1_regional_endpoint',
  250. clientConfig: 's3UsEast1RegionalEndpoint'
  251. });
  252. if (
  253. !(service._originalConfig || {}).endpoint &&
  254. req.httpRequest.region === 'us-east-1' &&
  255. config.s3UsEast1RegionalEndpoint === 'regional' &&
  256. req.httpRequest.endpoint.hostname.indexOf('s3.amazonaws.com') >= 0
  257. ) {
  258. var insertPoint = config.endpoint.indexOf('.amazonaws.com');
  259. var regionalEndpoint = config.endpoint.substring(0, insertPoint) +
  260. '.us-east-1' + config.endpoint.substring(insertPoint);
  261. req.httpRequest.updateEndpoint(regionalEndpoint);
  262. }
  263. },
  264. /**
  265. * S3 prefers dns-compatible bucket names to be moved from the uri path
  266. * to the hostname as a sub-domain. This is not possible, even for dns-compat
  267. * buckets when using SSL and the bucket name contains a dot ('.'). The
  268. * ssl wildcard certificate is only 1-level deep.
  269. *
  270. * @api private
  271. */
  272. populateURI: function populateURI(req) {
  273. var httpRequest = req.httpRequest;
  274. var b = req.params.Bucket;
  275. var service = req.service;
  276. var endpoint = httpRequest.endpoint;
  277. if (b) {
  278. if (!service.pathStyleBucketName(b)) {
  279. if (service.config.useAccelerateEndpoint && service.isValidAccelerateOperation(req.operation)) {
  280. if (service.config.useDualstackEndpoint) {
  281. endpoint.hostname = b + '.s3-accelerate.dualstack.amazonaws.com';
  282. } else {
  283. endpoint.hostname = b + '.s3-accelerate.amazonaws.com';
  284. }
  285. } else if (!service.config.s3BucketEndpoint) {
  286. endpoint.hostname =
  287. b + '.' + endpoint.hostname;
  288. }
  289. var port = endpoint.port;
  290. if (port !== 80 && port !== 443) {
  291. endpoint.host = endpoint.hostname + ':' +
  292. endpoint.port;
  293. } else {
  294. endpoint.host = endpoint.hostname;
  295. }
  296. httpRequest.virtualHostedBucket = b; // needed for signing the request
  297. service.removeVirtualHostedBucketFromPath(req);
  298. }
  299. }
  300. },
  301. /**
  302. * Takes the bucket name out of the path if bucket is virtual-hosted
  303. *
  304. * @api private
  305. */
  306. removeVirtualHostedBucketFromPath: function removeVirtualHostedBucketFromPath(req) {
  307. var httpRequest = req.httpRequest;
  308. var bucket = httpRequest.virtualHostedBucket;
  309. if (bucket && httpRequest.path) {
  310. if (req.params && req.params.Key) {
  311. var encodedS3Key = '/' + AWS.util.uriEscapePath(req.params.Key);
  312. if (httpRequest.path.indexOf(encodedS3Key) === 0 && (httpRequest.path.length === encodedS3Key.length || httpRequest.path[encodedS3Key.length] === '?')) {
  313. //path only contains key or path contains only key and querystring
  314. return;
  315. }
  316. }
  317. httpRequest.path = httpRequest.path.replace(new RegExp('/' + bucket), '');
  318. if (httpRequest.path[0] !== '/') {
  319. httpRequest.path = '/' + httpRequest.path;
  320. }
  321. }
  322. },
  323. /**
  324. * When user supply an access point ARN in the Bucket parameter, we need to
  325. * populate the URI according to the ARN.
  326. */
  327. populateUriFromAccessPointArn: function populateUriFromAccessPointArn(req) {
  328. var accessPointArn = req._parsedArn;
  329. var isOutpostArn = accessPointArn.service === 's3-outposts';
  330. var isObjectLambdaArn = accessPointArn.service === 's3-object-lambda';
  331. var outpostsSuffix = isOutpostArn ? '.' + accessPointArn.outpostId: '';
  332. var serviceName = isOutpostArn ? 's3-outposts': 's3-accesspoint';
  333. var fipsSuffix = !isOutpostArn && req.service.config.useFipsEndpoint ? '-fips': '';
  334. var dualStackSuffix = !isOutpostArn &&
  335. req.service.config.useDualstackEndpoint ? '.dualstack' : '';
  336. var endpoint = req.httpRequest.endpoint;
  337. var dnsSuffix = regionUtil.getEndpointSuffix(accessPointArn.region);
  338. var useArnRegion = req.service.config.s3UseArnRegion;
  339. endpoint.hostname = [
  340. accessPointArn.accessPoint + '-' + accessPointArn.accountId + outpostsSuffix,
  341. serviceName + fipsSuffix + dualStackSuffix,
  342. useArnRegion ? accessPointArn.region : req.service.config.region,
  343. dnsSuffix
  344. ].join('.');
  345. if (isObjectLambdaArn) {
  346. // should be in the format: "accesspoint/${accesspointName}"
  347. var serviceName = 's3-object-lambda';
  348. var accesspointName = accessPointArn.resource.split('/')[1];
  349. var fipsSuffix = req.service.config.useFipsEndpoint ? '-fips': '';
  350. endpoint.hostname = [
  351. accesspointName + '-' + accessPointArn.accountId,
  352. serviceName + fipsSuffix,
  353. useArnRegion ? accessPointArn.region : req.service.config.region,
  354. dnsSuffix
  355. ].join('.');
  356. }
  357. endpoint.host = endpoint.hostname;
  358. var encodedArn = AWS.util.uriEscape(req.params.Bucket);
  359. var path = req.httpRequest.path;
  360. //remove the Bucket value from path
  361. req.httpRequest.path = path.replace(new RegExp('/' + encodedArn), '');
  362. if (req.httpRequest.path[0] !== '/') {
  363. req.httpRequest.path = '/' + req.httpRequest.path;
  364. }
  365. req.httpRequest.region = accessPointArn.region; //region used to sign
  366. },
  367. /**
  368. * Adds Expect: 100-continue header if payload is greater-or-equal 1MB
  369. * @api private
  370. */
  371. addExpect100Continue: function addExpect100Continue(req) {
  372. var len = req.httpRequest.headers['Content-Length'];
  373. if (AWS.util.isNode() && (len >= 1024 * 1024 || req.params.Body instanceof AWS.util.stream.Stream)) {
  374. req.httpRequest.headers['Expect'] = '100-continue';
  375. }
  376. },
  377. /**
  378. * Adds a default content type if none is supplied.
  379. *
  380. * @api private
  381. */
  382. addContentType: function addContentType(req) {
  383. var httpRequest = req.httpRequest;
  384. if (httpRequest.method === 'GET' || httpRequest.method === 'HEAD') {
  385. // Content-Type is not set in GET/HEAD requests
  386. delete httpRequest.headers['Content-Type'];
  387. return;
  388. }
  389. if (!httpRequest.headers['Content-Type']) { // always have a Content-Type
  390. httpRequest.headers['Content-Type'] = 'application/octet-stream';
  391. }
  392. var contentType = httpRequest.headers['Content-Type'];
  393. if (AWS.util.isBrowser()) {
  394. if (typeof httpRequest.body === 'string' && !contentType.match(/;\s*charset=/)) {
  395. var charset = '; charset=UTF-8';
  396. httpRequest.headers['Content-Type'] += charset;
  397. } else {
  398. var replaceFn = function(_, prefix, charsetName) {
  399. return prefix + charsetName.toUpperCase();
  400. };
  401. httpRequest.headers['Content-Type'] =
  402. contentType.replace(/(;\s*charset=)(.+)$/, replaceFn);
  403. }
  404. }
  405. },
  406. /**
  407. * Checks whether checksums should be computed for the request if it's not
  408. * already set by {AWS.EventListeners.Core.COMPUTE_CHECKSUM}. It depends on
  409. * whether {AWS.Config.computeChecksums} is set.
  410. *
  411. * @param req [AWS.Request] the request to check against
  412. * @return [Boolean] whether to compute checksums for a request.
  413. * @api private
  414. */
  415. willComputeChecksums: function willComputeChecksums(req) {
  416. var rules = req.service.api.operations[req.operation].input.members;
  417. var body = req.httpRequest.body;
  418. var needsContentMD5 = req.service.config.computeChecksums &&
  419. rules.ContentMD5 &&
  420. !req.params.ContentMD5 &&
  421. body &&
  422. (AWS.util.Buffer.isBuffer(req.httpRequest.body) || typeof req.httpRequest.body === 'string');
  423. // Sha256 signing disabled, and not a presigned url
  424. if (needsContentMD5 && req.service.shouldDisableBodySigning(req) && !req.isPresigned()) {
  425. return true;
  426. }
  427. // SigV2 and presign, for backwards compatibility purpose.
  428. if (needsContentMD5 && this.getSignatureVersion(req) === 's3' && req.isPresigned()) {
  429. return true;
  430. }
  431. return false;
  432. },
  433. /**
  434. * A listener that computes the Content-MD5 and sets it in the header.
  435. * This listener is to support S3-specific features like
  436. * s3DisableBodySigning and SigV2 presign. Content MD5 logic for SigV4 is
  437. * handled in AWS.EventListeners.Core.COMPUTE_CHECKSUM
  438. *
  439. * @api private
  440. */
  441. computeContentMd5: function computeContentMd5(req) {
  442. if (req.service.willComputeChecksums(req)) {
  443. var md5 = AWS.util.crypto.md5(req.httpRequest.body, 'base64');
  444. req.httpRequest.headers['Content-MD5'] = md5;
  445. }
  446. },
  447. /**
  448. * @api private
  449. */
  450. computeSseCustomerKeyMd5: function computeSseCustomerKeyMd5(req) {
  451. var keys = {
  452. SSECustomerKey: 'x-amz-server-side-encryption-customer-key-MD5',
  453. CopySourceSSECustomerKey: 'x-amz-copy-source-server-side-encryption-customer-key-MD5'
  454. };
  455. AWS.util.each(keys, function(key, header) {
  456. if (req.params[key]) {
  457. var value = AWS.util.crypto.md5(req.params[key], 'base64');
  458. req.httpRequest.headers[header] = value;
  459. }
  460. });
  461. },
  462. /**
  463. * Returns true if the bucket name should be left in the URI path for
  464. * a request to S3. This function takes into account the current
  465. * endpoint protocol (e.g. http or https).
  466. *
  467. * @api private
  468. */
  469. pathStyleBucketName: function pathStyleBucketName(bucketName) {
  470. // user can force path style requests via the configuration
  471. if (this.config.s3ForcePathStyle) return true;
  472. if (this.config.s3BucketEndpoint) return false;
  473. if (s3util.dnsCompatibleBucketName(bucketName)) {
  474. return (this.config.sslEnabled && bucketName.match(/\./)) ? true : false;
  475. } else {
  476. return true; // not dns compatible names must always use path style
  477. }
  478. },
  479. /**
  480. * For COPY operations, some can be error even with status code 200.
  481. * SDK treats the response as exception when response body indicates
  482. * an exception or body is empty.
  483. *
  484. * @api private
  485. */
  486. extractErrorFrom200Response: function extractErrorFrom200Response(resp) {
  487. if (!operationsWith200StatusCodeError[resp.request.operation]) return;
  488. var httpResponse = resp.httpResponse;
  489. if (httpResponse.body && httpResponse.body.toString().match('<Error>')) {
  490. // Response body with '<Error>...</Error>' indicates an exception.
  491. // Get S3 client object. In ManagedUpload, this.service refers to
  492. // S3 client object.
  493. resp.data = null;
  494. var service = this.service ? this.service : this;
  495. service.extractError(resp);
  496. throw resp.error;
  497. } else if (!httpResponse.body || !httpResponse.body.toString().match(/<[\w_]/)) {
  498. // When body is empty or incomplete, S3 might stop the request on detecting client
  499. // side aborting the request.
  500. resp.data = null;
  501. throw AWS.util.error(new Error(), {
  502. code: 'InternalError',
  503. message: 'S3 aborted request'
  504. });
  505. }
  506. },
  507. /**
  508. * @return [Boolean] whether the error can be retried
  509. * @api private
  510. */
  511. retryableError: function retryableError(error, request) {
  512. if (operationsWith200StatusCodeError[request.operation] &&
  513. error.statusCode === 200) {
  514. return true;
  515. } else if (request._requestRegionForBucket &&
  516. request.service.bucketRegionCache[request._requestRegionForBucket]) {
  517. return false;
  518. } else if (error && error.code === 'RequestTimeout') {
  519. return true;
  520. } else if (error &&
  521. regionRedirectErrorCodes.indexOf(error.code) != -1 &&
  522. error.region && error.region != request.httpRequest.region) {
  523. request.httpRequest.region = error.region;
  524. if (error.statusCode === 301) {
  525. request.service.updateReqBucketRegion(request);
  526. }
  527. return true;
  528. } else {
  529. var _super = AWS.Service.prototype.retryableError;
  530. return _super.call(this, error, request);
  531. }
  532. },
  533. /**
  534. * Updates httpRequest with region. If region is not provided, then
  535. * the httpRequest will be updated based on httpRequest.region
  536. *
  537. * @api private
  538. */
  539. updateReqBucketRegion: function updateReqBucketRegion(request, region) {
  540. var httpRequest = request.httpRequest;
  541. if (typeof region === 'string' && region.length) {
  542. httpRequest.region = region;
  543. }
  544. if (!httpRequest.endpoint.host.match(/s3(?!-accelerate).*\.amazonaws\.com$/)) {
  545. return;
  546. }
  547. var service = request.service;
  548. var s3Config = service.config;
  549. var s3BucketEndpoint = s3Config.s3BucketEndpoint;
  550. if (s3BucketEndpoint) {
  551. delete s3Config.s3BucketEndpoint;
  552. }
  553. var newConfig = AWS.util.copy(s3Config);
  554. delete newConfig.endpoint;
  555. newConfig.region = httpRequest.region;
  556. httpRequest.endpoint = (new AWS.S3(newConfig)).endpoint;
  557. service.populateURI(request);
  558. s3Config.s3BucketEndpoint = s3BucketEndpoint;
  559. httpRequest.headers.Host = httpRequest.endpoint.host;
  560. if (request._asm.currentState === 'validate') {
  561. request.removeListener('build', service.populateURI);
  562. request.addListener('build', service.removeVirtualHostedBucketFromPath);
  563. }
  564. },
  565. /**
  566. * Provides a specialized parser for getBucketLocation -- all other
  567. * operations are parsed by the super class.
  568. *
  569. * @api private
  570. */
  571. extractData: function extractData(resp) {
  572. var req = resp.request;
  573. if (req.operation === 'getBucketLocation') {
  574. var match = resp.httpResponse.body.toString().match(/>(.+)<\/Location/);
  575. delete resp.data['_'];
  576. if (match) {
  577. resp.data.LocationConstraint = match[1];
  578. } else {
  579. resp.data.LocationConstraint = '';
  580. }
  581. }
  582. var bucket = req.params.Bucket || null;
  583. if (req.operation === 'deleteBucket' && typeof bucket === 'string' && !resp.error) {
  584. req.service.clearBucketRegionCache(bucket);
  585. } else {
  586. var headers = resp.httpResponse.headers || {};
  587. var region = headers['x-amz-bucket-region'] || null;
  588. if (!region && req.operation === 'createBucket' && !resp.error) {
  589. var createBucketConfiguration = req.params.CreateBucketConfiguration;
  590. if (!createBucketConfiguration) {
  591. region = 'us-east-1';
  592. } else if (createBucketConfiguration.LocationConstraint === 'EU') {
  593. region = 'eu-west-1';
  594. } else {
  595. region = createBucketConfiguration.LocationConstraint;
  596. }
  597. }
  598. if (region) {
  599. if (bucket && region !== req.service.bucketRegionCache[bucket]) {
  600. req.service.bucketRegionCache[bucket] = region;
  601. }
  602. }
  603. }
  604. req.service.extractRequestIds(resp);
  605. },
  606. /**
  607. * Extracts an error object from the http response.
  608. *
  609. * @api private
  610. */
  611. extractError: function extractError(resp) {
  612. var codes = {
  613. 304: 'NotModified',
  614. 403: 'Forbidden',
  615. 400: 'BadRequest',
  616. 404: 'NotFound'
  617. };
  618. var req = resp.request;
  619. var code = resp.httpResponse.statusCode;
  620. var body = resp.httpResponse.body || '';
  621. var headers = resp.httpResponse.headers || {};
  622. var region = headers['x-amz-bucket-region'] || null;
  623. var bucket = req.params.Bucket || null;
  624. var bucketRegionCache = req.service.bucketRegionCache;
  625. if (region && bucket && region !== bucketRegionCache[bucket]) {
  626. bucketRegionCache[bucket] = region;
  627. }
  628. var cachedRegion;
  629. if (codes[code] && body.length === 0) {
  630. if (bucket && !region) {
  631. cachedRegion = bucketRegionCache[bucket] || null;
  632. if (cachedRegion !== req.httpRequest.region) {
  633. region = cachedRegion;
  634. }
  635. }
  636. resp.error = AWS.util.error(new Error(), {
  637. code: codes[code],
  638. message: null,
  639. region: region
  640. });
  641. } else {
  642. var data = new AWS.XML.Parser().parse(body.toString());
  643. if (data.Region && !region) {
  644. region = data.Region;
  645. if (bucket && region !== bucketRegionCache[bucket]) {
  646. bucketRegionCache[bucket] = region;
  647. }
  648. } else if (bucket && !region && !data.Region) {
  649. cachedRegion = bucketRegionCache[bucket] || null;
  650. if (cachedRegion !== req.httpRequest.region) {
  651. region = cachedRegion;
  652. }
  653. }
  654. resp.error = AWS.util.error(new Error(), {
  655. code: data.Code || code,
  656. message: data.Message || null,
  657. region: region
  658. });
  659. }
  660. req.service.extractRequestIds(resp);
  661. },
  662. /**
  663. * If region was not obtained synchronously, then send async request
  664. * to get bucket region for errors resulting from wrong region.
  665. *
  666. * @api private
  667. */
  668. requestBucketRegion: function requestBucketRegion(resp, done) {
  669. var error = resp.error;
  670. var req = resp.request;
  671. var bucket = req.params.Bucket || null;
  672. if (!error || !bucket || error.region || req.operation === 'listObjects' ||
  673. (AWS.util.isNode() && req.operation === 'headBucket') ||
  674. (error.statusCode === 400 && req.operation !== 'headObject') ||
  675. regionRedirectErrorCodes.indexOf(error.code) === -1) {
  676. return done();
  677. }
  678. var reqOperation = AWS.util.isNode() ? 'headBucket' : 'listObjects';
  679. var reqParams = {Bucket: bucket};
  680. if (reqOperation === 'listObjects') reqParams.MaxKeys = 0;
  681. var regionReq = req.service[reqOperation](reqParams);
  682. regionReq._requestRegionForBucket = bucket;
  683. regionReq.send(function() {
  684. var region = req.service.bucketRegionCache[bucket] || null;
  685. error.region = region;
  686. done();
  687. });
  688. },
  689. /**
  690. * For browser only. If NetworkingError received, will attempt to obtain
  691. * the bucket region.
  692. *
  693. * @api private
  694. */
  695. reqRegionForNetworkingError: function reqRegionForNetworkingError(resp, done) {
  696. if (!AWS.util.isBrowser()) {
  697. return done();
  698. }
  699. var error = resp.error;
  700. var request = resp.request;
  701. var bucket = request.params.Bucket;
  702. if (!error || error.code !== 'NetworkingError' || !bucket ||
  703. request.httpRequest.region === 'us-east-1') {
  704. return done();
  705. }
  706. var service = request.service;
  707. var bucketRegionCache = service.bucketRegionCache;
  708. var cachedRegion = bucketRegionCache[bucket] || null;
  709. if (cachedRegion && cachedRegion !== request.httpRequest.region) {
  710. service.updateReqBucketRegion(request, cachedRegion);
  711. done();
  712. } else if (!s3util.dnsCompatibleBucketName(bucket)) {
  713. service.updateReqBucketRegion(request, 'us-east-1');
  714. if (bucketRegionCache[bucket] !== 'us-east-1') {
  715. bucketRegionCache[bucket] = 'us-east-1';
  716. }
  717. done();
  718. } else if (request.httpRequest.virtualHostedBucket) {
  719. var getRegionReq = service.listObjects({Bucket: bucket, MaxKeys: 0});
  720. service.updateReqBucketRegion(getRegionReq, 'us-east-1');
  721. getRegionReq._requestRegionForBucket = bucket;
  722. getRegionReq.send(function() {
  723. var region = service.bucketRegionCache[bucket] || null;
  724. if (region && region !== request.httpRequest.region) {
  725. service.updateReqBucketRegion(request, region);
  726. }
  727. done();
  728. });
  729. } else {
  730. // DNS-compatible path-style
  731. // (s3ForcePathStyle or bucket name with dot over https)
  732. // Cannot obtain region information for this case
  733. done();
  734. }
  735. },
  736. /**
  737. * Cache for bucket region.
  738. *
  739. * @api private
  740. */
  741. bucketRegionCache: {},
  742. /**
  743. * Clears bucket region cache.
  744. *
  745. * @api private
  746. */
  747. clearBucketRegionCache: function(buckets) {
  748. var bucketRegionCache = this.bucketRegionCache;
  749. if (!buckets) {
  750. buckets = Object.keys(bucketRegionCache);
  751. } else if (typeof buckets === 'string') {
  752. buckets = [buckets];
  753. }
  754. for (var i = 0; i < buckets.length; i++) {
  755. delete bucketRegionCache[buckets[i]];
  756. }
  757. return bucketRegionCache;
  758. },
  759. /**
  760. * Corrects request region if bucket's cached region is different
  761. *
  762. * @api private
  763. */
  764. correctBucketRegionFromCache: function correctBucketRegionFromCache(req) {
  765. var bucket = req.params.Bucket || null;
  766. if (bucket) {
  767. var service = req.service;
  768. var requestRegion = req.httpRequest.region;
  769. var cachedRegion = service.bucketRegionCache[bucket];
  770. if (cachedRegion && cachedRegion !== requestRegion) {
  771. service.updateReqBucketRegion(req, cachedRegion);
  772. }
  773. }
  774. },
  775. /**
  776. * Extracts S3 specific request ids from the http response.
  777. *
  778. * @api private
  779. */
  780. extractRequestIds: function extractRequestIds(resp) {
  781. var extendedRequestId = resp.httpResponse.headers ? resp.httpResponse.headers['x-amz-id-2'] : null;
  782. var cfId = resp.httpResponse.headers ? resp.httpResponse.headers['x-amz-cf-id'] : null;
  783. resp.extendedRequestId = extendedRequestId;
  784. resp.cfId = cfId;
  785. if (resp.error) {
  786. resp.error.requestId = resp.requestId || null;
  787. resp.error.extendedRequestId = extendedRequestId;
  788. resp.error.cfId = cfId;
  789. }
  790. },
  791. /**
  792. * Get a pre-signed URL for a given operation name.
  793. *
  794. * @note You must ensure that you have static or previously resolved
  795. * credentials if you call this method synchronously (with no callback),
  796. * otherwise it may not properly sign the request. If you cannot guarantee
  797. * this (you are using an asynchronous credential provider, i.e., EC2
  798. * IAM roles), you should always call this method with an asynchronous
  799. * callback.
  800. * @note Not all operation parameters are supported when using pre-signed
  801. * URLs. Certain parameters, such as `SSECustomerKey`, `ACL`, `Expires`,
  802. * `ContentLength`, or `Tagging` must be provided as headers when sending a
  803. * request. If you are using pre-signed URLs to upload from a browser and
  804. * need to use these fields, see {createPresignedPost}.
  805. * @note The default signer allows altering the request by adding corresponding
  806. * headers to set some parameters (e.g. Range) and these added parameters
  807. * won't be signed. You must use signatureVersion v4 to to include these
  808. * parameters in the signed portion of the URL and enforce exact matching
  809. * between headers and signed params in the URL.
  810. * @note This operation cannot be used with a promise. See note above regarding
  811. * asynchronous credentials and use with a callback.
  812. * @param operation [String] the name of the operation to call
  813. * @param params [map] parameters to pass to the operation. See the given
  814. * operation for the expected operation parameters. In addition, you can
  815. * also pass the "Expires" parameter to inform S3 how long the URL should
  816. * work for.
  817. * @option params Expires [Integer] (900) the number of seconds to expire
  818. * the pre-signed URL operation in. Defaults to 15 minutes.
  819. * @param callback [Function] if a callback is provided, this function will
  820. * pass the URL as the second parameter (after the error parameter) to
  821. * the callback function.
  822. * @return [String] if called synchronously (with no callback), returns the
  823. * signed URL.
  824. * @return [null] nothing is returned if a callback is provided.
  825. * @example Pre-signing a getObject operation (synchronously)
  826. * var params = {Bucket: 'bucket', Key: 'key'};
  827. * var url = s3.getSignedUrl('getObject', params);
  828. * console.log('The URL is', url);
  829. * @example Pre-signing a putObject (asynchronously)
  830. * var params = {Bucket: 'bucket', Key: 'key'};
  831. * s3.getSignedUrl('putObject', params, function (err, url) {
  832. * console.log('The URL is', url);
  833. * });
  834. * @example Pre-signing a putObject operation with a specific payload
  835. * var params = {Bucket: 'bucket', Key: 'key', Body: 'body'};
  836. * var url = s3.getSignedUrl('putObject', params);
  837. * console.log('The URL is', url);
  838. * @example Passing in a 1-minute expiry time for a pre-signed URL
  839. * var params = {Bucket: 'bucket', Key: 'key', Expires: 60};
  840. * var url = s3.getSignedUrl('getObject', params);
  841. * console.log('The URL is', url); // expires in 60 seconds
  842. */
  843. getSignedUrl: function getSignedUrl(operation, params, callback) {
  844. params = AWS.util.copy(params || {});
  845. var expires = params.Expires || 900;
  846. if (typeof expires !== 'number') {
  847. throw AWS.util.error(new Error(),
  848. { code: 'InvalidParameterException', message: 'The expiration must be a number, received ' + typeof expires });
  849. }
  850. delete params.Expires; // we can't validate this
  851. var request = this.makeRequest(operation, params);
  852. if (callback) {
  853. AWS.util.defer(function() {
  854. request.presign(expires, callback);
  855. });
  856. } else {
  857. return request.presign(expires, callback);
  858. }
  859. },
  860. /**
  861. * @!method getSignedUrlPromise()
  862. * Returns a 'thenable' promise that will be resolved with a pre-signed URL
  863. * for a given operation name.
  864. *
  865. * Two callbacks can be provided to the `then` method on the returned promise.
  866. * The first callback will be called if the promise is fulfilled, and the second
  867. * callback will be called if the promise is rejected.
  868. * @note Not all operation parameters are supported when using pre-signed
  869. * URLs. Certain parameters, such as `SSECustomerKey`, `ACL`, `Expires`,
  870. * `ContentLength`, or `Tagging` must be provided as headers when sending a
  871. * request. If you are using pre-signed URLs to upload from a browser and
  872. * need to use these fields, see {createPresignedPost}.
  873. * @param operation [String] the name of the operation to call
  874. * @param params [map] parameters to pass to the operation. See the given
  875. * operation for the expected operation parameters. In addition, you can
  876. * also pass the "Expires" parameter to inform S3 how long the URL should
  877. * work for.
  878. * @option params Expires [Integer] (900) the number of seconds to expire
  879. * the pre-signed URL operation in. Defaults to 15 minutes.
  880. * @callback fulfilledCallback function(url)
  881. * Called if the promise is fulfilled.
  882. * @param url [String] the signed url
  883. * @callback rejectedCallback function(err)
  884. * Called if the promise is rejected.
  885. * @param err [Error] if an error occurred, this value will be filled
  886. * @return [Promise] A promise that represents the state of the `refresh` call.
  887. * @example Pre-signing a getObject operation
  888. * var params = {Bucket: 'bucket', Key: 'key'};
  889. * var promise = s3.getSignedUrlPromise('getObject', params);
  890. * promise.then(function(url) {
  891. * console.log('The URL is', url);
  892. * }, function(err) { ... });
  893. * @example Pre-signing a putObject operation with a specific payload
  894. * var params = {Bucket: 'bucket', Key: 'key', Body: 'body'};
  895. * var promise = s3.getSignedUrlPromise('putObject', params);
  896. * promise.then(function(url) {
  897. * console.log('The URL is', url);
  898. * }, function(err) { ... });
  899. * @example Passing in a 1-minute expiry time for a pre-signed URL
  900. * var params = {Bucket: 'bucket', Key: 'key', Expires: 60};
  901. * var promise = s3.getSignedUrlPromise('getObject', params);
  902. * promise.then(function(url) {
  903. * console.log('The URL is', url);
  904. * }, function(err) { ... });
  905. */
  906. /**
  907. * Get a pre-signed POST policy to support uploading to S3 directly from an
  908. * HTML form.
  909. *
  910. * @param params [map]
  911. * @option params Bucket [String] The bucket to which the post should be
  912. * uploaded
  913. * @option params Expires [Integer] (3600) The number of seconds for which
  914. * the presigned policy should be valid.
  915. * @option params Conditions [Array] An array of conditions that must be met
  916. * for the presigned policy to allow the
  917. * upload. This can include required tags,
  918. * the accepted range for content lengths,
  919. * etc.
  920. * @see http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
  921. * @option params Fields [map] Fields to include in the form. All
  922. * values passed in as fields will be
  923. * signed as exact match conditions.
  924. * @param callback [Function]
  925. *
  926. * @note All fields passed in when creating presigned post data will be signed
  927. * as exact match conditions. Any fields that will be interpolated by S3
  928. * must be added to the fields hash after signing, and an appropriate
  929. * condition for such fields must be explicitly added to the Conditions
  930. * array passed to this function before signing.
  931. *
  932. * @example Presiging post data with a known key
  933. * var params = {
  934. * Bucket: 'bucket',
  935. * Fields: {
  936. * key: 'key'
  937. * }
  938. * };
  939. * s3.createPresignedPost(params, function(err, data) {
  940. * if (err) {
  941. * console.error('Presigning post data encountered an error', err);
  942. * } else {
  943. * console.log('The post data is', data);
  944. * }
  945. * });
  946. *
  947. * @example Presigning post data with an interpolated key
  948. * var params = {
  949. * Bucket: 'bucket',
  950. * Conditions: [
  951. * ['starts-with', '$key', 'path/to/uploads/']
  952. * ]
  953. * };
  954. * s3.createPresignedPost(params, function(err, data) {
  955. * if (err) {
  956. * console.error('Presigning post data encountered an error', err);
  957. * } else {
  958. * data.Fields.key = 'path/to/uploads/${filename}';
  959. * console.log('The post data is', data);
  960. * }
  961. * });
  962. *
  963. * @note You must ensure that you have static or previously resolved
  964. * credentials if you call this method synchronously (with no callback),
  965. * otherwise it may not properly sign the request. If you cannot guarantee
  966. * this (you are using an asynchronous credential provider, i.e., EC2
  967. * IAM roles), you should always call this method with an asynchronous
  968. * callback.
  969. *
  970. * @return [map] If called synchronously (with no callback), returns a hash
  971. * with the url to set as the form action and a hash of fields
  972. * to include in the form.
  973. * @return [null] Nothing is returned if a callback is provided.
  974. *
  975. * @callback callback function (err, data)
  976. * @param err [Error] the error object returned from the policy signer
  977. * @param data [map] The data necessary to construct an HTML form
  978. * @param data.url [String] The URL to use as the action of the form
  979. * @param data.fields [map] A hash of fields that must be included in the
  980. * form for the upload to succeed. This hash will
  981. * include the signed POST policy, your access key
  982. * ID and security token (if present), etc. These
  983. * may be safely included as input elements of type
  984. * 'hidden.'
  985. */
  986. createPresignedPost: function createPresignedPost(params, callback) {
  987. if (typeof params === 'function' && callback === undefined) {
  988. callback = params;
  989. params = null;
  990. }
  991. params = AWS.util.copy(params || {});
  992. var boundParams = this.config.params || {};
  993. var bucket = params.Bucket || boundParams.Bucket,
  994. self = this,
  995. config = this.config,
  996. endpoint = AWS.util.copy(this.endpoint);
  997. if (!config.s3BucketEndpoint) {
  998. endpoint.pathname = '/' + bucket;
  999. }
  1000. function finalizePost() {
  1001. return {
  1002. url: AWS.util.urlFormat(endpoint),
  1003. fields: self.preparePostFields(
  1004. config.credentials,
  1005. config.region,
  1006. bucket,
  1007. params.Fields,
  1008. params.Conditions,
  1009. params.Expires
  1010. )
  1011. };
  1012. }
  1013. if (callback) {
  1014. config.getCredentials(function (err) {
  1015. if (err) {
  1016. callback(err);
  1017. } else {
  1018. try {
  1019. callback(null, finalizePost());
  1020. } catch (err) {
  1021. callback(err);
  1022. }
  1023. }
  1024. });
  1025. } else {
  1026. return finalizePost();
  1027. }
  1028. },
  1029. /**
  1030. * @api private
  1031. */
  1032. preparePostFields: function preparePostFields(
  1033. credentials,
  1034. region,
  1035. bucket,
  1036. fields,
  1037. conditions,
  1038. expiresInSeconds
  1039. ) {
  1040. var now = this.getSkewCorrectedDate();
  1041. if (!credentials || !region || !bucket) {
  1042. throw new Error('Unable to create a POST object policy without a bucket,'
  1043. + ' region, and credentials');
  1044. }
  1045. fields = AWS.util.copy(fields || {});
  1046. conditions = (conditions || []).slice(0);
  1047. expiresInSeconds = expiresInSeconds || 3600;
  1048. var signingDate = AWS.util.date.iso8601(now).replace(/[:\-]|\.\d{3}/g, '');
  1049. var shortDate = signingDate.substr(0, 8);
  1050. var scope = v4Credentials.createScope(shortDate, region, 's3');
  1051. var credential = credentials.accessKeyId + '/' + scope;
  1052. fields['bucket'] = bucket;
  1053. fields['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
  1054. fields['X-Amz-Credential'] = credential;
  1055. fields['X-Amz-Date'] = signingDate;
  1056. if (credentials.sessionToken) {
  1057. fields['X-Amz-Security-Token'] = credentials.sessionToken;
  1058. }
  1059. for (var field in fields) {
  1060. if (fields.hasOwnProperty(field)) {
  1061. var condition = {};
  1062. condition[field] = fields[field];
  1063. conditions.push(condition);
  1064. }
  1065. }
  1066. fields.Policy = this.preparePostPolicy(
  1067. new Date(now.valueOf() + expiresInSeconds * 1000),
  1068. conditions
  1069. );
  1070. fields['X-Amz-Signature'] = AWS.util.crypto.hmac(
  1071. v4Credentials.getSigningKey(credentials, shortDate, region, 's3', true),
  1072. fields.Policy,
  1073. 'hex'
  1074. );
  1075. return fields;
  1076. },
  1077. /**
  1078. * @api private
  1079. */
  1080. preparePostPolicy: function preparePostPolicy(expiration, conditions) {
  1081. return AWS.util.base64.encode(JSON.stringify({
  1082. expiration: AWS.util.date.iso8601(expiration),
  1083. conditions: conditions
  1084. }));
  1085. },
  1086. /**
  1087. * @api private
  1088. */
  1089. prepareSignedUrl: function prepareSignedUrl(request) {
  1090. request.addListener('validate', request.service.noPresignedContentLength);
  1091. request.removeListener('build', request.service.addContentType);
  1092. if (!request.params.Body) {
  1093. // no Content-MD5/SHA-256 if body is not provided
  1094. request.removeListener('build', request.service.computeContentMd5);
  1095. } else {
  1096. request.addListener('afterBuild', AWS.EventListeners.Core.COMPUTE_SHA256);
  1097. }
  1098. },
  1099. /**
  1100. * @api private
  1101. * @param request
  1102. */
  1103. disableBodySigning: function disableBodySigning(request) {
  1104. var headers = request.httpRequest.headers;
  1105. // Add the header to anything that isn't a presigned url, unless that presigned url had a body defined
  1106. if (!Object.prototype.hasOwnProperty.call(headers, 'presigned-expires')) {
  1107. headers['X-Amz-Content-Sha256'] = 'UNSIGNED-PAYLOAD';
  1108. }
  1109. },
  1110. /**
  1111. * @api private
  1112. */
  1113. noPresignedContentLength: function noPresignedContentLength(request) {
  1114. if (request.params.ContentLength !== undefined) {
  1115. throw AWS.util.error(new Error(), {code: 'UnexpectedParameter',
  1116. message: 'ContentLength is not supported in pre-signed URLs.'});
  1117. }
  1118. },
  1119. createBucket: function createBucket(params, callback) {
  1120. // When creating a bucket *outside* the classic region, the location
  1121. // constraint must be set for the bucket and it must match the endpoint.
  1122. // This chunk of code will set the location constraint param based
  1123. // on the region (when possible), but it will not override a passed-in
  1124. // location constraint.
  1125. if (typeof params === 'function' || !params) {
  1126. callback = callback || params;
  1127. params = {};
  1128. }
  1129. var hostname = this.endpoint.hostname;
  1130. // copy params so that appending keys does not unintentioinallly
  1131. // mutate params object argument passed in by user
  1132. var copiedParams = AWS.util.copy(params);
  1133. if (hostname !== this.api.globalEndpoint && !params.CreateBucketConfiguration) {
  1134. copiedParams.CreateBucketConfiguration = { LocationConstraint: this.config.region };
  1135. }
  1136. return this.makeRequest('createBucket', copiedParams, callback);
  1137. },
  1138. writeGetObjectResponse: function writeGetObjectResponse(params, callback) {
  1139. var request = this.makeRequest('writeGetObjectResponse', AWS.util.copy(params), callback);
  1140. var hostname = this.endpoint.hostname;
  1141. if (hostname.indexOf(this.config.region) !== -1) {
  1142. // hostname specifies a region already
  1143. hostname = hostname.replace('s3.', OBJECT_LAMBDA_SERVICE + '.');
  1144. } else {
  1145. // Hostname doesn't have a region.
  1146. // Object Lambda requires an explicit region.
  1147. hostname = hostname.replace('s3.', OBJECT_LAMBDA_SERVICE + '.' + this.config.region + '.');
  1148. }
  1149. request.httpRequest.endpoint = new AWS.Endpoint(hostname, this.config);
  1150. return request;
  1151. },
  1152. /**
  1153. * @see AWS.S3.ManagedUpload
  1154. * @overload upload(params = {}, [options], [callback])
  1155. * Uploads an arbitrarily sized buffer, blob, or stream, using intelligent
  1156. * concurrent handling of parts if the payload is large enough. You can
  1157. * configure the concurrent queue size by setting `options`. Note that this
  1158. * is the only operation for which the SDK can retry requests with stream
  1159. * bodies.
  1160. *
  1161. * @param (see AWS.S3.putObject)
  1162. * @option (see AWS.S3.ManagedUpload.constructor)
  1163. * @return [AWS.S3.ManagedUpload] the managed upload object that can call
  1164. * `send()` or track progress.
  1165. * @example Uploading a stream object
  1166. * var params = {Bucket: 'bucket', Key: 'key', Body: stream};
  1167. * s3.upload(params, function(err, data) {
  1168. * console.log(err, data);
  1169. * });
  1170. * @example Uploading a stream with concurrency of 1 and partSize of 10mb
  1171. * var params = {Bucket: 'bucket', Key: 'key', Body: stream};
  1172. * var options = {partSize: 10 * 1024 * 1024, queueSize: 1};
  1173. * s3.upload(params, options, function(err, data) {
  1174. * console.log(err, data);
  1175. * });
  1176. * @callback callback function(err, data)
  1177. * @param err [Error] an error or null if no error occurred.
  1178. * @param data [map] The response data from the successful upload:
  1179. * @param data.Location [String] the URL of the uploaded object
  1180. * @param data.ETag [String] the ETag of the uploaded object
  1181. * @param data.Bucket [String] the bucket to which the object was uploaded
  1182. * @param data.Key [String] the key to which the object was uploaded
  1183. */
  1184. upload: function upload(params, options, callback) {
  1185. if (typeof options === 'function' && callback === undefined) {
  1186. callback = options;
  1187. options = null;
  1188. }
  1189. options = options || {};
  1190. options = AWS.util.merge(options || {}, {service: this, params: params});
  1191. var uploader = new AWS.S3.ManagedUpload(options);
  1192. if (typeof callback === 'function') uploader.send(callback);
  1193. return uploader;
  1194. },
  1195. /**
  1196. * @api private
  1197. */
  1198. setExpiresString: function setExpiresString(response) {
  1199. // Check if response contains Expires value, and populate ExpiresString.
  1200. if (response && response.httpResponse && response.httpResponse.headers) {
  1201. if ('expires' in response.httpResponse.headers) {
  1202. response.httpResponse.headers.expiresstring = response.httpResponse.headers.expires;
  1203. }
  1204. }
  1205. // Check if value in Expires is not a Date using parseTimestamp.
  1206. try {
  1207. if (response && response.httpResponse && response.httpResponse.headers) {
  1208. if ('expires' in response.httpResponse.headers) {
  1209. AWS.util.date.parseTimestamp(response.httpResponse.headers.expires);
  1210. }
  1211. }
  1212. } catch (e) {
  1213. console.log('AWS SDK', '(warning)', e);
  1214. delete response.httpResponse.headers.expires;
  1215. }
  1216. }
  1217. });
  1218. /**
  1219. * @api private
  1220. */
  1221. AWS.S3.addPromisesToClass = function addPromisesToClass(PromiseDependency) {
  1222. this.prototype.getSignedUrlPromise = AWS.util.promisifyMethod('getSignedUrl', PromiseDependency);
  1223. };
  1224. /**
  1225. * @api private
  1226. */
  1227. AWS.S3.deletePromisesFromClass = function deletePromisesFromClass() {
  1228. delete this.prototype.getSignedUrlPromise;
  1229. };
  1230. AWS.util.addPromises(AWS.S3);