source: node_modules/node-fetch-commonjs/index.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 71.3 KB
Line 
1/* eslint-disable */
2exports = module.exports = fetch;
3'use strict';
4
5Object.defineProperty(exports, '__esModule', { value: true });
6
7var http = require('http');
8var https = require('https');
9var zlib = require('zlib');
10var Stream = require('stream');
11var buffer = require('buffer');
12var util = require('util');
13var url = require('url');
14var net = require('net');
15var fs = require('fs');
16var path = require('path');
17var DOMException = require('node-domexception');
18
19/**
20 * Returns a `Buffer` instance from the given data URI `uri`.
21 *
22 * @param {String} uri Data URI to turn into a Buffer instance
23 * @returns {Buffer} Buffer instance from Data URI
24 * @api public
25 */
26function dataUriToBuffer(uri) {
27 if (!/^data:/i.test(uri)) {
28 throw new TypeError('`uri` does not appear to be a Data URI (must begin with "data:")');
29 }
30 // strip newlines
31 uri = uri.replace(/\r?\n/g, '');
32 // split the URI up into the "metadata" and the "data" portions
33 const firstComma = uri.indexOf(',');
34 if (firstComma === -1 || firstComma <= 4) {
35 throw new TypeError('malformed data: URI');
36 }
37 // remove the "data:" scheme and parse the metadata
38 const meta = uri.substring(5, firstComma).split(';');
39 let charset = '';
40 let base64 = false;
41 const type = meta[0] || 'text/plain';
42 let typeFull = type;
43 for (let i = 1; i < meta.length; i++) {
44 if (meta[i] === 'base64') {
45 base64 = true;
46 }
47 else if (meta[i]) {
48 typeFull += `;${meta[i]}`;
49 if (meta[i].indexOf('charset=') === 0) {
50 charset = meta[i].substring(8);
51 }
52 }
53 }
54 // defaults to US-ASCII only if type is not provided
55 if (!meta[0] && !charset.length) {
56 typeFull += ';charset=US-ASCII';
57 charset = 'US-ASCII';
58 }
59 // get the encoded data portion and decode URI-encoded chars
60 const encoding = base64 ? 'base64' : 'ascii';
61 const data = unescape(uri.substring(firstComma + 1));
62 const buffer = Buffer.from(data, encoding);
63 // set `.type` and `.typeFull` properties to MIME type
64 buffer.type = type;
65 buffer.typeFull = typeFull;
66 // set the `.charset` property
67 buffer.charset = charset;
68 return buffer;
69}
70
71/* c8 ignore start */
72// 64 KiB (same size chrome slice theirs blob into Uint8array's)
73const POOL_SIZE$1 = 65536;
74
75if (!globalThis.ReadableStream) {
76 // `node:stream/web` got introduced in v16.5.0 as experimental
77 // and it's preferred over the polyfilled version. So we also
78 // suppress the warning that gets emitted by NodeJS for using it.
79 try {
80 const process = require('node:process');
81 const { emitWarning } = process;
82 try {
83 process.emitWarning = () => {};
84 Object.assign(globalThis, require('node:stream/web'));
85 process.emitWarning = emitWarning;
86 } catch (error) {
87 process.emitWarning = emitWarning;
88 throw error
89 }
90 } catch (error) {
91 // fallback to polyfill implementation
92 Object.assign(globalThis, require('web-streams-polyfill/dist/ponyfill.es2018.js'));
93 }
94}
95
96try {
97 // Don't use node: prefix for this, require+node: is not supported until node v14.14
98 // Only `import()` can use prefix in 12.20 and later
99 const { Blob } = require('buffer');
100 if (Blob && !Blob.prototype.stream) {
101 Blob.prototype.stream = function name (params) {
102 let position = 0;
103 const blob = this;
104
105 return new ReadableStream({
106 type: 'bytes',
107 async pull (ctrl) {
108 const chunk = blob.slice(position, Math.min(blob.size, position + POOL_SIZE$1));
109 const buffer = await chunk.arrayBuffer();
110 position += buffer.byteLength;
111 ctrl.enqueue(new Uint8Array(buffer));
112
113 if (position === blob.size) {
114 ctrl.close();
115 }
116 }
117 })
118 };
119 }
120} catch (error) {}
121/* c8 ignore end */
122
123/*! fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
124
125
126// 64 KiB (same size chrome slice theirs blob into Uint8array's)
127const POOL_SIZE = 65536;
128
129/** @param {(Blob | Uint8Array)[]} parts */
130async function * toIterator (parts, clone = true) {
131 for (const part of parts) {
132 if ('stream' in part) {
133 yield * (/** @type {AsyncIterableIterator<Uint8Array>} */ (part.stream()));
134 } else if (ArrayBuffer.isView(part)) {
135 if (clone) {
136 let position = part.byteOffset;
137 const end = part.byteOffset + part.byteLength;
138 while (position !== end) {
139 const size = Math.min(end - position, POOL_SIZE);
140 const chunk = part.buffer.slice(position, position + size);
141 position += chunk.byteLength;
142 yield new Uint8Array(chunk);
143 }
144 } else {
145 yield part;
146 }
147 /* c8 ignore next 10 */
148 } else {
149 // For blobs that have arrayBuffer but no stream method (nodes buffer.Blob)
150 let position = 0, b = (/** @type {Blob} */ (part));
151 while (position !== b.size) {
152 const chunk = b.slice(position, Math.min(b.size, position + POOL_SIZE));
153 const buffer = await chunk.arrayBuffer();
154 position += buffer.byteLength;
155 yield new Uint8Array(buffer);
156 }
157 }
158 }
159}
160
161const _Blob = class Blob {
162 /** @type {Array.<(Blob|Uint8Array)>} */
163 #parts = []
164 #type = ''
165 #size = 0
166 #endings = 'transparent'
167
168 /**
169 * The Blob() constructor returns a new Blob object. The content
170 * of the blob consists of the concatenation of the values given
171 * in the parameter array.
172 *
173 * @param {*} blobParts
174 * @param {{ type?: string, endings?: string }} [options]
175 */
176 constructor (blobParts = [], options = {}) {
177 if (typeof blobParts !== 'object' || blobParts === null) {
178 throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.')
179 }
180
181 if (typeof blobParts[Symbol.iterator] !== 'function') {
182 throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.')
183 }
184
185 if (typeof options !== 'object' && typeof options !== 'function') {
186 throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.')
187 }
188
189 if (options === null) options = {};
190
191 const encoder = new TextEncoder();
192 for (const element of blobParts) {
193 let part;
194 if (ArrayBuffer.isView(element)) {
195 part = new Uint8Array(element.buffer.slice(element.byteOffset, element.byteOffset + element.byteLength));
196 } else if (element instanceof ArrayBuffer) {
197 part = new Uint8Array(element.slice(0));
198 } else if (element instanceof Blob) {
199 part = element;
200 } else {
201 part = encoder.encode(`${element}`);
202 }
203
204 this.#size += ArrayBuffer.isView(part) ? part.byteLength : part.size;
205 this.#parts.push(part);
206 }
207
208 this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`;
209 const type = options.type === undefined ? '' : String(options.type);
210 this.#type = /^[\x20-\x7E]*$/.test(type) ? type : '';
211 }
212
213 /**
214 * The Blob interface's size property returns the
215 * size of the Blob in bytes.
216 */
217 get size () {
218 return this.#size
219 }
220
221 /**
222 * The type property of a Blob object returns the MIME type of the file.
223 */
224 get type () {
225 return this.#type
226 }
227
228 /**
229 * The text() method in the Blob interface returns a Promise
230 * that resolves with a string containing the contents of
231 * the blob, interpreted as UTF-8.
232 *
233 * @return {Promise<string>}
234 */
235 async text () {
236 // More optimized than using this.arrayBuffer()
237 // that requires twice as much ram
238 const decoder = new TextDecoder();
239 let str = '';
240 for await (const part of toIterator(this.#parts, false)) {
241 str += decoder.decode(part, { stream: true });
242 }
243 // Remaining
244 str += decoder.decode();
245 return str
246 }
247
248 /**
249 * The arrayBuffer() method in the Blob interface returns a
250 * Promise that resolves with the contents of the blob as
251 * binary data contained in an ArrayBuffer.
252 *
253 * @return {Promise<ArrayBuffer>}
254 */
255 async arrayBuffer () {
256 // Easier way... Just a unnecessary overhead
257 // const view = new Uint8Array(this.size);
258 // await this.stream().getReader({mode: 'byob'}).read(view);
259 // return view.buffer;
260
261 const data = new Uint8Array(this.size);
262 let offset = 0;
263 for await (const chunk of toIterator(this.#parts, false)) {
264 data.set(chunk, offset);
265 offset += chunk.length;
266 }
267
268 return data.buffer
269 }
270
271 stream () {
272 const it = toIterator(this.#parts, true);
273
274 return new globalThis.ReadableStream({
275 // @ts-ignore
276 type: 'bytes',
277 async pull (ctrl) {
278 const chunk = await it.next();
279 chunk.done ? ctrl.close() : ctrl.enqueue(chunk.value);
280 },
281
282 async cancel () {
283 await it.return();
284 }
285 })
286 }
287
288 /**
289 * The Blob interface's slice() method creates and returns a
290 * new Blob object which contains data from a subset of the
291 * blob on which it's called.
292 *
293 * @param {number} [start]
294 * @param {number} [end]
295 * @param {string} [type]
296 */
297 slice (start = 0, end = this.size, type = '') {
298 const { size } = this;
299
300 let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size);
301 let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size);
302
303 const span = Math.max(relativeEnd - relativeStart, 0);
304 const parts = this.#parts;
305 const blobParts = [];
306 let added = 0;
307
308 for (const part of parts) {
309 // don't add the overflow to new blobParts
310 if (added >= span) {
311 break
312 }
313
314 const size = ArrayBuffer.isView(part) ? part.byteLength : part.size;
315 if (relativeStart && size <= relativeStart) {
316 // Skip the beginning and change the relative
317 // start & end position as we skip the unwanted parts
318 relativeStart -= size;
319 relativeEnd -= size;
320 } else {
321 let chunk;
322 if (ArrayBuffer.isView(part)) {
323 chunk = part.subarray(relativeStart, Math.min(size, relativeEnd));
324 added += chunk.byteLength;
325 } else {
326 chunk = part.slice(relativeStart, Math.min(size, relativeEnd));
327 added += chunk.size;
328 }
329 relativeEnd -= size;
330 blobParts.push(chunk);
331 relativeStart = 0; // All next sequential parts should start at 0
332 }
333 }
334
335 const blob = new Blob([], { type: String(type).toLowerCase() });
336 blob.#size = span;
337 blob.#parts = blobParts;
338
339 return blob
340 }
341
342 get [Symbol.toStringTag] () {
343 return 'Blob'
344 }
345
346 static [Symbol.hasInstance] (object) {
347 return (
348 object &&
349 typeof object === 'object' &&
350 typeof object.constructor === 'function' &&
351 (
352 typeof object.stream === 'function' ||
353 typeof object.arrayBuffer === 'function'
354 ) &&
355 /^(Blob|File)$/.test(object[Symbol.toStringTag])
356 )
357 }
358};
359
360Object.defineProperties(_Blob.prototype, {
361 size: { enumerable: true },
362 type: { enumerable: true },
363 slice: { enumerable: true }
364});
365
366/** @type {typeof globalThis.Blob} */
367const Blob = _Blob;
368
369const _File = class File extends Blob {
370 #lastModified = 0
371 #name = ''
372
373 /**
374 * @param {*[]} fileBits
375 * @param {string} fileName
376 * @param {{lastModified?: number, type?: string}} options
377 */// @ts-ignore
378 constructor (fileBits, fileName, options = {}) {
379 if (arguments.length < 2) {
380 throw new TypeError(`Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`)
381 }
382 super(fileBits, options);
383
384 if (options === null) options = {};
385
386 // Simulate WebIDL type casting for NaN value in lastModified option.
387 const lastModified = options.lastModified === undefined ? Date.now() : Number(options.lastModified);
388 if (!Number.isNaN(lastModified)) {
389 this.#lastModified = lastModified;
390 }
391
392 this.#name = String(fileName);
393 }
394
395 get name () {
396 return this.#name
397 }
398
399 get lastModified () {
400 return this.#lastModified
401 }
402
403 get [Symbol.toStringTag] () {
404 return 'File'
405 }
406
407 static [Symbol.hasInstance] (object) {
408 return !!object && object instanceof Blob &&
409 /^(File)$/.test(object[Symbol.toStringTag])
410 }
411};
412
413/** @type {typeof globalThis.File} */// @ts-ignore
414const File = _File;
415
416/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
417
418
419var {toStringTag:t,iterator:i,hasInstance:h}=Symbol,
420r=Math.random,
421m='append,set,get,getAll,delete,keys,values,entries,forEach,constructor'.split(','),
422f=(a,b,c)=>(a+='',/^(Blob|File)$/.test(b && b[t])?[(c=c!==void 0?c+'':b[t]=='File'?b.name:'blob',a),b.name!==c||b[t]=='blob'?new File([b],c,b):b]:[a,b+'']),
423e=(c,f)=>(f?c:c.replace(/\r?\n|\r/g,'\r\n')).replace(/\n/g,'%0A').replace(/\r/g,'%0D').replace(/"/g,'%22'),
424x=(n, a, e)=>{if(a.length<e){throw new TypeError(`Failed to execute '${n}' on 'FormData': ${e} arguments required, but only ${a.length} present.`)}};
425
426/** @type {typeof globalThis.FormData} */
427const FormData = class FormData {
428#d=[];
429constructor(...a){if(a.length)throw new TypeError(`Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'.`)}
430get [t]() {return 'FormData'}
431[i](){return this.entries()}
432static [h](o) {return o&&typeof o==='object'&&o[t]==='FormData'&&!m.some(m=>typeof o[m]!='function')}
433append(...a){x('append',arguments,2);this.#d.push(f(...a));}
434delete(a){x('delete',arguments,1);a+='';this.#d=this.#d.filter(([b])=>b!==a);}
435get(a){x('get',arguments,1);a+='';for(var b=this.#d,l=b.length,c=0;c<l;c++)if(b[c][0]===a)return b[c][1];return null}
436getAll(a,b){x('getAll',arguments,1);b=[];a+='';this.#d.forEach(c=>c[0]===a&&b.push(c[1]));return b}
437has(a){x('has',arguments,1);a+='';return this.#d.some(b=>b[0]===a)}
438forEach(a,b){x('forEach',arguments,1);for(var [c,d]of this)a.call(b,d,c,this);}
439set(...a){x('set',arguments,2);var b=[],c=!0;a=f(...a);this.#d.forEach(d=>{d[0]===a[0]?c&&(c=!b.push(a)):b.push(d);});c&&b.push(a);this.#d=b;}
440*entries(){yield*this.#d;}
441*keys(){for(var[a]of this)yield a;}
442*values(){for(var[,a]of this)yield a;}};
443
444/** @param {FormData} F */
445function formDataToBlob (F,B=Blob){
446var b=`${r()}${r()}`.replace(/\./g, '').slice(-28).padStart(32, '-'),c=[],p=`--${b}\r\nContent-Disposition: form-data; name="`;
447F.forEach((v,n)=>typeof v=='string'
448?c.push(p+e(n)+`"\r\n\r\n${v.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`)
449:c.push(p+e(n)+`"; filename="${e(v.name, 1)}"\r\nContent-Type: ${v.type||"application/octet-stream"}\r\n\r\n`, v, '\r\n'));
450c.push(`--${b}--`);
451return new B(c,{type:"multipart/form-data; boundary="+b})}
452
453class FetchBaseError extends Error {
454 constructor(message, type) {
455 super(message);
456 // Hide custom error implementation details from end-users
457 Error.captureStackTrace(this, this.constructor);
458
459 this.type = type;
460 }
461
462 get name() {
463 return this.constructor.name;
464 }
465
466 get [Symbol.toStringTag]() {
467 return this.constructor.name;
468 }
469}
470
471/**
472 * @typedef {{ address?: string, code: string, dest?: string, errno: number, info?: object, message: string, path?: string, port?: number, syscall: string}} SystemError
473*/
474
475/**
476 * FetchError interface for operational errors
477 */
478class FetchError extends FetchBaseError {
479 /**
480 * @param {string} message - Error message for human
481 * @param {string} [type] - Error type for machine
482 * @param {SystemError} [systemError] - For Node.js system error
483 */
484 constructor(message, type, systemError) {
485 super(message, type);
486 // When err.type is `system`, err.erroredSysCall contains system error and err.code contains system error code
487 if (systemError) {
488 // eslint-disable-next-line no-multi-assign
489 this.code = this.errno = systemError.code;
490 this.erroredSysCall = systemError.syscall;
491 }
492 }
493}
494
495/**
496 * Is.js
497 *
498 * Object type checks.
499 */
500
501const NAME = Symbol.toStringTag;
502
503/**
504 * Check if `obj` is a URLSearchParams object
505 * ref: https://github.com/node-fetch/node-fetch/issues/296#issuecomment-307598143
506 * @param {*} object - Object to check for
507 * @return {boolean}
508 */
509const isURLSearchParameters = object => {
510 return (
511 typeof object === 'object' &&
512 typeof object.append === 'function' &&
513 typeof object.delete === 'function' &&
514 typeof object.get === 'function' &&
515 typeof object.getAll === 'function' &&
516 typeof object.has === 'function' &&
517 typeof object.set === 'function' &&
518 typeof object.sort === 'function' &&
519 object[NAME] === 'URLSearchParams'
520 );
521};
522
523/**
524 * Check if `object` is a W3C `Blob` object (which `File` inherits from)
525 * @param {*} object - Object to check for
526 * @return {boolean}
527 */
528const isBlob = object => {
529 return (
530 object &&
531 typeof object === 'object' &&
532 typeof object.arrayBuffer === 'function' &&
533 typeof object.type === 'string' &&
534 typeof object.stream === 'function' &&
535 typeof object.constructor === 'function' &&
536 /^(Blob|File)$/.test(object[NAME])
537 );
538};
539
540/**
541 * Check if `obj` is an instance of AbortSignal.
542 * @param {*} object - Object to check for
543 * @return {boolean}
544 */
545const isAbortSignal = object => {
546 return (
547 typeof object === 'object' && (
548 object[NAME] === 'AbortSignal' ||
549 object[NAME] === 'EventTarget'
550 )
551 );
552};
553
554/**
555 * isDomainOrSubdomain reports whether sub is a subdomain (or exact match) of
556 * the parent domain.
557 *
558 * Both domains must already be in canonical form.
559 * @param {string|URL} original
560 * @param {string|URL} destination
561 */
562const isDomainOrSubdomain = (destination, original) => {
563 const orig = new URL(original).hostname;
564 const dest = new URL(destination).hostname;
565
566 return orig === dest || orig.endsWith(`.${dest}`);
567};
568
569/**
570 * isSameProtocol reports whether the two provided URLs use the same protocol.
571 *
572 * Both domains must already be in canonical form.
573 * @param {string|URL} original
574 * @param {string|URL} destination
575 */
576const isSameProtocol = (destination, original) => {
577 const orig = new URL(original).protocol;
578 const dest = new URL(destination).protocol;
579
580 return orig === dest;
581};
582
583const pipeline = util.promisify(Stream.pipeline);
584const INTERNALS$2 = Symbol('Body internals');
585
586/**
587 * Body mixin
588 *
589 * Ref: https://fetch.spec.whatwg.org/#body
590 *
591 * @param Stream body Readable stream
592 * @param Object opts Response options
593 * @return Void
594 */
595class Body {
596 constructor(body, {
597 size = 0
598 } = {}) {
599 let boundary = null;
600
601 if (body === null) {
602 // Body is undefined or null
603 body = null;
604 } else if (isURLSearchParameters(body)) {
605 // Body is a URLSearchParams
606 body = buffer.Buffer.from(body.toString());
607 } else if (isBlob(body)) ; else if (buffer.Buffer.isBuffer(body)) ; else if (util.types.isAnyArrayBuffer(body)) {
608 // Body is ArrayBuffer
609 body = buffer.Buffer.from(body);
610 } else if (ArrayBuffer.isView(body)) {
611 // Body is ArrayBufferView
612 body = buffer.Buffer.from(body.buffer, body.byteOffset, body.byteLength);
613 } else if (body instanceof Stream) ; else if (body instanceof FormData) {
614 // Body is FormData
615 body = formDataToBlob(body);
616 boundary = body.type.split('=')[1];
617 } else {
618 // None of the above
619 // coerce to string then buffer
620 body = buffer.Buffer.from(String(body));
621 }
622
623 let stream = body;
624
625 if (buffer.Buffer.isBuffer(body)) {
626 stream = Stream.Readable.from(body);
627 } else if (isBlob(body)) {
628 stream = Stream.Readable.from(body.stream());
629 }
630
631 this[INTERNALS$2] = {
632 body,
633 stream,
634 boundary,
635 disturbed: false,
636 error: null
637 };
638 this.size = size;
639
640 if (body instanceof Stream) {
641 body.on('error', error_ => {
642 const error = error_ instanceof FetchBaseError ?
643 error_ :
644 new FetchError(`Invalid response body while trying to fetch ${this.url}: ${error_.message}`, 'system', error_);
645 this[INTERNALS$2].error = error;
646 });
647 }
648 }
649
650 get body() {
651 return this[INTERNALS$2].stream;
652 }
653
654 get bodyUsed() {
655 return this[INTERNALS$2].disturbed;
656 }
657
658 /**
659 * Decode response as ArrayBuffer
660 *
661 * @return Promise
662 */
663 async arrayBuffer() {
664 const {buffer, byteOffset, byteLength} = await consumeBody(this);
665 return buffer.slice(byteOffset, byteOffset + byteLength);
666 }
667
668 async formData() {
669 const ct = this.headers.get('content-type');
670
671 if (ct.startsWith('application/x-www-form-urlencoded')) {
672 const formData = new FormData();
673 const parameters = new URLSearchParams(await this.text());
674
675 for (const [name, value] of parameters) {
676 formData.append(name, value);
677 }
678
679 return formData;
680 }
681
682 const {toFormData} = await Promise.resolve().then(function () { return require('./multipart-parser-25a14693.js'); });
683 return toFormData(this.body, ct);
684 }
685
686 /**
687 * Return raw response as Blob
688 *
689 * @return Promise
690 */
691 async blob() {
692 const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS$2].body && this[INTERNALS$2].body.type) || '';
693 const buf = await this.arrayBuffer();
694
695 return new Blob([buf], {
696 type: ct
697 });
698 }
699
700 /**
701 * Decode response as json
702 *
703 * @return Promise
704 */
705 async json() {
706 const text = await this.text();
707 return JSON.parse(text);
708 }
709
710 /**
711 * Decode response as text
712 *
713 * @return Promise
714 */
715 async text() {
716 const buffer = await consumeBody(this);
717 return new TextDecoder().decode(buffer);
718 }
719
720 /**
721 * Decode response as buffer (non-spec api)
722 *
723 * @return Promise
724 */
725 buffer() {
726 return consumeBody(this);
727 }
728}
729
730Body.prototype.buffer = util.deprecate(Body.prototype.buffer, 'Please use \'response.arrayBuffer()\' instead of \'response.buffer()\'', 'node-fetch#buffer');
731
732// In browsers, all properties are enumerable.
733Object.defineProperties(Body.prototype, {
734 body: {enumerable: true},
735 bodyUsed: {enumerable: true},
736 arrayBuffer: {enumerable: true},
737 blob: {enumerable: true},
738 json: {enumerable: true},
739 text: {enumerable: true},
740 data: {get: util.deprecate(() => {},
741 'data doesn\'t exist, use json(), text(), arrayBuffer(), or body instead',
742 'https://github.com/node-fetch/node-fetch/issues/1000 (response)')}
743});
744
745/**
746 * Consume and convert an entire Body to a Buffer.
747 *
748 * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
749 *
750 * @return Promise
751 */
752async function consumeBody(data) {
753 if (data[INTERNALS$2].disturbed) {
754 throw new TypeError(`body used already for: ${data.url}`);
755 }
756
757 data[INTERNALS$2].disturbed = true;
758
759 if (data[INTERNALS$2].error) {
760 throw data[INTERNALS$2].error;
761 }
762
763 const {body} = data;
764
765 // Body is null
766 if (body === null) {
767 return buffer.Buffer.alloc(0);
768 }
769
770 /* c8 ignore next 3 */
771 if (!(body instanceof Stream)) {
772 return buffer.Buffer.alloc(0);
773 }
774
775 // Body is stream
776 // get ready to actually consume the body
777 const accum = [];
778 let accumBytes = 0;
779
780 try {
781 for await (const chunk of body) {
782 if (data.size > 0 && accumBytes + chunk.length > data.size) {
783 const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
784 body.destroy(error);
785 throw error;
786 }
787
788 accumBytes += chunk.length;
789 accum.push(chunk);
790 }
791 } catch (error) {
792 const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, 'system', error);
793 throw error_;
794 }
795
796 if (body.readableEnded === true || body._readableState.ended === true) {
797 try {
798 if (accum.every(c => typeof c === 'string')) {
799 return buffer.Buffer.from(accum.join(''));
800 }
801
802 return buffer.Buffer.concat(accum, accumBytes);
803 } catch (error) {
804 throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error);
805 }
806 } else {
807 throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
808 }
809}
810
811/**
812 * Clone body given Res/Req instance
813 *
814 * @param Mixed instance Response or Request instance
815 * @param String highWaterMark highWaterMark for both PassThrough body streams
816 * @return Mixed
817 */
818const clone = (instance, highWaterMark) => {
819 let p1;
820 let p2;
821 let {body} = instance[INTERNALS$2];
822
823 // Don't allow cloning a used body
824 if (instance.bodyUsed) {
825 throw new Error('cannot clone body after it is used');
826 }
827
828 // Check that body is a stream and not form-data object
829 // note: we can't clone the form-data object without having it as a dependency
830 if ((body instanceof Stream) && (typeof body.getBoundary !== 'function')) {
831 // Tee instance body
832 p1 = new Stream.PassThrough({highWaterMark});
833 p2 = new Stream.PassThrough({highWaterMark});
834 body.pipe(p1);
835 body.pipe(p2);
836 // Set instance body to teed body and return the other teed body
837 instance[INTERNALS$2].stream = p1;
838 body = p2;
839 }
840
841 return body;
842};
843
844const getNonSpecFormDataBoundary = util.deprecate(
845 body => body.getBoundary(),
846 'form-data doesn\'t follow the spec and requires special treatment. Use alternative package',
847 'https://github.com/node-fetch/node-fetch/issues/1167'
848);
849
850/**
851 * Performs the operation "extract a `Content-Type` value from |object|" as
852 * specified in the specification:
853 * https://fetch.spec.whatwg.org/#concept-bodyinit-extract
854 *
855 * This function assumes that instance.body is present.
856 *
857 * @param {any} body Any options.body input
858 * @returns {string | null}
859 */
860const extractContentType = (body, request) => {
861 // Body is null or undefined
862 if (body === null) {
863 return null;
864 }
865
866 // Body is string
867 if (typeof body === 'string') {
868 return 'text/plain;charset=UTF-8';
869 }
870
871 // Body is a URLSearchParams
872 if (isURLSearchParameters(body)) {
873 return 'application/x-www-form-urlencoded;charset=UTF-8';
874 }
875
876 // Body is blob
877 if (isBlob(body)) {
878 return body.type || null;
879 }
880
881 // Body is a Buffer (Buffer, ArrayBuffer or ArrayBufferView)
882 if (buffer.Buffer.isBuffer(body) || util.types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) {
883 return null;
884 }
885
886 if (body instanceof FormData) {
887 return `multipart/form-data; boundary=${request[INTERNALS$2].boundary}`;
888 }
889
890 // Detect form data input from form-data module
891 if (body && typeof body.getBoundary === 'function') {
892 return `multipart/form-data;boundary=${getNonSpecFormDataBoundary(body)}`;
893 }
894
895 // Body is stream - can't really do much about this
896 if (body instanceof Stream) {
897 return null;
898 }
899
900 // Body constructor defaults other things to string
901 return 'text/plain;charset=UTF-8';
902};
903
904/**
905 * The Fetch Standard treats this as if "total bytes" is a property on the body.
906 * For us, we have to explicitly get it with a function.
907 *
908 * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
909 *
910 * @param {any} obj.body Body object from the Body instance.
911 * @returns {number | null}
912 */
913const getTotalBytes = request => {
914 const {body} = request[INTERNALS$2];
915
916 // Body is null or undefined
917 if (body === null) {
918 return 0;
919 }
920
921 // Body is Blob
922 if (isBlob(body)) {
923 return body.size;
924 }
925
926 // Body is Buffer
927 if (buffer.Buffer.isBuffer(body)) {
928 return body.length;
929 }
930
931 // Detect form data input from form-data module
932 if (body && typeof body.getLengthSync === 'function') {
933 return body.hasKnownLength && body.hasKnownLength() ? body.getLengthSync() : null;
934 }
935
936 // Body is stream
937 return null;
938};
939
940/**
941 * Write a Body to a Node.js WritableStream (e.g. http.Request) object.
942 *
943 * @param {Stream.Writable} dest The stream to write to.
944 * @param obj.body Body object from the Body instance.
945 * @returns {Promise<void>}
946 */
947const writeToStream = async (dest, {body}) => {
948 if (body === null) {
949 // Body is null
950 dest.end();
951 } else {
952 // Body is stream
953 await pipeline(body, dest);
954 }
955};
956
957/**
958 * Headers.js
959 *
960 * Headers class offers convenient helpers
961 */
962
963
964/* c8 ignore next 9 */
965const validateHeaderName = typeof http.validateHeaderName === 'function' ?
966 http.validateHeaderName :
967 name => {
968 if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) {
969 const error = new TypeError(`Header name must be a valid HTTP token [${name}]`);
970 Object.defineProperty(error, 'code', {value: 'ERR_INVALID_HTTP_TOKEN'});
971 throw error;
972 }
973 };
974
975/* c8 ignore next 9 */
976const validateHeaderValue = typeof http.validateHeaderValue === 'function' ?
977 http.validateHeaderValue :
978 (name, value) => {
979 if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) {
980 const error = new TypeError(`Invalid character in header content ["${name}"]`);
981 Object.defineProperty(error, 'code', {value: 'ERR_INVALID_CHAR'});
982 throw error;
983 }
984 };
985
986/**
987 * @typedef {Headers | Record<string, string> | Iterable<readonly [string, string]> | Iterable<Iterable<string>>} HeadersInit
988 */
989
990/**
991 * This Fetch API interface allows you to perform various actions on HTTP request and response headers.
992 * These actions include retrieving, setting, adding to, and removing.
993 * A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.
994 * You can add to this using methods like append() (see Examples.)
995 * In all methods of this interface, header names are matched by case-insensitive byte sequence.
996 *
997 */
998class Headers extends URLSearchParams {
999 /**
1000 * Headers class
1001 *
1002 * @constructor
1003 * @param {HeadersInit} [init] - Response headers
1004 */
1005 constructor(init) {
1006 // Validate and normalize init object in [name, value(s)][]
1007 /** @type {string[][]} */
1008 let result = [];
1009 if (init instanceof Headers) {
1010 const raw = init.raw();
1011 for (const [name, values] of Object.entries(raw)) {
1012 result.push(...values.map(value => [name, value]));
1013 }
1014 } else if (init == null) ; else if (typeof init === 'object' && !util.types.isBoxedPrimitive(init)) {
1015 const method = init[Symbol.iterator];
1016 // eslint-disable-next-line no-eq-null, eqeqeq
1017 if (method == null) {
1018 // Record<ByteString, ByteString>
1019 result.push(...Object.entries(init));
1020 } else {
1021 if (typeof method !== 'function') {
1022 throw new TypeError('Header pairs must be iterable');
1023 }
1024
1025 // Sequence<sequence<ByteString>>
1026 // Note: per spec we have to first exhaust the lists then process them
1027 result = [...init]
1028 .map(pair => {
1029 if (
1030 typeof pair !== 'object' || util.types.isBoxedPrimitive(pair)
1031 ) {
1032 throw new TypeError('Each header pair must be an iterable object');
1033 }
1034
1035 return [...pair];
1036 }).map(pair => {
1037 if (pair.length !== 2) {
1038 throw new TypeError('Each header pair must be a name/value tuple');
1039 }
1040
1041 return [...pair];
1042 });
1043 }
1044 } else {
1045 throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)');
1046 }
1047
1048 // Validate and lowercase
1049 result =
1050 result.length > 0 ?
1051 result.map(([name, value]) => {
1052 validateHeaderName(name);
1053 validateHeaderValue(name, String(value));
1054 return [String(name).toLowerCase(), String(value)];
1055 }) :
1056 undefined;
1057
1058 super(result);
1059
1060 // Returning a Proxy that will lowercase key names, validate parameters and sort keys
1061 // eslint-disable-next-line no-constructor-return
1062 return new Proxy(this, {
1063 get(target, p, receiver) {
1064 switch (p) {
1065 case 'append':
1066 case 'set':
1067 return (name, value) => {
1068 validateHeaderName(name);
1069 validateHeaderValue(name, String(value));
1070 return URLSearchParams.prototype[p].call(
1071 target,
1072 String(name).toLowerCase(),
1073 String(value)
1074 );
1075 };
1076
1077 case 'delete':
1078 case 'has':
1079 case 'getAll':
1080 return name => {
1081 validateHeaderName(name);
1082 return URLSearchParams.prototype[p].call(
1083 target,
1084 String(name).toLowerCase()
1085 );
1086 };
1087
1088 case 'keys':
1089 return () => {
1090 target.sort();
1091 return new Set(URLSearchParams.prototype.keys.call(target)).keys();
1092 };
1093
1094 default:
1095 return Reflect.get(target, p, receiver);
1096 }
1097 }
1098 });
1099 /* c8 ignore next */
1100 }
1101
1102 get [Symbol.toStringTag]() {
1103 return this.constructor.name;
1104 }
1105
1106 toString() {
1107 return Object.prototype.toString.call(this);
1108 }
1109
1110 get(name) {
1111 const values = this.getAll(name);
1112 if (values.length === 0) {
1113 return null;
1114 }
1115
1116 let value = values.join(', ');
1117 if (/^content-encoding$/i.test(name)) {
1118 value = value.toLowerCase();
1119 }
1120
1121 return value;
1122 }
1123
1124 forEach(callback, thisArg = undefined) {
1125 for (const name of this.keys()) {
1126 Reflect.apply(callback, thisArg, [this.get(name), name, this]);
1127 }
1128 }
1129
1130 * values() {
1131 for (const name of this.keys()) {
1132 yield this.get(name);
1133 }
1134 }
1135
1136 /**
1137 * @type {() => IterableIterator<[string, string]>}
1138 */
1139 * entries() {
1140 for (const name of this.keys()) {
1141 yield [name, this.get(name)];
1142 }
1143 }
1144
1145 [Symbol.iterator]() {
1146 return this.entries();
1147 }
1148
1149 /**
1150 * Node-fetch non-spec method
1151 * returning all headers and their values as array
1152 * @returns {Record<string, string[]>}
1153 */
1154 raw() {
1155 return [...this.keys()].reduce((result, key) => {
1156 result[key] = this.getAll(key);
1157 return result;
1158 }, {});
1159 }
1160
1161 /**
1162 * For better console.log(headers) and also to convert Headers into Node.js Request compatible format
1163 */
1164 [Symbol.for('nodejs.util.inspect.custom')]() {
1165 return [...this.keys()].reduce((result, key) => {
1166 const values = this.getAll(key);
1167 // Http.request() only supports string as Host header.
1168 // This hack makes specifying custom Host header possible.
1169 if (key === 'host') {
1170 result[key] = values[0];
1171 } else {
1172 result[key] = values.length > 1 ? values : values[0];
1173 }
1174
1175 return result;
1176 }, {});
1177 }
1178}
1179
1180/**
1181 * Re-shaping object for Web IDL tests
1182 * Only need to do it for overridden methods
1183 */
1184Object.defineProperties(
1185 Headers.prototype,
1186 ['get', 'entries', 'forEach', 'values'].reduce((result, property) => {
1187 result[property] = {enumerable: true};
1188 return result;
1189 }, {})
1190);
1191
1192/**
1193 * Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do
1194 * not conform to HTTP grammar productions.
1195 * @param {import('http').IncomingMessage['rawHeaders']} headers
1196 */
1197function fromRawHeaders(headers = []) {
1198 return new Headers(
1199 headers
1200 // Split into pairs
1201 .reduce((result, value, index, array) => {
1202 if (index % 2 === 0) {
1203 result.push(array.slice(index, index + 2));
1204 }
1205
1206 return result;
1207 }, [])
1208 .filter(([name, value]) => {
1209 try {
1210 validateHeaderName(name);
1211 validateHeaderValue(name, String(value));
1212 return true;
1213 } catch {
1214 return false;
1215 }
1216 })
1217
1218 );
1219}
1220
1221const redirectStatus = new Set([301, 302, 303, 307, 308]);
1222
1223/**
1224 * Redirect code matching
1225 *
1226 * @param {number} code - Status code
1227 * @return {boolean}
1228 */
1229const isRedirect = code => {
1230 return redirectStatus.has(code);
1231};
1232
1233/**
1234 * Response.js
1235 *
1236 * Response class provides content decoding
1237 */
1238
1239
1240const INTERNALS$1 = Symbol('Response internals');
1241
1242/**
1243 * Response class
1244 *
1245 * Ref: https://fetch.spec.whatwg.org/#response-class
1246 *
1247 * @param Stream body Readable stream
1248 * @param Object opts Response options
1249 * @return Void
1250 */
1251class Response extends Body {
1252 constructor(body = null, options = {}) {
1253 super(body, options);
1254
1255 // eslint-disable-next-line no-eq-null, eqeqeq, no-negated-condition
1256 const status = options.status != null ? options.status : 200;
1257
1258 const headers = new Headers(options.headers);
1259
1260 if (body !== null && !headers.has('Content-Type')) {
1261 const contentType = extractContentType(body, this);
1262 if (contentType) {
1263 headers.append('Content-Type', contentType);
1264 }
1265 }
1266
1267 this[INTERNALS$1] = {
1268 type: 'default',
1269 url: options.url,
1270 status,
1271 statusText: options.statusText || '',
1272 headers,
1273 counter: options.counter,
1274 highWaterMark: options.highWaterMark
1275 };
1276 }
1277
1278 get type() {
1279 return this[INTERNALS$1].type;
1280 }
1281
1282 get url() {
1283 return this[INTERNALS$1].url || '';
1284 }
1285
1286 get status() {
1287 return this[INTERNALS$1].status;
1288 }
1289
1290 /**
1291 * Convenience property representing if the request ended normally
1292 */
1293 get ok() {
1294 return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300;
1295 }
1296
1297 get redirected() {
1298 return this[INTERNALS$1].counter > 0;
1299 }
1300
1301 get statusText() {
1302 return this[INTERNALS$1].statusText;
1303 }
1304
1305 get headers() {
1306 return this[INTERNALS$1].headers;
1307 }
1308
1309 get highWaterMark() {
1310 return this[INTERNALS$1].highWaterMark;
1311 }
1312
1313 /**
1314 * Clone this response
1315 *
1316 * @return Response
1317 */
1318 clone() {
1319 return new Response(clone(this, this.highWaterMark), {
1320 type: this.type,
1321 url: this.url,
1322 status: this.status,
1323 statusText: this.statusText,
1324 headers: this.headers,
1325 ok: this.ok,
1326 redirected: this.redirected,
1327 size: this.size,
1328 highWaterMark: this.highWaterMark
1329 });
1330 }
1331
1332 /**
1333 * @param {string} url The URL that the new response is to originate from.
1334 * @param {number} status An optional status code for the response (e.g., 302.)
1335 * @returns {Response} A Response object.
1336 */
1337 static redirect(url, status = 302) {
1338 if (!isRedirect(status)) {
1339 throw new RangeError('Failed to execute "redirect" on "response": Invalid status code');
1340 }
1341
1342 return new Response(null, {
1343 headers: {
1344 location: new URL(url).toString()
1345 },
1346 status
1347 });
1348 }
1349
1350 static error() {
1351 const response = new Response(null, {status: 0, statusText: ''});
1352 response[INTERNALS$1].type = 'error';
1353 return response;
1354 }
1355
1356 static json(data = undefined, init = {}) {
1357 const body = JSON.stringify(data);
1358
1359 if (body === undefined) {
1360 throw new TypeError('data is not JSON serializable');
1361 }
1362
1363 const headers = new Headers(init && init.headers);
1364
1365 if (!headers.has('content-type')) {
1366 headers.set('content-type', 'application/json');
1367 }
1368
1369 return new Response(body, {
1370 ...init,
1371 headers
1372 });
1373 }
1374
1375 get [Symbol.toStringTag]() {
1376 return 'Response';
1377 }
1378}
1379
1380Object.defineProperties(Response.prototype, {
1381 type: {enumerable: true},
1382 url: {enumerable: true},
1383 status: {enumerable: true},
1384 ok: {enumerable: true},
1385 redirected: {enumerable: true},
1386 statusText: {enumerable: true},
1387 headers: {enumerable: true},
1388 clone: {enumerable: true}
1389});
1390
1391const getSearch = parsedURL => {
1392 if (parsedURL.search) {
1393 return parsedURL.search;
1394 }
1395
1396 const lastOffset = parsedURL.href.length - 1;
1397 const hash = parsedURL.hash || (parsedURL.href[lastOffset] === '#' ? '#' : '');
1398 return parsedURL.href[lastOffset - hash.length] === '?' ? '?' : '';
1399};
1400
1401/**
1402 * @external URL
1403 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/URL|URL}
1404 */
1405
1406/**
1407 * @module utils/referrer
1408 * @private
1409 */
1410
1411/**
1412 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#strip-url|Referrer Policy §8.4. Strip url for use as a referrer}
1413 * @param {string} URL
1414 * @param {boolean} [originOnly=false]
1415 */
1416function stripURLForUseAsAReferrer(url, originOnly = false) {
1417 // 1. If url is null, return no referrer.
1418 if (url == null) { // eslint-disable-line no-eq-null, eqeqeq
1419 return 'no-referrer';
1420 }
1421
1422 url = new URL(url);
1423
1424 // 2. If url's scheme is a local scheme, then return no referrer.
1425 if (/^(about|blob|data):$/.test(url.protocol)) {
1426 return 'no-referrer';
1427 }
1428
1429 // 3. Set url's username to the empty string.
1430 url.username = '';
1431
1432 // 4. Set url's password to null.
1433 // Note: `null` appears to be a mistake as this actually results in the password being `"null"`.
1434 url.password = '';
1435
1436 // 5. Set url's fragment to null.
1437 // Note: `null` appears to be a mistake as this actually results in the fragment being `"#null"`.
1438 url.hash = '';
1439
1440 // 6. If the origin-only flag is true, then:
1441 if (originOnly) {
1442 // 6.1. Set url's path to null.
1443 // Note: `null` appears to be a mistake as this actually results in the path being `"/null"`.
1444 url.pathname = '';
1445
1446 // 6.2. Set url's query to null.
1447 // Note: `null` appears to be a mistake as this actually results in the query being `"?null"`.
1448 url.search = '';
1449 }
1450
1451 // 7. Return url.
1452 return url;
1453}
1454
1455/**
1456 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#enumdef-referrerpolicy|enum ReferrerPolicy}
1457 */
1458const ReferrerPolicy = new Set([
1459 '',
1460 'no-referrer',
1461 'no-referrer-when-downgrade',
1462 'same-origin',
1463 'origin',
1464 'strict-origin',
1465 'origin-when-cross-origin',
1466 'strict-origin-when-cross-origin',
1467 'unsafe-url'
1468]);
1469
1470/**
1471 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#default-referrer-policy|default referrer policy}
1472 */
1473const DEFAULT_REFERRER_POLICY = 'strict-origin-when-cross-origin';
1474
1475/**
1476 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#referrer-policies|Referrer Policy §3. Referrer Policies}
1477 * @param {string} referrerPolicy
1478 * @returns {string} referrerPolicy
1479 */
1480function validateReferrerPolicy(referrerPolicy) {
1481 if (!ReferrerPolicy.has(referrerPolicy)) {
1482 throw new TypeError(`Invalid referrerPolicy: ${referrerPolicy}`);
1483 }
1484
1485 return referrerPolicy;
1486}
1487
1488/**
1489 * @see {@link https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy|Referrer Policy §3.2. Is origin potentially trustworthy?}
1490 * @param {external:URL} url
1491 * @returns `true`: "Potentially Trustworthy", `false`: "Not Trustworthy"
1492 */
1493function isOriginPotentiallyTrustworthy(url) {
1494 // 1. If origin is an opaque origin, return "Not Trustworthy".
1495 // Not applicable
1496
1497 // 2. Assert: origin is a tuple origin.
1498 // Not for implementations
1499
1500 // 3. If origin's scheme is either "https" or "wss", return "Potentially Trustworthy".
1501 if (/^(http|ws)s:$/.test(url.protocol)) {
1502 return true;
1503 }
1504
1505 // 4. If origin's host component matches one of the CIDR notations 127.0.0.0/8 or ::1/128 [RFC4632], return "Potentially Trustworthy".
1506 const hostIp = url.host.replace(/(^\[)|(]$)/g, '');
1507 const hostIPVersion = net.isIP(hostIp);
1508
1509 if (hostIPVersion === 4 && /^127\./.test(hostIp)) {
1510 return true;
1511 }
1512
1513 if (hostIPVersion === 6 && /^(((0+:){7})|(::(0+:){0,6}))0*1$/.test(hostIp)) {
1514 return true;
1515 }
1516
1517 // 5. If origin's host component is "localhost" or falls within ".localhost", and the user agent conforms to the name resolution rules in [let-localhost-be-localhost], return "Potentially Trustworthy".
1518 // We are returning FALSE here because we cannot ensure conformance to
1519 // let-localhost-be-loalhost (https://tools.ietf.org/html/draft-west-let-localhost-be-localhost)
1520 if (url.host === 'localhost' || url.host.endsWith('.localhost')) {
1521 return false;
1522 }
1523
1524 // 6. If origin's scheme component is file, return "Potentially Trustworthy".
1525 if (url.protocol === 'file:') {
1526 return true;
1527 }
1528
1529 // 7. If origin's scheme component is one which the user agent considers to be authenticated, return "Potentially Trustworthy".
1530 // Not supported
1531
1532 // 8. If origin has been configured as a trustworthy origin, return "Potentially Trustworthy".
1533 // Not supported
1534
1535 // 9. Return "Not Trustworthy".
1536 return false;
1537}
1538
1539/**
1540 * @see {@link https://w3c.github.io/webappsec-secure-contexts/#is-url-trustworthy|Referrer Policy §3.3. Is url potentially trustworthy?}
1541 * @param {external:URL} url
1542 * @returns `true`: "Potentially Trustworthy", `false`: "Not Trustworthy"
1543 */
1544function isUrlPotentiallyTrustworthy(url) {
1545 // 1. If url is "about:blank" or "about:srcdoc", return "Potentially Trustworthy".
1546 if (/^about:(blank|srcdoc)$/.test(url)) {
1547 return true;
1548 }
1549
1550 // 2. If url's scheme is "data", return "Potentially Trustworthy".
1551 if (url.protocol === 'data:') {
1552 return true;
1553 }
1554
1555 // Note: The origin of blob: and filesystem: URLs is the origin of the context in which they were
1556 // created. Therefore, blobs created in a trustworthy origin will themselves be potentially
1557 // trustworthy.
1558 if (/^(blob|filesystem):$/.test(url.protocol)) {
1559 return true;
1560 }
1561
1562 // 3. Return the result of executing §3.2 Is origin potentially trustworthy? on url's origin.
1563 return isOriginPotentiallyTrustworthy(url);
1564}
1565
1566/**
1567 * Modifies the referrerURL to enforce any extra security policy considerations.
1568 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer|Referrer Policy §8.3. Determine request's Referrer}, step 7
1569 * @callback module:utils/referrer~referrerURLCallback
1570 * @param {external:URL} referrerURL
1571 * @returns {external:URL} modified referrerURL
1572 */
1573
1574/**
1575 * Modifies the referrerOrigin to enforce any extra security policy considerations.
1576 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer|Referrer Policy §8.3. Determine request's Referrer}, step 7
1577 * @callback module:utils/referrer~referrerOriginCallback
1578 * @param {external:URL} referrerOrigin
1579 * @returns {external:URL} modified referrerOrigin
1580 */
1581
1582/**
1583 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer|Referrer Policy §8.3. Determine request's Referrer}
1584 * @param {Request} request
1585 * @param {object} o
1586 * @param {module:utils/referrer~referrerURLCallback} o.referrerURLCallback
1587 * @param {module:utils/referrer~referrerOriginCallback} o.referrerOriginCallback
1588 * @returns {external:URL} Request's referrer
1589 */
1590function determineRequestsReferrer(request, {referrerURLCallback, referrerOriginCallback} = {}) {
1591 // There are 2 notes in the specification about invalid pre-conditions. We return null, here, for
1592 // these cases:
1593 // > Note: If request's referrer is "no-referrer", Fetch will not call into this algorithm.
1594 // > Note: If request's referrer policy is the empty string, Fetch will not call into this
1595 // > algorithm.
1596 if (request.referrer === 'no-referrer' || request.referrerPolicy === '') {
1597 return null;
1598 }
1599
1600 // 1. Let policy be request's associated referrer policy.
1601 const policy = request.referrerPolicy;
1602
1603 // 2. Let environment be request's client.
1604 // not applicable to node.js
1605
1606 // 3. Switch on request's referrer:
1607 if (request.referrer === 'about:client') {
1608 return 'no-referrer';
1609 }
1610
1611 // "a URL": Let referrerSource be request's referrer.
1612 const referrerSource = request.referrer;
1613
1614 // 4. Let request's referrerURL be the result of stripping referrerSource for use as a referrer.
1615 let referrerURL = stripURLForUseAsAReferrer(referrerSource);
1616
1617 // 5. Let referrerOrigin be the result of stripping referrerSource for use as a referrer, with the
1618 // origin-only flag set to true.
1619 let referrerOrigin = stripURLForUseAsAReferrer(referrerSource, true);
1620
1621 // 6. If the result of serializing referrerURL is a string whose length is greater than 4096, set
1622 // referrerURL to referrerOrigin.
1623 if (referrerURL.toString().length > 4096) {
1624 referrerURL = referrerOrigin;
1625 }
1626
1627 // 7. The user agent MAY alter referrerURL or referrerOrigin at this point to enforce arbitrary
1628 // policy considerations in the interests of minimizing data leakage. For example, the user
1629 // agent could strip the URL down to an origin, modify its host, replace it with an empty
1630 // string, etc.
1631 if (referrerURLCallback) {
1632 referrerURL = referrerURLCallback(referrerURL);
1633 }
1634
1635 if (referrerOriginCallback) {
1636 referrerOrigin = referrerOriginCallback(referrerOrigin);
1637 }
1638
1639 // 8.Execute the statements corresponding to the value of policy:
1640 const currentURL = new URL(request.url);
1641
1642 switch (policy) {
1643 case 'no-referrer':
1644 return 'no-referrer';
1645
1646 case 'origin':
1647 return referrerOrigin;
1648
1649 case 'unsafe-url':
1650 return referrerURL;
1651
1652 case 'strict-origin':
1653 // 1. If referrerURL is a potentially trustworthy URL and request's current URL is not a
1654 // potentially trustworthy URL, then return no referrer.
1655 if (isUrlPotentiallyTrustworthy(referrerURL) && !isUrlPotentiallyTrustworthy(currentURL)) {
1656 return 'no-referrer';
1657 }
1658
1659 // 2. Return referrerOrigin.
1660 return referrerOrigin.toString();
1661
1662 case 'strict-origin-when-cross-origin':
1663 // 1. If the origin of referrerURL and the origin of request's current URL are the same, then
1664 // return referrerURL.
1665 if (referrerURL.origin === currentURL.origin) {
1666 return referrerURL;
1667 }
1668
1669 // 2. If referrerURL is a potentially trustworthy URL and request's current URL is not a
1670 // potentially trustworthy URL, then return no referrer.
1671 if (isUrlPotentiallyTrustworthy(referrerURL) && !isUrlPotentiallyTrustworthy(currentURL)) {
1672 return 'no-referrer';
1673 }
1674
1675 // 3. Return referrerOrigin.
1676 return referrerOrigin;
1677
1678 case 'same-origin':
1679 // 1. If the origin of referrerURL and the origin of request's current URL are the same, then
1680 // return referrerURL.
1681 if (referrerURL.origin === currentURL.origin) {
1682 return referrerURL;
1683 }
1684
1685 // 2. Return no referrer.
1686 return 'no-referrer';
1687
1688 case 'origin-when-cross-origin':
1689 // 1. If the origin of referrerURL and the origin of request's current URL are the same, then
1690 // return referrerURL.
1691 if (referrerURL.origin === currentURL.origin) {
1692 return referrerURL;
1693 }
1694
1695 // Return referrerOrigin.
1696 return referrerOrigin;
1697
1698 case 'no-referrer-when-downgrade':
1699 // 1. If referrerURL is a potentially trustworthy URL and request's current URL is not a
1700 // potentially trustworthy URL, then return no referrer.
1701 if (isUrlPotentiallyTrustworthy(referrerURL) && !isUrlPotentiallyTrustworthy(currentURL)) {
1702 return 'no-referrer';
1703 }
1704
1705 // 2. Return referrerURL.
1706 return referrerURL;
1707
1708 default:
1709 throw new TypeError(`Invalid referrerPolicy: ${policy}`);
1710 }
1711}
1712
1713/**
1714 * @see {@link https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header|Referrer Policy §8.1. Parse a referrer policy from a Referrer-Policy header}
1715 * @param {Headers} headers Response headers
1716 * @returns {string} policy
1717 */
1718function parseReferrerPolicyFromHeader(headers) {
1719 // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy`
1720 // and response’s header list.
1721 const policyTokens = (headers.get('referrer-policy') || '').split(/[,\s]+/);
1722
1723 // 2. Let policy be the empty string.
1724 let policy = '';
1725
1726 // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty
1727 // string, then set policy to token.
1728 // Note: This algorithm loops over multiple policy values to allow deployment of new policy
1729 // values with fallbacks for older user agents, as described in § 11.1 Unknown Policy Values.
1730 for (const token of policyTokens) {
1731 if (token && ReferrerPolicy.has(token)) {
1732 policy = token;
1733 }
1734 }
1735
1736 // 4. Return policy.
1737 return policy;
1738}
1739
1740/**
1741 * Request.js
1742 *
1743 * Request class contains server only options
1744 *
1745 * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/.
1746 */
1747
1748
1749const INTERNALS = Symbol('Request internals');
1750
1751/**
1752 * Check if `obj` is an instance of Request.
1753 *
1754 * @param {*} object
1755 * @return {boolean}
1756 */
1757const isRequest = object => {
1758 return (
1759 typeof object === 'object' &&
1760 typeof object[INTERNALS] === 'object'
1761 );
1762};
1763
1764const doBadDataWarn = util.deprecate(() => {},
1765 '.data is not a valid RequestInit property, use .body instead',
1766 'https://github.com/node-fetch/node-fetch/issues/1000 (request)');
1767
1768/**
1769 * Request class
1770 *
1771 * Ref: https://fetch.spec.whatwg.org/#request-class
1772 *
1773 * @param Mixed input Url or Request instance
1774 * @param Object init Custom options
1775 * @return Void
1776 */
1777class Request extends Body {
1778 constructor(input, init = {}) {
1779 let parsedURL;
1780
1781 // Normalize input and force URL to be encoded as UTF-8 (https://github.com/node-fetch/node-fetch/issues/245)
1782 if (isRequest(input)) {
1783 parsedURL = new URL(input.url);
1784 } else {
1785 parsedURL = new URL(input);
1786 input = {};
1787 }
1788
1789 if (parsedURL.username !== '' || parsedURL.password !== '') {
1790 throw new TypeError(`${parsedURL} is an url with embedded credentials.`);
1791 }
1792
1793 let method = init.method || input.method || 'GET';
1794 if (/^(delete|get|head|options|post|put)$/i.test(method)) {
1795 method = method.toUpperCase();
1796 }
1797
1798 if (!isRequest(init) && 'data' in init) {
1799 doBadDataWarn();
1800 }
1801
1802 // eslint-disable-next-line no-eq-null, eqeqeq
1803 if ((init.body != null || (isRequest(input) && input.body !== null)) &&
1804 (method === 'GET' || method === 'HEAD')) {
1805 throw new TypeError('Request with GET/HEAD method cannot have body');
1806 }
1807
1808 const inputBody = init.body ?
1809 init.body :
1810 (isRequest(input) && input.body !== null ?
1811 clone(input) :
1812 null);
1813
1814 super(inputBody, {
1815 size: init.size || input.size || 0
1816 });
1817
1818 const headers = new Headers(init.headers || input.headers || {});
1819
1820 if (inputBody !== null && !headers.has('Content-Type')) {
1821 const contentType = extractContentType(inputBody, this);
1822 if (contentType) {
1823 headers.set('Content-Type', contentType);
1824 }
1825 }
1826
1827 let signal = isRequest(input) ?
1828 input.signal :
1829 null;
1830 if ('signal' in init) {
1831 signal = init.signal;
1832 }
1833
1834 // eslint-disable-next-line no-eq-null, eqeqeq
1835 if (signal != null && !isAbortSignal(signal)) {
1836 throw new TypeError('Expected signal to be an instanceof AbortSignal or EventTarget');
1837 }
1838
1839 // §5.4, Request constructor steps, step 15.1
1840 // eslint-disable-next-line no-eq-null, eqeqeq
1841 let referrer = init.referrer == null ? input.referrer : init.referrer;
1842 if (referrer === '') {
1843 // §5.4, Request constructor steps, step 15.2
1844 referrer = 'no-referrer';
1845 } else if (referrer) {
1846 // §5.4, Request constructor steps, step 15.3.1, 15.3.2
1847 const parsedReferrer = new URL(referrer);
1848 // §5.4, Request constructor steps, step 15.3.3, 15.3.4
1849 referrer = /^about:(\/\/)?client$/.test(parsedReferrer) ? 'client' : parsedReferrer;
1850 } else {
1851 referrer = undefined;
1852 }
1853
1854 this[INTERNALS] = {
1855 method,
1856 redirect: init.redirect || input.redirect || 'follow',
1857 headers,
1858 parsedURL,
1859 signal,
1860 referrer
1861 };
1862
1863 // Node-fetch-only options
1864 this.follow = init.follow === undefined ? (input.follow === undefined ? 20 : input.follow) : init.follow;
1865 this.compress = init.compress === undefined ? (input.compress === undefined ? true : input.compress) : init.compress;
1866 this.counter = init.counter || input.counter || 0;
1867 this.agent = init.agent || input.agent;
1868 this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
1869 this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
1870
1871 // §5.4, Request constructor steps, step 16.
1872 // Default is empty string per https://fetch.spec.whatwg.org/#concept-request-referrer-policy
1873 this.referrerPolicy = init.referrerPolicy || input.referrerPolicy || '';
1874 }
1875
1876 /** @returns {string} */
1877 get method() {
1878 return this[INTERNALS].method;
1879 }
1880
1881 /** @returns {string} */
1882 get url() {
1883 return url.format(this[INTERNALS].parsedURL);
1884 }
1885
1886 /** @returns {Headers} */
1887 get headers() {
1888 return this[INTERNALS].headers;
1889 }
1890
1891 get redirect() {
1892 return this[INTERNALS].redirect;
1893 }
1894
1895 /** @returns {AbortSignal} */
1896 get signal() {
1897 return this[INTERNALS].signal;
1898 }
1899
1900 // https://fetch.spec.whatwg.org/#dom-request-referrer
1901 get referrer() {
1902 if (this[INTERNALS].referrer === 'no-referrer') {
1903 return '';
1904 }
1905
1906 if (this[INTERNALS].referrer === 'client') {
1907 return 'about:client';
1908 }
1909
1910 if (this[INTERNALS].referrer) {
1911 return this[INTERNALS].referrer.toString();
1912 }
1913
1914 return undefined;
1915 }
1916
1917 get referrerPolicy() {
1918 return this[INTERNALS].referrerPolicy;
1919 }
1920
1921 set referrerPolicy(referrerPolicy) {
1922 this[INTERNALS].referrerPolicy = validateReferrerPolicy(referrerPolicy);
1923 }
1924
1925 /**
1926 * Clone this request
1927 *
1928 * @return Request
1929 */
1930 clone() {
1931 return new Request(this);
1932 }
1933
1934 get [Symbol.toStringTag]() {
1935 return 'Request';
1936 }
1937}
1938
1939Object.defineProperties(Request.prototype, {
1940 method: {enumerable: true},
1941 url: {enumerable: true},
1942 headers: {enumerable: true},
1943 redirect: {enumerable: true},
1944 clone: {enumerable: true},
1945 signal: {enumerable: true},
1946 referrer: {enumerable: true},
1947 referrerPolicy: {enumerable: true}
1948});
1949
1950/**
1951 * Convert a Request to Node.js http request options.
1952 *
1953 * @param {Request} request - A Request instance
1954 * @return The options object to be passed to http.request
1955 */
1956const getNodeRequestOptions = request => {
1957 const {parsedURL} = request[INTERNALS];
1958 const headers = new Headers(request[INTERNALS].headers);
1959
1960 // Fetch step 1.3
1961 if (!headers.has('Accept')) {
1962 headers.set('Accept', '*/*');
1963 }
1964
1965 // HTTP-network-or-cache fetch steps 2.4-2.7
1966 let contentLengthValue = null;
1967 if (request.body === null && /^(post|put)$/i.test(request.method)) {
1968 contentLengthValue = '0';
1969 }
1970
1971 if (request.body !== null) {
1972 const totalBytes = getTotalBytes(request);
1973 // Set Content-Length if totalBytes is a number (that is not NaN)
1974 if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) {
1975 contentLengthValue = String(totalBytes);
1976 }
1977 }
1978
1979 if (contentLengthValue) {
1980 headers.set('Content-Length', contentLengthValue);
1981 }
1982
1983 // 4.1. Main fetch, step 2.6
1984 // > If request's referrer policy is the empty string, then set request's referrer policy to the
1985 // > default referrer policy.
1986 if (request.referrerPolicy === '') {
1987 request.referrerPolicy = DEFAULT_REFERRER_POLICY;
1988 }
1989
1990 // 4.1. Main fetch, step 2.7
1991 // > If request's referrer is not "no-referrer", set request's referrer to the result of invoking
1992 // > determine request's referrer.
1993 if (request.referrer && request.referrer !== 'no-referrer') {
1994 request[INTERNALS].referrer = determineRequestsReferrer(request);
1995 } else {
1996 request[INTERNALS].referrer = 'no-referrer';
1997 }
1998
1999 // 4.5. HTTP-network-or-cache fetch, step 6.9
2000 // > If httpRequest's referrer is a URL, then append `Referer`/httpRequest's referrer, serialized
2001 // > and isomorphic encoded, to httpRequest's header list.
2002 if (request[INTERNALS].referrer instanceof URL) {
2003 headers.set('Referer', request.referrer);
2004 }
2005
2006 // HTTP-network-or-cache fetch step 2.11
2007 if (!headers.has('User-Agent')) {
2008 headers.set('User-Agent', 'node-fetch');
2009 }
2010
2011 // HTTP-network-or-cache fetch step 2.15
2012 if (request.compress && !headers.has('Accept-Encoding')) {
2013 headers.set('Accept-Encoding', 'gzip, deflate, br');
2014 }
2015
2016 let {agent} = request;
2017 if (typeof agent === 'function') {
2018 agent = agent(parsedURL);
2019 }
2020
2021 // HTTP-network fetch step 4.2
2022 // chunked encoding is handled by Node.js
2023
2024 const search = getSearch(parsedURL);
2025
2026 // Pass the full URL directly to request(), but overwrite the following
2027 // options:
2028 const options = {
2029 // Overwrite search to retain trailing ? (issue #776)
2030 path: parsedURL.pathname + search,
2031 // The following options are not expressed in the URL
2032 method: request.method,
2033 headers: headers[Symbol.for('nodejs.util.inspect.custom')](),
2034 insecureHTTPParser: request.insecureHTTPParser,
2035 agent
2036 };
2037
2038 return {
2039 /** @type {URL} */
2040 parsedURL,
2041 options
2042 };
2043};
2044
2045/**
2046 * AbortError interface for cancelled requests
2047 */
2048class AbortError extends FetchBaseError {
2049 constructor(message, type = 'aborted') {
2050 super(message, type);
2051 }
2052}
2053
2054const { stat } = fs.promises;
2055
2056/**
2057 * @param {string} path filepath on the disk
2058 * @param {string} [type] mimetype to use
2059 */
2060const blobFromSync = (path, type) => fromBlob(fs.statSync(path), path, type);
2061
2062/**
2063 * @param {string} path filepath on the disk
2064 * @param {string} [type] mimetype to use
2065 * @returns {Promise<Blob>}
2066 */
2067const blobFrom = (path, type) => stat(path).then(stat => fromBlob(stat, path, type));
2068
2069/**
2070 * @param {string} path filepath on the disk
2071 * @param {string} [type] mimetype to use
2072 * @returns {Promise<File>}
2073 */
2074const fileFrom = (path, type) => stat(path).then(stat => fromFile(stat, path, type));
2075
2076/**
2077 * @param {string} path filepath on the disk
2078 * @param {string} [type] mimetype to use
2079 */
2080const fileFromSync = (path, type) => fromFile(fs.statSync(path), path, type);
2081
2082// @ts-ignore
2083const fromBlob = (stat, path, type = '') => new Blob([new BlobDataItem({
2084 path,
2085 size: stat.size,
2086 lastModified: stat.mtimeMs,
2087 start: 0
2088})], { type });
2089
2090// @ts-ignore
2091const fromFile = (stat, path$1, type = '') => new File([new BlobDataItem({
2092 path: path$1,
2093 size: stat.size,
2094 lastModified: stat.mtimeMs,
2095 start: 0
2096})], path.basename(path$1), { type, lastModified: stat.mtimeMs });
2097
2098/**
2099 * This is a blob backed up by a file on the disk
2100 * with minium requirement. Its wrapped around a Blob as a blobPart
2101 * so you have no direct access to this.
2102 *
2103 * @private
2104 */
2105class BlobDataItem {
2106 #path
2107 #start
2108
2109 constructor (options) {
2110 this.#path = options.path;
2111 this.#start = options.start;
2112 this.size = options.size;
2113 this.lastModified = options.lastModified;
2114 }
2115
2116 /**
2117 * Slicing arguments is first validated and formatted
2118 * to not be out of range by Blob.prototype.slice
2119 */
2120 slice (start, end) {
2121 return new BlobDataItem({
2122 path: this.#path,
2123 lastModified: this.lastModified,
2124 size: end - start,
2125 start: this.#start + start
2126 })
2127 }
2128
2129 async * stream () {
2130 const { mtimeMs } = await stat(this.#path);
2131 if (mtimeMs > this.lastModified) {
2132 throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError')
2133 }
2134 yield * fs.createReadStream(this.#path, {
2135 start: this.#start,
2136 end: this.#start + this.size - 1
2137 });
2138 }
2139
2140 get [Symbol.toStringTag] () {
2141 return 'Blob'
2142 }
2143}
2144
2145/**
2146 * Index.js
2147 *
2148 * a request API compatible with window.fetch
2149 *
2150 * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/.
2151 */
2152
2153
2154const supportedSchemas = new Set(['data:', 'http:', 'https:']);
2155
2156/**
2157 * Fetch function
2158 *
2159 * @param {string | URL | import('./request').default} url - Absolute url or Request instance
2160 * @param {*} [options_] - Fetch options
2161 * @return {Promise<import('./response').default>}
2162 */
2163async function fetch(url, options_) {
2164 return new Promise((resolve, reject) => {
2165 // Build request object
2166 const request = new Request(url, options_);
2167 const {parsedURL, options} = getNodeRequestOptions(request);
2168 if (!supportedSchemas.has(parsedURL.protocol)) {
2169 throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${parsedURL.protocol.replace(/:$/, '')}" is not supported.`);
2170 }
2171
2172 if (parsedURL.protocol === 'data:') {
2173 const data = dataUriToBuffer(request.url);
2174 const response = new Response(data, {headers: {'Content-Type': data.typeFull}});
2175 resolve(response);
2176 return;
2177 }
2178
2179 // Wrap http.request into fetch
2180 const send = (parsedURL.protocol === 'https:' ? https : http).request;
2181 const {signal} = request;
2182 let response = null;
2183
2184 const abort = () => {
2185 const error = new AbortError('The operation was aborted.');
2186 reject(error);
2187 if (request.body && request.body instanceof Stream.Readable) {
2188 request.body.destroy(error);
2189 }
2190
2191 if (!response || !response.body) {
2192 return;
2193 }
2194
2195 response.body.emit('error', error);
2196 };
2197
2198 if (signal && signal.aborted) {
2199 abort();
2200 return;
2201 }
2202
2203 const abortAndFinalize = () => {
2204 abort();
2205 finalize();
2206 };
2207
2208 // Send request
2209 const request_ = send(parsedURL.toString(), options);
2210
2211 if (signal) {
2212 signal.addEventListener('abort', abortAndFinalize);
2213 }
2214
2215 const finalize = () => {
2216 request_.abort();
2217 if (signal) {
2218 signal.removeEventListener('abort', abortAndFinalize);
2219 }
2220 };
2221
2222 request_.on('error', error => {
2223 reject(new FetchError(`request to ${request.url} failed, reason: ${error.message}`, 'system', error));
2224 finalize();
2225 });
2226
2227 fixResponseChunkedTransferBadEnding(request_, error => {
2228 if (response && response.body) {
2229 response.body.destroy(error);
2230 }
2231 });
2232
2233 /* c8 ignore next 18 */
2234 if (process.version < 'v14') {
2235 // Before Node.js 14, pipeline() does not fully support async iterators and does not always
2236 // properly handle when the socket close/end events are out of order.
2237 request_.on('socket', s => {
2238 let endedWithEventsCount;
2239 s.prependListener('end', () => {
2240 endedWithEventsCount = s._eventsCount;
2241 });
2242 s.prependListener('close', hadError => {
2243 // if end happened before close but the socket didn't emit an error, do it now
2244 if (response && endedWithEventsCount < s._eventsCount && !hadError) {
2245 const error = new Error('Premature close');
2246 error.code = 'ERR_STREAM_PREMATURE_CLOSE';
2247 response.body.emit('error', error);
2248 }
2249 });
2250 });
2251 }
2252
2253 request_.on('response', response_ => {
2254 request_.setTimeout(0);
2255 const headers = fromRawHeaders(response_.rawHeaders);
2256
2257 // HTTP fetch step 5
2258 if (isRedirect(response_.statusCode)) {
2259 // HTTP fetch step 5.2
2260 const location = headers.get('Location');
2261
2262 // HTTP fetch step 5.3
2263 let locationURL = null;
2264 try {
2265 locationURL = location === null ? null : new URL(location, request.url);
2266 } catch {
2267 // error here can only be invalid URL in Location: header
2268 // do not throw when options.redirect == manual
2269 // let the user extract the errorneous redirect URL
2270 if (request.redirect !== 'manual') {
2271 reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect'));
2272 finalize();
2273 return;
2274 }
2275 }
2276
2277 // HTTP fetch step 5.5
2278 switch (request.redirect) {
2279 case 'error':
2280 reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect'));
2281 finalize();
2282 return;
2283 case 'manual':
2284 // Nothing to do
2285 break;
2286 case 'follow': {
2287 // HTTP-redirect fetch step 2
2288 if (locationURL === null) {
2289 break;
2290 }
2291
2292 // HTTP-redirect fetch step 5
2293 if (request.counter >= request.follow) {
2294 reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
2295 finalize();
2296 return;
2297 }
2298
2299 // HTTP-redirect fetch step 6 (counter increment)
2300 // Create a new Request object.
2301 const requestOptions = {
2302 headers: new Headers(request.headers),
2303 follow: request.follow,
2304 counter: request.counter + 1,
2305 agent: request.agent,
2306 compress: request.compress,
2307 method: request.method,
2308 body: clone(request),
2309 signal: request.signal,
2310 size: request.size,
2311 referrer: request.referrer,
2312 referrerPolicy: request.referrerPolicy
2313 };
2314
2315 // when forwarding sensitive headers like "Authorization",
2316 // "WWW-Authenticate", and "Cookie" to untrusted targets,
2317 // headers will be ignored when following a redirect to a domain
2318 // that is not a subdomain match or exact match of the initial domain.
2319 // For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"
2320 // will forward the sensitive headers, but a redirect to "bar.com" will not.
2321 // headers will also be ignored when following a redirect to a domain using
2322 // a different protocol. For example, a redirect from "https://foo.com" to "http://foo.com"
2323 // will not forward the sensitive headers
2324 if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
2325 for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
2326 requestOptions.headers.delete(name);
2327 }
2328 }
2329
2330 // HTTP-redirect fetch step 9
2331 if (response_.statusCode !== 303 && request.body && options_.body instanceof Stream.Readable) {
2332 reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));
2333 finalize();
2334 return;
2335 }
2336
2337 // HTTP-redirect fetch step 11
2338 if (response_.statusCode === 303 || ((response_.statusCode === 301 || response_.statusCode === 302) && request.method === 'POST')) {
2339 requestOptions.method = 'GET';
2340 requestOptions.body = undefined;
2341 requestOptions.headers.delete('content-length');
2342 }
2343
2344 // HTTP-redirect fetch step 14
2345 const responseReferrerPolicy = parseReferrerPolicyFromHeader(headers);
2346 if (responseReferrerPolicy) {
2347 requestOptions.referrerPolicy = responseReferrerPolicy;
2348 }
2349
2350 // HTTP-redirect fetch step 15
2351 resolve(fetch(new Request(locationURL, requestOptions)));
2352 finalize();
2353 return;
2354 }
2355
2356 default:
2357 return reject(new TypeError(`Redirect option '${request.redirect}' is not a valid value of RequestRedirect`));
2358 }
2359 }
2360
2361 // Prepare response
2362 if (signal) {
2363 response_.once('end', () => {
2364 signal.removeEventListener('abort', abortAndFinalize);
2365 });
2366 }
2367
2368 let body = Stream.pipeline(response_, new Stream.PassThrough(), error => {
2369 if (error) {
2370 reject(error);
2371 }
2372 });
2373 // see https://github.com/nodejs/node/pull/29376
2374 /* c8 ignore next 3 */
2375 if (process.version < 'v12.10') {
2376 response_.on('aborted', abortAndFinalize);
2377 }
2378
2379 const responseOptions = {
2380 url: request.url,
2381 status: response_.statusCode,
2382 statusText: response_.statusMessage,
2383 headers,
2384 size: request.size,
2385 counter: request.counter,
2386 highWaterMark: request.highWaterMark
2387 };
2388
2389 // HTTP-network fetch step 12.1.1.3
2390 const codings = headers.get('Content-Encoding');
2391
2392 // HTTP-network fetch step 12.1.1.4: handle content codings
2393
2394 // in following scenarios we ignore compression support
2395 // 1. compression support is disabled
2396 // 2. HEAD request
2397 // 3. no Content-Encoding header
2398 // 4. no content response (204)
2399 // 5. content not modified response (304)
2400 if (!request.compress || request.method === 'HEAD' || codings === null || response_.statusCode === 204 || response_.statusCode === 304) {
2401 response = new Response(body, responseOptions);
2402 resolve(response);
2403 return;
2404 }
2405
2406 // For Node v6+
2407 // Be less strict when decoding compressed responses, since sometimes
2408 // servers send slightly invalid responses that are still accepted
2409 // by common browsers.
2410 // Always using Z_SYNC_FLUSH is what cURL does.
2411 const zlibOptions = {
2412 flush: zlib.Z_SYNC_FLUSH,
2413 finishFlush: zlib.Z_SYNC_FLUSH
2414 };
2415
2416 // For gzip
2417 if (codings === 'gzip' || codings === 'x-gzip') {
2418 body = Stream.pipeline(body, zlib.createGunzip(zlibOptions), error => {
2419 if (error) {
2420 reject(error);
2421 }
2422 });
2423 response = new Response(body, responseOptions);
2424 resolve(response);
2425 return;
2426 }
2427
2428 // For deflate
2429 if (codings === 'deflate' || codings === 'x-deflate') {
2430 // Handle the infamous raw deflate response from old servers
2431 // a hack for old IIS and Apache servers
2432 const raw = Stream.pipeline(response_, new Stream.PassThrough(), error => {
2433 if (error) {
2434 reject(error);
2435 }
2436 });
2437 raw.once('data', chunk => {
2438 // See http://stackoverflow.com/questions/37519828
2439 if ((chunk[0] & 0x0F) === 0x08) {
2440 body = Stream.pipeline(body, zlib.createInflate(), error => {
2441 if (error) {
2442 reject(error);
2443 }
2444 });
2445 } else {
2446 body = Stream.pipeline(body, zlib.createInflateRaw(), error => {
2447 if (error) {
2448 reject(error);
2449 }
2450 });
2451 }
2452
2453 response = new Response(body, responseOptions);
2454 resolve(response);
2455 });
2456 raw.once('end', () => {
2457 // Some old IIS servers return zero-length OK deflate responses, so
2458 // 'data' is never emitted. See https://github.com/node-fetch/node-fetch/pull/903
2459 if (!response) {
2460 response = new Response(body, responseOptions);
2461 resolve(response);
2462 }
2463 });
2464 return;
2465 }
2466
2467 // For br
2468 if (codings === 'br') {
2469 body = Stream.pipeline(body, zlib.createBrotliDecompress(), error => {
2470 if (error) {
2471 reject(error);
2472 }
2473 });
2474 response = new Response(body, responseOptions);
2475 resolve(response);
2476 return;
2477 }
2478
2479 // Otherwise, use response as-is
2480 response = new Response(body, responseOptions);
2481 resolve(response);
2482 });
2483
2484 // eslint-disable-next-line promise/prefer-await-to-then
2485 writeToStream(request_, request).catch(reject);
2486 });
2487}
2488
2489function fixResponseChunkedTransferBadEnding(request, errorCallback) {
2490 const LAST_CHUNK = buffer.Buffer.from('0\r\n\r\n');
2491
2492 let isChunkedTransfer = false;
2493 let properLastChunkReceived = false;
2494 let previousChunk;
2495
2496 request.on('response', response => {
2497 const {headers} = response;
2498 isChunkedTransfer = headers['transfer-encoding'] === 'chunked' && !headers['content-length'];
2499 });
2500
2501 request.on('socket', socket => {
2502 const onSocketClose = () => {
2503 if (isChunkedTransfer && !properLastChunkReceived) {
2504 const error = new Error('Premature close');
2505 error.code = 'ERR_STREAM_PREMATURE_CLOSE';
2506 errorCallback(error);
2507 }
2508 };
2509
2510 const onData = buf => {
2511 properLastChunkReceived = buffer.Buffer.compare(buf.slice(-5), LAST_CHUNK) === 0;
2512
2513 // Sometimes final 0-length chunk and end of message code are in separate packets
2514 if (!properLastChunkReceived && previousChunk) {
2515 properLastChunkReceived = (
2516 buffer.Buffer.compare(previousChunk.slice(-3), LAST_CHUNK.slice(0, 3)) === 0 &&
2517 buffer.Buffer.compare(buf.slice(-2), LAST_CHUNK.slice(3)) === 0
2518 );
2519 }
2520
2521 previousChunk = buf;
2522 };
2523
2524 socket.prependListener('close', onSocketClose);
2525 socket.on('data', onData);
2526
2527 request.on('close', () => {
2528 socket.removeListener('close', onSocketClose);
2529 socket.removeListener('data', onData);
2530 });
2531 });
2532}
2533
2534exports.AbortError = AbortError;
2535exports.Blob = Blob;
2536exports.FetchError = FetchError;
2537exports.File = File;
2538exports.FormData = FormData;
2539exports.Headers = Headers;
2540exports.Request = Request;
2541exports.Response = Response;
2542exports.blobFrom = blobFrom;
2543exports.blobFromSync = blobFromSync;
2544exports.default = fetch;
2545exports.fileFrom = fileFrom;
2546exports.fileFromSync = fileFromSync;
2547exports.isRedirect = isRedirect;
2548/* eslint-enable */
2549//# sourceMappingURL=index.js.map
Note: See TracBrowser for help on using the repository browser.