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