index.es.js (41171B)
1 process.emitWarning("The .es.js file is deprecated. Use .mjs instead."); 2 3 import Stream from 'stream'; 4 import http from 'http'; 5 import Url from 'url'; 6 import https from 'https'; 7 import zlib from 'zlib'; 8 9 // Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js 10 11 // fix for "Readable" isn't a named export issue 12 const Readable = Stream.Readable; 13 14 const BUFFER = Symbol('buffer'); 15 const TYPE = Symbol('type'); 16 17 class Blob { 18 constructor() { 19 this[TYPE] = ''; 20 21 const blobParts = arguments[0]; 22 const options = arguments[1]; 23 24 const buffers = []; 25 let size = 0; 26 27 if (blobParts) { 28 const a = blobParts; 29 const length = Number(a.length); 30 for (let i = 0; i < length; i++) { 31 const element = a[i]; 32 let buffer; 33 if (element instanceof Buffer) { 34 buffer = element; 35 } else if (ArrayBuffer.isView(element)) { 36 buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); 37 } else if (element instanceof ArrayBuffer) { 38 buffer = Buffer.from(element); 39 } else if (element instanceof Blob) { 40 buffer = element[BUFFER]; 41 } else { 42 buffer = Buffer.from(typeof element === 'string' ? element : String(element)); 43 } 44 size += buffer.length; 45 buffers.push(buffer); 46 } 47 } 48 49 this[BUFFER] = Buffer.concat(buffers); 50 51 let type = options && options.type !== undefined && String(options.type).toLowerCase(); 52 if (type && !/[^\u0020-\u007E]/.test(type)) { 53 this[TYPE] = type; 54 } 55 } 56 get size() { 57 return this[BUFFER].length; 58 } 59 get type() { 60 return this[TYPE]; 61 } 62 text() { 63 return Promise.resolve(this[BUFFER].toString()); 64 } 65 arrayBuffer() { 66 const buf = this[BUFFER]; 67 const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); 68 return Promise.resolve(ab); 69 } 70 stream() { 71 const readable = new Readable(); 72 readable._read = function () {}; 73 readable.push(this[BUFFER]); 74 readable.push(null); 75 return readable; 76 } 77 toString() { 78 return '[object Blob]'; 79 } 80 slice() { 81 const size = this.size; 82 83 const start = arguments[0]; 84 const end = arguments[1]; 85 let relativeStart, relativeEnd; 86 if (start === undefined) { 87 relativeStart = 0; 88 } else if (start < 0) { 89 relativeStart = Math.max(size + start, 0); 90 } else { 91 relativeStart = Math.min(start, size); 92 } 93 if (end === undefined) { 94 relativeEnd = size; 95 } else if (end < 0) { 96 relativeEnd = Math.max(size + end, 0); 97 } else { 98 relativeEnd = Math.min(end, size); 99 } 100 const span = Math.max(relativeEnd - relativeStart, 0); 101 102 const buffer = this[BUFFER]; 103 const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); 104 const blob = new Blob([], { type: arguments[2] }); 105 blob[BUFFER] = slicedBuffer; 106 return blob; 107 } 108 } 109 110 Object.defineProperties(Blob.prototype, { 111 size: { enumerable: true }, 112 type: { enumerable: true }, 113 slice: { enumerable: true } 114 }); 115 116 Object.defineProperty(Blob.prototype, Symbol.toStringTag, { 117 value: 'Blob', 118 writable: false, 119 enumerable: false, 120 configurable: true 121 }); 122 123 /** 124 * fetch-error.js 125 * 126 * FetchError interface for operational errors 127 */ 128 129 /** 130 * Create FetchError instance 131 * 132 * @param String message Error message for human 133 * @param String type Error type for machine 134 * @param String systemError For Node.js system error 135 * @return FetchError 136 */ 137 function FetchError(message, type, systemError) { 138 Error.call(this, message); 139 140 this.message = message; 141 this.type = type; 142 143 // when err.type is `system`, err.code contains system error code 144 if (systemError) { 145 this.code = this.errno = systemError.code; 146 } 147 148 // hide custom error implementation details from end-users 149 Error.captureStackTrace(this, this.constructor); 150 } 151 152 FetchError.prototype = Object.create(Error.prototype); 153 FetchError.prototype.constructor = FetchError; 154 FetchError.prototype.name = 'FetchError'; 155 156 let convert; 157 try { 158 convert = require('encoding').convert; 159 } catch (e) {} 160 161 const INTERNALS = Symbol('Body internals'); 162 163 // fix an issue where "PassThrough" isn't a named export for node <10 164 const PassThrough = Stream.PassThrough; 165 166 /** 167 * Body mixin 168 * 169 * Ref: https://fetch.spec.whatwg.org/#body 170 * 171 * @param Stream body Readable stream 172 * @param Object opts Response options 173 * @return Void 174 */ 175 function Body(body) { 176 var _this = this; 177 178 var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 179 _ref$size = _ref.size; 180 181 let size = _ref$size === undefined ? 0 : _ref$size; 182 var _ref$timeout = _ref.timeout; 183 let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; 184 185 if (body == null) { 186 // body is undefined or null 187 body = null; 188 } else if (isURLSearchParams(body)) { 189 // body is a URLSearchParams 190 body = Buffer.from(body.toString()); 191 } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { 192 // body is ArrayBuffer 193 body = Buffer.from(body); 194 } else if (ArrayBuffer.isView(body)) { 195 // body is ArrayBufferView 196 body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); 197 } else if (body instanceof Stream) ; else { 198 // none of the above 199 // coerce to string then buffer 200 body = Buffer.from(String(body)); 201 } 202 this[INTERNALS] = { 203 body, 204 disturbed: false, 205 error: null 206 }; 207 this.size = size; 208 this.timeout = timeout; 209 210 if (body instanceof Stream) { 211 body.on('error', function (err) { 212 const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); 213 _this[INTERNALS].error = error; 214 }); 215 } 216 } 217 218 Body.prototype = { 219 get body() { 220 return this[INTERNALS].body; 221 }, 222 223 get bodyUsed() { 224 return this[INTERNALS].disturbed; 225 }, 226 227 /** 228 * Decode response as ArrayBuffer 229 * 230 * @return Promise 231 */ 232 arrayBuffer() { 233 return consumeBody.call(this).then(function (buf) { 234 return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); 235 }); 236 }, 237 238 /** 239 * Return raw response as Blob 240 * 241 * @return Promise 242 */ 243 blob() { 244 let ct = this.headers && this.headers.get('content-type') || ''; 245 return consumeBody.call(this).then(function (buf) { 246 return Object.assign( 247 // Prevent copying 248 new Blob([], { 249 type: ct.toLowerCase() 250 }), { 251 [BUFFER]: buf 252 }); 253 }); 254 }, 255 256 /** 257 * Decode response as json 258 * 259 * @return Promise 260 */ 261 json() { 262 var _this2 = this; 263 264 return consumeBody.call(this).then(function (buffer) { 265 try { 266 return JSON.parse(buffer.toString()); 267 } catch (err) { 268 return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); 269 } 270 }); 271 }, 272 273 /** 274 * Decode response as text 275 * 276 * @return Promise 277 */ 278 text() { 279 return consumeBody.call(this).then(function (buffer) { 280 return buffer.toString(); 281 }); 282 }, 283 284 /** 285 * Decode response as buffer (non-spec api) 286 * 287 * @return Promise 288 */ 289 buffer() { 290 return consumeBody.call(this); 291 }, 292 293 /** 294 * Decode response as text, while automatically detecting the encoding and 295 * trying to decode to UTF-8 (non-spec api) 296 * 297 * @return Promise 298 */ 299 textConverted() { 300 var _this3 = this; 301 302 return consumeBody.call(this).then(function (buffer) { 303 return convertBody(buffer, _this3.headers); 304 }); 305 } 306 }; 307 308 // In browsers, all properties are enumerable. 309 Object.defineProperties(Body.prototype, { 310 body: { enumerable: true }, 311 bodyUsed: { enumerable: true }, 312 arrayBuffer: { enumerable: true }, 313 blob: { enumerable: true }, 314 json: { enumerable: true }, 315 text: { enumerable: true } 316 }); 317 318 Body.mixIn = function (proto) { 319 for (const name of Object.getOwnPropertyNames(Body.prototype)) { 320 // istanbul ignore else: future proof 321 if (!(name in proto)) { 322 const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); 323 Object.defineProperty(proto, name, desc); 324 } 325 } 326 }; 327 328 /** 329 * Consume and convert an entire Body to a Buffer. 330 * 331 * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body 332 * 333 * @return Promise 334 */ 335 function consumeBody() { 336 var _this4 = this; 337 338 if (this[INTERNALS].disturbed) { 339 return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); 340 } 341 342 this[INTERNALS].disturbed = true; 343 344 if (this[INTERNALS].error) { 345 return Body.Promise.reject(this[INTERNALS].error); 346 } 347 348 let body = this.body; 349 350 // body is null 351 if (body === null) { 352 return Body.Promise.resolve(Buffer.alloc(0)); 353 } 354 355 // body is blob 356 if (isBlob(body)) { 357 body = body.stream(); 358 } 359 360 // body is buffer 361 if (Buffer.isBuffer(body)) { 362 return Body.Promise.resolve(body); 363 } 364 365 // istanbul ignore if: should never happen 366 if (!(body instanceof Stream)) { 367 return Body.Promise.resolve(Buffer.alloc(0)); 368 } 369 370 // body is stream 371 // get ready to actually consume the body 372 let accum = []; 373 let accumBytes = 0; 374 let abort = false; 375 376 return new Body.Promise(function (resolve, reject) { 377 let resTimeout; 378 379 // allow timeout on slow response body 380 if (_this4.timeout) { 381 resTimeout = setTimeout(function () { 382 abort = true; 383 reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); 384 }, _this4.timeout); 385 } 386 387 // handle stream errors 388 body.on('error', function (err) { 389 if (err.name === 'AbortError') { 390 // if the request was aborted, reject with this Error 391 abort = true; 392 reject(err); 393 } else { 394 // other errors, such as incorrect content-encoding 395 reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); 396 } 397 }); 398 399 body.on('data', function (chunk) { 400 if (abort || chunk === null) { 401 return; 402 } 403 404 if (_this4.size && accumBytes + chunk.length > _this4.size) { 405 abort = true; 406 reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); 407 return; 408 } 409 410 accumBytes += chunk.length; 411 accum.push(chunk); 412 }); 413 414 body.on('end', function () { 415 if (abort) { 416 return; 417 } 418 419 clearTimeout(resTimeout); 420 421 try { 422 resolve(Buffer.concat(accum, accumBytes)); 423 } catch (err) { 424 // handle streams that have accumulated too much data (issue #414) 425 reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); 426 } 427 }); 428 }); 429 } 430 431 /** 432 * Detect buffer encoding and convert to target encoding 433 * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding 434 * 435 * @param Buffer buffer Incoming buffer 436 * @param String encoding Target encoding 437 * @return String 438 */ 439 function convertBody(buffer, headers) { 440 if (typeof convert !== 'function') { 441 throw new Error('The package `encoding` must be installed to use the textConverted() function'); 442 } 443 444 const ct = headers.get('content-type'); 445 let charset = 'utf-8'; 446 let res, str; 447 448 // header 449 if (ct) { 450 res = /charset=([^;]*)/i.exec(ct); 451 } 452 453 // no charset in content type, peek at response body for at most 1024 bytes 454 str = buffer.slice(0, 1024).toString(); 455 456 // html5 457 if (!res && str) { 458 res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str); 459 } 460 461 // html4 462 if (!res && str) { 463 res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str); 464 if (!res) { 465 res = /<meta[\s]+?content=(['"])(.+?)\1[\s]+?http-equiv=(['"])content-type\3/i.exec(str); 466 if (res) { 467 res.pop(); // drop last quote 468 } 469 } 470 471 if (res) { 472 res = /charset=(.*)/i.exec(res.pop()); 473 } 474 } 475 476 // xml 477 if (!res && str) { 478 res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str); 479 } 480 481 // found charset 482 if (res) { 483 charset = res.pop(); 484 485 // prevent decode issues when sites use incorrect encoding 486 // ref: https://hsivonen.fi/encoding-menu/ 487 if (charset === 'gb2312' || charset === 'gbk') { 488 charset = 'gb18030'; 489 } 490 } 491 492 // turn raw buffers into a single utf-8 buffer 493 return convert(buffer, 'UTF-8', charset).toString(); 494 } 495 496 /** 497 * Detect a URLSearchParams object 498 * ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143 499 * 500 * @param Object obj Object to detect by type or brand 501 * @return String 502 */ 503 function isURLSearchParams(obj) { 504 // Duck-typing as a necessary condition. 505 if (typeof obj !== 'object' || typeof obj.append !== 'function' || typeof obj.delete !== 'function' || typeof obj.get !== 'function' || typeof obj.getAll !== 'function' || typeof obj.has !== 'function' || typeof obj.set !== 'function') { 506 return false; 507 } 508 509 // Brand-checking and more duck-typing as optional condition. 510 return obj.constructor.name === 'URLSearchParams' || Object.prototype.toString.call(obj) === '[object URLSearchParams]' || typeof obj.sort === 'function'; 511 } 512 513 /** 514 * Check if `obj` is a W3C `Blob` object (which `File` inherits from) 515 * @param {*} obj 516 * @return {boolean} 517 */ 518 function isBlob(obj) { 519 return typeof obj === 'object' && typeof obj.arrayBuffer === 'function' && typeof obj.type === 'string' && typeof obj.stream === 'function' && typeof obj.constructor === 'function' && typeof obj.constructor.name === 'string' && /^(Blob|File)$/.test(obj.constructor.name) && /^(Blob|File)$/.test(obj[Symbol.toStringTag]); 520 } 521 522 /** 523 * Clone body given Res/Req instance 524 * 525 * @param Mixed instance Response or Request instance 526 * @return Mixed 527 */ 528 function clone(instance) { 529 let p1, p2; 530 let body = instance.body; 531 532 // don't allow cloning a used body 533 if (instance.bodyUsed) { 534 throw new Error('cannot clone body after it is used'); 535 } 536 537 // check that body is a stream and not form-data object 538 // note: we can't clone the form-data object without having it as a dependency 539 if (body instanceof Stream && typeof body.getBoundary !== 'function') { 540 // tee instance body 541 p1 = new PassThrough(); 542 p2 = new PassThrough(); 543 body.pipe(p1); 544 body.pipe(p2); 545 // set instance body to teed body and return the other teed body 546 instance[INTERNALS].body = p1; 547 body = p2; 548 } 549 550 return body; 551 } 552 553 /** 554 * Performs the operation "extract a `Content-Type` value from |object|" as 555 * specified in the specification: 556 * https://fetch.spec.whatwg.org/#concept-bodyinit-extract 557 * 558 * This function assumes that instance.body is present. 559 * 560 * @param Mixed instance Any options.body input 561 */ 562 function extractContentType(body) { 563 if (body === null) { 564 // body is null 565 return null; 566 } else if (typeof body === 'string') { 567 // body is string 568 return 'text/plain;charset=UTF-8'; 569 } else if (isURLSearchParams(body)) { 570 // body is a URLSearchParams 571 return 'application/x-www-form-urlencoded;charset=UTF-8'; 572 } else if (isBlob(body)) { 573 // body is blob 574 return body.type || null; 575 } else if (Buffer.isBuffer(body)) { 576 // body is buffer 577 return null; 578 } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { 579 // body is ArrayBuffer 580 return null; 581 } else if (ArrayBuffer.isView(body)) { 582 // body is ArrayBufferView 583 return null; 584 } else if (typeof body.getBoundary === 'function') { 585 // detect form data input from form-data module 586 return `multipart/form-data;boundary=${body.getBoundary()}`; 587 } else if (body instanceof Stream) { 588 // body is stream 589 // can't really do much about this 590 return null; 591 } else { 592 // Body constructor defaults other things to string 593 return 'text/plain;charset=UTF-8'; 594 } 595 } 596 597 /** 598 * The Fetch Standard treats this as if "total bytes" is a property on the body. 599 * For us, we have to explicitly get it with a function. 600 * 601 * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes 602 * 603 * @param Body instance Instance of Body 604 * @return Number? Number of bytes, or null if not possible 605 */ 606 function getTotalBytes(instance) { 607 const body = instance.body; 608 609 610 if (body === null) { 611 // body is null 612 return 0; 613 } else if (isBlob(body)) { 614 return body.size; 615 } else if (Buffer.isBuffer(body)) { 616 // body is buffer 617 return body.length; 618 } else if (body && typeof body.getLengthSync === 'function') { 619 // detect form data input from form-data module 620 if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x 621 body.hasKnownLength && body.hasKnownLength()) { 622 // 2.x 623 return body.getLengthSync(); 624 } 625 return null; 626 } else { 627 // body is stream 628 return null; 629 } 630 } 631 632 /** 633 * Write a Body to a Node.js WritableStream (e.g. http.Request) object. 634 * 635 * @param Body instance Instance of Body 636 * @return Void 637 */ 638 function writeToStream(dest, instance) { 639 const body = instance.body; 640 641 642 if (body === null) { 643 // body is null 644 dest.end(); 645 } else if (isBlob(body)) { 646 body.stream().pipe(dest); 647 } else if (Buffer.isBuffer(body)) { 648 // body is buffer 649 dest.write(body); 650 dest.end(); 651 } else { 652 // body is stream 653 body.pipe(dest); 654 } 655 } 656 657 // expose Promise 658 Body.Promise = global.Promise; 659 660 /** 661 * headers.js 662 * 663 * Headers class offers convenient helpers 664 */ 665 666 const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/; 667 const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/; 668 669 function validateName(name) { 670 name = `${name}`; 671 if (invalidTokenRegex.test(name) || name === '') { 672 throw new TypeError(`${name} is not a legal HTTP header name`); 673 } 674 } 675 676 function validateValue(value) { 677 value = `${value}`; 678 if (invalidHeaderCharRegex.test(value)) { 679 throw new TypeError(`${value} is not a legal HTTP header value`); 680 } 681 } 682 683 /** 684 * Find the key in the map object given a header name. 685 * 686 * Returns undefined if not found. 687 * 688 * @param String name Header name 689 * @return String|Undefined 690 */ 691 function find(map, name) { 692 name = name.toLowerCase(); 693 for (const key in map) { 694 if (key.toLowerCase() === name) { 695 return key; 696 } 697 } 698 return undefined; 699 } 700 701 const MAP = Symbol('map'); 702 class Headers { 703 /** 704 * Headers class 705 * 706 * @param Object headers Response headers 707 * @return Void 708 */ 709 constructor() { 710 let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; 711 712 this[MAP] = Object.create(null); 713 714 if (init instanceof Headers) { 715 const rawHeaders = init.raw(); 716 const headerNames = Object.keys(rawHeaders); 717 718 for (const headerName of headerNames) { 719 for (const value of rawHeaders[headerName]) { 720 this.append(headerName, value); 721 } 722 } 723 724 return; 725 } 726 727 // We don't worry about converting prop to ByteString here as append() 728 // will handle it. 729 if (init == null) ; else if (typeof init === 'object') { 730 const method = init[Symbol.iterator]; 731 if (method != null) { 732 if (typeof method !== 'function') { 733 throw new TypeError('Header pairs must be iterable'); 734 } 735 736 // sequence<sequence<ByteString>> 737 // Note: per spec we have to first exhaust the lists then process them 738 const pairs = []; 739 for (const pair of init) { 740 if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { 741 throw new TypeError('Each header pair must be iterable'); 742 } 743 pairs.push(Array.from(pair)); 744 } 745 746 for (const pair of pairs) { 747 if (pair.length !== 2) { 748 throw new TypeError('Each header pair must be a name/value tuple'); 749 } 750 this.append(pair[0], pair[1]); 751 } 752 } else { 753 // record<ByteString, ByteString> 754 for (const key of Object.keys(init)) { 755 const value = init[key]; 756 this.append(key, value); 757 } 758 } 759 } else { 760 throw new TypeError('Provided initializer must be an object'); 761 } 762 } 763 764 /** 765 * Return combined header value given name 766 * 767 * @param String name Header name 768 * @return Mixed 769 */ 770 get(name) { 771 name = `${name}`; 772 validateName(name); 773 const key = find(this[MAP], name); 774 if (key === undefined) { 775 return null; 776 } 777 778 return this[MAP][key].join(', '); 779 } 780 781 /** 782 * Iterate over all headers 783 * 784 * @param Function callback Executed for each item with parameters (value, name, thisArg) 785 * @param Boolean thisArg `this` context for callback function 786 * @return Void 787 */ 788 forEach(callback) { 789 let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; 790 791 let pairs = getHeaders(this); 792 let i = 0; 793 while (i < pairs.length) { 794 var _pairs$i = pairs[i]; 795 const name = _pairs$i[0], 796 value = _pairs$i[1]; 797 798 callback.call(thisArg, value, name, this); 799 pairs = getHeaders(this); 800 i++; 801 } 802 } 803 804 /** 805 * Overwrite header values given name 806 * 807 * @param String name Header name 808 * @param String value Header value 809 * @return Void 810 */ 811 set(name, value) { 812 name = `${name}`; 813 value = `${value}`; 814 validateName(name); 815 validateValue(value); 816 const key = find(this[MAP], name); 817 this[MAP][key !== undefined ? key : name] = [value]; 818 } 819 820 /** 821 * Append a value onto existing header 822 * 823 * @param String name Header name 824 * @param String value Header value 825 * @return Void 826 */ 827 append(name, value) { 828 name = `${name}`; 829 value = `${value}`; 830 validateName(name); 831 validateValue(value); 832 const key = find(this[MAP], name); 833 if (key !== undefined) { 834 this[MAP][key].push(value); 835 } else { 836 this[MAP][name] = [value]; 837 } 838 } 839 840 /** 841 * Check for header name existence 842 * 843 * @param String name Header name 844 * @return Boolean 845 */ 846 has(name) { 847 name = `${name}`; 848 validateName(name); 849 return find(this[MAP], name) !== undefined; 850 } 851 852 /** 853 * Delete all header values given name 854 * 855 * @param String name Header name 856 * @return Void 857 */ 858 delete(name) { 859 name = `${name}`; 860 validateName(name); 861 const key = find(this[MAP], name); 862 if (key !== undefined) { 863 delete this[MAP][key]; 864 } 865 } 866 867 /** 868 * Return raw headers (non-spec api) 869 * 870 * @return Object 871 */ 872 raw() { 873 return this[MAP]; 874 } 875 876 /** 877 * Get an iterator on keys. 878 * 879 * @return Iterator 880 */ 881 keys() { 882 return createHeadersIterator(this, 'key'); 883 } 884 885 /** 886 * Get an iterator on values. 887 * 888 * @return Iterator 889 */ 890 values() { 891 return createHeadersIterator(this, 'value'); 892 } 893 894 /** 895 * Get an iterator on entries. 896 * 897 * This is the default iterator of the Headers object. 898 * 899 * @return Iterator 900 */ 901 [Symbol.iterator]() { 902 return createHeadersIterator(this, 'key+value'); 903 } 904 } 905 Headers.prototype.entries = Headers.prototype[Symbol.iterator]; 906 907 Object.defineProperty(Headers.prototype, Symbol.toStringTag, { 908 value: 'Headers', 909 writable: false, 910 enumerable: false, 911 configurable: true 912 }); 913 914 Object.defineProperties(Headers.prototype, { 915 get: { enumerable: true }, 916 forEach: { enumerable: true }, 917 set: { enumerable: true }, 918 append: { enumerable: true }, 919 has: { enumerable: true }, 920 delete: { enumerable: true }, 921 keys: { enumerable: true }, 922 values: { enumerable: true }, 923 entries: { enumerable: true } 924 }); 925 926 function getHeaders(headers) { 927 let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; 928 929 const keys = Object.keys(headers[MAP]).sort(); 930 return keys.map(kind === 'key' ? function (k) { 931 return k.toLowerCase(); 932 } : kind === 'value' ? function (k) { 933 return headers[MAP][k].join(', '); 934 } : function (k) { 935 return [k.toLowerCase(), headers[MAP][k].join(', ')]; 936 }); 937 } 938 939 const INTERNAL = Symbol('internal'); 940 941 function createHeadersIterator(target, kind) { 942 const iterator = Object.create(HeadersIteratorPrototype); 943 iterator[INTERNAL] = { 944 target, 945 kind, 946 index: 0 947 }; 948 return iterator; 949 } 950 951 const HeadersIteratorPrototype = Object.setPrototypeOf({ 952 next() { 953 // istanbul ignore if 954 if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { 955 throw new TypeError('Value of `this` is not a HeadersIterator'); 956 } 957 958 var _INTERNAL = this[INTERNAL]; 959 const target = _INTERNAL.target, 960 kind = _INTERNAL.kind, 961 index = _INTERNAL.index; 962 963 const values = getHeaders(target, kind); 964 const len = values.length; 965 if (index >= len) { 966 return { 967 value: undefined, 968 done: true 969 }; 970 } 971 972 this[INTERNAL].index = index + 1; 973 974 return { 975 value: values[index], 976 done: false 977 }; 978 } 979 }, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); 980 981 Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { 982 value: 'HeadersIterator', 983 writable: false, 984 enumerable: false, 985 configurable: true 986 }); 987 988 /** 989 * Export the Headers object in a form that Node.js can consume. 990 * 991 * @param Headers headers 992 * @return Object 993 */ 994 function exportNodeCompatibleHeaders(headers) { 995 const obj = Object.assign({ __proto__: null }, headers[MAP]); 996 997 // http.request() only supports string as Host header. This hack makes 998 // specifying custom Host header possible. 999 const hostHeaderKey = find(headers[MAP], 'Host'); 1000 if (hostHeaderKey !== undefined) { 1001 obj[hostHeaderKey] = obj[hostHeaderKey][0]; 1002 } 1003 1004 return obj; 1005 } 1006 1007 /** 1008 * Create a Headers object from an object of headers, ignoring those that do 1009 * not conform to HTTP grammar productions. 1010 * 1011 * @param Object obj Object of headers 1012 * @return Headers 1013 */ 1014 function createHeadersLenient(obj) { 1015 const headers = new Headers(); 1016 for (const name of Object.keys(obj)) { 1017 if (invalidTokenRegex.test(name)) { 1018 continue; 1019 } 1020 if (Array.isArray(obj[name])) { 1021 for (const val of obj[name]) { 1022 if (invalidHeaderCharRegex.test(val)) { 1023 continue; 1024 } 1025 if (headers[MAP][name] === undefined) { 1026 headers[MAP][name] = [val]; 1027 } else { 1028 headers[MAP][name].push(val); 1029 } 1030 } 1031 } else if (!invalidHeaderCharRegex.test(obj[name])) { 1032 headers[MAP][name] = [obj[name]]; 1033 } 1034 } 1035 return headers; 1036 } 1037 1038 const INTERNALS$1 = Symbol('Response internals'); 1039 1040 // fix an issue where "STATUS_CODES" aren't a named export for node <10 1041 const STATUS_CODES = http.STATUS_CODES; 1042 1043 /** 1044 * Response class 1045 * 1046 * @param Stream body Readable stream 1047 * @param Object opts Response options 1048 * @return Void 1049 */ 1050 class Response { 1051 constructor() { 1052 let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; 1053 let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1054 1055 Body.call(this, body, opts); 1056 1057 const status = opts.status || 200; 1058 const headers = new Headers(opts.headers); 1059 1060 if (body != null && !headers.has('Content-Type')) { 1061 const contentType = extractContentType(body); 1062 if (contentType) { 1063 headers.append('Content-Type', contentType); 1064 } 1065 } 1066 1067 this[INTERNALS$1] = { 1068 url: opts.url, 1069 status, 1070 statusText: opts.statusText || STATUS_CODES[status], 1071 headers, 1072 counter: opts.counter 1073 }; 1074 } 1075 1076 get url() { 1077 return this[INTERNALS$1].url || ''; 1078 } 1079 1080 get status() { 1081 return this[INTERNALS$1].status; 1082 } 1083 1084 /** 1085 * Convenience property representing if the request ended normally 1086 */ 1087 get ok() { 1088 return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; 1089 } 1090 1091 get redirected() { 1092 return this[INTERNALS$1].counter > 0; 1093 } 1094 1095 get statusText() { 1096 return this[INTERNALS$1].statusText; 1097 } 1098 1099 get headers() { 1100 return this[INTERNALS$1].headers; 1101 } 1102 1103 /** 1104 * Clone this response 1105 * 1106 * @return Response 1107 */ 1108 clone() { 1109 return new Response(clone(this), { 1110 url: this.url, 1111 status: this.status, 1112 statusText: this.statusText, 1113 headers: this.headers, 1114 ok: this.ok, 1115 redirected: this.redirected 1116 }); 1117 } 1118 } 1119 1120 Body.mixIn(Response.prototype); 1121 1122 Object.defineProperties(Response.prototype, { 1123 url: { enumerable: true }, 1124 status: { enumerable: true }, 1125 ok: { enumerable: true }, 1126 redirected: { enumerable: true }, 1127 statusText: { enumerable: true }, 1128 headers: { enumerable: true }, 1129 clone: { enumerable: true } 1130 }); 1131 1132 Object.defineProperty(Response.prototype, Symbol.toStringTag, { 1133 value: 'Response', 1134 writable: false, 1135 enumerable: false, 1136 configurable: true 1137 }); 1138 1139 const INTERNALS$2 = Symbol('Request internals'); 1140 1141 // fix an issue where "format", "parse" aren't a named export for node <10 1142 const parse_url = Url.parse; 1143 const format_url = Url.format; 1144 1145 const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; 1146 1147 /** 1148 * Check if a value is an instance of Request. 1149 * 1150 * @param Mixed input 1151 * @return Boolean 1152 */ 1153 function isRequest(input) { 1154 return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; 1155 } 1156 1157 function isAbortSignal(signal) { 1158 const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal); 1159 return !!(proto && proto.constructor.name === 'AbortSignal'); 1160 } 1161 1162 /** 1163 * Request class 1164 * 1165 * @param Mixed input Url or Request instance 1166 * @param Object init Custom options 1167 * @return Void 1168 */ 1169 class Request { 1170 constructor(input) { 1171 let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1172 1173 let parsedURL; 1174 1175 // normalize input 1176 if (!isRequest(input)) { 1177 if (input && input.href) { 1178 // in order to support Node.js' Url objects; though WHATWG's URL objects 1179 // will fall into this branch also (since their `toString()` will return 1180 // `href` property anyway) 1181 parsedURL = parse_url(input.href); 1182 } else { 1183 // coerce input to a string before attempting to parse 1184 parsedURL = parse_url(`${input}`); 1185 } 1186 input = {}; 1187 } else { 1188 parsedURL = parse_url(input.url); 1189 } 1190 1191 let method = init.method || input.method || 'GET'; 1192 method = method.toUpperCase(); 1193 1194 if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { 1195 throw new TypeError('Request with GET/HEAD method cannot have body'); 1196 } 1197 1198 let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; 1199 1200 Body.call(this, inputBody, { 1201 timeout: init.timeout || input.timeout || 0, 1202 size: init.size || input.size || 0 1203 }); 1204 1205 const headers = new Headers(init.headers || input.headers || {}); 1206 1207 if (inputBody != null && !headers.has('Content-Type')) { 1208 const contentType = extractContentType(inputBody); 1209 if (contentType) { 1210 headers.append('Content-Type', contentType); 1211 } 1212 } 1213 1214 let signal = isRequest(input) ? input.signal : null; 1215 if ('signal' in init) signal = init.signal; 1216 1217 if (signal != null && !isAbortSignal(signal)) { 1218 throw new TypeError('Expected signal to be an instanceof AbortSignal'); 1219 } 1220 1221 this[INTERNALS$2] = { 1222 method, 1223 redirect: init.redirect || input.redirect || 'follow', 1224 headers, 1225 parsedURL, 1226 signal 1227 }; 1228 1229 // node-fetch-only options 1230 this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; 1231 this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; 1232 this.counter = init.counter || input.counter || 0; 1233 this.agent = init.agent || input.agent; 1234 } 1235 1236 get method() { 1237 return this[INTERNALS$2].method; 1238 } 1239 1240 get url() { 1241 return format_url(this[INTERNALS$2].parsedURL); 1242 } 1243 1244 get headers() { 1245 return this[INTERNALS$2].headers; 1246 } 1247 1248 get redirect() { 1249 return this[INTERNALS$2].redirect; 1250 } 1251 1252 get signal() { 1253 return this[INTERNALS$2].signal; 1254 } 1255 1256 /** 1257 * Clone this request 1258 * 1259 * @return Request 1260 */ 1261 clone() { 1262 return new Request(this); 1263 } 1264 } 1265 1266 Body.mixIn(Request.prototype); 1267 1268 Object.defineProperty(Request.prototype, Symbol.toStringTag, { 1269 value: 'Request', 1270 writable: false, 1271 enumerable: false, 1272 configurable: true 1273 }); 1274 1275 Object.defineProperties(Request.prototype, { 1276 method: { enumerable: true }, 1277 url: { enumerable: true }, 1278 headers: { enumerable: true }, 1279 redirect: { enumerable: true }, 1280 clone: { enumerable: true }, 1281 signal: { enumerable: true } 1282 }); 1283 1284 /** 1285 * Convert a Request to Node.js http request options. 1286 * 1287 * @param Request A Request instance 1288 * @return Object The options object to be passed to http.request 1289 */ 1290 function getNodeRequestOptions(request) { 1291 const parsedURL = request[INTERNALS$2].parsedURL; 1292 const headers = new Headers(request[INTERNALS$2].headers); 1293 1294 // fetch step 1.3 1295 if (!headers.has('Accept')) { 1296 headers.set('Accept', '*/*'); 1297 } 1298 1299 // Basic fetch 1300 if (!parsedURL.protocol || !parsedURL.hostname) { 1301 throw new TypeError('Only absolute URLs are supported'); 1302 } 1303 1304 if (!/^https?:$/.test(parsedURL.protocol)) { 1305 throw new TypeError('Only HTTP(S) protocols are supported'); 1306 } 1307 1308 if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { 1309 throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); 1310 } 1311 1312 // HTTP-network-or-cache fetch steps 2.4-2.7 1313 let contentLengthValue = null; 1314 if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { 1315 contentLengthValue = '0'; 1316 } 1317 if (request.body != null) { 1318 const totalBytes = getTotalBytes(request); 1319 if (typeof totalBytes === 'number') { 1320 contentLengthValue = String(totalBytes); 1321 } 1322 } 1323 if (contentLengthValue) { 1324 headers.set('Content-Length', contentLengthValue); 1325 } 1326 1327 // HTTP-network-or-cache fetch step 2.11 1328 if (!headers.has('User-Agent')) { 1329 headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); 1330 } 1331 1332 // HTTP-network-or-cache fetch step 2.15 1333 if (request.compress && !headers.has('Accept-Encoding')) { 1334 headers.set('Accept-Encoding', 'gzip,deflate'); 1335 } 1336 1337 let agent = request.agent; 1338 if (typeof agent === 'function') { 1339 agent = agent(parsedURL); 1340 } 1341 1342 if (!headers.has('Connection') && !agent) { 1343 headers.set('Connection', 'close'); 1344 } 1345 1346 // HTTP-network fetch step 4.2 1347 // chunked encoding is handled by Node.js 1348 1349 return Object.assign({}, parsedURL, { 1350 method: request.method, 1351 headers: exportNodeCompatibleHeaders(headers), 1352 agent 1353 }); 1354 } 1355 1356 /** 1357 * abort-error.js 1358 * 1359 * AbortError interface for cancelled requests 1360 */ 1361 1362 /** 1363 * Create AbortError instance 1364 * 1365 * @param String message Error message for human 1366 * @return AbortError 1367 */ 1368 function AbortError(message) { 1369 Error.call(this, message); 1370 1371 this.type = 'aborted'; 1372 this.message = message; 1373 1374 // hide custom error implementation details from end-users 1375 Error.captureStackTrace(this, this.constructor); 1376 } 1377 1378 AbortError.prototype = Object.create(Error.prototype); 1379 AbortError.prototype.constructor = AbortError; 1380 AbortError.prototype.name = 'AbortError'; 1381 1382 // fix an issue where "PassThrough", "resolve" aren't a named export for node <10 1383 const PassThrough$1 = Stream.PassThrough; 1384 const resolve_url = Url.resolve; 1385 1386 /** 1387 * Fetch function 1388 * 1389 * @param Mixed url Absolute url or Request instance 1390 * @param Object opts Fetch options 1391 * @return Promise 1392 */ 1393 function fetch(url, opts) { 1394 1395 // allow custom promise 1396 if (!fetch.Promise) { 1397 throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); 1398 } 1399 1400 Body.Promise = fetch.Promise; 1401 1402 // wrap http.request into fetch 1403 return new fetch.Promise(function (resolve, reject) { 1404 // build request object 1405 const request = new Request(url, opts); 1406 const options = getNodeRequestOptions(request); 1407 1408 const send = (options.protocol === 'https:' ? https : http).request; 1409 const signal = request.signal; 1410 1411 let response = null; 1412 1413 const abort = function abort() { 1414 let error = new AbortError('The user aborted a request.'); 1415 reject(error); 1416 if (request.body && request.body instanceof Stream.Readable) { 1417 request.body.destroy(error); 1418 } 1419 if (!response || !response.body) return; 1420 response.body.emit('error', error); 1421 }; 1422 1423 if (signal && signal.aborted) { 1424 abort(); 1425 return; 1426 } 1427 1428 const abortAndFinalize = function abortAndFinalize() { 1429 abort(); 1430 finalize(); 1431 }; 1432 1433 // send request 1434 const req = send(options); 1435 let reqTimeout; 1436 1437 if (signal) { 1438 signal.addEventListener('abort', abortAndFinalize); 1439 } 1440 1441 function finalize() { 1442 req.abort(); 1443 if (signal) signal.removeEventListener('abort', abortAndFinalize); 1444 clearTimeout(reqTimeout); 1445 } 1446 1447 if (request.timeout) { 1448 req.once('socket', function (socket) { 1449 reqTimeout = setTimeout(function () { 1450 reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); 1451 finalize(); 1452 }, request.timeout); 1453 }); 1454 } 1455 1456 req.on('error', function (err) { 1457 reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); 1458 finalize(); 1459 }); 1460 1461 req.on('response', function (res) { 1462 clearTimeout(reqTimeout); 1463 1464 const headers = createHeadersLenient(res.headers); 1465 1466 // HTTP fetch step 5 1467 if (fetch.isRedirect(res.statusCode)) { 1468 // HTTP fetch step 5.2 1469 const location = headers.get('Location'); 1470 1471 // HTTP fetch step 5.3 1472 const locationURL = location === null ? null : resolve_url(request.url, location); 1473 1474 // HTTP fetch step 5.5 1475 switch (request.redirect) { 1476 case 'error': 1477 reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect')); 1478 finalize(); 1479 return; 1480 case 'manual': 1481 // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. 1482 if (locationURL !== null) { 1483 // handle corrupted header 1484 try { 1485 headers.set('Location', locationURL); 1486 } catch (err) { 1487 // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request 1488 reject(err); 1489 } 1490 } 1491 break; 1492 case 'follow': 1493 // HTTP-redirect fetch step 2 1494 if (locationURL === null) { 1495 break; 1496 } 1497 1498 // HTTP-redirect fetch step 5 1499 if (request.counter >= request.follow) { 1500 reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); 1501 finalize(); 1502 return; 1503 } 1504 1505 // HTTP-redirect fetch step 6 (counter increment) 1506 // Create a new Request object. 1507 const requestOpts = { 1508 headers: new Headers(request.headers), 1509 follow: request.follow, 1510 counter: request.counter + 1, 1511 agent: request.agent, 1512 compress: request.compress, 1513 method: request.method, 1514 body: request.body, 1515 signal: request.signal, 1516 timeout: request.timeout, 1517 size: request.size 1518 }; 1519 1520 // HTTP-redirect fetch step 9 1521 if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { 1522 reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); 1523 finalize(); 1524 return; 1525 } 1526 1527 // HTTP-redirect fetch step 11 1528 if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { 1529 requestOpts.method = 'GET'; 1530 requestOpts.body = undefined; 1531 requestOpts.headers.delete('content-length'); 1532 } 1533 1534 // HTTP-redirect fetch step 15 1535 resolve(fetch(new Request(locationURL, requestOpts))); 1536 finalize(); 1537 return; 1538 } 1539 } 1540 1541 // prepare response 1542 res.once('end', function () { 1543 if (signal) signal.removeEventListener('abort', abortAndFinalize); 1544 }); 1545 let body = res.pipe(new PassThrough$1()); 1546 1547 const response_options = { 1548 url: request.url, 1549 status: res.statusCode, 1550 statusText: res.statusMessage, 1551 headers: headers, 1552 size: request.size, 1553 timeout: request.timeout, 1554 counter: request.counter 1555 }; 1556 1557 // HTTP-network fetch step 12.1.1.3 1558 const codings = headers.get('Content-Encoding'); 1559 1560 // HTTP-network fetch step 12.1.1.4: handle content codings 1561 1562 // in following scenarios we ignore compression support 1563 // 1. compression support is disabled 1564 // 2. HEAD request 1565 // 3. no Content-Encoding header 1566 // 4. no content response (204) 1567 // 5. content not modified response (304) 1568 if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { 1569 response = new Response(body, response_options); 1570 resolve(response); 1571 return; 1572 } 1573 1574 // For Node v6+ 1575 // Be less strict when decoding compressed responses, since sometimes 1576 // servers send slightly invalid responses that are still accepted 1577 // by common browsers. 1578 // Always using Z_SYNC_FLUSH is what cURL does. 1579 const zlibOptions = { 1580 flush: zlib.Z_SYNC_FLUSH, 1581 finishFlush: zlib.Z_SYNC_FLUSH 1582 }; 1583 1584 // for gzip 1585 if (codings == 'gzip' || codings == 'x-gzip') { 1586 body = body.pipe(zlib.createGunzip(zlibOptions)); 1587 response = new Response(body, response_options); 1588 resolve(response); 1589 return; 1590 } 1591 1592 // for deflate 1593 if (codings == 'deflate' || codings == 'x-deflate') { 1594 // handle the infamous raw deflate response from old servers 1595 // a hack for old IIS and Apache servers 1596 const raw = res.pipe(new PassThrough$1()); 1597 raw.once('data', function (chunk) { 1598 // see http://stackoverflow.com/questions/37519828 1599 if ((chunk[0] & 0x0F) === 0x08) { 1600 body = body.pipe(zlib.createInflate()); 1601 } else { 1602 body = body.pipe(zlib.createInflateRaw()); 1603 } 1604 response = new Response(body, response_options); 1605 resolve(response); 1606 }); 1607 return; 1608 } 1609 1610 // for br 1611 if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { 1612 body = body.pipe(zlib.createBrotliDecompress()); 1613 response = new Response(body, response_options); 1614 resolve(response); 1615 return; 1616 } 1617 1618 // otherwise, use response as-is 1619 response = new Response(body, response_options); 1620 resolve(response); 1621 }); 1622 1623 writeToStream(req, request); 1624 }); 1625 } 1626 /** 1627 * Redirect code matching 1628 * 1629 * @param Number code Status code 1630 * @return Boolean 1631 */ 1632 fetch.isRedirect = function (code) { 1633 return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; 1634 }; 1635 1636 // expose Promise 1637 fetch.Promise = global.Promise; 1638 1639 export default fetch; 1640 export { Headers, Request, Response, FetchError };