git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

s3.js (29035B)


      1 var AWS = require('../core');
      2 
      3 // Pull in managed upload extension
      4 require('../s3/managed_upload');
      5 
      6 /**
      7  * @api private
      8  */
      9 var operationsWith200StatusCodeError = {
     10   'completeMultipartUpload': true,
     11   'copyObject': true,
     12   'uploadPartCopy': true
     13 };
     14 
     15 /**
     16  * @api private
     17  */
     18  var regionRedirectErrorCodes = [
     19   'AuthorizationHeaderMalformed', // non-head operations on virtual-hosted global bucket endpoints
     20   'BadRequest', // head operations on virtual-hosted global bucket endpoints
     21   'PermanentRedirect', // non-head operations on path-style or regional endpoints
     22   301 // head operations on path-style or regional endpoints
     23  ];
     24 
     25 AWS.util.update(AWS.S3.prototype, {
     26   /**
     27    * @api private
     28    */
     29   getSignerClass: function getSignerClass(request) {
     30     var defaultApiVersion = this.api.signatureVersion;
     31     var userDefinedVersion = this._originalConfig ? this._originalConfig.signatureVersion : null;
     32     var regionDefinedVersion = this.config.signatureVersion;
     33     var isPresigned = request ? request.isPresigned() : false;
     34     /*
     35       1) User defined version specified:
     36         a) always return user defined version
     37       2) No user defined version specified:
     38         a) default to lowest version the region supports
     39     */
     40     if (userDefinedVersion) {
     41       userDefinedVersion = userDefinedVersion === 'v2' ? 's3' : userDefinedVersion;
     42       return AWS.Signers.RequestSigner.getVersion(userDefinedVersion);
     43     }
     44     if (regionDefinedVersion) {
     45       defaultApiVersion = regionDefinedVersion;
     46     }
     47 
     48     return AWS.Signers.RequestSigner.getVersion(defaultApiVersion);
     49   },
     50 
     51   /**
     52    * @api private
     53    */
     54   validateService: function validateService() {
     55     var msg;
     56     var messages = [];
     57 
     58     // default to us-east-1 when no region is provided
     59     if (!this.config.region) this.config.region = 'us-east-1';
     60 
     61     if (!this.config.endpoint && this.config.s3BucketEndpoint) {
     62       messages.push('An endpoint must be provided when configuring ' +
     63                     '`s3BucketEndpoint` to true.');
     64     }
     65     if (messages.length === 1) {
     66       msg = messages[0];
     67     } else if (messages.length > 1) {
     68       msg = 'Multiple configuration errors:\n' + messages.join('\n');
     69     }
     70     if (msg) {
     71       throw AWS.util.error(new Error(),
     72         {name: 'InvalidEndpoint', message: msg});
     73     }
     74   },
     75 
     76   /**
     77    * @api private
     78    */
     79   shouldDisableBodySigning: function shouldDisableBodySigning(request) {
     80     var signerClass = this.getSignerClass();
     81     if (this.config.s3DisableBodySigning === true && signerClass === AWS.Signers.V4
     82         && request.httpRequest.endpoint.protocol === 'https:') {
     83       return true;
     84     }
     85     return false;
     86   },
     87 
     88   /**
     89    * @api private
     90    */
     91   setupRequestListeners: function setupRequestListeners(request) {
     92     request.addListener('validate', this.validateScheme);
     93     request.addListener('validate', this.validateBucketEndpoint);
     94     request.addListener('validate', this.correctBucketRegionFromCache);
     95     request.addListener('build', this.addContentType);
     96     request.addListener('build', this.populateURI);
     97     request.addListener('build', this.computeContentMd5);
     98     request.addListener('build', this.computeSseCustomerKeyMd5);
     99     request.addListener('afterBuild', this.addExpect100Continue);
    100     request.removeListener('validate',
    101       AWS.EventListeners.Core.VALIDATE_REGION);
    102     request.addListener('extractError', this.extractError);
    103     request.onAsync('extractError', this.requestBucketRegion);
    104     request.addListener('extractData', this.extractData);
    105     request.addListener('extractData', AWS.util.hoistPayloadMember);
    106     request.addListener('beforePresign', this.prepareSignedUrl);
    107     if (AWS.util.isBrowser()) {
    108       request.onAsync('retry', this.reqRegionForNetworkingError);
    109     }
    110     if (this.shouldDisableBodySigning(request))  {
    111       request.removeListener('afterBuild', AWS.EventListeners.Core.COMPUTE_SHA256);
    112       request.addListener('afterBuild', this.disableBodySigning);
    113     }
    114   },
    115 
    116   /**
    117    * @api private
    118    */
    119   validateScheme: function(req) {
    120     var params = req.params,
    121         scheme = req.httpRequest.endpoint.protocol,
    122         sensitive = params.SSECustomerKey || params.CopySourceSSECustomerKey;
    123     if (sensitive && scheme !== 'https:') {
    124       var msg = 'Cannot send SSE keys over HTTP. Set \'sslEnabled\'' +
    125         'to \'true\' in your configuration';
    126       throw AWS.util.error(new Error(),
    127         { code: 'ConfigError', message: msg });
    128     }
    129   },
    130 
    131   /**
    132    * @api private
    133    */
    134   validateBucketEndpoint: function(req) {
    135     if (!req.params.Bucket && req.service.config.s3BucketEndpoint) {
    136       var msg = 'Cannot send requests to root API with `s3BucketEndpoint` set.';
    137       throw AWS.util.error(new Error(),
    138         { code: 'ConfigError', message: msg });
    139     }
    140   },
    141 
    142   /**
    143    * @api private
    144    */
    145   isValidAccelerateOperation: function isValidAccelerateOperation(operation) {
    146     var invalidOperations = [
    147       'createBucket',
    148       'deleteBucket',
    149       'listBuckets'
    150     ];
    151     return invalidOperations.indexOf(operation) === -1;
    152   },
    153 
    154 
    155   /**
    156    * S3 prefers dns-compatible bucket names to be moved from the uri path
    157    * to the hostname as a sub-domain.  This is not possible, even for dns-compat
    158    * buckets when using SSL and the bucket name contains a dot ('.').  The
    159    * ssl wildcard certificate is only 1-level deep.
    160    *
    161    * @api private
    162    */
    163   populateURI: function populateURI(req) {
    164     var httpRequest = req.httpRequest;
    165     var b = req.params.Bucket;
    166     var service = req.service;
    167     var endpoint = httpRequest.endpoint;
    168 
    169     if (b) {
    170       if (!service.pathStyleBucketName(b)) {
    171         if (service.config.useAccelerateEndpoint && service.isValidAccelerateOperation(req.operation)) {
    172           if (service.config.useDualstack) {
    173             endpoint.hostname = b + '.s3-accelerate.dualstack.amazonaws.com';
    174           } else {
    175             endpoint.hostname = b + '.s3-accelerate.amazonaws.com';
    176           }
    177         } else if (!service.config.s3BucketEndpoint) {
    178           endpoint.hostname =
    179             b + '.' + endpoint.hostname;
    180         }
    181 
    182         var port = endpoint.port;
    183         if (port !== 80 && port !== 443) {
    184           endpoint.host = endpoint.hostname + ':' +
    185             endpoint.port;
    186         } else {
    187           endpoint.host = endpoint.hostname;
    188         }
    189 
    190         httpRequest.virtualHostedBucket = b; // needed for signing the request
    191         service.removeVirtualHostedBucketFromPath(req);
    192       }
    193     }
    194   },
    195 
    196   /**
    197    * Takes the bucket name out of the path if bucket is virtual-hosted
    198    *
    199    * @api private
    200    */
    201   removeVirtualHostedBucketFromPath: function removeVirtualHostedBucketFromPath(req) {
    202     var httpRequest = req.httpRequest;
    203     var bucket = httpRequest.virtualHostedBucket;
    204     if (bucket && httpRequest.path) {
    205       httpRequest.path = httpRequest.path.replace(new RegExp('/' + bucket), '');
    206       if (httpRequest.path[0] !== '/') {
    207         httpRequest.path = '/' + httpRequest.path;
    208       }
    209     }
    210   },
    211 
    212   /**
    213    * Adds Expect: 100-continue header if payload is greater-or-equal 1MB
    214    * @api private
    215    */
    216   addExpect100Continue: function addExpect100Continue(req) {
    217     var len = req.httpRequest.headers['Content-Length'];
    218     if (AWS.util.isNode() && len >= 1024 * 1024) {
    219       req.httpRequest.headers['Expect'] = '100-continue';
    220     }
    221   },
    222 
    223   /**
    224    * Adds a default content type if none is supplied.
    225    *
    226    * @api private
    227    */
    228   addContentType: function addContentType(req) {
    229     var httpRequest = req.httpRequest;
    230     if (httpRequest.method === 'GET' || httpRequest.method === 'HEAD') {
    231       // Content-Type is not set in GET/HEAD requests
    232       delete httpRequest.headers['Content-Type'];
    233       return;
    234     }
    235 
    236     if (!httpRequest.headers['Content-Type']) { // always have a Content-Type
    237       httpRequest.headers['Content-Type'] = 'application/octet-stream';
    238     }
    239 
    240     var contentType = httpRequest.headers['Content-Type'];
    241     if (AWS.util.isBrowser()) {
    242       if (typeof httpRequest.body === 'string' && !contentType.match(/;\s*charset=/)) {
    243         var charset = '; charset=UTF-8';
    244         httpRequest.headers['Content-Type'] += charset;
    245       } else {
    246         var replaceFn = function(_, prefix, charsetName) {
    247           return prefix + charsetName.toUpperCase();
    248         };
    249 
    250         httpRequest.headers['Content-Type'] =
    251           contentType.replace(/(;\s*charset=)(.+)$/, replaceFn);
    252       }
    253     }
    254   },
    255 
    256   /**
    257    * @api private
    258    */
    259   computableChecksumOperations: {
    260     putBucketCors: true,
    261     putBucketLifecycle: true,
    262     putBucketLifecycleConfiguration: true,
    263     putBucketTagging: true,
    264     deleteObjects: true,
    265     putBucketReplication: true
    266   },
    267 
    268   /**
    269    * Checks whether checksums should be computed for the request.
    270    * If the request requires checksums to be computed, this will always
    271    * return true, otherwise it depends on whether {AWS.Config.computeChecksums}
    272    * is set.
    273    *
    274    * @param req [AWS.Request] the request to check against
    275    * @return [Boolean] whether to compute checksums for a request.
    276    * @api private
    277    */
    278   willComputeChecksums: function willComputeChecksums(req) {
    279     if (this.computableChecksumOperations[req.operation]) return true;
    280     if (!this.config.computeChecksums) return false;
    281 
    282     // TODO: compute checksums for Stream objects
    283     if (!AWS.util.Buffer.isBuffer(req.httpRequest.body) &&
    284         typeof req.httpRequest.body !== 'string') {
    285       return false;
    286     }
    287 
    288     var rules = req.service.api.operations[req.operation].input.members;
    289 
    290     // Sha256 signing disabled, and not a presigned url
    291     if (req.service.shouldDisableBodySigning(req) && !Object.prototype.hasOwnProperty.call(req.httpRequest.headers, 'presigned-expires')) {
    292       if (rules.ContentMD5 && !req.params.ContentMD5) {
    293         return true;
    294       }
    295     }
    296 
    297     // V4 signer uses SHA256 signatures so only compute MD5 if it is required
    298     if (req.service.getSignerClass(req) === AWS.Signers.V4) {
    299       if (rules.ContentMD5 && !rules.ContentMD5.required) return false;
    300     }
    301 
    302     if (rules.ContentMD5 && !req.params.ContentMD5) return true;
    303   },
    304 
    305   /**
    306    * A listener that computes the Content-MD5 and sets it in the header.
    307    * @see AWS.S3.willComputeChecksums
    308    * @api private
    309    */
    310   computeContentMd5: function computeContentMd5(req) {
    311     if (req.service.willComputeChecksums(req)) {
    312       var md5 = AWS.util.crypto.md5(req.httpRequest.body, 'base64');
    313       req.httpRequest.headers['Content-MD5'] = md5;
    314     }
    315   },
    316 
    317   /**
    318    * @api private
    319    */
    320   computeSseCustomerKeyMd5: function computeSseCustomerKeyMd5(req) {
    321     var keys = {
    322       SSECustomerKey: 'x-amz-server-side-encryption-customer-key-MD5',
    323       CopySourceSSECustomerKey: 'x-amz-copy-source-server-side-encryption-customer-key-MD5'
    324     };
    325     AWS.util.each(keys, function(key, header) {
    326       if (req.params[key]) {
    327         var value = AWS.util.crypto.md5(req.params[key], 'base64');
    328         req.httpRequest.headers[header] = value;
    329       }
    330     });
    331   },
    332 
    333   /**
    334    * Returns true if the bucket name should be left in the URI path for
    335    * a request to S3.  This function takes into account the current
    336    * endpoint protocol (e.g. http or https).
    337    *
    338    * @api private
    339    */
    340   pathStyleBucketName: function pathStyleBucketName(bucketName) {
    341     // user can force path style requests via the configuration
    342     if (this.config.s3ForcePathStyle) return true;
    343     if (this.config.s3BucketEndpoint) return false;
    344 
    345     if (this.dnsCompatibleBucketName(bucketName)) {
    346       return (this.config.sslEnabled && bucketName.match(/\./)) ? true : false;
    347     } else {
    348       return true; // not dns compatible names must always use path style
    349     }
    350   },
    351 
    352   /**
    353    * Returns true if the bucket name is DNS compatible.  Buckets created
    354    * outside of the classic region MUST be DNS compatible.
    355    *
    356    * @api private
    357    */
    358   dnsCompatibleBucketName: function dnsCompatibleBucketName(bucketName) {
    359     var b = bucketName;
    360     var domain = new RegExp(/^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$/);
    361     var ipAddress = new RegExp(/(\d+\.){3}\d+/);
    362     var dots = new RegExp(/\.\./);
    363     return (b.match(domain) && !b.match(ipAddress) && !b.match(dots)) ? true : false;
    364   },
    365 
    366   /**
    367    * @return [Boolean] whether response contains an error
    368    * @api private
    369    */
    370   successfulResponse: function successfulResponse(resp) {
    371     var req = resp.request;
    372     var httpResponse = resp.httpResponse;
    373     if (operationsWith200StatusCodeError[req.operation] &&
    374         httpResponse.body.toString().match('<Error>')) {
    375       return false;
    376     } else {
    377       return httpResponse.statusCode < 300;
    378     }
    379   },
    380 
    381   /**
    382    * @return [Boolean] whether the error can be retried
    383    * @api private
    384    */
    385   retryableError: function retryableError(error, request) {
    386     if (operationsWith200StatusCodeError[request.operation] &&
    387         error.statusCode === 200) {
    388       return true;
    389     } else if (request._requestRegionForBucket &&
    390         request.service.bucketRegionCache[request._requestRegionForBucket]) {
    391       return false;
    392     } else if (error && error.code === 'RequestTimeout') {
    393       return true;
    394     } else if (error &&
    395         regionRedirectErrorCodes.indexOf(error.code) != -1 &&
    396         error.region && error.region != request.httpRequest.region) {
    397       request.httpRequest.region = error.region;
    398       if (error.statusCode === 301) {
    399         request.service.updateReqBucketRegion(request);
    400       }
    401       return true;
    402     } else {
    403       var _super = AWS.Service.prototype.retryableError;
    404       return _super.call(this, error, request);
    405     }
    406   },
    407 
    408   /**
    409    * Updates httpRequest with region. If region is not provided, then
    410    * the httpRequest will be updated based on httpRequest.region
    411    *
    412    * @api private
    413    */
    414   updateReqBucketRegion: function updateReqBucketRegion(request, region) {
    415     var httpRequest = request.httpRequest;
    416     if (typeof region === 'string' && region.length) {
    417       httpRequest.region = region;
    418     }
    419     if (!httpRequest.endpoint.host.match(/s3(?!-accelerate).*\.amazonaws\.com$/)) {
    420       return;
    421     }
    422     var service = request.service;
    423     var s3Config = service.config;
    424     var s3BucketEndpoint = s3Config.s3BucketEndpoint;
    425     if (s3BucketEndpoint) {
    426       delete s3Config.s3BucketEndpoint;
    427     }
    428     var newConfig = AWS.util.copy(s3Config);
    429     delete newConfig.endpoint;
    430     newConfig.region = httpRequest.region;
    431 
    432     httpRequest.endpoint = (new AWS.S3(newConfig)).endpoint;
    433     service.populateURI(request);
    434     s3Config.s3BucketEndpoint = s3BucketEndpoint;
    435     httpRequest.headers.Host = httpRequest.endpoint.host;
    436 
    437     if (request._asm.currentState === 'validate') {
    438       request.removeListener('build', service.populateURI);
    439       request.addListener('build', service.removeVirtualHostedBucketFromPath);
    440     }
    441   },
    442 
    443   /**
    444    * Provides a specialized parser for getBucketLocation -- all other
    445    * operations are parsed by the super class.
    446    *
    447    * @api private
    448    */
    449   extractData: function extractData(resp) {
    450     var req = resp.request;
    451     if (req.operation === 'getBucketLocation') {
    452       var match = resp.httpResponse.body.toString().match(/>(.+)<\/Location/);
    453       delete resp.data['_'];
    454       if (match) {
    455         resp.data.LocationConstraint = match[1];
    456       } else {
    457         resp.data.LocationConstraint = '';
    458       }
    459     }
    460     var bucket = req.params.Bucket || null;
    461     if (req.operation === 'deleteBucket' && typeof bucket === 'string' && !resp.error) {
    462       req.service.clearBucketRegionCache(bucket);
    463     } else {
    464       var headers = resp.httpResponse.headers || {};
    465       var region = headers['x-amz-bucket-region'] || null;
    466       if (!region && req.operation === 'createBucket' && !resp.error) {
    467         var createBucketConfiguration = req.params.CreateBucketConfiguration;
    468         if (!createBucketConfiguration) {
    469           region = 'us-east-1';
    470         } else if (createBucketConfiguration.LocationConstraint === 'EU') {
    471           region = 'eu-west-1';
    472         } else {
    473           region = createBucketConfiguration.LocationConstraint;
    474         }
    475       }
    476       if (region) {
    477           if (bucket && region !== req.service.bucketRegionCache[bucket]) {
    478             req.service.bucketRegionCache[bucket] = region;
    479           }
    480       }
    481     }
    482     req.service.extractRequestIds(resp);
    483   },
    484 
    485   /**
    486    * Extracts an error object from the http response.
    487    *
    488    * @api private
    489    */
    490   extractError: function extractError(resp) {
    491     var codes = {
    492       304: 'NotModified',
    493       403: 'Forbidden',
    494       400: 'BadRequest',
    495       404: 'NotFound'
    496     };
    497 
    498     var req = resp.request;
    499     var code = resp.httpResponse.statusCode;
    500     var body = resp.httpResponse.body || '';
    501 
    502     var headers = resp.httpResponse.headers || {};
    503     var region = headers['x-amz-bucket-region'] || null;
    504     var bucket = req.params.Bucket || null;
    505     var bucketRegionCache = req.service.bucketRegionCache;
    506     if (region && bucket && region !== bucketRegionCache[bucket]) {
    507       bucketRegionCache[bucket] = region;
    508     }
    509 
    510     var cachedRegion;
    511     if (codes[code] && body.length === 0) {
    512       if (bucket && !region) {
    513         cachedRegion = bucketRegionCache[bucket] || null;
    514         if (cachedRegion !== req.httpRequest.region) {
    515           region = cachedRegion;
    516         }
    517       }
    518       resp.error = AWS.util.error(new Error(), {
    519         code: codes[code],
    520         message: null,
    521         region: region
    522       });
    523     } else {
    524       var data = new AWS.XML.Parser().parse(body.toString());
    525 
    526       if (data.Region && !region) {
    527         region = data.Region;
    528         if (bucket && region !== bucketRegionCache[bucket]) {
    529           bucketRegionCache[bucket] = region;
    530         }
    531       } else if (bucket && !region && !data.Region) {
    532         cachedRegion = bucketRegionCache[bucket] || null;
    533         if (cachedRegion !== req.httpRequest.region) {
    534           region = cachedRegion;
    535         }
    536       }
    537 
    538       resp.error = AWS.util.error(new Error(), {
    539         code: data.Code || code,
    540         message: data.Message || null,
    541         region: region
    542       });
    543     }
    544     req.service.extractRequestIds(resp);
    545   },
    546 
    547   /**
    548    * If region was not obtained synchronously, then send async request
    549    * to get bucket region for errors resulting from wrong region.
    550    *
    551    * @api private
    552    */
    553   requestBucketRegion: function requestBucketRegion(resp, done) {
    554     var error = resp.error;
    555     var req = resp.request;
    556     var bucket = req.params.Bucket || null;
    557 
    558     if (!error || !bucket || error.region || req.operation === 'listObjects' ||
    559         (AWS.util.isNode() && req.operation === 'headBucket') ||
    560         (error.statusCode === 400 && req.operation !== 'headObject') ||
    561         regionRedirectErrorCodes.indexOf(error.code) === -1) {
    562       return done();
    563     }
    564     var reqOperation = AWS.util.isNode() ? 'headBucket' : 'listObjects';
    565     var reqParams = {Bucket: bucket};
    566     if (reqOperation === 'listObjects') reqParams.MaxKeys = 0;
    567     var regionReq = req.service[reqOperation](reqParams);
    568     regionReq._requestRegionForBucket = bucket;
    569     regionReq.send(function() {
    570       var region = req.service.bucketRegionCache[bucket] || null;
    571       error.region = region;
    572       done();
    573     });
    574   },
    575 
    576    /**
    577    * For browser only. If NetworkingError received, will attempt to obtain
    578    * the bucket region.
    579    *
    580    * @api private
    581    */
    582    reqRegionForNetworkingError: function reqRegionForNetworkingError(resp, done) {
    583     if (!AWS.util.isBrowser()) {
    584       return done();
    585     }
    586     var error = resp.error;
    587     var request = resp.request;
    588     var bucket = request.params.Bucket;
    589     if (!error || error.code !== 'NetworkingError' || !bucket ||
    590         request.httpRequest.region === 'us-east-1') {
    591       return done();
    592     }
    593     var service = request.service;
    594     var bucketRegionCache = service.bucketRegionCache;
    595     var cachedRegion = bucketRegionCache[bucket] || null;
    596 
    597     if (cachedRegion && cachedRegion !== request.httpRequest.region) {
    598       service.updateReqBucketRegion(request, cachedRegion);
    599       done();
    600     } else if (!service.dnsCompatibleBucketName(bucket)) {
    601       service.updateReqBucketRegion(request, 'us-east-1');
    602       if (bucketRegionCache[bucket] !== 'us-east-1') {
    603         bucketRegionCache[bucket] = 'us-east-1';
    604       }
    605       done();
    606     } else if (request.httpRequest.virtualHostedBucket) {
    607       var getRegionReq = service.listObjects({Bucket: bucket, MaxKeys: 0});
    608       service.updateReqBucketRegion(getRegionReq, 'us-east-1');
    609       getRegionReq._requestRegionForBucket = bucket;
    610 
    611       getRegionReq.send(function() {
    612         var region = service.bucketRegionCache[bucket] || null;
    613         if (region && region !== request.httpRequest.region) {
    614           service.updateReqBucketRegion(request, region);
    615         }
    616         done();
    617       });
    618     } else {
    619       // DNS-compatible path-style
    620       // (s3ForcePathStyle or bucket name with dot over https)
    621       // Cannot obtain region information for this case
    622       done();
    623     }
    624    },
    625 
    626   /**
    627    * Cache for bucket region.
    628    *
    629    * @api private
    630    */
    631    bucketRegionCache: {},
    632 
    633   /**
    634    * Clears bucket region cache.
    635    *
    636    * @api private
    637    */
    638    clearBucketRegionCache: function(buckets) {
    639     var bucketRegionCache = this.bucketRegionCache;
    640     if (!buckets) {
    641       buckets = Object.keys(bucketRegionCache);
    642     } else if (typeof buckets === 'string') {
    643       buckets = [buckets];
    644     }
    645     for (var i = 0; i < buckets.length; i++) {
    646       delete bucketRegionCache[buckets[i]];
    647     }
    648     return bucketRegionCache;
    649    },
    650 
    651    /**
    652     * Corrects request region if bucket's cached region is different
    653     *
    654     * @api private
    655     */
    656   correctBucketRegionFromCache: function correctBucketRegionFromCache(req) {
    657     var bucket = req.params.Bucket || null;
    658     if (bucket) {
    659       var service = req.service;
    660       var requestRegion = req.httpRequest.region;
    661       var cachedRegion = service.bucketRegionCache[bucket];
    662       if (cachedRegion && cachedRegion !== requestRegion) {
    663         service.updateReqBucketRegion(req, cachedRegion);
    664       }
    665     }
    666   },
    667 
    668   /**
    669    * Extracts S3 specific request ids from the http response.
    670    *
    671    * @api private
    672    */
    673   extractRequestIds: function extractRequestIds(resp) {
    674     var extendedRequestId = resp.httpResponse.headers ? resp.httpResponse.headers['x-amz-id-2'] : null;
    675     var cfId = resp.httpResponse.headers ? resp.httpResponse.headers['x-amz-cf-id'] : null;
    676     resp.extendedRequestId = extendedRequestId;
    677     resp.cfId = cfId;
    678 
    679     if (resp.error) {
    680       resp.error.requestId = resp.requestId || null;
    681       resp.error.extendedRequestId = extendedRequestId;
    682       resp.error.cfId = cfId;
    683     }
    684   },
    685 
    686   /**
    687    * Get a pre-signed URL for a given operation name.
    688    *
    689    * @note You must ensure that you have static or previously resolved
    690    *   credentials if you call this method synchronously (with no callback),
    691    *   otherwise it may not properly sign the request. If you cannot guarantee
    692    *   this (you are using an asynchronous credential provider, i.e., EC2
    693    *   IAM roles), you should always call this method with an asynchronous
    694    *   callback.
    695    * @param operation [String] the name of the operation to call
    696    * @param params [map] parameters to pass to the operation. See the given
    697    *   operation for the expected operation parameters. In addition, you can
    698    *   also pass the "Expires" parameter to inform S3 how long the URL should
    699    *   work for.
    700    * @option params Expires [Integer] (900) the number of seconds to expire
    701    *   the pre-signed URL operation in. Defaults to 15 minutes.
    702    * @param callback [Function] if a callback is provided, this function will
    703    *   pass the URL as the second parameter (after the error parameter) to
    704    *   the callback function.
    705    * @return [String] if called synchronously (with no callback), returns the
    706    *   signed URL.
    707    * @return [null] nothing is returned if a callback is provided.
    708    * @example Pre-signing a getObject operation (synchronously)
    709    *   var params = {Bucket: 'bucket', Key: 'key'};
    710    *   var url = s3.getSignedUrl('getObject', params);
    711    *   console.log('The URL is', url);
    712    * @example Pre-signing a putObject (asynchronously)
    713    *   var params = {Bucket: 'bucket', Key: 'key'};
    714    *   s3.getSignedUrl('putObject', params, function (err, url) {
    715    *     console.log('The URL is', url);
    716    *   });
    717    * @example Pre-signing a putObject operation with a specific payload
    718    *   var params = {Bucket: 'bucket', Key: 'key', Body: 'body'};
    719    *   var url = s3.getSignedUrl('putObject', params);
    720    *   console.log('The URL is', url);
    721    * @example Passing in a 1-minute expiry time for a pre-signed URL
    722    *   var params = {Bucket: 'bucket', Key: 'key', Expires: 60};
    723    *   var url = s3.getSignedUrl('getObject', params);
    724    *   console.log('The URL is', url); // expires in 60 seconds
    725    */
    726   getSignedUrl: function getSignedUrl(operation, params, callback) {
    727     params = AWS.util.copy(params || {});
    728     var expires = params.Expires || 900;
    729     delete params.Expires; // we can't validate this
    730     var request = this.makeRequest(operation, params);
    731     return request.presign(expires, callback);
    732   },
    733 
    734   /**
    735    * @api private
    736    */
    737   prepareSignedUrl: function prepareSignedUrl(request) {
    738     request.addListener('validate', request.service.noPresignedContentLength);
    739     request.removeListener('build', request.service.addContentType);
    740     if (!request.params.Body) {
    741       // no Content-MD5/SHA-256 if body is not provided
    742       request.removeListener('build', request.service.computeContentMd5);
    743     } else {
    744       request.addListener('afterBuild', AWS.EventListeners.Core.COMPUTE_SHA256);
    745     }
    746   },
    747 
    748   /**
    749    * @api private
    750    * @param request
    751    */
    752   disableBodySigning: function disableBodySigning(request) {
    753     var headers = request.httpRequest.headers;
    754     // Add the header to anything that isn't a presigned url, unless that presigned url had a body defined
    755     if (!Object.prototype.hasOwnProperty.call(headers, 'presigned-expires')) {
    756       headers['X-Amz-Content-Sha256'] = 'UNSIGNED-PAYLOAD';
    757     }
    758   },
    759 
    760   /**
    761    * @api private
    762    */
    763   noPresignedContentLength: function noPresignedContentLength(request) {
    764     if (request.params.ContentLength !== undefined) {
    765       throw AWS.util.error(new Error(), {code: 'UnexpectedParameter',
    766         message: 'ContentLength is not supported in pre-signed URLs.'});
    767     }
    768   },
    769 
    770   createBucket: function createBucket(params, callback) {
    771     // When creating a bucket *outside* the classic region, the location
    772     // constraint must be set for the bucket and it must match the endpoint.
    773     // This chunk of code will set the location constraint param based
    774     // on the region (when possible), but it will not override a passed-in
    775     // location constraint.
    776     if (typeof params === 'function' || !params) {
    777       callback = callback || params;
    778       params = {};
    779     }
    780     var hostname = this.endpoint.hostname;
    781     if (hostname !== this.api.globalEndpoint && !params.CreateBucketConfiguration) {
    782       params.CreateBucketConfiguration = { LocationConstraint: this.config.region };
    783     }
    784     return this.makeRequest('createBucket', params, callback);
    785   },
    786 
    787   /**
    788    * @overload upload(params = {}, [options], [callback])
    789    *   Uploads an arbitrarily sized buffer, blob, or stream, using intelligent
    790    *   concurrent handling of parts if the payload is large enough. You can
    791    *   configure the concurrent queue size by setting `options`. Note that this
    792    *   is the only operation for which the SDK can retry requests with stream
    793    *   bodies.
    794    *
    795    *   @param (see AWS.S3.putObject)
    796    *   @option (see AWS.S3.ManagedUpload.constructor)
    797    *   @return [AWS.S3.ManagedUpload] the managed upload object that can call
    798    *     `send()` or track progress.
    799    *   @example Uploading a stream object
    800    *     var params = {Bucket: 'bucket', Key: 'key', Body: stream};
    801    *     s3.upload(params, function(err, data) {
    802    *       console.log(err, data);
    803    *     });
    804    *   @example Uploading a stream with concurrency of 1 and partSize of 10mb
    805    *     var params = {Bucket: 'bucket', Key: 'key', Body: stream};
    806    *     var options = {partSize: 10 * 1024 * 1024, queueSize: 1};
    807    *     s3.upload(params, options, function(err, data) {
    808    *       console.log(err, data);
    809    *     });
    810    * @callback callback function(err, data)
    811    *   @param err [Error] an error or null if no error occurred.
    812    *   @param data [map] The response data from the successful upload:
    813    *     * `Location` (String) the URL of the uploaded object
    814    *     * `ETag` (String) the ETag of the uploaded object
    815    *     * `Bucket` (String) the bucket to which the object was uploaded
    816    *     * `Key` (String) the key to which the object was uploaded
    817    *   @see AWS.S3.ManagedUpload
    818    */
    819   upload: function upload(params, options, callback) {
    820     if (typeof options === 'function' && callback === undefined) {
    821       callback = options;
    822       options = null;
    823     }
    824 
    825     options = options || {};
    826     options = AWS.util.merge(options || {}, {service: this, params: params});
    827 
    828     var uploader = new AWS.S3.ManagedUpload(options);
    829     if (typeof callback === 'function') uploader.send(callback);
    830     return uploader;
    831   }
    832 });