util.js
   1  /* eslint guard-for-in:0 */
   2  var AWS;
   3  
   4  /**
   5   * A set of utility methods for use with the AWS SDK.
   6   *
   7   * @!attribute abort
   8   *   Return this value from an iterator function {each} or {arrayEach}
   9   *   to break out of the iteration.
  10   *   @example Breaking out of an iterator function
  11   *     AWS.util.each({a: 1, b: 2, c: 3}, function(key, value) {
  12   *       if (key == 'b') return AWS.util.abort;
  13   *     });
  14   *   @see each
  15   *   @see arrayEach
  16   * @api private
  17   */
  18  var util = {
  19    environment: 'nodejs',
  20    engine: function engine() {
  21      if (util.isBrowser() && typeof navigator !== 'undefined') {
  22        return navigator.userAgent;
  23      } else {
  24        var engine = process.platform + '/' + process.version;
  25        if (process.env.AWS_EXECUTION_ENV) {
  26          engine += ' exec-env/' + process.env.AWS_EXECUTION_ENV;
  27        }
  28        return engine;
  29      }
  30    },
  31  
  32    userAgent: function userAgent() {
  33      var name = util.environment;
  34      var agent = 'aws-sdk-' + name + '/' + require('./core').VERSION;
  35      if (name === 'nodejs') agent += ' ' + util.engine();
  36      return agent;
  37    },
  38  
  39    uriEscape: function uriEscape(string) {
  40      var output = encodeURIComponent(string);
  41      output = output.replace(/[^A-Za-z0-9_.~\-%]+/g, escape);
  42  
  43      // AWS percent-encodes some extra non-standard characters in a URI
  44      output = output.replace(/[*]/g, function(ch) {
  45        return '%' + ch.charCodeAt(0).toString(16).toUpperCase();
  46      });
  47  
  48      return output;
  49    },
  50  
  51    uriEscapePath: function uriEscapePath(string) {
  52      var parts = [];
  53      util.arrayEach(string.split('/'), function (part) {
  54        parts.push(util.uriEscape(part));
  55      });
  56      return parts.join('/');
  57    },
  58  
  59    urlParse: function urlParse(url) {
  60      return util.url.parse(url);
  61    },
  62  
  63    urlFormat: function urlFormat(url) {
  64      return util.url.format(url);
  65    },
  66  
  67    queryStringParse: function queryStringParse(qs) {
  68      return util.querystring.parse(qs);
  69    },
  70  
  71    queryParamsToString: function queryParamsToString(params) {
  72      var items = [];
  73      var escape = util.uriEscape;
  74      var sortedKeys = Object.keys(params).sort();
  75  
  76      util.arrayEach(sortedKeys, function(name) {
  77        var value = params[name];
  78        var ename = escape(name);
  79        var result = ename + '=';
  80        if (Array.isArray(value)) {
  81          var vals = [];
  82          util.arrayEach(value, function(item) { vals.push(escape(item)); });
  83          result = ename + '=' + vals.sort().join('&' + ename + '=');
  84        } else if (value !== undefined && value !== null) {
  85          result = ename + '=' + escape(value);
  86        }
  87        items.push(result);
  88      });
  89  
  90      return items.join('&');
  91    },
  92  
  93    readFileSync: function readFileSync(path) {
  94      if (util.isBrowser()) return null;
  95      return require('fs').readFileSync(path, 'utf-8');
  96    },
  97  
  98    base64: {
  99      encode: function encode64(string) {
 100        if (typeof string === 'number') {
 101          throw util.error(new Error('Cannot base64 encode number ' + string));
 102        }
 103        if (string === null || typeof string === 'undefined') {
 104          return string;
 105        }
 106        var buf = util.buffer.toBuffer(string);
 107        return buf.toString('base64');
 108      },
 109  
 110      decode: function decode64(string) {
 111        if (typeof string === 'number') {
 112          throw util.error(new Error('Cannot base64 decode number ' + string));
 113        }
 114        if (string === null || typeof string === 'undefined') {
 115          return string;
 116        }
 117        return util.buffer.toBuffer(string, 'base64');
 118      }
 119  
 120    },
 121  
 122    buffer: {
 123      /**
 124       * Buffer constructor for Node buffer and buffer pollyfill
 125       */
 126      toBuffer: function(data, encoding) {
 127        return (typeof util.Buffer.from === 'function' && util.Buffer.from !== Uint8Array.from) ?
 128          util.Buffer.from(data, encoding) : new util.Buffer(data, encoding);
 129      },
 130  
 131      alloc: function(size, fill, encoding) {
 132        if (typeof size !== 'number') {
 133          throw new Error('size passed to alloc must be a number.');
 134        }
 135        if (typeof util.Buffer.alloc === 'function') {
 136          return util.Buffer.alloc(size, fill, encoding);
 137        } else {
 138          var buf = new util.Buffer(size);
 139          if (fill !== undefined && typeof buf.fill === 'function') {
 140            buf.fill(fill, undefined, undefined, encoding);
 141          }
 142          return buf;
 143        }
 144      },
 145  
 146      toStream: function toStream(buffer) {
 147        if (!util.Buffer.isBuffer(buffer)) buffer =  util.buffer.toBuffer(buffer);
 148  
 149        var readable = new (util.stream.Readable)();
 150        var pos = 0;
 151        readable._read = function(size) {
 152          if (pos >= buffer.length) return readable.push(null);
 153  
 154          var end = pos + size;
 155          if (end > buffer.length) end = buffer.length;
 156          readable.push(buffer.slice(pos, end));
 157          pos = end;
 158        };
 159  
 160        return readable;
 161      },
 162  
 163      /**
 164       * Concatenates a list of Buffer objects.
 165       */
 166      concat: function(buffers) {
 167        var length = 0,
 168            offset = 0,
 169            buffer = null, i;
 170  
 171        for (i = 0; i < buffers.length; i++) {
 172          length += buffers[i].length;
 173        }
 174  
 175        buffer = util.buffer.alloc(length);
 176  
 177        for (i = 0; i < buffers.length; i++) {
 178          buffers[i].copy(buffer, offset);
 179          offset += buffers[i].length;
 180        }
 181  
 182        return buffer;
 183      }
 184    },
 185  
 186    string: {
 187      byteLength: function byteLength(string) {
 188        if (string === null || string === undefined) return 0;
 189        if (typeof string === 'string') string = util.buffer.toBuffer(string);
 190  
 191        if (typeof string.byteLength === 'number') {
 192          return string.byteLength;
 193        } else if (typeof string.length === 'number') {
 194          return string.length;
 195        } else if (typeof string.size === 'number') {
 196          return string.size;
 197        } else if (typeof string.path === 'string') {
 198          return require('fs').lstatSync(string.path).size;
 199        } else {
 200          throw util.error(new Error('Cannot determine length of ' + string),
 201            { object: string });
 202        }
 203      },
 204  
 205      upperFirst: function upperFirst(string) {
 206        return string[0].toUpperCase() + string.substr(1);
 207      },
 208  
 209      lowerFirst: function lowerFirst(string) {
 210        return string[0].toLowerCase() + string.substr(1);
 211      }
 212    },
 213  
 214    ini: {
 215      parse: function string(ini) {
 216        var currentSection, map = {};
 217        util.arrayEach(ini.split(/\r?\n/), function(line) {
 218          line = line.split(/(^|\s)[;#]/)[0]; // remove comments
 219          var section = line.match(/^\s*\[([^\[\]]+)\]\s*$/);
 220          if (section) {
 221            currentSection = section[1];
 222            if (currentSection === '__proto__' || currentSection.split(/\s/)[1] === '__proto__') {
 223              throw util.error(
 224                new Error('Cannot load profile name \'' + currentSection + '\' from shared ini file.')
 225              );
 226            }
 227          } else if (currentSection) {
 228            var item = line.match(/^\s*(.+?)\s*=\s*(.+?)\s*$/);
 229            if (item) {
 230              map[currentSection] = map[currentSection] || {};
 231              map[currentSection][item[1]] = item[2];
 232            }
 233          }
 234        });
 235  
 236        return map;
 237      }
 238    },
 239  
 240    fn: {
 241      noop: function() {},
 242      callback: function (err) { if (err) throw err; },
 243  
 244      /**
 245       * Turn a synchronous function into as "async" function by making it call
 246       * a callback. The underlying function is called with all but the last argument,
 247       * which is treated as the callback. The callback is passed passed a first argument
 248       * of null on success to mimick standard node callbacks.
 249       */
 250      makeAsync: function makeAsync(fn, expectedArgs) {
 251        if (expectedArgs && expectedArgs <= fn.length) {
 252          return fn;
 253        }
 254  
 255        return function() {
 256          var args = Array.prototype.slice.call(arguments, 0);
 257          var callback = args.pop();
 258          var result = fn.apply(null, args);
 259          callback(result);
 260        };
 261      }
 262    },
 263  
 264    /**
 265     * Date and time utility functions.
 266     */
 267    date: {
 268  
 269      /**
 270       * @return [Date] the current JavaScript date object. Since all
 271       *   AWS services rely on this date object, you can override
 272       *   this function to provide a special time value to AWS service
 273       *   requests.
 274       */
 275      getDate: function getDate() {
 276        if (!AWS) AWS = require('./core');
 277        if (AWS.config.systemClockOffset) { // use offset when non-zero
 278          return new Date(new Date().getTime() + AWS.config.systemClockOffset);
 279        } else {
 280          return new Date();
 281        }
 282      },
 283  
 284      /**
 285       * @return [String] the date in ISO-8601 format
 286       */
 287      iso8601: function iso8601(date) {
 288        if (date === undefined) { date = util.date.getDate(); }
 289        return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
 290      },
 291  
 292      /**
 293       * @return [String] the date in RFC 822 format
 294       */
 295      rfc822: function rfc822(date) {
 296        if (date === undefined) { date = util.date.getDate(); }
 297        return date.toUTCString();
 298      },
 299  
 300      /**
 301       * @return [Integer] the UNIX timestamp value for the current time
 302       */
 303      unixTimestamp: function unixTimestamp(date) {
 304        if (date === undefined) { date = util.date.getDate(); }
 305        return date.getTime() / 1000;
 306      },
 307  
 308      /**
 309       * @param [String,number,Date] date
 310       * @return [Date]
 311       */
 312      from: function format(date) {
 313        if (typeof date === 'number') {
 314          return new Date(date * 1000); // unix timestamp
 315        } else {
 316          return new Date(date);
 317        }
 318      },
 319  
 320      /**
 321       * Given a Date or date-like value, this function formats the
 322       * date into a string of the requested value.
 323       * @param [String,number,Date] date
 324       * @param [String] formatter Valid formats are:
 325       #   * 'iso8601'
 326       #   * 'rfc822'
 327       #   * 'unixTimestamp'
 328       * @return [String]
 329       */
 330      format: function format(date, formatter) {
 331        if (!formatter) formatter = 'iso8601';
 332        return util.date[formatter](util.date.from(date));
 333      },
 334  
 335      parseTimestamp: function parseTimestamp(value) {
 336        if (typeof value === 'number') { // unix timestamp (number)
 337          return new Date(value * 1000);
 338        } else if (value.match(/^\d+$/)) { // unix timestamp
 339          return new Date(value * 1000);
 340        } else if (value.match(/^\d{4}/)) { // iso8601
 341          return new Date(value);
 342        } else if (value.match(/^\w{3},/)) { // rfc822
 343          return new Date(value);
 344        } else {
 345          throw util.error(
 346            new Error('unhandled timestamp format: ' + value),
 347            {code: 'TimestampParserError'});
 348        }
 349      }
 350  
 351    },
 352  
 353    crypto: {
 354      crc32Table: [
 355       0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
 356       0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
 357       0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
 358       0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
 359       0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
 360       0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
 361       0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
 362       0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
 363       0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
 364       0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
 365       0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
 366       0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
 367       0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
 368       0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
 369       0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
 370       0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
 371       0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
 372       0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
 373       0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
 374       0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
 375       0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
 376       0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
 377       0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
 378       0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
 379       0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
 380       0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
 381       0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
 382       0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
 383       0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
 384       0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
 385       0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
 386       0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
 387       0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
 388       0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
 389       0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
 390       0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
 391       0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
 392       0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
 393       0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
 394       0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
 395       0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
 396       0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
 397       0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
 398       0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
 399       0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
 400       0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
 401       0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
 402       0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
 403       0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
 404       0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
 405       0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
 406       0x2D02EF8D],
 407  
 408      crc32: function crc32(data) {
 409        var tbl = util.crypto.crc32Table;
 410        var crc = 0 ^ -1;
 411  
 412        if (typeof data === 'string') {
 413          data = util.buffer.toBuffer(data);
 414        }
 415  
 416        for (var i = 0; i < data.length; i++) {
 417          var code = data.readUInt8(i);
 418          crc = (crc >>> 8) ^ tbl[(crc ^ code) & 0xFF];
 419        }
 420        return (crc ^ -1) >>> 0;
 421      },
 422  
 423      hmac: function hmac(key, string, digest, fn) {
 424        if (!digest) digest = 'binary';
 425        if (digest === 'buffer') { digest = undefined; }
 426        if (!fn) fn = 'sha256';
 427        if (typeof string === 'string') string = util.buffer.toBuffer(string);
 428        return util.crypto.lib.createHmac(fn, key).update(string).digest(digest);
 429      },
 430  
 431      md5: function md5(data, digest, callback) {
 432        return util.crypto.hash('md5', data, digest, callback);
 433      },
 434  
 435      sha256: function sha256(data, digest, callback) {
 436        return util.crypto.hash('sha256', data, digest, callback);
 437      },
 438  
 439      hash: function(algorithm, data, digest, callback) {
 440        var hash = util.crypto.createHash(algorithm);
 441        if (!digest) { digest = 'binary'; }
 442        if (digest === 'buffer') { digest = undefined; }
 443        if (typeof data === 'string') data = util.buffer.toBuffer(data);
 444        var sliceFn = util.arraySliceFn(data);
 445        var isBuffer = util.Buffer.isBuffer(data);
 446        //Identifying objects with an ArrayBuffer as buffers
 447        if (util.isBrowser() && typeof ArrayBuffer !== 'undefined' && data && data.buffer instanceof ArrayBuffer) isBuffer = true;
 448  
 449        if (callback && typeof data === 'object' &&
 450            typeof data.on === 'function' && !isBuffer) {
 451          data.on('data', function(chunk) { hash.update(chunk); });
 452          data.on('error', function(err) { callback(err); });
 453          data.on('end', function() { callback(null, hash.digest(digest)); });
 454        } else if (callback && sliceFn && !isBuffer &&
 455                   typeof FileReader !== 'undefined') {
 456          // this might be a File/Blob
 457          var index = 0, size = 1024 * 512;
 458          var reader = new FileReader();
 459          reader.onerror = function() {
 460            callback(new Error('Failed to read data.'));
 461          };
 462          reader.onload = function() {
 463            var buf = new util.Buffer(new Uint8Array(reader.result));
 464            hash.update(buf);
 465            index += buf.length;
 466            reader._continueReading();
 467          };
 468          reader._continueReading = function() {
 469            if (index >= data.size) {
 470              callback(null, hash.digest(digest));
 471              return;
 472            }
 473  
 474            var back = index + size;
 475            if (back > data.size) back = data.size;
 476            reader.readAsArrayBuffer(sliceFn.call(data, index, back));
 477          };
 478  
 479          reader._continueReading();
 480        } else {
 481          if (util.isBrowser() && typeof data === 'object' && !isBuffer) {
 482            data = new util.Buffer(new Uint8Array(data));
 483          }
 484          var out = hash.update(data).digest(digest);
 485          if (callback) callback(null, out);
 486          return out;
 487        }
 488      },
 489  
 490      toHex: function toHex(data) {
 491        var out = [];
 492        for (var i = 0; i < data.length; i++) {
 493          out.push(('0' + data.charCodeAt(i).toString(16)).substr(-2, 2));
 494        }
 495        return out.join('');
 496      },
 497  
 498      createHash: function createHash(algorithm) {
 499        return util.crypto.lib.createHash(algorithm);
 500      }
 501  
 502    },
 503  
 504    /** @!ignore */
 505  
 506    /* Abort constant */
 507    abort: {},
 508  
 509    each: function each(object, iterFunction) {
 510      for (var key in object) {
 511        if (Object.prototype.hasOwnProperty.call(object, key)) {
 512          var ret = iterFunction.call(this, key, object[key]);
 513          if (ret === util.abort) break;
 514        }
 515      }
 516    },
 517  
 518    arrayEach: function arrayEach(array, iterFunction) {
 519      for (var idx in array) {
 520        if (Object.prototype.hasOwnProperty.call(array, idx)) {
 521          var ret = iterFunction.call(this, array[idx], parseInt(idx, 10));
 522          if (ret === util.abort) break;
 523        }
 524      }
 525    },
 526  
 527    update: function update(obj1, obj2) {
 528      util.each(obj2, function iterator(key, item) {
 529        obj1[key] = item;
 530      });
 531      return obj1;
 532    },
 533  
 534    merge: function merge(obj1, obj2) {
 535      return util.update(util.copy(obj1), obj2);
 536    },
 537  
 538    copy: function copy(object) {
 539      if (object === null || object === undefined) return object;
 540      var dupe = {};
 541      // jshint forin:false
 542      for (var key in object) {
 543        dupe[key] = object[key];
 544      }
 545      return dupe;
 546    },
 547  
 548    isEmpty: function isEmpty(obj) {
 549      for (var prop in obj) {
 550        if (Object.prototype.hasOwnProperty.call(obj, prop)) {
 551          return false;
 552        }
 553      }
 554      return true;
 555    },
 556  
 557    arraySliceFn: function arraySliceFn(obj) {
 558      var fn = obj.slice || obj.webkitSlice || obj.mozSlice;
 559      return typeof fn === 'function' ? fn : null;
 560    },
 561  
 562    isType: function isType(obj, type) {
 563      // handle cross-"frame" objects
 564      if (typeof type === 'function') type = util.typeName(type);
 565      return Object.prototype.toString.call(obj) === '[object ' + type + ']';
 566    },
 567  
 568    typeName: function typeName(type) {
 569      if (Object.prototype.hasOwnProperty.call(type, 'name')) return type.name;
 570      var str = type.toString();
 571      var match = str.match(/^\s*function (.+)\(/);
 572      return match ? match[1] : str;
 573    },
 574  
 575    error: function error(err, options) {
 576      var originalError = null;
 577      if (typeof err.message === 'string' && err.message !== '') {
 578        if (typeof options === 'string' || (options && options.message)) {
 579          originalError = util.copy(err);
 580          originalError.message = err.message;
 581        }
 582      }
 583      err.message = err.message || null;
 584  
 585      if (typeof options === 'string') {
 586        err.message = options;
 587      } else if (typeof options === 'object' && options !== null) {
 588        util.update(err, options);
 589        if (options.message)
 590          err.message = options.message;
 591        if (options.code || options.name)
 592          err.code = options.code || options.name;
 593        if (options.stack)
 594          err.stack = options.stack;
 595      }
 596  
 597      if (typeof Object.defineProperty === 'function') {
 598        Object.defineProperty(err, 'name', {writable: true, enumerable: false});
 599        Object.defineProperty(err, 'message', {enumerable: true});
 600      }
 601  
 602      err.name = String(options && options.name || err.name || err.code || 'Error');
 603      err.time = new Date();
 604  
 605      if (originalError) err.originalError = originalError;
 606  
 607      return err;
 608    },
 609  
 610    /**
 611     * @api private
 612     */
 613    inherit: function inherit(klass, features) {
 614      var newObject = null;
 615      if (features === undefined) {
 616        features = klass;
 617        klass = Object;
 618        newObject = {};
 619      } else {
 620        var ctor = function ConstructorWrapper() {};
 621        ctor.prototype = klass.prototype;
 622        newObject = new ctor();
 623      }
 624  
 625      // constructor not supplied, create pass-through ctor
 626      if (features.constructor === Object) {
 627        features.constructor = function() {
 628          if (klass !== Object) {
 629            return klass.apply(this, arguments);
 630          }
 631        };
 632      }
 633  
 634      features.constructor.prototype = newObject;
 635      util.update(features.constructor.prototype, features);
 636      features.constructor.__super__ = klass;
 637      return features.constructor;
 638    },
 639  
 640    /**
 641     * @api private
 642     */
 643    mixin: function mixin() {
 644      var klass = arguments[0];
 645      for (var i = 1; i < arguments.length; i++) {
 646        // jshint forin:false
 647        for (var prop in arguments[i].prototype) {
 648          var fn = arguments[i].prototype[prop];
 649          if (prop !== 'constructor') {
 650            klass.prototype[prop] = fn;
 651          }
 652        }
 653      }
 654      return klass;
 655    },
 656  
 657    /**
 658     * @api private
 659     */
 660    hideProperties: function hideProperties(obj, props) {
 661      if (typeof Object.defineProperty !== 'function') return;
 662  
 663      util.arrayEach(props, function (key) {
 664        Object.defineProperty(obj, key, {
 665          enumerable: false, writable: true, configurable: true });
 666      });
 667    },
 668  
 669    /**
 670     * @api private
 671     */
 672    property: function property(obj, name, value, enumerable, isValue) {
 673      var opts = {
 674        configurable: true,
 675        enumerable: enumerable !== undefined ? enumerable : true
 676      };
 677      if (typeof value === 'function' && !isValue) {
 678        opts.get = value;
 679      }
 680      else {
 681        opts.value = value; opts.writable = true;
 682      }
 683  
 684      Object.defineProperty(obj, name, opts);
 685    },
 686  
 687    /**
 688     * @api private
 689     */
 690    memoizedProperty: function memoizedProperty(obj, name, get, enumerable) {
 691      var cachedValue = null;
 692  
 693      // build enumerable attribute for each value with lazy accessor.
 694      util.property(obj, name, function() {
 695        if (cachedValue === null) {
 696          cachedValue = get();
 697        }
 698        return cachedValue;
 699      }, enumerable);
 700    },
 701  
 702    /**
 703     * TODO Remove in major version revision
 704     * This backfill populates response data without the
 705     * top-level payload name.
 706     *
 707     * @api private
 708     */
 709    hoistPayloadMember: function hoistPayloadMember(resp) {
 710      var req = resp.request;
 711      var operationName = req.operation;
 712      var operation = req.service.api.operations[operationName];
 713      var output = operation.output;
 714      if (output.payload && !operation.hasEventOutput) {
 715        var payloadMember = output.members[output.payload];
 716        var responsePayload = resp.data[output.payload];
 717        if (payloadMember.type === 'structure') {
 718          util.each(responsePayload, function(key, value) {
 719            util.property(resp.data, key, value, false);
 720          });
 721        }
 722      }
 723    },
 724  
 725    /**
 726     * Compute SHA-256 checksums of streams
 727     *
 728     * @api private
 729     */
 730    computeSha256: function computeSha256(body, done) {
 731      if (util.isNode()) {
 732        var Stream = util.stream.Stream;
 733        var fs = require('fs');
 734        if (typeof Stream === 'function' && body instanceof Stream) {
 735          if (typeof body.path === 'string') { // assume file object
 736            var settings = {};
 737            if (typeof body.start === 'number') {
 738              settings.start = body.start;
 739            }
 740            if (typeof body.end === 'number') {
 741              settings.end = body.end;
 742            }
 743            body = fs.createReadStream(body.path, settings);
 744          } else { // TODO support other stream types
 745            return done(new Error('Non-file stream objects are ' +
 746                                  'not supported with SigV4'));
 747          }
 748        }
 749      }
 750  
 751      util.crypto.sha256(body, 'hex', function(err, sha) {
 752        if (err) done(err);
 753        else done(null, sha);
 754      });
 755    },
 756  
 757    /**
 758     * @api private
 759     */
 760    isClockSkewed: function isClockSkewed(serverTime) {
 761      if (serverTime) {
 762        util.property(AWS.config, 'isClockSkewed',
 763          Math.abs(new Date().getTime() - serverTime) >= 300000, false);
 764        return AWS.config.isClockSkewed;
 765      }
 766    },
 767  
 768    applyClockOffset: function applyClockOffset(serverTime) {
 769      if (serverTime)
 770        AWS.config.systemClockOffset = serverTime - new Date().getTime();
 771    },
 772  
 773    /**
 774     * @api private
 775     */
 776    extractRequestId: function extractRequestId(resp) {
 777      var requestId = resp.httpResponse.headers['x-amz-request-id'] ||
 778                       resp.httpResponse.headers['x-amzn-requestid'];
 779  
 780      if (!requestId && resp.data && resp.data.ResponseMetadata) {
 781        requestId = resp.data.ResponseMetadata.RequestId;
 782      }
 783  
 784      if (requestId) {
 785        resp.requestId = requestId;
 786      }
 787  
 788      if (resp.error) {
 789        resp.error.requestId = requestId;
 790      }
 791    },
 792  
 793    /**
 794     * @api private
 795     */
 796    addPromises: function addPromises(constructors, PromiseDependency) {
 797      var deletePromises = false;
 798      if (PromiseDependency === undefined && AWS && AWS.config) {
 799        PromiseDependency = AWS.config.getPromisesDependency();
 800      }
 801      if (PromiseDependency === undefined && typeof Promise !== 'undefined') {
 802        PromiseDependency = Promise;
 803      }
 804      if (typeof PromiseDependency !== 'function') deletePromises = true;
 805      if (!Array.isArray(constructors)) constructors = [constructors];
 806  
 807      for (var ind = 0; ind < constructors.length; ind++) {
 808        var constructor = constructors[ind];
 809        if (deletePromises) {
 810          if (constructor.deletePromisesFromClass) {
 811            constructor.deletePromisesFromClass();
 812          }
 813        } else if (constructor.addPromisesToClass) {
 814          constructor.addPromisesToClass(PromiseDependency);
 815        }
 816      }
 817    },
 818  
 819    /**
 820     * @api private
 821     * Return a function that will return a promise whose fate is decided by the
 822     * callback behavior of the given method with `methodName`. The method to be
 823     * promisified should conform to node.js convention of accepting a callback as
 824     * last argument and calling that callback with error as the first argument
 825     * and success value on the second argument.
 826     */
 827    promisifyMethod: function promisifyMethod(methodName, PromiseDependency) {
 828      return function promise() {
 829        var self = this;
 830        var args = Array.prototype.slice.call(arguments);
 831        return new PromiseDependency(function(resolve, reject) {
 832          args.push(function(err, data) {
 833            if (err) {
 834              reject(err);
 835            } else {
 836              resolve(data);
 837            }
 838          });
 839          self[methodName].apply(self, args);
 840        });
 841      };
 842    },
 843  
 844    /**
 845     * @api private
 846     */
 847    isDualstackAvailable: function isDualstackAvailable(service) {
 848      if (!service) return false;
 849      var metadata = require('../apis/metadata.json');
 850      if (typeof service !== 'string') service = service.serviceIdentifier;
 851      if (typeof service !== 'string' || !metadata.hasOwnProperty(service)) return false;
 852      return !!metadata[service].dualstackAvailable;
 853    },
 854  
 855    /**
 856     * @api private
 857     */
 858    calculateRetryDelay: function calculateRetryDelay(retryCount, retryDelayOptions, err) {
 859      if (!retryDelayOptions) retryDelayOptions = {};
 860      var customBackoff = retryDelayOptions.customBackoff || null;
 861      if (typeof customBackoff === 'function') {
 862        return customBackoff(retryCount, err);
 863      }
 864      var base = typeof retryDelayOptions.base === 'number' ? retryDelayOptions.base : 100;
 865      var delay = Math.random() * (Math.pow(2, retryCount) * base);
 866      return delay;
 867    },
 868  
 869    /**
 870     * @api private
 871     */
 872    handleRequestWithRetries: function handleRequestWithRetries(httpRequest, options, cb) {
 873      if (!options) options = {};
 874      var http = AWS.HttpClient.getInstance();
 875      var httpOptions = options.httpOptions || {};
 876      var retryCount = 0;
 877  
 878      var errCallback = function(err) {
 879        var maxRetries = options.maxRetries || 0;
 880        if (err && err.code === 'TimeoutError') err.retryable = true;
 881  
 882        // Call `calculateRetryDelay()` only when relevant, see #3401
 883        if (err && err.retryable && retryCount < maxRetries) {
 884          var delay = util.calculateRetryDelay(retryCount, options.retryDelayOptions, err);
 885          if (delay >= 0) {
 886            retryCount++;
 887            setTimeout(sendRequest, delay + (err.retryAfter || 0));
 888            return;
 889          }
 890        }
 891        cb(err);
 892      };
 893  
 894      var sendRequest = function() {
 895        var data = '';
 896        http.handleRequest(httpRequest, httpOptions, function(httpResponse) {
 897          httpResponse.on('data', function(chunk) { data += chunk.toString(); });
 898          httpResponse.on('end', function() {
 899            var statusCode = httpResponse.statusCode;
 900            if (statusCode < 300) {
 901              cb(null, data);
 902            } else {
 903              var retryAfter = parseInt(httpResponse.headers['retry-after'], 10) * 1000 || 0;
 904              var err = util.error(new Error(),
 905                {
 906                  statusCode: statusCode,
 907                  retryable: statusCode >= 500 || statusCode === 429
 908                }
 909              );
 910              if (retryAfter && err.retryable) err.retryAfter = retryAfter;
 911              errCallback(err);
 912            }
 913          });
 914        }, errCallback);
 915      };
 916  
 917      AWS.util.defer(sendRequest);
 918    },
 919  
 920    /**
 921     * @api private
 922     */
 923    uuid: {
 924      v4: function uuidV4() {
 925        return require('uuid').v4();
 926      }
 927    },
 928  
 929    /**
 930     * @api private
 931     */
 932    convertPayloadToString: function convertPayloadToString(resp) {
 933      var req = resp.request;
 934      var operation = req.operation;
 935      var rules = req.service.api.operations[operation].output || {};
 936      if (rules.payload && resp.data[rules.payload]) {
 937        resp.data[rules.payload] = resp.data[rules.payload].toString();
 938      }
 939    },
 940  
 941    /**
 942     * @api private
 943     */
 944    defer: function defer(callback) {
 945      if (typeof process === 'object' && typeof process.nextTick === 'function') {
 946        process.nextTick(callback);
 947      } else if (typeof setImmediate === 'function') {
 948        setImmediate(callback);
 949      } else {
 950        setTimeout(callback, 0);
 951      }
 952    },
 953  
 954    /**
 955     * @api private
 956     */
 957    getRequestPayloadShape: function getRequestPayloadShape(req) {
 958      var operations = req.service.api.operations;
 959      if (!operations) return undefined;
 960      var operation = (operations || {})[req.operation];
 961      if (!operation || !operation.input || !operation.input.payload) return undefined;
 962      return operation.input.members[operation.input.payload];
 963    },
 964  
 965    getProfilesFromSharedConfig: function getProfilesFromSharedConfig(iniLoader, filename) {
 966      var profiles = {};
 967      var profilesFromConfig = {};
 968      if (process.env[util.configOptInEnv]) {
 969        var profilesFromConfig = iniLoader.loadFrom({
 970          isConfig: true,
 971          filename: process.env[util.sharedConfigFileEnv]
 972        });
 973      }
 974      var profilesFromCreds= {};
 975      try {
 976        var profilesFromCreds = iniLoader.loadFrom({
 977          filename: filename ||
 978            (process.env[util.configOptInEnv] && process.env[util.sharedCredentialsFileEnv])
 979        });
 980      } catch (error) {
 981        // if using config, assume it is fully descriptive without a credentials file:
 982        if (!process.env[util.configOptInEnv]) throw error;
 983      }
 984      for (var i = 0, profileNames = Object.keys(profilesFromConfig); i < profileNames.length; i++) {
 985        profiles[profileNames[i]] = objectAssign(profiles[profileNames[i]] || {}, profilesFromConfig[profileNames[i]]);
 986      }
 987      for (var i = 0, profileNames = Object.keys(profilesFromCreds); i < profileNames.length; i++) {
 988        profiles[profileNames[i]] = objectAssign(profiles[profileNames[i]] || {}, profilesFromCreds[profileNames[i]]);
 989      }
 990      return profiles;
 991  
 992      /**
 993       * Roughly the semantics of `Object.assign(target, source)`
 994       */
 995      function objectAssign(target, source) {
 996        for (var i = 0, keys = Object.keys(source); i < keys.length; i++) {
 997          target[keys[i]] = source[keys[i]];
 998        }
 999        return target;
1000      }
1001    },
1002  
1003    /**
1004     * @api private
1005     */
1006    ARN: {
1007      validate: function validateARN(str) {
1008        return str && str.indexOf('arn:') === 0 && str.split(':').length >= 6;
1009      },
1010      parse: function parseARN(arn) {
1011        var matched = arn.split(':');
1012        return {
1013          partition: matched[1],
1014          service: matched[2],
1015          region: matched[3],
1016          accountId: matched[4],
1017          resource: matched.slice(5).join(':')
1018        };
1019      },
1020      build: function buildARN(arnObject) {
1021        if (
1022          arnObject.service === undefined ||
1023          arnObject.region === undefined ||
1024          arnObject.accountId === undefined ||
1025          arnObject.resource === undefined
1026        ) throw util.error(new Error('Input ARN object is invalid'));
1027        return 'arn:'+ (arnObject.partition || 'aws') + ':' + arnObject.service +
1028          ':' + arnObject.region + ':' + arnObject.accountId + ':' + arnObject.resource;
1029      }
1030    },
1031  
1032    /**
1033     * @api private
1034     */
1035    defaultProfile: 'default',
1036  
1037    /**
1038     * @api private
1039     */
1040    configOptInEnv: 'AWS_SDK_LOAD_CONFIG',
1041  
1042    /**
1043     * @api private
1044     */
1045    sharedCredentialsFileEnv: 'AWS_SHARED_CREDENTIALS_FILE',
1046  
1047    /**
1048     * @api private
1049     */
1050    sharedConfigFileEnv: 'AWS_CONFIG_FILE',
1051  
1052    /**
1053     * @api private
1054     */
1055    imdsDisabledEnv: 'AWS_EC2_METADATA_DISABLED'
1056  };
1057  
1058  /**
1059   * @api private
1060   */
1061  module.exports = util;