index.es.js (40940B)
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 465 if (res) { 466 res = /charset=(.*)/i.exec(res.pop()); 467 } 468 } 469 470 // xml 471 if (!res && str) { 472 res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str); 473 } 474 475 // found charset 476 if (res) { 477 charset = res.pop(); 478 479 // prevent decode issues when sites use incorrect encoding 480 // ref: https://hsivonen.fi/encoding-menu/ 481 if (charset === 'gb2312' || charset === 'gbk') { 482 charset = 'gb18030'; 483 } 484 } 485 486 // turn raw buffers into a single utf-8 buffer 487 return convert(buffer, 'UTF-8', charset).toString(); 488 } 489 490 /** 491 * Detect a URLSearchParams object 492 * ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143 493 * 494 * @param Object obj Object to detect by type or brand 495 * @return String 496 */ 497 function isURLSearchParams(obj) { 498 // Duck-typing as a necessary condition. 499 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') { 500 return false; 501 } 502 503 // Brand-checking and more duck-typing as optional condition. 504 return obj.constructor.name === 'URLSearchParams' || Object.prototype.toString.call(obj) === '[object URLSearchParams]' || typeof obj.sort === 'function'; 505 } 506 507 /** 508 * Check if `obj` is a W3C `Blob` object (which `File` inherits from) 509 * @param {*} obj 510 * @return {boolean} 511 */ 512 function isBlob(obj) { 513 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]); 514 } 515 516 /** 517 * Clone body given Res/Req instance 518 * 519 * @param Mixed instance Response or Request instance 520 * @return Mixed 521 */ 522 function clone(instance) { 523 let p1, p2; 524 let body = instance.body; 525 526 // don't allow cloning a used body 527 if (instance.bodyUsed) { 528 throw new Error('cannot clone body after it is used'); 529 } 530 531 // check that body is a stream and not form-data object 532 // note: we can't clone the form-data object without having it as a dependency 533 if (body instanceof Stream && typeof body.getBoundary !== 'function') { 534 // tee instance body 535 p1 = new PassThrough(); 536 p2 = new PassThrough(); 537 body.pipe(p1); 538 body.pipe(p2); 539 // set instance body to teed body and return the other teed body 540 instance[INTERNALS].body = p1; 541 body = p2; 542 } 543 544 return body; 545 } 546 547 /** 548 * Performs the operation "extract a `Content-Type` value from |object|" as 549 * specified in the specification: 550 * https://fetch.spec.whatwg.org/#concept-bodyinit-extract 551 * 552 * This function assumes that instance.body is present. 553 * 554 * @param Mixed instance Any options.body input 555 */ 556 function extractContentType(body) { 557 if (body === null) { 558 // body is null 559 return null; 560 } else if (typeof body === 'string') { 561 // body is string 562 return 'text/plain;charset=UTF-8'; 563 } else if (isURLSearchParams(body)) { 564 // body is a URLSearchParams 565 return 'application/x-www-form-urlencoded;charset=UTF-8'; 566 } else if (isBlob(body)) { 567 // body is blob 568 return body.type || null; 569 } else if (Buffer.isBuffer(body)) { 570 // body is buffer 571 return null; 572 } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { 573 // body is ArrayBuffer 574 return null; 575 } else if (ArrayBuffer.isView(body)) { 576 // body is ArrayBufferView 577 return null; 578 } else if (typeof body.getBoundary === 'function') { 579 // detect form data input from form-data module 580 return `multipart/form-data;boundary=${body.getBoundary()}`; 581 } else if (body instanceof Stream) { 582 // body is stream 583 // can't really do much about this 584 return null; 585 } else { 586 // Body constructor defaults other things to string 587 return 'text/plain;charset=UTF-8'; 588 } 589 } 590 591 /** 592 * The Fetch Standard treats this as if "total bytes" is a property on the body. 593 * For us, we have to explicitly get it with a function. 594 * 595 * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes 596 * 597 * @param Body instance Instance of Body 598 * @return Number? Number of bytes, or null if not possible 599 */ 600 function getTotalBytes(instance) { 601 const body = instance.body; 602 603 604 if (body === null) { 605 // body is null 606 return 0; 607 } else if (isBlob(body)) { 608 return body.size; 609 } else if (Buffer.isBuffer(body)) { 610 // body is buffer 611 return body.length; 612 } else if (body && typeof body.getLengthSync === 'function') { 613 // detect form data input from form-data module 614 if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x 615 body.hasKnownLength && body.hasKnownLength()) { 616 // 2.x 617 return body.getLengthSync(); 618 } 619 return null; 620 } else { 621 // body is stream 622 return null; 623 } 624 } 625 626 /** 627 * Write a Body to a Node.js WritableStream (e.g. http.Request) object. 628 * 629 * @param Body instance Instance of Body 630 * @return Void 631 */ 632 function writeToStream(dest, instance) { 633 const body = instance.body; 634 635 636 if (body === null) { 637 // body is null 638 dest.end(); 639 } else if (isBlob(body)) { 640 body.stream().pipe(dest); 641 } else if (Buffer.isBuffer(body)) { 642 // body is buffer 643 dest.write(body); 644 dest.end(); 645 } else { 646 // body is stream 647 body.pipe(dest); 648 } 649 } 650 651 // expose Promise 652 Body.Promise = global.Promise; 653 654 /** 655 * headers.js 656 * 657 * Headers class offers convenient helpers 658 */ 659 660 const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/; 661 const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/; 662 663 function validateName(name) { 664 name = `${name}`; 665 if (invalidTokenRegex.test(name) || name === '') { 666 throw new TypeError(`${name} is not a legal HTTP header name`); 667 } 668 } 669 670 function validateValue(value) { 671 value = `${value}`; 672 if (invalidHeaderCharRegex.test(value)) { 673 throw new TypeError(`${value} is not a legal HTTP header value`); 674 } 675 } 676 677 /** 678 * Find the key in the map object given a header name. 679 * 680 * Returns undefined if not found. 681 * 682 * @param String name Header name 683 * @return String|Undefined 684 */ 685 function find(map, name) { 686 name = name.toLowerCase(); 687 for (const key in map) { 688 if (key.toLowerCase() === name) { 689 return key; 690 } 691 } 692 return undefined; 693 } 694 695 const MAP = Symbol('map'); 696 class Headers { 697 /** 698 * Headers class 699 * 700 * @param Object headers Response headers 701 * @return Void 702 */ 703 constructor() { 704 let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; 705 706 this[MAP] = Object.create(null); 707 708 if (init instanceof Headers) { 709 const rawHeaders = init.raw(); 710 const headerNames = Object.keys(rawHeaders); 711 712 for (const headerName of headerNames) { 713 for (const value of rawHeaders[headerName]) { 714 this.append(headerName, value); 715 } 716 } 717 718 return; 719 } 720 721 // We don't worry about converting prop to ByteString here as append() 722 // will handle it. 723 if (init == null) ; else if (typeof init === 'object') { 724 const method = init[Symbol.iterator]; 725 if (method != null) { 726 if (typeof method !== 'function') { 727 throw new TypeError('Header pairs must be iterable'); 728 } 729 730 // sequence<sequence<ByteString>> 731 // Note: per spec we have to first exhaust the lists then process them 732 const pairs = []; 733 for (const pair of init) { 734 if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { 735 throw new TypeError('Each header pair must be iterable'); 736 } 737 pairs.push(Array.from(pair)); 738 } 739 740 for (const pair of pairs) { 741 if (pair.length !== 2) { 742 throw new TypeError('Each header pair must be a name/value tuple'); 743 } 744 this.append(pair[0], pair[1]); 745 } 746 } else { 747 // record<ByteString, ByteString> 748 for (const key of Object.keys(init)) { 749 const value = init[key]; 750 this.append(key, value); 751 } 752 } 753 } else { 754 throw new TypeError('Provided initializer must be an object'); 755 } 756 } 757 758 /** 759 * Return combined header value given name 760 * 761 * @param String name Header name 762 * @return Mixed 763 */ 764 get(name) { 765 name = `${name}`; 766 validateName(name); 767 const key = find(this[MAP], name); 768 if (key === undefined) { 769 return null; 770 } 771 772 return this[MAP][key].join(', '); 773 } 774 775 /** 776 * Iterate over all headers 777 * 778 * @param Function callback Executed for each item with parameters (value, name, thisArg) 779 * @param Boolean thisArg `this` context for callback function 780 * @return Void 781 */ 782 forEach(callback) { 783 let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; 784 785 let pairs = getHeaders(this); 786 let i = 0; 787 while (i < pairs.length) { 788 var _pairs$i = pairs[i]; 789 const name = _pairs$i[0], 790 value = _pairs$i[1]; 791 792 callback.call(thisArg, value, name, this); 793 pairs = getHeaders(this); 794 i++; 795 } 796 } 797 798 /** 799 * Overwrite header values given name 800 * 801 * @param String name Header name 802 * @param String value Header value 803 * @return Void 804 */ 805 set(name, value) { 806 name = `${name}`; 807 value = `${value}`; 808 validateName(name); 809 validateValue(value); 810 const key = find(this[MAP], name); 811 this[MAP][key !== undefined ? key : name] = [value]; 812 } 813 814 /** 815 * Append a value onto existing header 816 * 817 * @param String name Header name 818 * @param String value Header value 819 * @return Void 820 */ 821 append(name, value) { 822 name = `${name}`; 823 value = `${value}`; 824 validateName(name); 825 validateValue(value); 826 const key = find(this[MAP], name); 827 if (key !== undefined) { 828 this[MAP][key].push(value); 829 } else { 830 this[MAP][name] = [value]; 831 } 832 } 833 834 /** 835 * Check for header name existence 836 * 837 * @param String name Header name 838 * @return Boolean 839 */ 840 has(name) { 841 name = `${name}`; 842 validateName(name); 843 return find(this[MAP], name) !== undefined; 844 } 845 846 /** 847 * Delete all header values given name 848 * 849 * @param String name Header name 850 * @return Void 851 */ 852 delete(name) { 853 name = `${name}`; 854 validateName(name); 855 const key = find(this[MAP], name); 856 if (key !== undefined) { 857 delete this[MAP][key]; 858 } 859 } 860 861 /** 862 * Return raw headers (non-spec api) 863 * 864 * @return Object 865 */ 866 raw() { 867 return this[MAP]; 868 } 869 870 /** 871 * Get an iterator on keys. 872 * 873 * @return Iterator 874 */ 875 keys() { 876 return createHeadersIterator(this, 'key'); 877 } 878 879 /** 880 * Get an iterator on values. 881 * 882 * @return Iterator 883 */ 884 values() { 885 return createHeadersIterator(this, 'value'); 886 } 887 888 /** 889 * Get an iterator on entries. 890 * 891 * This is the default iterator of the Headers object. 892 * 893 * @return Iterator 894 */ 895 [Symbol.iterator]() { 896 return createHeadersIterator(this, 'key+value'); 897 } 898 } 899 Headers.prototype.entries = Headers.prototype[Symbol.iterator]; 900 901 Object.defineProperty(Headers.prototype, Symbol.toStringTag, { 902 value: 'Headers', 903 writable: false, 904 enumerable: false, 905 configurable: true 906 }); 907 908 Object.defineProperties(Headers.prototype, { 909 get: { enumerable: true }, 910 forEach: { enumerable: true }, 911 set: { enumerable: true }, 912 append: { enumerable: true }, 913 has: { enumerable: true }, 914 delete: { enumerable: true }, 915 keys: { enumerable: true }, 916 values: { enumerable: true }, 917 entries: { enumerable: true } 918 }); 919 920 function getHeaders(headers) { 921 let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; 922 923 const keys = Object.keys(headers[MAP]).sort(); 924 return keys.map(kind === 'key' ? function (k) { 925 return k.toLowerCase(); 926 } : kind === 'value' ? function (k) { 927 return headers[MAP][k].join(', '); 928 } : function (k) { 929 return [k.toLowerCase(), headers[MAP][k].join(', ')]; 930 }); 931 } 932 933 const INTERNAL = Symbol('internal'); 934 935 function createHeadersIterator(target, kind) { 936 const iterator = Object.create(HeadersIteratorPrototype); 937 iterator[INTERNAL] = { 938 target, 939 kind, 940 index: 0 941 }; 942 return iterator; 943 } 944 945 const HeadersIteratorPrototype = Object.setPrototypeOf({ 946 next() { 947 // istanbul ignore if 948 if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { 949 throw new TypeError('Value of `this` is not a HeadersIterator'); 950 } 951 952 var _INTERNAL = this[INTERNAL]; 953 const target = _INTERNAL.target, 954 kind = _INTERNAL.kind, 955 index = _INTERNAL.index; 956 957 const values = getHeaders(target, kind); 958 const len = values.length; 959 if (index >= len) { 960 return { 961 value: undefined, 962 done: true 963 }; 964 } 965 966 this[INTERNAL].index = index + 1; 967 968 return { 969 value: values[index], 970 done: false 971 }; 972 } 973 }, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); 974 975 Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { 976 value: 'HeadersIterator', 977 writable: false, 978 enumerable: false, 979 configurable: true 980 }); 981 982 /** 983 * Export the Headers object in a form that Node.js can consume. 984 * 985 * @param Headers headers 986 * @return Object 987 */ 988 function exportNodeCompatibleHeaders(headers) { 989 const obj = Object.assign({ __proto__: null }, headers[MAP]); 990 991 // http.request() only supports string as Host header. This hack makes 992 // specifying custom Host header possible. 993 const hostHeaderKey = find(headers[MAP], 'Host'); 994 if (hostHeaderKey !== undefined) { 995 obj[hostHeaderKey] = obj[hostHeaderKey][0]; 996 } 997 998 return obj; 999 } 1000 1001 /** 1002 * Create a Headers object from an object of headers, ignoring those that do 1003 * not conform to HTTP grammar productions. 1004 * 1005 * @param Object obj Object of headers 1006 * @return Headers 1007 */ 1008 function createHeadersLenient(obj) { 1009 const headers = new Headers(); 1010 for (const name of Object.keys(obj)) { 1011 if (invalidTokenRegex.test(name)) { 1012 continue; 1013 } 1014 if (Array.isArray(obj[name])) { 1015 for (const val of obj[name]) { 1016 if (invalidHeaderCharRegex.test(val)) { 1017 continue; 1018 } 1019 if (headers[MAP][name] === undefined) { 1020 headers[MAP][name] = [val]; 1021 } else { 1022 headers[MAP][name].push(val); 1023 } 1024 } 1025 } else if (!invalidHeaderCharRegex.test(obj[name])) { 1026 headers[MAP][name] = [obj[name]]; 1027 } 1028 } 1029 return headers; 1030 } 1031 1032 const INTERNALS$1 = Symbol('Response internals'); 1033 1034 // fix an issue where "STATUS_CODES" aren't a named export for node <10 1035 const STATUS_CODES = http.STATUS_CODES; 1036 1037 /** 1038 * Response class 1039 * 1040 * @param Stream body Readable stream 1041 * @param Object opts Response options 1042 * @return Void 1043 */ 1044 class Response { 1045 constructor() { 1046 let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; 1047 let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1048 1049 Body.call(this, body, opts); 1050 1051 const status = opts.status || 200; 1052 const headers = new Headers(opts.headers); 1053 1054 if (body != null && !headers.has('Content-Type')) { 1055 const contentType = extractContentType(body); 1056 if (contentType) { 1057 headers.append('Content-Type', contentType); 1058 } 1059 } 1060 1061 this[INTERNALS$1] = { 1062 url: opts.url, 1063 status, 1064 statusText: opts.statusText || STATUS_CODES[status], 1065 headers, 1066 counter: opts.counter 1067 }; 1068 } 1069 1070 get url() { 1071 return this[INTERNALS$1].url || ''; 1072 } 1073 1074 get status() { 1075 return this[INTERNALS$1].status; 1076 } 1077 1078 /** 1079 * Convenience property representing if the request ended normally 1080 */ 1081 get ok() { 1082 return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; 1083 } 1084 1085 get redirected() { 1086 return this[INTERNALS$1].counter > 0; 1087 } 1088 1089 get statusText() { 1090 return this[INTERNALS$1].statusText; 1091 } 1092 1093 get headers() { 1094 return this[INTERNALS$1].headers; 1095 } 1096 1097 /** 1098 * Clone this response 1099 * 1100 * @return Response 1101 */ 1102 clone() { 1103 return new Response(clone(this), { 1104 url: this.url, 1105 status: this.status, 1106 statusText: this.statusText, 1107 headers: this.headers, 1108 ok: this.ok, 1109 redirected: this.redirected 1110 }); 1111 } 1112 } 1113 1114 Body.mixIn(Response.prototype); 1115 1116 Object.defineProperties(Response.prototype, { 1117 url: { enumerable: true }, 1118 status: { enumerable: true }, 1119 ok: { enumerable: true }, 1120 redirected: { enumerable: true }, 1121 statusText: { enumerable: true }, 1122 headers: { enumerable: true }, 1123 clone: { enumerable: true } 1124 }); 1125 1126 Object.defineProperty(Response.prototype, Symbol.toStringTag, { 1127 value: 'Response', 1128 writable: false, 1129 enumerable: false, 1130 configurable: true 1131 }); 1132 1133 const INTERNALS$2 = Symbol('Request internals'); 1134 1135 // fix an issue where "format", "parse" aren't a named export for node <10 1136 const parse_url = Url.parse; 1137 const format_url = Url.format; 1138 1139 const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; 1140 1141 /** 1142 * Check if a value is an instance of Request. 1143 * 1144 * @param Mixed input 1145 * @return Boolean 1146 */ 1147 function isRequest(input) { 1148 return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; 1149 } 1150 1151 function isAbortSignal(signal) { 1152 const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal); 1153 return !!(proto && proto.constructor.name === 'AbortSignal'); 1154 } 1155 1156 /** 1157 * Request class 1158 * 1159 * @param Mixed input Url or Request instance 1160 * @param Object init Custom options 1161 * @return Void 1162 */ 1163 class Request { 1164 constructor(input) { 1165 let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1166 1167 let parsedURL; 1168 1169 // normalize input 1170 if (!isRequest(input)) { 1171 if (input && input.href) { 1172 // in order to support Node.js' Url objects; though WHATWG's URL objects 1173 // will fall into this branch also (since their `toString()` will return 1174 // `href` property anyway) 1175 parsedURL = parse_url(input.href); 1176 } else { 1177 // coerce input to a string before attempting to parse 1178 parsedURL = parse_url(`${input}`); 1179 } 1180 input = {}; 1181 } else { 1182 parsedURL = parse_url(input.url); 1183 } 1184 1185 let method = init.method || input.method || 'GET'; 1186 method = method.toUpperCase(); 1187 1188 if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { 1189 throw new TypeError('Request with GET/HEAD method cannot have body'); 1190 } 1191 1192 let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; 1193 1194 Body.call(this, inputBody, { 1195 timeout: init.timeout || input.timeout || 0, 1196 size: init.size || input.size || 0 1197 }); 1198 1199 const headers = new Headers(init.headers || input.headers || {}); 1200 1201 if (inputBody != null && !headers.has('Content-Type')) { 1202 const contentType = extractContentType(inputBody); 1203 if (contentType) { 1204 headers.append('Content-Type', contentType); 1205 } 1206 } 1207 1208 let signal = isRequest(input) ? input.signal : null; 1209 if ('signal' in init) signal = init.signal; 1210 1211 if (signal != null && !isAbortSignal(signal)) { 1212 throw new TypeError('Expected signal to be an instanceof AbortSignal'); 1213 } 1214 1215 this[INTERNALS$2] = { 1216 method, 1217 redirect: init.redirect || input.redirect || 'follow', 1218 headers, 1219 parsedURL, 1220 signal 1221 }; 1222 1223 // node-fetch-only options 1224 this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; 1225 this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; 1226 this.counter = init.counter || input.counter || 0; 1227 this.agent = init.agent || input.agent; 1228 } 1229 1230 get method() { 1231 return this[INTERNALS$2].method; 1232 } 1233 1234 get url() { 1235 return format_url(this[INTERNALS$2].parsedURL); 1236 } 1237 1238 get headers() { 1239 return this[INTERNALS$2].headers; 1240 } 1241 1242 get redirect() { 1243 return this[INTERNALS$2].redirect; 1244 } 1245 1246 get signal() { 1247 return this[INTERNALS$2].signal; 1248 } 1249 1250 /** 1251 * Clone this request 1252 * 1253 * @return Request 1254 */ 1255 clone() { 1256 return new Request(this); 1257 } 1258 } 1259 1260 Body.mixIn(Request.prototype); 1261 1262 Object.defineProperty(Request.prototype, Symbol.toStringTag, { 1263 value: 'Request', 1264 writable: false, 1265 enumerable: false, 1266 configurable: true 1267 }); 1268 1269 Object.defineProperties(Request.prototype, { 1270 method: { enumerable: true }, 1271 url: { enumerable: true }, 1272 headers: { enumerable: true }, 1273 redirect: { enumerable: true }, 1274 clone: { enumerable: true }, 1275 signal: { enumerable: true } 1276 }); 1277 1278 /** 1279 * Convert a Request to Node.js http request options. 1280 * 1281 * @param Request A Request instance 1282 * @return Object The options object to be passed to http.request 1283 */ 1284 function getNodeRequestOptions(request) { 1285 const parsedURL = request[INTERNALS$2].parsedURL; 1286 const headers = new Headers(request[INTERNALS$2].headers); 1287 1288 // fetch step 1.3 1289 if (!headers.has('Accept')) { 1290 headers.set('Accept', '*/*'); 1291 } 1292 1293 // Basic fetch 1294 if (!parsedURL.protocol || !parsedURL.hostname) { 1295 throw new TypeError('Only absolute URLs are supported'); 1296 } 1297 1298 if (!/^https?:$/.test(parsedURL.protocol)) { 1299 throw new TypeError('Only HTTP(S) protocols are supported'); 1300 } 1301 1302 if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { 1303 throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); 1304 } 1305 1306 // HTTP-network-or-cache fetch steps 2.4-2.7 1307 let contentLengthValue = null; 1308 if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { 1309 contentLengthValue = '0'; 1310 } 1311 if (request.body != null) { 1312 const totalBytes = getTotalBytes(request); 1313 if (typeof totalBytes === 'number') { 1314 contentLengthValue = String(totalBytes); 1315 } 1316 } 1317 if (contentLengthValue) { 1318 headers.set('Content-Length', contentLengthValue); 1319 } 1320 1321 // HTTP-network-or-cache fetch step 2.11 1322 if (!headers.has('User-Agent')) { 1323 headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); 1324 } 1325 1326 // HTTP-network-or-cache fetch step 2.15 1327 if (request.compress && !headers.has('Accept-Encoding')) { 1328 headers.set('Accept-Encoding', 'gzip,deflate'); 1329 } 1330 1331 let agent = request.agent; 1332 if (typeof agent === 'function') { 1333 agent = agent(parsedURL); 1334 } 1335 1336 if (!headers.has('Connection') && !agent) { 1337 headers.set('Connection', 'close'); 1338 } 1339 1340 // HTTP-network fetch step 4.2 1341 // chunked encoding is handled by Node.js 1342 1343 return Object.assign({}, parsedURL, { 1344 method: request.method, 1345 headers: exportNodeCompatibleHeaders(headers), 1346 agent 1347 }); 1348 } 1349 1350 /** 1351 * abort-error.js 1352 * 1353 * AbortError interface for cancelled requests 1354 */ 1355 1356 /** 1357 * Create AbortError instance 1358 * 1359 * @param String message Error message for human 1360 * @return AbortError 1361 */ 1362 function AbortError(message) { 1363 Error.call(this, message); 1364 1365 this.type = 'aborted'; 1366 this.message = message; 1367 1368 // hide custom error implementation details from end-users 1369 Error.captureStackTrace(this, this.constructor); 1370 } 1371 1372 AbortError.prototype = Object.create(Error.prototype); 1373 AbortError.prototype.constructor = AbortError; 1374 AbortError.prototype.name = 'AbortError'; 1375 1376 // fix an issue where "PassThrough", "resolve" aren't a named export for node <10 1377 const PassThrough$1 = Stream.PassThrough; 1378 const resolve_url = Url.resolve; 1379 1380 /** 1381 * Fetch function 1382 * 1383 * @param Mixed url Absolute url or Request instance 1384 * @param Object opts Fetch options 1385 * @return Promise 1386 */ 1387 function fetch(url, opts) { 1388 1389 // allow custom promise 1390 if (!fetch.Promise) { 1391 throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); 1392 } 1393 1394 Body.Promise = fetch.Promise; 1395 1396 // wrap http.request into fetch 1397 return new fetch.Promise(function (resolve, reject) { 1398 // build request object 1399 const request = new Request(url, opts); 1400 const options = getNodeRequestOptions(request); 1401 1402 const send = (options.protocol === 'https:' ? https : http).request; 1403 const signal = request.signal; 1404 1405 let response = null; 1406 1407 const abort = function abort() { 1408 let error = new AbortError('The user aborted a request.'); 1409 reject(error); 1410 if (request.body && request.body instanceof Stream.Readable) { 1411 request.body.destroy(error); 1412 } 1413 if (!response || !response.body) return; 1414 response.body.emit('error', error); 1415 }; 1416 1417 if (signal && signal.aborted) { 1418 abort(); 1419 return; 1420 } 1421 1422 const abortAndFinalize = function abortAndFinalize() { 1423 abort(); 1424 finalize(); 1425 }; 1426 1427 // send request 1428 const req = send(options); 1429 let reqTimeout; 1430 1431 if (signal) { 1432 signal.addEventListener('abort', abortAndFinalize); 1433 } 1434 1435 function finalize() { 1436 req.abort(); 1437 if (signal) signal.removeEventListener('abort', abortAndFinalize); 1438 clearTimeout(reqTimeout); 1439 } 1440 1441 if (request.timeout) { 1442 req.once('socket', function (socket) { 1443 reqTimeout = setTimeout(function () { 1444 reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); 1445 finalize(); 1446 }, request.timeout); 1447 }); 1448 } 1449 1450 req.on('error', function (err) { 1451 reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); 1452 finalize(); 1453 }); 1454 1455 req.on('response', function (res) { 1456 clearTimeout(reqTimeout); 1457 1458 const headers = createHeadersLenient(res.headers); 1459 1460 // HTTP fetch step 5 1461 if (fetch.isRedirect(res.statusCode)) { 1462 // HTTP fetch step 5.2 1463 const location = headers.get('Location'); 1464 1465 // HTTP fetch step 5.3 1466 const locationURL = location === null ? null : resolve_url(request.url, location); 1467 1468 // HTTP fetch step 5.5 1469 switch (request.redirect) { 1470 case 'error': 1471 reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect')); 1472 finalize(); 1473 return; 1474 case 'manual': 1475 // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. 1476 if (locationURL !== null) { 1477 // handle corrupted header 1478 try { 1479 headers.set('Location', locationURL); 1480 } catch (err) { 1481 // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request 1482 reject(err); 1483 } 1484 } 1485 break; 1486 case 'follow': 1487 // HTTP-redirect fetch step 2 1488 if (locationURL === null) { 1489 break; 1490 } 1491 1492 // HTTP-redirect fetch step 5 1493 if (request.counter >= request.follow) { 1494 reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); 1495 finalize(); 1496 return; 1497 } 1498 1499 // HTTP-redirect fetch step 6 (counter increment) 1500 // Create a new Request object. 1501 const requestOpts = { 1502 headers: new Headers(request.headers), 1503 follow: request.follow, 1504 counter: request.counter + 1, 1505 agent: request.agent, 1506 compress: request.compress, 1507 method: request.method, 1508 body: request.body, 1509 signal: request.signal, 1510 timeout: request.timeout 1511 }; 1512 1513 // HTTP-redirect fetch step 9 1514 if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { 1515 reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); 1516 finalize(); 1517 return; 1518 } 1519 1520 // HTTP-redirect fetch step 11 1521 if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { 1522 requestOpts.method = 'GET'; 1523 requestOpts.body = undefined; 1524 requestOpts.headers.delete('content-length'); 1525 } 1526 1527 // HTTP-redirect fetch step 15 1528 resolve(fetch(new Request(locationURL, requestOpts))); 1529 finalize(); 1530 return; 1531 } 1532 } 1533 1534 // prepare response 1535 res.once('end', function () { 1536 if (signal) signal.removeEventListener('abort', abortAndFinalize); 1537 }); 1538 let body = res.pipe(new PassThrough$1()); 1539 1540 const response_options = { 1541 url: request.url, 1542 status: res.statusCode, 1543 statusText: res.statusMessage, 1544 headers: headers, 1545 size: request.size, 1546 timeout: request.timeout, 1547 counter: request.counter 1548 }; 1549 1550 // HTTP-network fetch step 12.1.1.3 1551 const codings = headers.get('Content-Encoding'); 1552 1553 // HTTP-network fetch step 12.1.1.4: handle content codings 1554 1555 // in following scenarios we ignore compression support 1556 // 1. compression support is disabled 1557 // 2. HEAD request 1558 // 3. no Content-Encoding header 1559 // 4. no content response (204) 1560 // 5. content not modified response (304) 1561 if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { 1562 response = new Response(body, response_options); 1563 resolve(response); 1564 return; 1565 } 1566 1567 // For Node v6+ 1568 // Be less strict when decoding compressed responses, since sometimes 1569 // servers send slightly invalid responses that are still accepted 1570 // by common browsers. 1571 // Always using Z_SYNC_FLUSH is what cURL does. 1572 const zlibOptions = { 1573 flush: zlib.Z_SYNC_FLUSH, 1574 finishFlush: zlib.Z_SYNC_FLUSH 1575 }; 1576 1577 // for gzip 1578 if (codings == 'gzip' || codings == 'x-gzip') { 1579 body = body.pipe(zlib.createGunzip(zlibOptions)); 1580 response = new Response(body, response_options); 1581 resolve(response); 1582 return; 1583 } 1584 1585 // for deflate 1586 if (codings == 'deflate' || codings == 'x-deflate') { 1587 // handle the infamous raw deflate response from old servers 1588 // a hack for old IIS and Apache servers 1589 const raw = res.pipe(new PassThrough$1()); 1590 raw.once('data', function (chunk) { 1591 // see http://stackoverflow.com/questions/37519828 1592 if ((chunk[0] & 0x0F) === 0x08) { 1593 body = body.pipe(zlib.createInflate()); 1594 } else { 1595 body = body.pipe(zlib.createInflateRaw()); 1596 } 1597 response = new Response(body, response_options); 1598 resolve(response); 1599 }); 1600 return; 1601 } 1602 1603 // for br 1604 if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { 1605 body = body.pipe(zlib.createBrotliDecompress()); 1606 response = new Response(body, response_options); 1607 resolve(response); 1608 return; 1609 } 1610 1611 // otherwise, use response as-is 1612 response = new Response(body, response_options); 1613 resolve(response); 1614 }); 1615 1616 writeToStream(req, request); 1617 }); 1618 } 1619 /** 1620 * Redirect code matching 1621 * 1622 * @param Number code Status code 1623 * @return Boolean 1624 */ 1625 fetch.isRedirect = function (code) { 1626 return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; 1627 }; 1628 1629 // expose Promise 1630 fetch.Promise = global.Promise; 1631 1632 export default fetch; 1633 export { Headers, Request, Response, FetchError };