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;