Branch data Line data Source code
1 [ + ]: 229 : /**
2 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
3 : 229 : * url: https://xhr.spec.whatwg.org/
4 : 229 : *
5 : 229 : * class XMLHttpRequestEventTarget extends EventTarget
6 : 229 : * class XMLHttpRequestUpload extends XMLHttpRequestEventTarget
7 : 229 : * const XMLHttpRequestResponseType
8 : 229 : * export class XMLHttpRequest extends XMLHttpRequestEventTarget
9 : 229 : * export class ProgressEvent extends Event
10 : 229 : * export class FormData
11 : 229 : *
12 : 229 : * function makeEntry
13 : 229 : * function readBlobSync
14 : 229 : * function handleError
15 : 229 : * function requestErrorSteps
16 : 229 : * function setResponseToNetworkError
17 : 229 : *
18 : 229 : * function getResponseMimeType
19 : 229 : * function getFinalMimeType
20 : 229 : * function getFinalEncoding
21 : 229 : * function setDocumentResponse
22 : 229 : * function getTextResponse
23 : 229 : *
24 : 229 : * class ClientRequestData
25 : 229 : * function requestDataURL
26 : 229 : *
27 : 229 : * @module whatwg-xhr
28 : 229 : * @desc Main module - A Node.js implementation of the {@link https://xhr.spec.whatwg.org/ WHATWG XMLHttpRequest Living Standard} for non-browser environments.
29 : 229 : * @version 1.0.0
30 : 229 : * @author Essam A. El-Sherif
31 : 229 : */
32 : 229 :
33 : 229 : /* Import nodeJS core modules */
34 : 229 : import fs from 'node:fs';
35 : 229 : import http from 'node:http';
36 : 229 : import https from 'node:https';
37 : 229 : import process from 'node:process';
38 : 229 : import threads from 'node:worker_threads';
39 : 229 : import { spawn, spawnSync} from 'node:child_process';
40 : 229 : import { dirname } from 'node:path';
41 : 229 : import { fileURLToPath } from 'node:url';
42 : 229 : import { Readable } from 'node:stream';
43 : 229 :
44 : 229 : /**
45 : 229 : * Global object available in all node modules >= v17.0.0
46 : 229 : *
47 : 229 : * DOMException Added in: node v17.0.0
48 : 229 : *
49 : 229 : * @external DOMException
50 : 229 : * @desc Global object available in all node modules >= v17.0.0, defined by {@link https://webidl.spec.whatwg.org/#idl-DOMException WHATWG IDL Living Standard} and implemented by {@link https://nodejs.org/docs/latest/api/globals.html#domexception node.js}.
51 : 229 : */
52 : 229 :
53 : 229 : /**
54 : 229 : * Global object available in all node modules >= v15.4.0.
55 : 229 : *
56 : 229 : * @interface EventTarget
57 : 229 : * @desc A browser-compatible implementation of the EventTarget class, defined by {@link https://dom.spec.whatwg.org/#eventtarget WHATWG DOM Living Standard} and implemented by {@link https://nodejs.org/docs/latest/api/globals.html#eventtarget node.js}.
58 : 229 : */
59 : 229 :
60 : 229 : /**
61 : 229 : * Global object available in all node modules >= v15.4.0.
62 : 229 : *
63 : 229 : * @interface Event
64 : 229 : * @desc A browser-compatible implementation of the Event class, defined by {@link https://dom.spec.whatwg.org/#event WHATWG DOM Living Standard} and implemented by {@link https://nodejs.org/docs/latest/api/globals.html#event node.js}.
65 : 229 : */
66 : 229 :
67 : 229 : /* Import npm-packages dependencies */
68 : 229 : import whatwgEncoding from 'whatwg-encoding';
69 : 229 : import htmlEncodingSniffer from 'html-encoding-sniffer';
70 : 229 :
71 : 229 : /* Import from local helper modules */
72 : 229 : import {
73 : 229 : parse as contentTypeParse,
74 : 229 : format as contentTypeFormat
75 : 229 : } from './helper/content-type.js';
76 : 229 :
77 : 229 : import {
78 : 229 : dataURLProcessor,
79 : 229 : parseMIMEType,
80 : 229 : serializeAMimeType
81 : 229 : } from './helper/whatwg-misc.js';
82 : 229 :
83 : 229 : import {
84 : 229 : forbiddenRequestHeader,
85 : 229 : forbiddenHttpRequestMethod,
86 : 229 : safelyExtractBodyWithType
87 : 229 : } from './helper/whatwg-fetch.js';
88 : 229 :
89 : 229 : import {
90 : 229 : xmlEncodingSniffer
91 : 229 : } from './helper/xml-encoding-sniffer.js';
92 : 229 :
93 : 229 : import {
94 : 229 : File as FilePolyfill
95 : 229 : } from './helper/file-polyfill.js';
96 : 229 :
97 : 229 : /* Emulate commonJS __filename & __dirname global constants */
98 : 229 : const __filename = fileURLToPath(import.meta.url);
99 : 229 : const __dirname = dirname(__filename);
100 : 229 :
101 : 229 : /**
102 : 229 : * Global object added in node modules v20.0.0.
103 : 229 : *
104 : 229 : * @const {object} File
105 : 229 : * @desc The File interface provides information about files and allows JavaScript in a web page to access their content.
106 : 229 : * @see module:file-polyfill.File
107 : 229 : */
108 : 229 : const File = FilePolyfill;
109 : 229 :
110 : 229 : /**
111 : 229 : * @const {boolean} disableHeaderCheck
112 : 229 : * @static
113 : 229 : * @desc Allow request-headers forbidden by {@link https://xhr.spec.whatwg.org/ XHR Specs}.
114 : 229 : */
115 : 229 : export const disableHeaderCheck = false;
116 : 229 :
117 : 229 : /**
118 : 229 : * @func defaultHeaders
119 : 229 : * @return {object} Default headers to include in each XMLHttpRequest.
120 : 229 : */
121 [ + ]: 229 : const defaultHeaders = () => ({
122 : 2970 : 'User-Agent' : 'whatwg-xhr',
123 : 2970 : 'Accept': '*/*',
124 : 2970 : 'Accept-Language': 'en-US,en;q=0.5'
125 : 229 : });
126 : 229 : /****/
127 : 229 :
128 : 229 : /** @const {WeakMap} xmlHttpRequestEventTarget - Map any XMLHttpRequestEventTarget object created to an associated data object. */
129 : 229 : const xmlHttpRequestEventTarget = new WeakMap();
130 : 229 :
131 : 229 : /** @const {WeakMap} xmlHttpRequest - Map any XMLHttpRequest object created to an associated data object. */
132 : 229 : const xmlHttpRequest = new WeakMap();
133 : 229 :
134 : 229 : /** @const {WeakMap} progressEvent - Map any ProgressEvent object created to an associated data object. */
135 : 229 : const progressEvent = new WeakMap();
136 : 229 :
137 : 229 : /** @const {WeakMap} formData - Map any FormData object created to an associated data object. */
138 : 229 : const formData = new WeakMap();
139 : 229 :
140 : 229 : let allowXMLHttpRequestUploadConstructor = false;
141 : 229 :
142 : 229 : /**
143 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
144 : 229 : * url: https://xhr.spec.whatwg.org/#interface-xmlhttprequest
145 : 229 : *
146 : 229 : * interface XMLHttpRequestEventTarget : EventTarget{
147 : 229 : * // event handlers
148 : 229 : * attribute EventHandler onloadstart;
149 : 229 : * attribute EventHandler onprogress;
150 : 229 : * attribute EventHandler onabort;
151 : 229 : * attribute EventHandler onerror;
152 : 229 : * attribute EventHandler onload;
153 : 229 : * attribute EventHandler ontimeout;
154 : 229 : * attribute EventHandler onloadend;
155 : 229 : * };
156 : 229 : *
157 : 229 : * @class XMLHttpRequestEventTarget
158 : 229 : * @extends module:whatwg-xhr~EventTarget
159 : 229 : * @desc Interface that describes the event handlers shared on XMLHttpRequest and XMLHttpRequestUpload interfaces.
160 : 229 : */
161 : 229 : class XMLHttpRequestEventTarget extends EventTarget{
162 [ + ]: 229 : constructor(){
163 : 1506 : super();
164 : 1506 :
165 : 1506 : const eventHandlers = {};
166 : 1506 :
167 : 1506 : eventHandlers['onloadstart'] = null; // xhr.spec. attribute EventHandler
168 : 1506 : eventHandlers['onprogress' ] = null; // xhr.spec. attribute EventHandler
169 : 1506 : eventHandlers['onabort' ] = null; // xhr.spec. attribute EventHandler
170 : 1506 : eventHandlers['onerror' ] = null; // xhr.spec. attribute EventHandler
171 : 1506 : eventHandlers['onload' ] = null; // xhr.spec. attribute EventHandler
172 : 1506 : eventHandlers['ontimeout' ] = null; // xhr.spec. attribute EventHandler
173 : 1506 : eventHandlers['onloadend' ] = null; // xhr.spec. attribute EventHandler
174 : 1506 :
175 : 1506 : // Map any XMLHttpRequestEventTarget object created to an associated data object.
176 : 1506 : xmlHttpRequestEventTarget.set(this, eventHandlers);
177 : 1506 : }
178 : 229 :
179 : 229 : /**
180 : 229 : * @member {function} onloadstart
181 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
182 : 229 : * @instance
183 : 229 : * @desc Get/Set the event handler fired whenever the 'loadstart' event is emitted.
184 : 229 : */
185 [ + ]: 229 : get onloadstart(){ return xmlHttpRequestEventTarget.get(this)['onloadstart']; }
186 : 229 :
187 [ + ]: 229 : set onloadstart(callback){
188 : 248 : this.removeEventListener('loadstart', xmlHttpRequestEventTarget.get(this)['onloadstart']);
189 : 248 : xmlHttpRequestEventTarget.get(this)['onloadstart'] = null;
190 : 248 :
191 [ + ]: 248 : if(typeof callback === 'function'){
192 : 32 : this.addEventListener('loadstart', callback);
193 : 32 : xmlHttpRequestEventTarget.get(this)['onloadstart'] = callback;
194 : 32 : }
195 : 248 : }
196 : 229 :
197 : 229 : /**
198 : 229 : * @member {function} onprogress
199 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
200 : 229 : * @instance
201 : 229 : * @desc Get/Set the event handler fired whenever the 'progress' event is emitted.
202 : 229 : */
203 [ + ]: 229 : get onprogress(){ return xmlHttpRequestEventTarget.get(this)['onprogress']; }
204 : 229 :
205 [ + ]: 229 : set onprogress(callback){
206 : 135 : this.removeEventListener('progress', xmlHttpRequestEventTarget.get(this)['onprogress']);
207 : 135 : xmlHttpRequestEventTarget.get(this)['onprogress'] = null;
208 : 135 :
209 [ + ]: 135 : if(typeof callback === 'function'){
210 : 19 : this.addEventListener('progress', callback);
211 : 19 : xmlHttpRequestEventTarget.get(this)['onprogress'] = callback;
212 : 19 : }
213 : 135 : }
214 : 229 :
215 : 229 : /**
216 : 229 : * @member {function} onabort
217 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
218 : 229 : * @instance
219 : 229 : * @desc Get/Set the event handler fired whenever the 'abort' event is emitted.
220 : 229 : */
221 [ + ]: 229 : get onabort(){ return xmlHttpRequestEventTarget.get(this)['onabort']; }
222 : 229 :
223 [ + ]: 229 : set onabort(callback){
224 : 105 : this.removeEventListener('abort', xmlHttpRequestEventTarget.get(this)['onabort']);
225 : 105 : xmlHttpRequestEventTarget.get(this)['onabort'] = null;
226 : 105 :
227 [ + ]: 105 : if(typeof callback === 'function'){
228 : 3 : this.addEventListener('abort', callback);
229 : 3 : xmlHttpRequestEventTarget.get(this)['onabort'] = callback;
230 : 3 : }
231 : 105 : }
232 : 229 :
233 : 229 : /**
234 : 229 : * @member {function} onerror
235 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
236 : 229 : * @instance
237 : 229 : * @desc Get/Set the event handler fired whenever the 'error' event is emitted.
238 : 229 : */
239 [ + ]: 229 : get onerror(){ return xmlHttpRequestEventTarget.get(this)['onerror']; }
240 : 229 :
241 [ + ]: 229 : set onerror(callback){
242 : 357 : this.removeEventListener('error', xmlHttpRequestEventTarget.get(this)['onerror']);
243 : 357 : xmlHttpRequestEventTarget.get(this)['onerror'] = null;
244 : 357 :
245 [ + ]: 357 : if(typeof callback === 'function'){
246 : 32 : this.addEventListener('error', callback);
247 : 32 : xmlHttpRequestEventTarget.get(this)['onerror'] = callback;
248 : 32 : }
249 : 357 : }
250 : 229 :
251 : 229 : /**
252 : 229 : * @member {function} onload
253 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
254 : 229 : * @instance
255 : 229 : * @desc Get/Set the event handler fired whenever the 'load' event is emitted.
256 : 229 : */
257 [ + ]: 229 : get onload(){ return xmlHttpRequestEventTarget.get(this)['onload']; }
258 : 229 :
259 [ + ]: 229 : set onload(callback){
260 : 409 : this.removeEventListener('load', xmlHttpRequestEventTarget.get(this)['onload']);
261 : 409 : xmlHttpRequestEventTarget.get(this)['onload'] = null;
262 : 409 :
263 [ + ]: 409 : if(typeof callback === 'function'){
264 : 71 : this.addEventListener('load', callback);
265 : 71 : xmlHttpRequestEventTarget.get(this)['onload'] = callback;
266 : 71 : }
267 : 409 : }
268 : 229 :
269 : 229 : /**
270 : 229 : * @member {function} ontimeout
271 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
272 : 229 : * @instance
273 : 229 : * @desc Get/Set the event handler fired whenever the 'timeout' event is emitted.
274 : 229 : */
275 [ + ]: 229 : get ontimeout(){ return xmlHttpRequestEventTarget.get(this)['ontimeout']; }
276 : 229 :
277 [ + ]: 229 : set ontimeout(callback){
278 : 106 : this.removeEventListener('timeout', xmlHttpRequestEventTarget.get(this)['ontimeout']);
279 : 106 : xmlHttpRequestEventTarget.get(this)['ontimeout'] = null;
280 : 106 :
281 [ + ]: 106 : if(typeof callback === 'function'){
282 : 3 : this.addEventListener('timeout', callback);
283 : 3 : xmlHttpRequestEventTarget.get(this)['ontimeout'] = callback;
284 : 3 : }
285 : 106 : }
286 : 229 :
287 : 229 : /**
288 : 229 : * @member {function} onloadend
289 : 229 : * @memberof module:whatwg-xhr~XMLHttpRequestEventTarget
290 : 229 : * @instance
291 : 229 : * @desc Get/Set the event handler fired whenever the 'loadend' event is emitted.
292 : 229 : */
293 [ + ]: 229 : get onloadend(){ return xmlHttpRequestEventTarget.get(this)['onloadend']; }
294 : 229 :
295 [ + ]: 229 : set onloadend(callback){
296 : 383 : this.removeEventListener('loadend', xmlHttpRequestEventTarget.get(this)['onloadend']);
297 : 383 : xmlHttpRequestEventTarget.get(this)['onloadend'] = null;
298 : 383 :
299 [ + ]: 383 : if(typeof callback === 'function'){
300 : 43 : this.addEventListener('loadend', callback);
301 : 43 : xmlHttpRequestEventTarget.get(this)['onloadend'] = callback;
302 : 43 : }
303 : 383 : }
304 : 229 : }
305 : 229 :
306 : 229 : /**
307 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
308 : 229 : * url: https://xhr.spec.whatwg.org/#interface-xmlhttprequest
309 : 229 : *
310 : 229 : * interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
311 : 229 : * };
312 : 229 : *
313 : 229 : * @class XMLHttpRequestUpload
314 : 229 : * @extends module:whatwg-xhr~XMLHttpRequestEventTarget
315 : 229 : * @desc Interface that represents the upload process for a specific XMLHttpRequest.
316 : 229 : */
317 : 229 : class XMLHttpRequestUpload extends XMLHttpRequestEventTarget{
318 [ + ]: 229 : constructor(){
319 : 753 : super();
320 : 753 : if(!allowXMLHttpRequestUploadConstructor)
321 [ - ]: 753 : throw new TypeError(`Illegal constructor.`);
322 : 753 : }
323 : 229 : }
324 : 229 :
325 : 229 : /**
326 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
327 : 229 : * url: https://xhr.spec.whatwg.org/#interface-xmlhttprequest
328 : 229 : *
329 : 229 : * enum XMLHttpRequestResponseType{
330 : 229 : * "",
331 : 229 : * "arraybuffer",
332 : 229 : * "blob",
333 : 229 : * "document",
334 : 229 : * "json",
335 : 229 : * "text"
336 : 229 : * };
337 : 229 : *
338 : 229 : * @const {object} XMLHttpRequestResponseType
339 : 229 : * @readonly
340 : 229 : * @desc Enumerated set of response types.
341 : 229 : */
342 : 229 : const XMLHttpRequestResponseType = {
343 : 229 : '': null,
344 : 229 : 'arraybuffer': null,
345 : 229 : 'blob': null,
346 : 229 : 'document': null,
347 : 229 : 'json': null,
348 : 229 : 'text': null,
349 : 229 : };
350 : 229 :
351 : 229 : Object.preventExtensions(XMLHttpRequestResponseType);
352 : 229 : Object.freeze(XMLHttpRequestResponseType);
353 : 229 :
354 : 229 : /**
355 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
356 : 229 : * url: https://xhr.spec.whatwg.org/#interface-xmlhttprequest
357 : 229 : *
358 : 229 : * interface XMLHttpRequest : XMLHttpRequestEventTarget{
359 : 229 : * constructor();
360 : 229 : *
361 : 229 : * // event handler
362 : 229 : * attribute EventHandler onreadystatechange;
363 : 229 : *
364 : 229 : * // states
365 : 229 : * const unsigned short UNSENT = 0;
366 : 229 : * const unsigned short OPENED = 1;
367 : 229 : * const unsigned short HEADERS_RECEIVED = 2;
368 : 229 : * const unsigned short LOADING = 3;
369 : 229 : * const unsigned short DONE = 4;
370 : 229 : *
371 : 229 : * readonly attribute unsigned short readyState;
372 : 229 : *
373 : 229 : * // request
374 : 229 : * undefined open(ByteString method, USVString url);
375 : 229 : * undefined open(ByteString method, USVString url, boolean async,
376 : 229 : * optional USVString? username = null, optional USVString? password = null);
377 : 229 : * undefined setRequestHeader(ByteString name, ByteString value);
378 : 229 : *
379 : 229 : * attribute unsigned long timeout;
380 : 229 : * attribute boolean withCredentials;
381 : 229 : *
382 : 229 : * [SameObject] readonly attribute XMLHttpRequestUpload upload;
383 : 229 : *
384 : 229 : * undefined send(optional (Document or XMLHttpRequestBodyInit)? body = null);
385 : 229 : * undefined abort();
386 : 229 : *
387 : 229 : * // response
388 : 229 : * readonly attribute USVString responseURL;
389 : 229 : * readonly attribute unsigned short status;
390 : 229 : * readonly attribute ByteString statusText;
391 : 229 : *
392 : 229 : * ByteString getResponseHeader(ByteString name);
393 : 229 : * ByteString getAllResponseHeaders();
394 : 229 : * undefined overrideMimeType(DOMString mime);
395 : 229 : *
396 : 229 : * attribute XMLHttpRequestResponseType responseType;
397 : 229 : *
398 : 229 : * readonly attribute any response;
399 : 229 : * readonly attribute USVString responseText;
400 : 229 : *
401 : 229 : * [Exposed=Window] readonly attribute Document? responseXML;
402 : 229 : * };
403 : 229 : *
404 : 229 : * @class XMLHttpRequest
405 : 229 : * @extends module:whatwg-xhr~XMLHttpRequestEventTarget
406 : 229 : * @static
407 : 229 : * @desc Interface that represents the objects used to interact with a server.
408 : 229 : */
409 : 229 : export class XMLHttpRequest extends XMLHttpRequestEventTarget{
410 [ + ]: 229 : constructor(){
411 : 753 : super();
412 : 753 :
413 : 753 : const requestData = {};
414 : 753 :
415 : 753 : // event handler
416 : 753 : requestData['onreadystatechange'] = null; // xhr.spec. attribute EventHandler
417 : 753 :
418 : 753 : // states - An XMLHttpRequest object has an associated:
419 : 753 : requestData['readyState'] = this.UNSENT; // xhr.spec. readonly attribute unsigned short state: initially unsent.
420 : 753 :
421 : 753 : // request - An XMLHttpRequest object has an associated:
422 : 753 : requestData['timeout'] = 0; // xhr.spec. attribute unsigned long timeout: An unsigned integer, initially 0.
423 : 753 : requestData['withCredentials'] = false; // xhr.spec. attribute boolean cross-origin credentials: A boolean, initially false.
424 : 753 :
425 : 753 : allowXMLHttpRequestUploadConstructor = true; //
426 : 753 : requestData['upload'] = new XMLHttpRequestUpload(); // xhr.spec. readonly attribute XMLHttpRequestUpload upload object:
427 : 753 : allowXMLHttpRequestUploadConstructor = false; // an XMLHttpRequestUpload object.
428 : 753 :
429 : 753 : // response - An XMLHttpRequest object has an associated:
430 : 753 : requestData['status' ] = 0; // xhr.spec. readonly attribute unsigned short status;
431 : 753 : requestData['statusText'] = ''; // xhr.spec. readonly attribute ByteString statusText;
432 : 753 :
433 : 753 : requestData['response' ] = ''; // xhr.spec. readonly attribute any response: a response, initially a network error.
434 : 753 : requestData['responseText'] = ''; // xhr.spec. readonly attribute ByteString responseText.
435 : 753 : requestData['responseType'] = ''; // xhr.spec. response type: one of "","arraybuffer","blob","document","json" and "text"; initially "".
436 : 753 : requestData['responseURL' ] = ''; // xhr.spec. readonly attribute USVString responseURL;
437 : 753 : requestData['responseXML' ] = null; // xhr.spec. [Exposed=Window] readonly attribute Document? responseXML;
438 : 753 :
439 : 753 : // associated data - An XMLHttpRequest object has an associated:
440 : 753 : requestData['sendFlag'] = false; // xhr.spec. send() flag: a flag, initially unset.
441 : 753 : requestData['method' ] = ''; // xhr.spec. request method: a method.
442 : 753 : requestData['url' ] = null; // xhr.spec. request URL: A URL.
443 : 753 : requestData['async' ] = true; // xhr.spec. synchronous flag: a flag, initially unset.
444 : 753 :
445 : 753 : requestData['user' ] = null;
446 : 753 : requestData['password'] = null;
447 : 753 :
448 : 753 : requestData['requestHeaders' ] = {}; // xhr.spec. author request headers: a header list, initially empty.
449 : 753 : requestData['requestHeadersCase'] = {};
450 : 753 :
451 : 753 : requestData['requestBody' ] = null; // xhr.spec. request body: initially null.
452 : 753 : requestData['uploadCompleteFlag'] = false; // xhr.spec. upload complete flag: a flag, initially unset.
453 : 753 : requestData['uploadListenerFlag'] = false; // xhr.spec. upload listener flag: a flag, initially unset.
454 : 753 : requestData['timeoutFlag' ] = false; // xhr.spec. timeout flag: a flag, initially unset.
455 : 753 : requestData['receivedBytes' ] = Buffer.alloc(0); // xhr.spec. received bytes: a byte sequence, initially the empty byte sequence.
456 : 753 : requestData['responseObject' ] = null; // xhr.spec. response object: an object, failure, or null, initially null.
457 : 753 : requestData['fetchController' ] = null; // xhr.spec. fetch controller: a fetch controller, initially a new fetch controller.
458 : 753 : requestData['overrideMimeType' ] = null; // xhr.spec. A MIME type or null, initially null. Can get a value when overrideMimeType() is invoked.
459 : 753 :
460 : 753 : requestData['_redirectCount' ] = 0; // fetch.spec.2.2.5. A request has an associated redirect count. Unless stated otherwise, it is zero.
461 : 753 : requestData['_responseAbort' ] = false; // fetch.spec.2.2.6. A response can have an associated aborted flag, which is initially unset.
462 : 753 : requestData['_responseNetworkError'] = false;
463 : 753 : requestData['_responseBody' ] = null; // fetch.spec.2.2.6. A response has an associated body (null or a body). Unless stated otherwise it is null.
464 : 753 :
465 : 753 : // Map any XMLHttpRequest object created to an associated data object.
466 : 753 : xmlHttpRequest.set(this, requestData);
467 : 753 : }
468 : 229 :
469 : 229 : /**
470 : 229 : * @member {function} onreadystatechange
471 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
472 : 229 : * @instance
473 : 229 : * @desc Get/Set the event handler fired whenever there's change in readyState.
474 : 229 : */
475 : 229 : get onreadystatechange(){ return xmlHttpRequest.get(this)['onreadystatechange']; }
476 : 229 :
477 [ + ]: 229 : set onreadystatechange(callback){
478 : 231 : this.removeEventListener('readystatechange', xmlHttpRequest.get(this)['onreadystatechange']);
479 : 231 : xmlHttpRequest.get(this)['onreadystatechange'] = null;
480 : 231 :
481 [ + ]: 231 : if(typeof callback === 'function'){
482 : 142 : this.addEventListener('readystatechange', callback);
483 : 142 : xmlHttpRequest.get(this)['onreadystatechange'] = callback;
484 : 142 : }
485 : 231 : }
486 : 229 :
487 : 229 : /**
488 : 229 : * @member {number} readyState
489 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
490 : 229 : * @readonly
491 : 229 : * @instance
492 : 229 : * @desc Get the state an XMLHttpRequest client is in.
493 : 229 : */
494 [ + ]: 229 : get readyState(){ return xmlHttpRequest.get(this)['readyState']; }
495 : 229 :
496 : 229 : /**
497 : 229 : * @member {object} upload
498 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
499 : 229 : * @readonly
500 : 229 : * @instance
501 : 229 : * @desc Get an XMLHttpRequestUpload object that can be observed to monitor an upload's progress.
502 : 229 : */
503 [ + ]: 229 : get upload(){ return xmlHttpRequest.get(this)['upload']; }
504 : 229 :
505 : 229 : /**
506 : 229 : * @member {number} status
507 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
508 : 229 : * @readonly
509 : 229 : * @instance
510 : 229 : * @desc Get the numerical HTTP status code of the server response.
511 : 229 : */
512 [ + ]: 229 : get status(){ return xmlHttpRequest.get(this)['status']; }
513 : 229 :
514 : 229 : /**
515 : 229 : * @member {string} statusText
516 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
517 : 229 : * @readonly
518 : 229 : * @instance
519 : 229 : * @desc Get the HTTP status message of the server response.
520 : 229 : */
521 [ + ]: 229 : get statusText(){ return xmlHttpRequest.get(this)['statusText']; }
522 : 229 :
523 : 229 : /**
524 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
525 : 229 : * url: https://xhr.spec.whatwg.org/#the-response-attribute
526 : 229 : *
527 : 229 : * client.response
528 : 229 : *
529 : 229 : * Returns the response body.
530 : 229 : *
531 : 229 : * The response getter steps are:
532 : 229 : * 1. If this’s response type is the empty string or "text", then:
533 : 229 : * 1. If this’s state is not loading or done, then return the empty string.
534 : 229 : * 2. Return the result of getting a text response for this.
535 : 229 : * 2. If this’s state is not done, then return null.
536 : 229 : * 3. If this’s response object is failure, then return null.
537 : 229 : * 4. If this’s response object is non-null, then return it.
538 : 229 : * 5. If this’s response type is "arraybuffer", then set this’s response object to a new ArrayBuffer object representing this’s received bytes.
539 : 229 : * If this throws an exception, then set this’s response object to failure and return null.
540 : 229 : * Note: Allocating an ArrayBuffer object is not guaranteed to succeed. [ECMASCRIPT]
541 : 229 : * 6. Otherwise, if this’s response type is "blob", set this’s response object to a new Blob object representing this’s received bytes with type
542 : 229 : * set to the result of get a final MIME type for this.
543 : 229 : * 7. Otherwise, if this’s response type is "document", set a document response for this.
544 : 229 : * 8. Otherwise:
545 : 229 : * 1. Assert: this’s response type is "json".
546 : 229 : * 2. If this’s response’s body is null, then return null.
547 : 229 : * 3. Let jsonObject be the result of running parse JSON from bytes on this’s received bytes. If that threw an exception, then return null.
548 : 229 : * 4. Set this’s response object to jsonObject.
549 : 229 : * 9. Return this’s response object.
550 : 229 : *
551 : 229 : * @member {ArrayBuffer|Blob|Document|object|string} response
552 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
553 : 229 : * @readonly
554 : 229 : * @instance
555 : 229 : * @desc Get the response's body content received from a server following a request being sent.
556 : 229 : */
557 [ + ]: 229 : get response(){
558 : 297 :
559 : 297 : const requestData = xmlHttpRequest.get(this);
560 : 297 :
561 : 297 : // xhr.spec.1. If this’s response type is the empty string or "text", then:
562 [ + ][ + ]: 297 : if(requestData['responseType'] === '' || requestData['responseType'] === 'text'){
563 : 57 :
564 : 57 : // xhr.spec.1.1. If this’s state is not loading or done, then return the empty string.
565 [ + ]: 57 : if(requestData['readyState'] !== this.LOADING && requestData['readyState'] !== this.DONE){
566 : 19 : return '';
567 [ + ]: 19 : }
568 : 38 :
569 : 38 : // xhr.spec.1.2. Return the result of getting a text response for this.
570 : 38 : return getTextResponse.call(this);
571 [ + ]: 38 : }
572 : 28 :
573 : 28 : // xhr.spec.2. If this’s state is not done, then return null.
574 : 28 : if(requestData['readyState'] !== this.DONE)
575 [ + ][ - ]: 297 : return null;
576 : 28 :
577 : 28 : // xhr.spec.3. If this’s response object is failure, then return null.
578 : 28 : if(requestData['responseObject'] === 'failure')
579 [ + ][ - ]: 297 : return null;
580 : 28 :
581 : 28 : // xhr.spec.4. If this’s response object is non-null, then return it.
582 : 28 : if(requestData['responseObject'] !== null)
583 [ + ][ + ]: 297 : return requestData['responseObject'];
584 : 13 :
585 : 13 : // xhr.spec.5. If this’s response type is "arraybuffer"
586 [ + ]: 297 : if(requestData['responseType'] === 'arraybuffer'){
587 : 1 : // xhr.spec.5. set this’s response object to a new ArrayBuffer object representing this’s received bytes.
588 : 1 : try{
589 : 1 : requestData['responseObject'] = new Uint8Array(requestData['receivedBytes']).buffer;
590 : 1 : }
591 : 1 : /* node:coverage disable */
592 [ - ]: : catch(e){
593 : : // xhr.spec.5. If this throws an exception, then set this’s response object to failure and return null.
594 : : requestData['responseObject'] = 'failure';
595 : : return null;
596 : : }
597 : : /* node:coverage enable */
598 [ + ]: 1 : }
599 : 12 : else
600 : 12 : // xhr.spec.6. Otherwise, if this’s response type is "blob",
601 [ + ]: 12 : if(requestData['responseType'] === 'blob'){
602 : 2 :
603 : 2 : // xhr.spec.6. set this’s response object to a new Blob object representing this’s received bytes with type
604 : 2 : // set to the result of get a final MIME type for this.
605 : 2 :
606 : 2 : if(!(requestData['responseObject'] instanceof Blob)){
607 : 2 :
608 : 2 : requestData['responseObject'] = new Blob([requestData['receivedBytes']], {
609 : 2 : endings: 'native',
610 : 2 : type: getFinalMimeType.call(this)
611 : 2 : });
612 : 2 : }
613 [ + ]: 2 : }
614 : 10 : else
615 : 10 : /* node:coverage disable */
616 : : // xhr.spec.7. Otherwise, if this’s response type is "document", set a document response for this.
617 [ - ]: : if(requestData['responseType'] === 'document'){
618 : : // @todo...
619 : : }
620 : : /* node:coverage enable */
621 : 10 : // xhr.spec.8. Otherwise:
622 : 10 : else{
623 : 10 : // xhr.spec.8.1. Assert: this’s response type is "json".
624 : 10 : if(requestData['responseType'] === 'json'){
625 : 10 :
626 : 10 : // xhr.spec.8.2. If this’s response’s body is null, then return null.
627 : 10 : // @author. if(requestData['_responseBody'] === null)
628 : 10 : if(requestData['receivedBytes'].length === 0)
629 [ + ][ + ]: 10 : return null;
630 : 6 :
631 : 6 : // xhr.spec.8.3. Let jsonObject be the result of running parse JSON from bytes on this’s received bytes.
632 : 6 : // If that threw an exception, then return null.
633 : 6 : try{
634 : 6 : let jsonObject = JSON.parse(requestData['receivedBytes']);
635 : 6 :
636 : 6 : // xhr.spec.8.4. Set this’s response object to jsonObject.
637 : 6 : requestData['responseObject'] = jsonObject;
638 [ + ]: 10 : }catch(e){
639 : 4 : return null;
640 : 4 : }
641 : 10 : }
642 [ + ]: 10 : }
643 : 5 : // xhr.spec.9. Return this’s response object.
644 : 5 : return requestData['responseObject'];
645 : 297 : }
646 : 229 :
647 : 229 : /**
648 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
649 : 229 : * url: https://xhr.spec.whatwg.org/#the-responsetext-attribute
650 : 229 : *
651 : 229 : * client.responseText
652 : 229 : *
653 : 229 : * Returns response as text.
654 : 229 : * Throws an "InvalidStateError" DOMException if responseType is not the empty string or "text".
655 : 229 : *
656 : 229 : * The responseText getter steps are:
657 : 229 : * 1. If this’s response type is not the empty string or "text", then throw an "InvalidStateError" DOMException.
658 : 229 : * 2. If this’s state is not loading or done, then return the empty string.
659 : 229 : * 3. Return the result of <getting a text response> for this.
660 : 229 : *
661 : 229 : * @member {string} responseText
662 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
663 : 229 : * @readonly
664 : 229 : * @instance
665 : 229 : * @desc Get the text received from a server following a request being sent.
666 : 229 : */
667 [ + ]: 229 : get responseText(){
668 : 476 : const requestData = xmlHttpRequest.get(this);
669 : 476 :
670 : 476 : // xhr.spec.1. If this’s response type is not the empty string or "text", then throw an "InvalidStateError" DOMException.
671 [ + ]: 476 : if(requestData['responseType'] !== '' && requestData['responseType'] !== 'text')
672 [ - ]: 476 : throw new DOMException(`XMLHttpRequest.responseText getter: responseText is only available if responseType is '' or 'text'.`, 'InvalidAccessError');
673 : 476 :
674 : 476 : // xhr.spec.2. If this’s state is not loading or done, then return the empty string.
675 [ + ][ + ]: 476 : if(requestData['readyState'] !== this.LOADING && requestData['readyState'] !== this.DONE){
676 : 3 : return '';
677 [ + ]: 3 : }
678 : 261 :
679 : 261 : // xhr.spec.3. Return the result of <getting a text response> for this.
680 : 261 : return getTextResponse.call(this);
681 : 476 : }
682 : 229 :
683 : 229 : /**
684 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
685 : 229 : * url: https://xhr.spec.whatwg.org/#the-responsetype-attribute
686 : 229 : *
687 : 229 : * client.responseType[ = value ]
688 : 229 : *
689 : 229 : * Returns the response type.
690 : 229 : * Can be set to change the response type. Values are: the empty string (default), "arraybuffer", "blob", "document", "json", and "text".
691 : 229 : * When set: setting to "document" is ignored if the current global object is not a Window object.
692 : 229 : * When set: throws an "InvalidStateError" DOMException if state is loading or done.
693 : 229 : * When set: throws an "InvalidAccessError" DOMException if the synchronous flag is set and the current global object is a Window object.
694 : 229 : *
695 : 229 : * The responseType getter steps are to return this’s response type.
696 : 229 : * The responseType setter steps are:
697 : 229 : * 1. If the current global object is not a Window object and the given value is "document", then return.
698 : 229 : * 2. If this’s state is loading or done, then throw an "InvalidStateError" DOMException.
699 : 229 : * 3. If the current global object is a Window object and this’s synchronous flag is set, then throw an "InvalidAccessError" DOMException.
700 : 229 : * 4. Set this’s response type to the given value.
701 : 229 : *
702 : 229 : * @member {string} responseType
703 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
704 : 229 : * @instance
705 : 229 : * @desc Get/Set an enumerated string value specifying the type of data contained in the response.
706 : 229 : */
707 [ + ]: 229 : get responseType(){
708 : 456 : return xmlHttpRequest.get(this)['responseType'];
709 : 456 : }
710 : 229 :
711 [ + ]: 229 : set responseType(type){
712 [ + ]: 39 : if(String(type) in XMLHttpRequestResponseType){
713 : 31 : const requestData = xmlHttpRequest.get(this);
714 : 31 :
715 : 31 : // xhr.spec.3. If the current global object is a Window object and this’s synchronous flag is set,
716 : 31 : // then throw an "InvalidAccessError" DOMException.
717 : 31 :
718 : 31 : // @author. xhr.spec.3. will also be applicable to main thread
719 : 31 : if(threads.isMainThread){
720 : 31 : if(!requestData['async'])
721 [ - ]: 31 : throw new DOMException(
722 : 0 : `XMLHttpRequest.responseType setter: synchronous XMLHttpRequests do not support timeout and responseType`,
723 : 0 : 'InvalidAccessError'
724 : 0 : );
725 [ - ]: 31 : }
726 : 0 : /* node:coverage disable */
727 : : else{
728 : : // xhr.spec.1. If the current global object is not a Window object and the given value is "document", then return.
729 : : if(String(type).toLowerCase() === 'document')
730 : : return;
731 : : }
732 : : /* node:coverage enable */
733 : 31 :
734 : 31 : // xhr.spec.2. If this’s state is loading or done, then throw an "InvalidStateError" DOMException.
735 : 31 : if(requestData['readyState'] === this.LOADING || requestData['readyState'] === this.DONE)
736 [ - ]: 31 : throw new DOMException(`XMLHttpRequest.responseType setter: Cannot set 'responseType' property on XMLHttpRequest after 'send()' (when its state is LOADING or DONE).`, 'InvalidStateError');
737 : 31 :
738 : 31 : requestData['responseType'] = String(type).toLowerCase();
739 : 31 : }
740 : 39 : }
741 : 229 :
742 : 229 : /**
743 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
744 : 229 : * url: https://xhr.spec.whatwg.org/#the-responsexml-attribute
745 : 229 : *
746 : 229 : * The responseXML getter
747 : 229 : *
748 : 229 : * client . responseXML
749 : 229 : *
750 : 229 : * Returns the response as document.
751 : 229 : * Throws an "InvalidStateError" DOMException if responseType is not the empty string or "document".
752 : 229 : *
753 : 229 : * The responseXML getter steps are:
754 : 229 : * 1. If this’s response type is not the empty string or "document", then throw an "InvalidStateError" DOMException.
755 : 229 : * 2. If this’s state is not done, then return null.
756 : 229 : * 3. Assert: this’s response object is not failure.
757 : 229 : * 4. If this’s response object is non-null, then return it.
758 : 229 : * 5. Set a document response for this.
759 : 229 : * 6. Return this’s response object.
760 : 229 : *
761 : 229 : * @member {Document|null} responseXML
762 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
763 : 229 : * @readonly
764 : 229 : * @instance
765 : 229 : * @desc Get a Document containing the HTML or XML retrieved by the request, or null.
766 : 229 : */
767 [ + ]: 229 : get responseXML(){
768 : 219 :
769 : 219 : const requestData = xmlHttpRequest.get(this);
770 : 219 :
771 : 219 : /* node:coverage disable */
772 : : // xhr.spec.1. If this’s response type is not the empty string or "document", then throw an "InvalidStateError" DOMException.
773 [ - ][ - ]: : if(this.responseType !== '' && this.responseType !== 'document'){
774 : : throw new DOMException(
775 : : `XMLHttpRequest.responseXML getter: responseXML is only available if responseType is '' or 'document'.`, 'InvalidStateError'); // firefox 102
776 : : }
777 : : /* node:coverage enable */
778 : 219 :
779 : 219 : // xhr.spec.2. If this’s state is not done, then return null.
780 : 219 : if(xmlHttpRequest.get(this)['readyState'] !== this.DONE)
781 [ + ][ + ]: 219 : return null;
782 : 4 :
783 : 4 : // xhr.spec.3. Assert: this’s response object is not failure.
784 : 4 : if(requestData['responseObject'] === 'failure')
785 [ + ][ - ]: 219 : return null;
786 : 4 :
787 : 4 : // xhr.spec.4. If this’s response object is non-null, then return it.
788 : 4 : if(requestData['responseObject'] !== null)
789 [ + ][ - ]: 219 : return requestData['responseObject'];
790 : 4 :
791 : 4 : // xhr.spec.5. Set a document response for this.
792 : 4 : setDocumentResponse.call(this);
793 : 4 :
794 : 4 : // xhr.spec.6. Return this’s response object.
795 : 4 : return xmlHttpRequest.get(this)['responseObject'];
796 : 219 : }
797 : 229 :
798 : 229 : /**
799 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
800 : 229 : * url: https://xhr.spec.whatwg.org/#the-responseurl-attribute
801 : 229 : *
802 : 229 : * The responseURL getter
803 : 229 : *
804 : 229 : * The responseURL getter steps are to return the empty string if this’s response’s URL is null;
805 : 229 : * otherwise its serialization with the exclude fragment flag set.
806 : 229 : *
807 : 229 : * @member {string} responseURL
808 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
809 : 229 : * @readonly
810 : 229 : * @instance
811 : 229 : * @desc Get the serialized URL of the response or the empty string if the URL is null.
812 : 229 : */
813 [ + ]: 229 : get responseURL(){
814 : 212 : const requestData = xmlHttpRequest.get(this);
815 : 212 :
816 [ - ]: 212 : if(requestData['response'] && requestData['response'].url){
817 : 0 : return requestData['response'].url.replace(/#.*$/, '');
818 : 0 : }
819 : 212 :
820 : 212 : return '';
821 : 212 : }
822 : 229 :
823 : 229 : /**
824 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
825 : 229 : * url: https://xhr.spec.whatwg.org/#the-timeout-attribute
826 : 229 : * client.timeout
827 : 229 : *
828 : 229 : * Can be set to a time in milliseconds. When set to a non-zero value will cause fetching to terminate after the given time has passed.
829 : 229 : * When the time has passed, the request has not yet completed, and this’s synchronous flag is unset, a timeout event will then be dispatched,
830 : 229 : * or a "TimeoutError" DOMException will be thrown otherwise (for the send() method).
831 : 229 : * When set: throws an "InvalidAccessError" DOMException if the synchronous flag is set and the current global object is a Window object.
832 : 229 : *
833 : 229 : * The timeout getter steps are to return this’s timeout.
834 : 229 : * The timeout setter steps are:
835 : 229 : * 1. If the current global object is a Window object and this’s synchronous flag is set, then throw an "InvalidAccessError" DOMException.
836 : 229 : * 2. Set this’s timeout to the given value.
837 : 229 : * Note: This implies that the timeout attribute can be set while fetching is in progress. If that occurs it will still be measured relative
838 : 229 : * to the start of fetching.
839 : 229 : *
840 : 229 : * @member {number} timeout
841 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
842 : 229 : * @instance
843 : 229 : * @desc Get/Set an unsigned long representing the number of milliseconds a request can take before automatically being terminated.
844 : 229 : */
845 : 229 : get timeout(){ return xmlHttpRequest.get(this)['timeout']; }
846 : 229 :
847 [ + ]: 229 : set timeout(int){
848 : 3 :
849 : 3 : // xhr.spec.1. If the current global object is a Window object and this’s synchronous flag is set, then throw an "InvalidAccessError" DOMException.
850 [ - ]: 3 : if(globalThis.Window && !xmlHttpRequest.get(this)['async'])
851 [ - ]: 3 : throw new DOMException(`XMLHttpRequest.timeout setter: synchronous XMLHttpRequests do not support timeout and responseType`, 'InvalidAccessError');
852 : 3 :
853 : 3 : // @author. xhr.spec.1. will also be applicable to main thread
854 : 3 : if(threads.isMainThread && !xmlHttpRequest.get(this)['async'])
855 [ - ]: 3 : throw new DOMException(`XMLHttpRequest.timeout setter: synchronous XMLHttpRequests do not support timeout and responseType`, 'InvalidAccessError');
856 : 3 :
857 : 3 : // xhr.spec.2. Set this’s timeout to the given value.
858 [ - ]: 3 : xmlHttpRequest.get(this)['timeout'] = isNaN(int) ? 0 : int;
859 : 3 : }
860 : 229 :
861 : 229 : /**
862 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
863 : 229 : * url: https://xhr.spec.whatwg.org/#the-withcredentials-attribute
864 : 229 : * client.withCredentials
865 : 229 : *
866 : 229 : * True when credentials are to be included in a cross-origin request.
867 : 229 : * False when they are to be excluded in a cross-origin request and when cookies are to be ignored in its response. Initially false.
868 : 229 : * When set: throws an "InvalidStateError" DOMException if state is not unsent or opened, or if the send() flag is set.
869 : 229 : *
870 : 229 : * The withCredentials getter steps are to return this’s cross-origin credentials.
871 : 229 : * The withCredentials setter steps are:
872 : 229 : * 1. If this’s state is not unsent or opened, then throw an "InvalidStateError" DOMException.
873 : 229 : * 2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
874 : 229 : * 3. Set this’s cross-origin credentials to the given value.
875 : 229 : *
876 : 229 : * @member {boolean} withCredentials
877 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
878 : 229 : * @instance
879 : 229 : * @desc Get/Set a boolean value that indicates whether or not cross-site Access-Control requests should be made using credentials.
880 : 229 : */
881 : 229 : get withCredentials(){ return xmlHttpRequest.get(this)['withCredentials']; }
882 : 229 :
883 [ + ]: 229 : set withCredentials(cred = false){
884 : 1 : const requestData = xmlHttpRequest.get(this);
885 : 1 :
886 : 1 : // xhr.spec.1. If this’s state is not unsent or opened, then throw an "InvalidStateError" DOMException.
887 : 1 : // xhr.spec.2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
888 : 1 : if(requestData['readyState'] > this.OPENED || requestData['sendFlag'])
889 [ - ]: 1 : throw new DOMException(`XMLHttpRequest.withCredentials setter: XMLHttpRequest must not be sending.`, 'InvalidStateError');
890 : 0 :
891 : 0 : // xhr.spec.3. Set this’s cross-origin credentials to the given value.
892 : 0 : xmlHttpRequest.get(this)['withCredentials'] = Boolean(cred);
893 : 1 : }
894 : 229 : } // @endof class XMLHttpRequest extends XMLHttpRequestEventTarget
895 : 229 :
896 : 229 : /**
897 : 229 : * @const {number} UNSENT
898 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
899 : 229 : * @instance
900 : 229 : * @default 0
901 : 229 : * @desc State of XMLHttpRequest object, client has been created, open() not called yet.
902 : 229 : */
903 : 229 : Object.defineProperty(XMLHttpRequest.prototype, 'UNSENT', {value: 0, writable: false, enumerable: true, configurable: false});
904 : 229 :
905 : 229 : /**
906 : 229 : * @const {number} OPENED
907 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
908 : 229 : * @instance
909 : 229 : * @default 1
910 : 229 : * @desc State of XMLHttpRequest object, open() has been called.
911 : 229 : */
912 : 229 : Object.defineProperty(XMLHttpRequest.prototype, 'OPENED', {value: 1, writable: false, enumerable: true, configurable: false});
913 : 229 :
914 : 229 : /**
915 : 229 : * @const {number} HEADERS_RECEIVED
916 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
917 : 229 : * @instance
918 : 229 : * @default 2
919 : 229 : * @desc State of XMLHttpRequest object, send() has been called, headers and status are available.
920 : 229 : */
921 : 229 : Object.defineProperty(XMLHttpRequest.prototype, 'HEADERS_RECEIVED', {value: 2, writable: false, enumerable: true, configurable: false});
922 : 229 :
923 : 229 : /**
924 : 229 : * @const {number} LOADING
925 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
926 : 229 : * @instance
927 : 229 : * @default 3
928 : 229 : * @desc State of XMLHttpRequest object, downloading, responseText holds partial data.
929 : 229 : */
930 : 229 : Object.defineProperty(XMLHttpRequest.prototype, 'LOADING', {value: 3, writable: false, enumerable: true, configurable: false});
931 : 229 :
932 : 229 : /**
933 : 229 : * @const {number} DONE
934 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
935 : 229 : * @instance
936 : 229 : * @default 4
937 : 229 : * @desc State of XMLHttpRequest object, the operation is complete.
938 : 229 : */
939 : 229 : Object.defineProperty(XMLHttpRequest.prototype, 'DONE', {value: 4, writable: false, enumerable: true, configurable: false});
940 : 229 :
941 : 229 : /**
942 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
943 : 229 : * url: https://xhr.spec.whatwg.org/#the-open()-method
944 : 229 : *
945 : 229 : * client.open(method, url [, async = true [, username = null [, password = null]]])
946 : 229 : *
947 : 229 : * Sets the request method, request URL, and synchronous flag.
948 : 229 : * Throws a "SyntaxError" DOMException if either method is not a valid method or url cannot be parsed.
949 : 229 : * Throws a "SecurityError" DOMException if method is a case-insensitive match for `CONNECT`, `TRACE`, or `TRACK`.
950 : 229 : * Throws an "InvalidAccessError" DOMException if async is false, the current global object is a Window object, and the timeout attribute is not zero
951 : 229 : * or the responseType attribute is not the empty string.
952 : 229 : *
953 : 229 : * Synchronous XMLHttpRequest outside of workers is in the process of being removed from the web platform as it has detrimental effects to the end user’s
954 : 229 : * experience. (This is a long process that takes many years.) Developers must not pass false for the async argument when the current global object is a
955 : 229 : * Window object. User agents are strongly encouraged to warn about such usage in developer tools and may experiment with throwing an "InvalidAccessError"
956 : 229 : * DOMException when it occurs.
957 : 229 : *
958 : 229 : * The open(method, url) and open(method, url, async, username, password) method steps are:
959 : 229 : *
960 : 229 : * 1. If this’s relevant global object is a Window object and its associated Document is not fully active, then throw an "InvalidStateError" DOMException.
961 : 229 : * 2. If method is not a method, then throw a "SyntaxError" DOMException.
962 : 229 : * 3. If method is a forbidden method, then throw a "SecurityError" DOMException.
963 : 229 : * 4. Normalize method.
964 : 229 : * 5. Let parsedURL be the result of parsing url with this’s relevant settings object’s API base URL and this’s relevant settings object’s API URL
965 : 229 : * character encoding.
966 : 229 : * 6. If parsedURL is failure, then throw a "SyntaxError" DOMException.
967 : 229 : * 7. If the async argument is omitted, set async to true, and set username and password to null.
968 : 229 : * Note: Unfortunately legacy content prevents treating the async argument being undefined identical from it being omitted.
969 : 229 : * 8. If parsedURL’s host is non-null, then:
970 : 229 : * 1. If the username argument is not null, set the username given parsedURL and username.
971 : 229 : * 2. If the password argument is not null, set the password given parsedURL and password.
972 : 229 : * 9. If async is false, the current global object is a Window object, and either this’s timeout is not 0 or this’s response type is not the empty
973 : 229 : * string, then throw an "InvalidAccessError" DOMException.
974 : 229 : * 10. Terminate this’s fetch controller.
975 : 229 : * Note: A fetch can be ongoing at this point.
976 : 229 : * 11. Set variables associated with the object as follows:
977 : 229 : * Unset this’s send() flag.
978 : 229 : * Unset this’s upload listener flag.
979 : 229 : * Set this’s request method to method.
980 : 229 : * Set this’s request URL to parsedURL.
981 : 229 : * Set this’s synchronous flag if async is false; otherwise unset this’s synchronous flag.
982 : 229 : * Empty this’s author request headers.
983 : 229 : * Set this’s response to a network error.
984 : 229 : * Set this’s received bytes to the empty byte sequence.
985 : 229 : * Set this’s response object to null.
986 : 229 : * Note: Override MIME type is not overridden here as the overrideMimeType() method can be invoked before the open() method.
987 : 229 : * 12. If this’s state is not opened, then:
988 : 229 : * 1. Set this’s state to opened.
989 : 229 : * 2. Fire an event named readystatechange at this.
990 : 229 : *
991 : 229 : * @method open
992 : 229 : * @instance
993 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
994 : 229 : * @param {string} method - The HTTP request method to use, such as "GET", "POST", "PUT", "DELETE", etc. Ignored for non-HTTP(S) URLs.
995 : 229 : * @param {string} url - A string or any other object with a stringifier that provides the URL of the resource to request.
996 : 229 : * @param {boolean} is_async - (Optional) Boolean parameter, defaulting to true, indicating whether or not to perform the operation asynchronously.
997 : 229 : * @param {string} username - (Optional) user name to use for authentication purposes; by default, this is the null value.
998 : 229 : * @param {string} password - (Optional) password to use for authentication purposes; by default, this is the null value.
999 : 229 : * @desc XMLHttpRequest method that initializes a newly-created request, or re-initializes an existing one.
1000 : 229 : * @requires module:whatwg-fetch.forbiddenHttpRequestMethod
1001 : 229 : */
1002 [ + ]: 229 : XMLHttpRequest.prototype.open = function open(method, url, is_async = true, username = null, password = null){
1003 : 772 :
1004 : 772 : // xhr.spec.1. If this’s relevant global object is a Window object and its associated Document is not fully active, then throw an "InvalidStateError" DOMException.
1005 [ - ]: 772 : if(globalThis.Window && !document.isConnected)
1006 [ - ]: 772 : throw new DOMException(`XMLHttpRequest.open: ......`, 'InvalidStateError');
1007 : 772 :
1008 : 772 : // xhr.spec.2. If method is not a method, then throw a "SyntaxError" DOMException
1009 : 772 : if(typeof method === 'function')
1010 [ - ]: 772 : throw new DOMException(`XMLHttpRequest.open: an invalid or illegal method string was specified`, 'SyntaxError');
1011 : 772 :
1012 : 772 : // xhr.spec.2. If method is not a method, then throw a "SyntaxError" DOMException
1013 : 772 : method = String(method);
1014 : 772 : if(/[^\w]/.test(method))
1015 [ + ][ + ]: 772 : throw new DOMException(`XMLHttpRequest.open: an invalid or illegal method string was specified`, 'SyntaxError');
1016 : 537 :
1017 : 537 : // xhr.spec.3. If method is a forbidden method, then throw a "SecurityError" DOMException
1018 : 537 : if(forbiddenHttpRequestMethod.has(String(method).toLowerCase()))
1019 [ + ][ + ]: 772 : throw new DOMException(`XMLHttpRequest.open: the operation is insecure.`, 'SecurityError');
1020 : 528 :
1021 : 528 : // xhr.spec.4. Normalize method.
1022 : 528 : // fetch.spec. To normalize a method, if it is a byte-case-insensitive match for `DELETE`, `GET`, `HEAD`, `OPTIONS`, `POST`, or `PUT`, byte-uppercase it.
1023 [ + ][ + ]: 772 : method = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'].find(m => m === String(method).toUpperCase()) || String(method);
1024 : 772 :
1025 : 772 : // xhr.spec.5. Let parsedURL be the result of parsing url with this’s relevant settings object’s API base URL and
1026 : 772 : // this’s relevant settings object’s API URL character encoding.
1027 : 772 : let parsedURL;
1028 : 772 : try{
1029 : 772 : parsedURL = new URL(url);
1030 : 772 : }
1031 [ + ]: 772 : catch(e){
1032 : 1 : // xhr.spec.6. If parsedURL is failure, then throw a "SyntaxError" DOMException
1033 : 1 : throw new DOMException(`XMLHttpRequest.open: an invalid or illegal url string was specified`, 'SyntaxError');
1034 [ + ]: 1 : }
1035 : 527 :
1036 : 527 : // xhr.spec.7. If the async argument is omitted, set async to true, and set username and password to null.
1037 : 527 : // @author. already handled by default arguments: open(method, url, is_async = true, username = null, password = null)
1038 : 527 :
1039 : 527 : // xhr.spec.8. If parsedURL’s host is non-null, then:
1040 [ + ]: 772 : if(parsedURL.host){
1041 : 511 : // xhr.spec.8.1. If the username argument is not null, set the username given parsedURL and username.
1042 [ + ]: 511 : if(username){
1043 : 1 : /*
1044 : 1 : * URL Living Standard — Last Updated 24 May 2023
1045 : 1 : * url: https://url.spec.whatwg.org/#set-the-username
1046 : 1 : *
1047 : 1 : * To set the username given a url and username, set url’s username to the result of running UTF-8 percent-encode on username
1048 : 1 : * using the userinfo percent-encode set.
1049 : 1 : */
1050 : 1 : parsedURL.username = username;
1051 : 1 : }
1052 : 511 : // xhr.spec.8.2. If the password argument is not null, set the password given parsedURL and password.
1053 [ + ]: 511 : if(password){
1054 : 1 : /*
1055 : 1 : * URL Living Standard — Last Updated 24 May 2023
1056 : 1 : * url: https://url.spec.whatwg.org/#set-the-password
1057 : 1 : *
1058 : 1 : * To set the password given a url and password, set url’s password to the result of running UTF-8 percent-encode on password
1059 : 1 : * using the userinfo percent-encode set.
1060 : 1 : */
1061 : 1 : parsedURL.password = password;
1062 : 1 : }
1063 [ + ]: 511 : }
1064 : 527 :
1065 : 527 : const requestData = xmlHttpRequest.get(this);
1066 : 527 :
1067 : 527 : // xhr.spec.9. If async is false, the current global object is a Window object, and either this’s timeout is not 0 or this’s response type is not the empty
1068 : 527 : // string, then throw an "InvalidAccessError" DOMException.
1069 [ - ][ - ]: 772 : if(globalThis.Window && is_async === false && (requestData['timeout'] || requestData['responseType']))
1070 [ + ][ - ]: 772 : throw new DOMException(`XMLHttpRequest.open: synchronous XMLHttpRequests do not support timeout and responseType`, 'InvalidAccessError');
1071 : 527 :
1072 : 527 : // @author. xhr.spec.9. will also be applicable to main thread
1073 [ + ]: 772 : if(threads.isMainThread && is_async === false && (requestData['timeout'] || requestData['responseType']))
1074 [ + ][ + ]: 772 : throw new DOMException(`XMLHttpRequest.open: synchronous XMLHttpRequests do not support timeout and responseType`, 'InvalidAccessError');
1075 : 522 :
1076 : 522 : // xhr.spec.10. Terminate this’s fetch controller. Note: A fetch can be ongoing at this point.
1077 [ + ]: 772 : if(requestData['fetchController']){
1078 : 2 : requestData['fetchController'].destroy();
1079 : 2 : requestData['fetchController'] = null;
1080 [ + ]: 2 : }
1081 : 522 :
1082 : 522 : // xhr.spec.11. Set variables associated with the object as follows:
1083 : 522 : // xhr.spec.11. Unset this’s send() flag.
1084 : 522 : requestData['sendFlag'] = false;
1085 : 522 :
1086 : 522 : // xhr.spec.11. Unset this’s upload listener flag.
1087 : 522 : requestData['uploadListenerFlag'] = false;
1088 : 522 :
1089 : 522 : // xhr.spec.11. Set this’s request method to method.
1090 : 522 : requestData['method'] = method;
1091 : 522 :
1092 : 522 : // xhr.spec.11. Set this’s request URL to parsedURL.
1093 : 522 : requestData['url'] = parsedURL.href;
1094 : 522 :
1095 : 522 : // xhr.spec.11. Set this’s synchronous flag if async is false; otherwise unset this’s synchronous flag.
1096 : 522 : requestData['async'] = !(is_async === false);
1097 : 522 :
1098 : 522 : // xhr.spec.11. Empty this’s author request headers.
1099 : 522 :
1100 : 522 : requestData['requestHeaders' ] = {};
1101 : 522 : requestData['requestHeadersCase'] = {};
1102 : 522 :
1103 : 522 : // xhr.spec.11. Set this’s response to a network error.
1104 : 522 : setResponseToNetworkError.call(this);
1105 : 522 :
1106 : 522 : // xhr.spec.11. Set this’s received bytes to the empty byte sequence.
1107 : 522 : requestData['receivedBytes'] = Buffer.alloc(0);
1108 : 522 :
1109 : 522 : // xhr.spec.11. Set this’s response object to null.
1110 : 522 : requestData['responseObject'] = null;
1111 : 522 :
1112 : 522 : // xhr.spec.12. If this’s state is not opened, then:
1113 [ + ]: 772 : if(requestData['readyState'] !== this.OPENED){
1114 : 514 :
1115 : 514 : // xhr.spec.12.1. Set this’s state to opened.
1116 : 514 : requestData['readyState'] = this.OPENED;
1117 : 514 :
1118 : 514 : // xhr.spec.12.2. Fire an event named readystatechange at this.
1119 : 514 : this.dispatchEvent(new Event('readystatechange'));
1120 : 514 : }
1121 : 229 : }; // @endof method XMLHttpRequest.prototype.open
1122 : 229 :
1123 : 229 : /**
1124 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
1125 : 229 : * url: https://xhr.spec.whatwg.org/#the-abort()-method
1126 : 229 : *
1127 : 229 : * client.abort()
1128 : 229 : * Cancels any network activity.
1129 : 229 : *
1130 : 229 : * The abort() method steps are:
1131 : 229 : * 1. Abort this’s fetch controller.
1132 : 229 : * 2. If this’s state is opened with this’s send() flag set, headers received, or loading, then run the request error steps for this and abort.
1133 : 229 : * 3. If this’s state is done, then set this’s state to unsent and this’s response to a network error.
1134 : 229 : * Note: No readystatechange event is dispatched.
1135 : 229 : *
1136 : 229 : * @method abort
1137 : 229 : * @instance
1138 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
1139 : 229 : * @desc XMLHttpRequest method that aborts the request if it has already been sent.
1140 : 229 : */
1141 [ + ]: 229 : XMLHttpRequest.prototype.abort = function abort(){
1142 : 6 :
1143 : 6 : const requestData = xmlHttpRequest.get(this);
1144 : 6 :
1145 : 6 : // xhr.spec.1. Abort this’s fetch controller
1146 [ + ]: 6 : if(requestData['fetchController']){
1147 : 3 : requestData['fetchController'].destroy();
1148 : 3 : requestData['fetchController'] = null;
1149 : 3 : }
1150 : 6 :
1151 : 6 : // xhr.spec.2. If this’s state is opened with this’s send() flag set, headers received,
1152 : 6 : // or loading, then run the request error steps for this and abort.
1153 : 6 : if(
1154 [ + ][ + ]: 6 : requestData['readyState'] === this.OPENED && requestData['sendFlag'] ||
1155 [ + ]: 6 : requestData['readyState'] === this.HEADERS_RECEIVED ||
1156 : 1 : requestData['readyState'] === this.LOADING
1157 [ + ]: 6 : ){
1158 : 5 : requestErrorSteps.call(this, 'abort');
1159 : 5 : }
1160 : 6 :
1161 : 6 : // xhr.spec.3. If this’s state is done, then set this’s state to unsent and this’s response to a network error.
1162 [ + ]: 6 : if(requestData['readyState'] === this.DONE){
1163 : 5 :
1164 : 5 : requestData['readyState'] = this.UNSENT;
1165 : 5 : setResponseToNetworkError.call(this);
1166 : 5 : }
1167 : 229 : }; // @endof method XMLHttpRequest.prototype.abort
1168 : 229 :
1169 : 229 : /**
1170 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
1171 : 229 : * url: https://xhr.spec.whatwg.org/#the-setrequestheader()-method
1172 : 229 : *
1173 : 229 : * client.setRequestHeader(name, value)
1174 : 229 : * Appends a value to an existing request header or adds a new request header.
1175 : 229 : * Throws an "InvalidStateError" DOMException if either state is not opened or the send() flag is set.
1176 : 229 : * Throws a "SyntaxError" DOMException if name is not a header name or if value is not a header value.
1177 : 229 : *
1178 : 229 : * The setRequestHeader(name, value) method must run these steps:
1179 : 229 : * 1. If this’s state is not opened, then throw an "InvalidStateError" DOMException.
1180 : 229 : * 2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
1181 : 229 : * 3. Normalize value.
1182 : 229 : * 4. If name is not a header name or value is not a header value, then throw a "SyntaxError" DOMException.
1183 : 229 : * 5. An empty byte sequence represents an empty header value.
1184 : 229 : * 6. If (name, value) is a forbidden request-header, then return.
1185 : 229 : * 7. Combine (name, value) in this’s author request headers.
1186 : 229 : *
1187 : 229 : * @method setRequestHeader
1188 : 229 : * @instance
1189 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
1190 : 229 : * @param {string} name - The name of the header whose value is to be set.
1191 : 229 : * @param {string} value - The value to set as the body of the header.
1192 : 229 : * @desc XMLHttpRequest method that sets the value of an HTTP request header.
1193 : 229 : * @requires module:whatwg-fetch.forbiddenRequestHeader
1194 : 229 : */
1195 [ + ]: 229 : XMLHttpRequest.prototype.setRequestHeader = function setRequestHeader(name, value){
1196 : 2436 :
1197 : 2436 : // @author. validate the number of arguments
1198 : 2436 : if(arguments.length < 2)
1199 [ + ][ + ]: 2436 : throw new TypeError(`XMLHttpRequest.setRequestHeader: At least 2 arguments required, but only ${arguments.length} passed`); // firefox 102
1200 : 1610 :
1201 : 1610 : const requestData = xmlHttpRequest.get(this);
1202 : 1610 :
1203 : 1610 : // xhr.spec.1. If this’s state is not opened, then throw an "InvalidStateError" DOMException.
1204 : 1610 : if(requestData['readyState'] !== this.OPENED)
1205 [ + ][ + ]: 2436 : throw new DOMException(`XMLHttpRequest.send: XMLHttpRequest state must be OPENED.`, 'InvalidStateError'); // firefox 102
1206 : 1605 :
1207 : 1605 : // xhr.spec.2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
1208 : 1605 : if(requestData['sendFlag'])
1209 [ + ][ + ]: 2436 : throw new DOMException(`XMLHttpRequest.send: XMLHttpRequest must not be sending.`, 'InvalidStateError'); // firefox 102
1210 : 1604 :
1211 : 1604 : // xhr.spec.3. Normalize value.
1212 : 1604 : // fetch.spec. To normalize a byte sequence potentialValue, remove any leading and trailing HTTP whitespace bytes from potentialValue.
1213 : 1604 : value = String(value).trim();
1214 : 1604 :
1215 : 1604 : // xhr.spec.4. If name is not a header name or value is not a header value, then throw a "SyntaxError" DOMException.
1216 : 1604 : try{
1217 : 1604 : http.validateHeaderName(name);
1218 : 1604 : }
1219 [ + ]: 2436 : catch(err){
1220 : 71 :
1221 : 71 : name = String(name);
1222 [ + ]: 71 : for(let i = 0; i < name.length; i++){
1223 [ + ]: 110 : if(name[i].codePointAt(0) > 0xFF){
1224 : 2 : let msg = '';
1225 : 2 :
1226 : 2 : msg += `XMLHttpRequest.setRequestHeader: `;
1227 : 2 : msg += `Cannot convert argument 1 to ByteString because the character at index ${i} `;
1228 : 2 : msg += `has value ${name[i].codePointAt(0)} which is greater than 255.`
1229 : 2 :
1230 : 2 : throw new TypeError(msg); // firefox 102
1231 : 2 : }
1232 [ + ]: 110 : }
1233 : 69 :
1234 : 69 : throw new DOMException(`XMLHttpRequest.setRequestHeader: Invalid header name.`, 'SyntaxError'); // firefox 102
1235 [ + ]: 69 : }
1236 : 1533 :
1237 : 1533 : try{
1238 : 1533 : http.validateHeaderValue(name, value);
1239 : 1533 : }
1240 [ + ]: 2436 : catch(err){
1241 : 4 :
1242 [ + ]: 4 : for(let i = 0; i < value.length; i++){
1243 [ + ]: 10 : if(value[i].codePointAt(0) > 0xFF){
1244 : 1 : let msg = '';
1245 : 1 :
1246 : 1 : msg += `XMLHttpRequest.setRequestHeader: `;
1247 : 1 : msg += `Cannot convert argument 2 to ByteString because the character at index ${i} `;
1248 : 1 : msg += `has value ${value[i].codePointAt(0)} which is greater than 255.`
1249 : 1 :
1250 : 1 : throw new TypeError(msg); // firefox 102
1251 : 1 : }
1252 [ + ]: 10 : }
1253 : 3 :
1254 : 3 : throw new DOMException(`XMLHttpRequest.setRequestHeader: Invalid header value.`, 'SyntaxError'); // firefox 102
1255 [ + ]: 3 : }
1256 : 1529 :
1257 : 1529 : // xhr.spec.6. If (name, value) is a forbidden request-header, then return.
1258 [ + ]: 2436 : if(forbiddenRequestHeader(name, value)){
1259 : 22 : return;
1260 [ + ]: 22 : }
1261 : 1507 :
1262 : 1507 : const headers = requestData['requestHeaders'];
1263 : 1507 : const headersCase = requestData['requestHeadersCase'];
1264 : 1507 :
1265 : 1507 : // xhr.spec.7. Combine (name, value) in this’s author request headers.
1266 [ + ]: 2436 : if(headersCase[name.toLowerCase()]){
1267 : 13 : let nameCase = headersCase[name.toLowerCase()];
1268 : 13 : headers[nameCase] = headers[nameCase] ? headers[nameCase] + ', ' + value : value;
1269 [ + ]: 13 : }
1270 : 1494 : else{
1271 : 1494 : headersCase[name.toLowerCase()] = name;
1272 : 1494 : headers[name] = value;
1273 : 1494 : }
1274 : 229 : }; // @endof method XMLHttpRequest.prototype.setRequestHeader
1275 : 229 :
1276 : 229 : /**
1277 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
1278 : 229 : * url: https://xhr.spec.whatwg.org/#the-send()-method
1279 : 229 : *
1280 : 229 : * client.send([body = null])
1281 : 229 : * Initiates the request. The body argument provides the request body, if any, and is ignored if the request method is GET or HEAD.
1282 : 229 : * Throws an "InvalidStateError" DOMException if either state is not opened or the send() flag is set.
1283 : 229 : *
1284 : 229 : * The send(body) method steps are:
1285 : 229 : * 1. If this’s state is not opened, then throw an "InvalidStateError" DOMException.
1286 : 229 : * 2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
1287 : 229 : * 3. If this’s request method is `GET` or `HEAD`, then set body to null.
1288 : 229 : * 4. If body is not null, then:
1289 : 229 : * 1. Let extractedContentType be null.
1290 : 229 : * 2. If body is a Document, then set this’s request body to body, serialized, converted, and UTF-8 encoded.
1291 : 229 : * 3. Otherwise:
1292 : 229 : * 1. Let bodyWithType be the result of safely extracting body.
1293 : 229 : * 2. Set this’s request body to bodyWithType’s body.
1294 : 229 : * 3. Set extractedContentType to bodyWithType’s type.
1295 : 229 : * 4. Let originalAuthorContentType be the result of getting `Content-Type` from this’s author request headers.
1296 : 229 : * 5. If originalAuthorContentType is non-null, then:
1297 : 229 : * 1. If body is a Document or a USVString, then:
1298 : 229 : * 1. Let contentTypeRecord be the result of parsing originalAuthorContentType.
1299 : 229 : * 2. If contentTypeRecord is not failure, contentTypeRecord’s parameters["charset"] exists, and parameters["charset"] is not an ASCII case-insensitive
1300 : 229 : * match for "UTF-8", then:
1301 : 229 : * 1. Set contentTypeRecord’s parameters["charset"] to "UTF-8".
1302 : 229 : * 2. Let newContentTypeSerialized be the result of serializing contentTypeRecord.
1303 : 229 : * 3. Set (`Content-Type`, newContentTypeSerialized) in this’s author request headers.
1304 : 229 : * 6. Otherwise:
1305 : 229 : * 1. If body is an HTML document, then set (`Content-Type`, `text/html;charset=UTF-8`) in this’s author request headers.
1306 : 229 : * 2. Otherwise, if body is an XML document, set (`Content-Type`, `application/xml;charset=UTF-8`) in this’s author request headers.
1307 : 229 : * 3. Otherwise, if extractedContentType is not null, set (`Content-Type`, extractedContentType) in this’s author request headers.
1308 : 229 : * 5. If one or more event listeners are registered on this’s upload object, then set this’s upload listener flag.
1309 : 229 : * 6. Let req be a new request, initialized as follows:
1310 : 229 : * method This’s request method.
1311 : 229 : * URL This’s request URL.
1312 : 229 : * header list This’s author request headers.
1313 : 229 : * unsafe-request flag Set.
1314 : 229 : * body This’s request body.
1315 : 229 : * client This’s relevant settings object.
1316 : 229 : * mode "cors".
1317 : 229 : * use-CORS-preflight flag Set if this’s upload listener flag is set.
1318 : 229 : * credentials mode If this’s cross-origin credentials is true, then "include"; otherwise "same-origin".
1319 : 229 : * use-URL-credentials flag Set if this’s request URL includes credentials.
1320 : 229 : * initiator type "xmlhttprequest".
1321 : 229 : * 7. Unset this’s upload complete flag.
1322 : 229 : * 8. Unset this’s timed out flag.
1323 : 229 : * 9. If req’s body is null, then set this’s upload complete flag.
1324 : 229 : * 10. Set this’s send() flag.
1325 : 229 : * 11. If this’s synchronous flag is unset, then:
1326 : 229 : * 1. Fire a progress event named loadstart at this with 0 and 0.
1327 : 229 : * 2. Let requestBodyTransmitted be 0.
1328 : 229 : * 3. Let requestBodyLength be req’s body’s length, if req’s body is non-null; otherwise 0.
1329 : 229 : * 4. Assert: requestBodyLength is an integer.
1330 : 229 : * 5. If this’s upload complete flag is unset and this’s upload listener flag is set, then fire a progress event named loadstart at this’s upload object
1331 : 229 : * with requestBodyTransmitted and requestBodyLength.
1332 : 229 : * 6. If this’s state is not opened or this’s send() flag is unset, then return.
1333 : 229 : * 7. Let processRequestBodyChunkLength, given a bytesLength, be these steps:
1334 : 229 : * 1. Increase requestBodyTransmitted by bytesLength.
1335 : 229 : * 2. If not roughly 50ms have passed since these steps were last invoked, then return.
1336 : 229 : * 3. If this’s upload listener flag is set, then fire a progress event named progress at this’s upload object with requestBodyTransmitted and
1337 : 229 : * requestBodyLength.
1338 : 229 : * Note: These steps are only invoked when new bytes are transmitted.
1339 : 229 : * 8. Let processRequestEndOfBody be these steps:
1340 : 229 : * 1. Set this’s upload complete flag.
1341 : 229 : * 2. If this’s upload listener flag is unset, then return.
1342 : 229 : * 3. Fire a progress event named progress at this’s upload object with requestBodyTransmitted and requestBodyLength.
1343 : 229 : * 4. Fire a progress event named load at this’s upload object with requestBodyTransmitted and requestBodyLength.
1344 : 229 : * 5. Fire a progress event named loadend at this’s upload object with requestBodyTransmitted and requestBodyLength.
1345 : 229 : * 9. Let processResponse, given a response, be these steps:
1346 : 229 : * 1. Set this’s response to response.
1347 : 229 : * 2. Handle errors for this.
1348 : 229 : * 3. If this’s response is a network error, then return.
1349 : 229 : * 4. Set this’s state to headers received.
1350 : 229 : * 5. Fire an event named readystatechange at this.
1351 : 229 : * 6. If this’s state is not headers received, then return.
1352 : 229 : * 7. If this’s response’s body is null, then run handle response end-of-body for this and return.
1353 : 229 : * 8. Let length be the result of extracting a length from this’s response’s header list.
1354 : 229 : * 9. If length is not an integer, then set it to 0.
1355 : 229 : * 10. Let processBodyChunk given bytes be these steps:
1356 : 229 : * 1. Append bytes to this’s received bytes.
1357 : 229 : * 2. If not roughly 50ms have passed since these steps were last invoked, then return.
1358 : 229 : * 3. If this’s state is headers received, then set this’s state to loading.
1359 : 229 : * 4. Fire an event named readystatechange at this.
1360 : 229 : * Note: Web compatibility is the reason readystatechange fires more often than this’s state changes.
1361 : 229 : * 5. Fire a progress event named progress at this with this’s received bytes’s length and length.
1362 : 229 : * 11. Let processEndOfBody be this step: run handle response end-of-body for this.
1363 : 229 : * 12. Let processBodyError be these steps:
1364 : 229 : * 1. Set this’s response to a network error.
1365 : 229 : * 2. Run handle errors for this.
1366 : 229 : * 13. Incrementally read this’s response’s body, given processBodyChunk, processEndOfBody, processBodyError, and this’s relevant global object.
1367 : 229 : * 10. Set this’s fetch controller to the result of fetching req with processRequestBodyChunkLength set to processRequestBodyChunkLength,
1368 : 229 : * processRequestEndOfBody set to processRequestEndOfBody, and processResponse set to processResponse.
1369 : 229 : * 11. Let now be the present time.
1370 : 229 : * 12. Run these steps in parallel:
1371 : 229 : * 1. Wait until either req’s done flag is set or this’s timeout is not 0 and this’s timeout milliseconds have passed since now.
1372 : 229 : * 2. If req’s done flag is unset, then set this’s timed out flag and terminate this’s fetch controller.
1373 : 229 : * 12. Otherwise, if this’s synchronous flag is set:
1374 : 229 : * 1. Let processedResponse be false.
1375 : 229 : * 2. Let processResponseConsumeBody, given a response and nullOrFailureOrBytes, be these steps:
1376 : 229 : * 1. If nullOrFailureOrBytes is not failure, then set this’s response to response.
1377 : 229 : * 2. If nullOrFailureOrBytes is a byte sequence, then append nullOrFailureOrBytes to this’s received bytes.
1378 : 229 : * 3. Set processedResponse to true.
1379 : 229 : * 3. Set this’s fetch controller to the result of fetching req with processResponseConsumeBody set to processResponseConsumeBody and
1380 : 229 : * useParallelQueue set to true.
1381 : 229 : * 4. Let now be the present time.
1382 : 229 : * 5. Pause until either processedResponse is true or this’s timeout is not 0 and this’s timeout milliseconds have passed since now.
1383 : 229 : * 6. If processedResponse is false, then set this’s timed out flag and terminate this’s fetch controller.
1384 : 229 : * 7. Report timing for this’s fetch controller given the current global object.
1385 : 229 : * 8. Run handle response end-of-body for this.
1386 : 229 : *
1387 : 229 : * To handle response end-of-body for an XMLHttpRequest object xhr, run these steps:
1388 : 229 : * 1. Handle errors for xhr.
1389 : 229 : * 2. If xhr’s response is a network error, then return.
1390 : 229 : * 3. Let transmitted be xhr’s received bytes’s length.
1391 : 229 : * 4. Let length be the result of extracting a length from this’s response’s header list.
1392 : 229 : * 5. If length is not an integer, then set it to 0.
1393 : 229 : * 6. If xhr’s synchronous flag is unset, then fire a progress event named progress at xhr with transmitted and length.
1394 : 229 : * 7. Set xhr’s state to done.
1395 : 229 : * 8. Unset xhr’s send() flag.
1396 : 229 : * 9. Fire an event named readystatechange at xhr.
1397 : 229 : * 10. Fire a progress event named load at xhr with transmitted and length.
1398 : 229 : * 11. Fire a progress event named loadend at xhr with transmitted and length.
1399 : 229 : *
1400 : 229 : * @method send
1401 : 229 : * @instance
1402 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
1403 : 229 : * @param {Document| Blob|ArrayBuffer|TypedArray|DataView|FormData|URLSearchParams|string|null} body - The request body.
1404 : 229 : * @desc XMLHttpRequest method that sends the request to the server.
1405 : 229 : * @requires module:whatwg-fetch.safelyExtractBodyWithType
1406 : 229 : * @requires module:helper-content-type.parse
1407 : 229 : * @requires module:helper-content-type.format
1408 : 229 : */
1409 [ + ]: 229 : XMLHttpRequest.prototype.send = function send(body = null){
1410 : 766 :
1411 : 766 : const xhr = this;
1412 : 766 : const requestData = xmlHttpRequest.get(xhr);
1413 : 766 :
1414 : 766 : // xhr.spec.1. If this’s state is not opened, then throw an "InvalidStateError" DOMException.
1415 : 766 : if(requestData['readyState'] !== xhr.OPENED)
1416 [ + ][ + ]: 766 : throw new DOMException(`XMLHttpRequest.send: XMLHttpRequest state must be OPENED.`, 'InvalidStateError'); // firefox 102
1417 : 492 :
1418 : 492 : // xhr.spec.2. If this’s send() flag is set, then throw an "InvalidStateError" DOMException.
1419 : 492 : if(requestData['sendFlag'])
1420 [ + ][ + ]: 766 : throw new DOMException(`XMLHttpRequest.send: XMLHttpRequest must not be sending.`, 'InvalidStateError'); // firefox 102
1421 : 491 :
1422 : 491 : // xhr.spec.3. If this’s request method is `GET` or `HEAD`, then set body to null.
1423 [ + ][ + ]: 766 : if(requestData['method'].toUpperCase() === 'GET' || requestData['method'].toUpperCase() === 'HEAD'){
1424 : 263 : body = null;
1425 [ + ]: 263 : }
1426 : 491 :
1427 : 491 : // xhr.spec.4. If body is not null, then:
1428 [ + ][ + ]: 766 : if(body !== null && body !== ''){
1429 : 85 :
1430 : 85 : // xhr.spec.4.1. Let extractedContentType be null.
1431 : 85 : let extractedContentType = null;
1432 : 85 :
1433 : 85 : // xhr.spec.4.2. If body is a Document, then set this’s request body to body, serialized, converted, and UTF-8 encoded.
1434 : 85 : if(globalThis.Document && body instanceof globalThis.Document){
1435 : 85 : // @todo. xhr.spec.4.2. set this’s request body to body, serialized, converted, and UTF-8 encoded.
1436 : 85 : }
1437 : 85 : // xhr.spec.4.3. Otherwise:
1438 : 85 : else{
1439 : 85 : // xhr.spec.4.3.1. Let bodyWithType be the result of safely extracting body.
1440 : 85 : let bodyWithType = safelyExtractBodyWithType(body);
1441 : 85 :
1442 : 85 : // xhr.spec.4.3.2. Set this’s request body to bodyWithType’s body.
1443 : 85 : requestData['requestBody'] = bodyWithType.body;
1444 : 85 :
1445 : 85 : // xhr.spec.4.3.3. Set extractedContentType to bodyWithType’s type.
1446 : 85 : extractedContentType = bodyWithType.type;
1447 : 85 : }
1448 : 85 :
1449 : 85 : // xhr.spec.4.4. Let originalAuthorContentType be the result of getting `Content-Type` from this’s author request headers.
1450 : 85 : let originalAuthorContentType = null;
1451 : 85 :
1452 : 85 : if(requestData['requestHeadersCase']['content-type'])
1453 [ + ]: 85 : originalAuthorContentType = requestData['requestHeaders'][requestData['requestHeadersCase']['content-type']];
1454 : 85 :
1455 : 85 : // xhr.spec.4.5. If originalAuthorContentType is non-null, then:
1456 [ + ]: 85 : if(originalAuthorContentType !== null){
1457 : 31 :
1458 : 31 : // xhr.spec.4.5.1. If body is a Document or a USVString, then:
1459 : 31 : if(
1460 : 31 : (globalThis.Document && body instanceof globalThis.Document) ||
1461 [ + ][ + ]: 31 : typeof body === 'string' || typeof requestData['requestBody'].source === 'string'){
1462 : 21 : try{
1463 : 21 : // xhr.spec.4.5.1.1. Let contentTypeRecord be the result of parsing originalAuthorContentType.
1464 : 21 : const contentTypeRecord = contentTypeParse(originalAuthorContentType);
1465 : 21 :
1466 : 21 : // xhr.spec.4.5.1.2. If contentTypeRecord is not failure,
1467 : 21 : // contentTypeRecord’s parameters["charset"] exists,
1468 : 21 : // and parameters["charset"] is not an ASCII case-insensitive match for "UTF-8", then:
1469 : 21 : let charset = contentTypeRecord.parameters['charset'];
1470 [ + ][ + ]: 21 : if(charset && charset.toUpperCase() !== 'UTF-8'){
1471 : 6 :
1472 : 6 : // xhr.spec.4.5.1.2.1. Set contentTypeRecord’s parameters["charset"] to "UTF-8".
1473 : 6 : contentTypeRecord.parameters['charset'] = 'UTF-8';
1474 : 6 :
1475 : 6 : // xhr.spec.4.5.1.2.2. Let newContentTypeSerialized be the result of serializing contentTypeRecord.
1476 : 6 : let newContentTypeSerialized = contentTypeFormat(contentTypeRecord);
1477 : 6 :
1478 : 6 : // xhr.spec.4.5.1.2.3. Set (`Content-Type`, newContentTypeSerialized) in this’s author request headers.
1479 : 6 : // @author. first delete the existing Content-Type header
1480 : 6 : delete requestData['requestHeaders'][requestData['requestHeadersCase']['content-type']];
1481 : 6 : delete requestData['requestHeadersCase']['content-type'];
1482 : 6 :
1483 : 6 : this.setRequestHeader('Content-Type', newContentTypeSerialized);
1484 : 6 : }
1485 [ + ]: 21 : }catch(e){}
1486 : 21 : }
1487 [ + ]: 31 : }
1488 : 54 : // xhr.spec.4.6. Otherwise:
1489 : 54 : else{
1490 : 54 : // xhr.spec.4.6.1. If body is an HTML document, then set (`Content-Type`, `text/html;charset=UTF-8`) in this’s author request headers.
1491 : 54 : if(globalThis.Document && body instanceof globalThis.Document && body.type === 'html')
1492 : 54 : this.setRequestHeader('Content-Type', 'text/html;charset=UTF-8');
1493 : 54 : else
1494 : 54 : // xhr.spec.4.6.2. Otherwise, if body is an XML document, set (`Content-Type`, `application/xml;charset=UTF-8`) in this’s author request headers.
1495 : 54 : if(globalThis.Document && body instanceof globalThis.Document && body.type === 'xml')
1496 : 54 : this.setRequestHeader('Content-Type', 'application/xml;charset=UTF-8');
1497 : 54 : else
1498 : 54 : // xhr.spec.4.6.3. Otherwise, if extractedContentType is not null, set (`Content-Type`, extractedContentType) in this’s author request headers.
1499 [ + ]: 54 : if(extractedContentType){
1500 : 41 : this.setRequestHeader('Content-Type', extractedContentType);
1501 : 41 : }
1502 : 54 : }
1503 [ + ]: 766 : } // @endof if(body !== null && body !== undefined)
1504 : 491 :
1505 : 491 : // xhr.spec.5. If one or more event listeners are registered on this’s upload object, then set this’s upload listener flag.
1506 : 491 : if(
1507 [ + ]: 766 : requestData['upload']['onloadstart'] !== null ||
1508 [ + ]: 766 : requestData['upload']['onprogress' ] !== null ||
1509 [ + ]: 766 : requestData['upload']['onabort' ] !== null ||
1510 [ + ]: 766 : requestData['upload']['onerror' ] !== null ||
1511 [ + ]: 766 : requestData['upload']['onload' ] !== null ||
1512 [ + ]: 766 : requestData['upload']['ontimeout' ] !== null ||
1513 : 470 : requestData['upload']['onloadend' ] !== null
1514 [ + ]: 766 : ){
1515 : 25 : requestData['uploadListenerFlag'] = true;
1516 [ + ]: 25 : }
1517 : 466 : else
1518 : 466 : // @author. an implementation dependent method to inspect if any event listeners had been registered by calling addEventListener.
1519 : 466 : // @todo. suggest an implementation independent method
1520 [ + ][ + ]: 466 : if(Object.getOwnPropertySymbols(requestData['upload']).filter(e => requestData['upload'][e].size).length){
1521 : 3 : requestData['uploadListenerFlag'] = true;
1522 [ + ]: 3 : }
1523 : 491 :
1524 : 491 : // xhr.spec.6. Let req be a new request, initialized as follows:
1525 [ + ][ - ]: 766 : let req_auth = requestData['user'] ? `${requestData['user']}:${requestData['password'] === undefined ? '' : requestData['password']}` : undefined;
1526 : 766 :
1527 : 766 : // xhr.spec.6. method: This’s request method.
1528 : 766 : let req_method = requestData['method'];
1529 : 766 :
1530 : 766 : // xhr.spec.6. URL: This’s request URL.
1531 : 766 : let req_url = new URL(requestData['url']);
1532 : 766 :
1533 : 766 : let req_host = req_url.hostname;
1534 [ + ]: 766 : let req_port = req_url.port || (req_url.protocol === 'https:' ? 443 : 80);
1535 [ + ][ + ]: 766 : let req_url_path = req_url.pathname + (req_url.search ? req_url.search : '');
1536 : 766 : let req_protocol = req_url.protocol;
1537 : 766 :
1538 : 766 : // xhr.spec.6. header list: This’s author request headers.
1539 : 766 : const req_headers = requestData['requestHeaders'];
1540 : 766 : const req_headersCase = requestData['requestHeadersCase'];
1541 : 766 :
1542 [ + ]: 766 : for(let name in defaultHeaders()){
1543 [ + ]: 2292 : if(!req_headersCase[name.toLowerCase()]){
1544 : 1324 : xhr.setRequestHeader(name, defaultHeaders()[name]);
1545 : 1324 : }
1546 [ + ]: 2292 : }
1547 : 491 :
1548 : 491 : // xhr.spec.6. unsafe-request flag: Set.
1549 : 491 : // xhr.spec.6. body: This’s request body.
1550 : 491 : // xhr.spec.6. client: This’s relevant settings object.
1551 : 491 : // xhr.spec.6. mode: "cors".
1552 : 491 : // xhr.spec.6. use-CORS-preflight flag: Set if this’s upload listener flag is set.
1553 : 491 : // xhr.spec.6. credentials mode: If this’s cross-origin credentials is true, then "include"; otherwise "same-origin".
1554 : 491 : // xhr.spec.6. use-URL-credentials flag: Set if this’s request URL includes credentials.
1555 : 491 : // xhr.spec.6. initiator type: "xmlhttprequest".
1556 : 491 :
1557 : 491 : // @author. use http.request(url[, options][, callback])
1558 : 491 : const clientRequestOptions = {
1559 : 491 : agent: false,
1560 : 491 : auth: req_auth,
1561 : 491 : headers: req_headers,
1562 : 491 : host: req_host,
1563 : 491 : port: req_port,
1564 : 491 : method: req_method,
1565 : 491 : path: req_url_path,
1566 : 491 : protocol: req_url.protocol,
1567 : 491 : setHost: true,
1568 : 491 : timeout: requestData['timeout'],
1569 : 491 : withCredentials: requestData['withCredentials'],
1570 : 491 : // insecureHTTPParser: true,
1571 : 491 : url: requestData['url'],
1572 : 491 : };
1573 : 491 :
1574 : 491 : // @author. choose the proper protocol
1575 : 491 : let doClientRequest;
1576 : 491 :
1577 : 491 : if(req_protocol === 'https:')
1578 [ + ][ - ]: 766 : doClientRequest = https.request;
1579 : 491 : else
1580 : 491 : if(req_protocol === 'http:')
1581 [ + ][ + ][ + ]: 491 : doClientRequest = http.request;
1582 : 3 : else
1583 : 3 : if(req_protocol === 'data:')
1584 [ + ][ + ]: 3 : doClientRequest = requestDataURL;
1585 : 3 : else
1586 [ + ]: 3 : doClientRequest = http.request;
1587 : 491 :
1588 : 491 : // xhr.spec.7. Unset this’s upload complete flag.
1589 : 491 : requestData['uploadCompleteFlag'] = false;
1590 : 491 :
1591 : 491 : // xhr.spec.8. Unset this’s timed out flag.
1592 : 491 : requestData['timeoutFlag'] = false;
1593 : 491 :
1594 : 491 : // xhr.spec.9. If req’s body is null, then set this’s upload complete flag.
1595 : 491 : if(requestData['requestBody'] === null)
1596 [ + ][ + ]: 766 : requestData['uploadCompleteFlag'] = true;
1597 : 491 :
1598 : 491 : // xhr.spec.10. Set this’s send() flag.
1599 : 491 : requestData['sendFlag'] = true;
1600 : 491 :
1601 : 491 : // xhr.spec.11. If this’s synchronous flag is unset, then:
1602 [ + ]: 766 : if(requestData['async']){
1603 : 263 :
1604 : 263 : // xhr.spec.11.1. Fire a progress event named loadstart at this with 0 and 0.
1605 : 263 : xhr.dispatchEvent(new ProgressEvent('loadstart', {loaded: 0, total: 0}));
1606 : 263 :
1607 : 263 : // xhr.spec.11.2. Let requestBodyTransmitted be 0.
1608 : 263 : let requestBodyTransmitted = 0;
1609 : 263 :
1610 : 263 : // xhr.spec.11.3. Let requestBodyLength be req’s body’s length, if req’s body is non-null; otherwise 0.
1611 [ + ][ + ]: 263 : let requestBodyLength = requestData['requestBody'] ? requestData['requestBody'].length : 0;
1612 : 263 :
1613 : 263 : // xhr.spec.11.4. Assert: requestBodyLength is an integer.
1614 : 263 : if(isNaN(requestBodyLength))
1615 [ - ]: 263 : requestBodyLength = 0;
1616 : 263 :
1617 : 263 : // xhr.spec.11.5. If this’s upload complete flag is unset and this’s upload listener flag is set,
1618 : 263 : // then fire a progress event named loadstart at this’s upload object with requestBodyTransmitted and requestBodyLength.
1619 [ + ]: 263 : if(!requestData['uploadCompleteFlag'] && requestData['uploadListenerFlag'])
1620 [ + ]: 263 : requestData['upload'].dispatchEvent(new ProgressEvent('loadstart', {loaded: requestBodyTransmitted, total: requestBodyLength}));
1621 : 263 :
1622 : 263 : // xhr.spec.11.6. If this’s state is not opened or this’s send() flag is unset, then return.
1623 [ + ]: 263 : if(requestData['readyState'] !== xhr.OPENED || !requestData['sendFlag'])
1624 [ + ][ + ]: 263 : return;
1625 : 260 :
1626 : 260 : // xhr.spec.11.7. Let processRequestBodyChunkLength, given a bytesLength, be these steps:
1627 : 260 : // Note: These steps are only invoked when new bytes are transmitted.
1628 [ + ]: 260 : const processRequestBodyChunkLength = () => {
1629 : 84 : let bytesLength = requestData['requestBody'].length; // @?????
1630 : 84 :
1631 : 84 : // xhr.spec.11.7.1. Increase requestBodyTransmitted by bytesLength.
1632 : 84 : requestBodyTransmitted += bytesLength;
1633 : 84 :
1634 : 84 : // xhr.spec.11.7.2. If not roughly 50ms have passed since these steps were last invoked, then return.
1635 : 84 : // @todo...
1636 : 84 :
1637 : 84 : // xhr.spec.11.7.3. If this’s upload listener flag is set, then fire a progress event named progress at this’s upload object
1638 : 84 : // with requestBodyTransmitted and requestBodyLength.
1639 [ + ]: 84 : if(requestData['uploadListenerFlag']){
1640 : 19 : requestData['upload'].dispatchEvent(new ProgressEvent('progress', {loaded: requestBodyTransmitted, total: requestBodyLength}));
1641 : 19 : }
1642 : 84 : }
1643 : 260 :
1644 : 260 : // xhr.spec.11.8. Let processRequestEndOfBody be these steps:
1645 [ + ]: 260 : const processRequestEndOfBody = () => {
1646 : 79 :
1647 : 79 : // xhr.spec.11.8.1. Set this’s upload complete flag.
1648 : 79 : requestData['uploadCompleteFlag'] = true;
1649 : 79 :
1650 : 79 : // xhr.spec.11.8.2. If this’s upload listener flag is unset, then return.
1651 [ + ][ + ]: 79 : if(!requestData['uploadListenerFlag']) return;
1652 : 15 :
1653 : 15 : // xhr.spec.11.8.3. Fire a progress event named progress at this’s upload object with requestBodyTransmitted and requestBodyLength.
1654 : 15 : // requestData['upload'].dispatchEvent(new ProgressEvent('progress', {loaded: requestBodyTransmitted, total: requestBodyLength}));
1655 : 15 :
1656 : 15 : // xhr.spec.11.8.4. Fire a progress event named load at this’s upload object with requestBodyTransmitted and requestBodyLength.
1657 : 15 : requestData['upload'].dispatchEvent(new ProgressEvent('load', {loaded: requestBodyTransmitted, total: requestBodyLength}));
1658 : 15 :
1659 : 15 : // xhr.spec.11.8.5. Fire a progress event named loadend at this’s upload object with requestBodyTransmitted and requestBodyLength.
1660 : 15 : requestData['upload'].dispatchEvent(new ProgressEvent('loadend', {loaded: requestBodyTransmitted, total: requestBodyLength}));
1661 : 79 : }
1662 : 260 :
1663 : 260 : // xhr.spec.11.9. Let processResponse, given a response, be these steps:
1664 [ + ]: 260 : const processResponse = (response) => {
1665 : 505 :
1666 : 505 : // xhr.spec.11.9.1. Set this’s response to response.
1667 : 505 : requestData['response'] = response;
1668 : 505 :
1669 : 505 : // xhr.spec.11.9.2. Handle errors for this. // @?????
1670 : 505 : handleError.call(xhr);
1671 : 505 :
1672 : 505 : // xhr.spec.11.9.3. If this’s response is a network error, then return. // @?????
1673 [ + ]: 505 : if(!response.statusCode){
1674 : 1 : setResponseToNetworkError.call(this);
1675 : 1 : return;
1676 [ + ]: 1 : }
1677 : 243 : else
1678 [ + ]: 243 : if( response.headers['location'] &&
1679 : 67 : (
1680 [ + ]: 67 : response.statusCode === 301 ||
1681 [ + ]: 67 : response.statusCode === 302 ||
1682 [ + ]: 67 : response.statusCode === 303 ||
1683 [ + ]: 67 : response.statusCode === 307 ||
1684 : 9 : response.statusCode === 308
1685 : 67 : )
1686 [ + ]: 243 : ){
1687 [ + ]: 59 : if(requestData['_redirectCount'] >= 20){
1688 : 4 :
1689 : 4 : setResponseToNetworkError.call(xhr);
1690 : 4 : handleError.call(xhr);
1691 : 4 :
1692 : 4 : return;
1693 [ + ]: 4 : }
1694 : 97 : requestData['_redirectCount']++;
1695 : 97 :
1696 : 97 : let loc = response.headers['location'];
1697 : 97 :
1698 : 97 : let parsedURL;
1699 : 97 : try{
1700 : 97 : parsedURL = new URL(loc, new URL(requestData['url']).origin);
1701 : 97 : }
1702 : 59 : catch(e){
1703 : 59 : // xhr.spec.open.6. If parsedURL is failure, then throw a "SyntaxError" DOMException
1704 : 59 : throw new DOMException(`XMLHttpRequest.open: an invalid or illegal url string was specified`, 'SyntaxError');
1705 [ + ]: 59 : }
1706 : 97 :
1707 : 97 : requestData['url'] = parsedURL.href;
1708 : 97 : requestData['receivedBytes'] = Buffer.alloc(0);
1709 : 97 : requestData['responseObject'] = null;
1710 : 97 : requestData['sendFlag'] = false;
1711 : 97 : requestData['async'] = true;
1712 : 97 :
1713 : 97 : if(
1714 : 97 : response.statusCode >= 301 &&
1715 [ + ]: 59 : response.statusCode <= 303 &&
1716 [ + ]: 59 : requestData['method'].toUpperCase() !== 'GET' &&
1717 : 4 : requestData['method'].toUpperCase() !== 'HEAD'
1718 [ + ]: 59 : ){
1719 : 4 : requestData['method'] = 'GET';
1720 : 4 : requestData['requestBody'] = body = null;
1721 : 4 :
1722 : 4 : for(let headerCase in requestData['requestHeadersCase'])
1723 [ + ]: 4 : if(
1724 [ + ]: 15 : headerCase === 'content-type' ||
1725 [ + ]: 15 : headerCase === 'content-length' ||
1726 [ + ]: 15 : headerCase === 'content-encoding' ||
1727 [ + ]: 15 : headerCase === 'content-location' ||
1728 : 12 : headerCase === 'content-language'
1729 [ + ]: 15 : ){
1730 : 3 : let header = requestData['requestHeadersCase'][headerCase];
1731 : 3 :
1732 : 3 : delete requestData['requestHeaders'][header];
1733 : 3 : delete requestData['requestHeadersCase'][headerCase];
1734 : 3 : }
1735 [ + ]: 4 : }
1736 : 97 :
1737 : 97 : xhr.onloadstart = null;
1738 : 97 :
1739 : 97 : xhr.upload.onloadstart = null;
1740 : 97 : xhr.upload.onprogress = null;
1741 : 97 : xhr.upload.onabort = null;
1742 : 97 : xhr.upload.onerror = null;
1743 : 97 : xhr.upload.onload = null;
1744 : 97 : xhr.upload.ontimeout = null;
1745 : 97 : xhr.upload.onloadend = null;
1746 : 97 :
1747 : 97 : xhr.send(body);
1748 : 97 :
1749 : 97 : return;
1750 [ + ]: 97 : }
1751 : 184 :
1752 : 184 : requestData['status'] = response.statusCode;
1753 : 184 : requestData['statusText'] = response.statusMessage;
1754 : 184 :
1755 : 184 : // xhr.spec.11.9.4. Set this’s state to headers received.
1756 : 184 : requestData['readyState'] = xhr.HEADERS_RECEIVED;
1757 : 184 :
1758 : 184 : // xhr.spec.11.9.5. Fire an event named readystatechange at this.
1759 : 184 : xhr.dispatchEvent(new Event('readystatechange'));
1760 : 184 :
1761 : 184 : // xhr.spec.11.9.6. If this’s state is not headers received, then return.
1762 [ + ][ - ]: 505 : if(requestData['readyState'] !== xhr.HEADERS_RECEIVED) return;
1763 : 184 :
1764 : 184 : // xhr.spec.11.9.7. If this’s response’s body is null, then run handle response end-of-body for this and return.
1765 : 184 :
1766 : 184 :
1767 : 184 : // xhr.spec.11.9.8. Let length be the result of extracting a length from this’s response’s header list.
1768 : 184 : // xhr.spec.11.9.9. If length is not an integer, then set it to 0.
1769 : 184 : let length = 0;
1770 : 184 :
1771 [ + ]: 505 : if(response.headers['content-length'] && !isNaN(+response.headers['content-length']))
1772 [ + ][ + ]: 505 : length = +response.headers['content-length'];
1773 : 184 :
1774 : 184 : // xhr.spec.11.9.10. Let processBodyChunk given bytes be these steps:
1775 [ + ]: 184 : response.on('data', (chunk) => {
1776 : 318 :
1777 : 318 : // xhr.spec.11.9.10.1. Append bytes to this’s received bytes.
1778 : 318 : requestData['receivedBytes'] = Buffer.concat([requestData['receivedBytes'], chunk]);
1779 : 318 :
1780 : 318 : // xhr.spec.11.9.10.2. If not roughly 50ms have passed since these steps were last invoked, then return.
1781 : 318 : // @todo...
1782 : 318 :
1783 : 318 : // xhr.spec.11.9.10.3. If this’s state is headers received, then set this’s state to loading.
1784 : 318 : if(requestData['readyState'] === xhr.HEADERS_RECEIVED)
1785 [ + ]: 318 : requestData['readyState'] = xhr.LOADING;
1786 : 318 :
1787 : 318 : // xhr.spec.11.9.10.4. Fire an event named readystatechange at this.
1788 : 318 : xhr.dispatchEvent(new Event('readystatechange'));
1789 : 318 :
1790 : 318 : // xhr.spec.11.9.10.5. Fire a progress event named progress at this with this’s received bytes’s length and length.
1791 : 318 : xhr.dispatchEvent(new ProgressEvent('progress', {loaded: requestData['receivedBytes'].length, total: length}));
1792 : 184 : });
1793 : 184 :
1794 : 184 : // xhr.spec.11.9.11. Let processEndOfBody be this step: run handle response end-of-body for this.
1795 [ + ]: 184 : response.on('end', () => {
1796 : 394 :
1797 : 394 : // xhr.spec.11.9.11.1. Handle errors for this. // @?????
1798 : 394 : handleError.call(xhr);
1799 : 394 :
1800 : 394 : // xhr.spec.11.9.11.2. If xhr’s response is a network error, then return. // @?????
1801 [ - ]: 394 : if(!response.statusCode) return;
1802 : 394 :
1803 : 394 : // xhr.spec.11.9.11.3. Let transmitted be xhr’s received bytes’s length.
1804 : 394 : let transmitted = requestData['receivedBytes'].length;
1805 : 394 :
1806 : 394 : // xhr.spec.11.9.11.4. Let length be the result of extracting a length from this’s response’s header list.
1807 : 394 : // xhr.spec.11.9.11.5. If length is not an integer, then set it to 0.
1808 : 394 : let length = 0;
1809 : 394 :
1810 [ + ]: 394 : if(response.headers['content-length'] && !isNaN(+response.headers['content-length']))
1811 [ + ]: 394 : length = +response.headers['content-length'];
1812 : 394 :
1813 : 394 : // xhr.spec.11.9.11.6. If xhr’s synchronous flag is unset, then fire a progress event named progress at xhr with transmitted and length.
1814 : 394 : // @?????
1815 : 394 : if(requestData['async'] && transmitted === 0)
1816 [ + ]: 394 : xhr.dispatchEvent(new ProgressEvent('progress', {loaded: transmitted, total: length}));
1817 : 394 :
1818 : 394 : // xhr.spec.11.9.11.7. Set xhr’s state to done.
1819 : 394 : requestData['readyState'] = xhr.DONE;
1820 : 394 :
1821 : 394 : // xhr.spec.11.9.11.8. Unset xhr’s send() flag.
1822 : 394 : requestData['sendFlag'] = false;
1823 : 394 :
1824 : 394 : // xhr.spec.11.9.11.9. Fire an event named readystatechange at xhr.
1825 : 394 : xhr.dispatchEvent(new Event('readystatechange'));
1826 : 394 :
1827 : 394 : // xhr.spec.11.9.11.10. Fire a progress event named "load" at xhr with transmitted and length.
1828 : 394 : xhr.dispatchEvent(new ProgressEvent('load', {loaded: transmitted, total: length}));
1829 : 394 :
1830 : 394 : // xhr.spec.11.9.11.11. Fire a progress event named loadend at xhr with transmitted and length.
1831 : 394 : xhr.dispatchEvent(new ProgressEvent('loadend', {loaded: transmitted, total: length}));
1832 : 184 : });
1833 : 184 :
1834 : 184 : // xhr.spec.11.9.12. Let processBodyError be these steps:
1835 [ + ]: 184 : response.on('error', (error) => {
1836 : 3 :
1837 : 3 : // xhr.spec.11.9.12.1. Set this’s response to a network error.
1838 : 3 : setResponseToNetworkError.call(xhr);
1839 : 3 :
1840 : 3 : // xhr.spec.11.9.12.2 Run handle errors for this.
1841 : 3 : handleError.call(xhr, error);
1842 : 184 : });
1843 : 260 : }; // @endof const processResponse = (response) => { ... }
1844 : 260 :
1845 : 260 : // xhr.spec.11.10. Set this’s fetch controller to the result of fetching req ... ???????????????????????????????????????????????????
1846 : 260 : let clientRequest = null;
1847 : 260 :
1848 : 260 : try{
1849 : 260 : clientRequest = requestData['fetchController'] = doClientRequest(clientRequestOptions);
1850 [ + ][ + ]: 263 : }catch(err){
1851 : 3 :
1852 : 3 : // xhr.spec.11.9.12.1. Set this’s response to a network error.
1853 : 3 : setResponseToNetworkError.call(xhr);
1854 : 3 :
1855 : 3 : // xhr.spec.11.9.12.2 Run handle errors for this.
1856 : 3 : handleError.call(xhr, err);
1857 : 3 :
1858 : 3 : return;
1859 [ + ]: 3 : }
1860 : 3 :
1861 [ + ]: 3 : clientRequest.on('close', () => {
1862 : 3 : });
1863 : 3 :
1864 [ + ]: 3 : clientRequest.on('error', (err) => {
1865 : 27 : setResponseToNetworkError.call(xhr);
1866 : 27 : handleError.call(xhr, err);
1867 : 3 : });
1868 : 3 :
1869 : 3 : // xhr.spec.11.7. Note: These steps are only invoked when new bytes are transmitted.
1870 [ + ]: 263 : if(requestData['requestBody']){
1871 : 35 : clientRequest.setHeader('Content-Length', requestData['requestBody'].length);
1872 : 35 :
1873 [ + ]: 35 : (async () => {
1874 : 84 : let buffer = Buffer.alloc(0);
1875 : 84 :
1876 [ + ]: 84 : if(requestData['requestBody'].source instanceof Blob){
1877 : 7 : buffer = Buffer.from (await requestData['requestBody'].source.arrayBuffer());
1878 [ + ]: 7 : }
1879 : 28 : else
1880 [ + ]: 28 : if(requestData['requestBody'].source instanceof FormData){
1881 [ + ]: 5 : await new Promise((resolve, reject) => {
1882 [ + ]: 5 : requestData['requestBody'].stream.on('data', (chunk) => {
1883 : 15 : buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
1884 : 5 : });
1885 : 5 :
1886 [ + ]: 5 : requestData['requestBody'].stream.on('end', () => {
1887 : 5 : resolve();
1888 : 5 : });
1889 : 5 : });
1890 [ + ]: 5 : }
1891 : 23 : else{
1892 : 23 : buffer = await Promise.resolve(requestData['requestBody'].source);
1893 : 23 : }
1894 : 84 :
1895 : 84 : clientRequest.write(buffer, processRequestBodyChunkLength); // ?????????????????????????????
1896 : 84 :
1897 : 84 : // xhr.spec.11.8. Let processRequestEndOfBody be these steps:
1898 : 84 : clientRequest.end(processRequestEndOfBody);
1899 : 35 : })();
1900 [ + ][ + ]: 35 : }
1901 : 3 : else{
1902 : 3 : clientRequest.end();
1903 [ + ]: 3 : }
1904 : 3 :
1905 : 3 : // xhr.spec.11.9. Let processResponse, given a response, be these steps:
1906 : 3 : clientRequest.on('response', processResponse);
1907 : 3 :
1908 : 3 : // xhr.spec.11.11. Let now be the present time.
1909 : 3 : let now = Date.now();
1910 : 3 :
1911 : 3 : // xhr.spec.11.12. Run these steps in parallel:
1912 : 3 : // xhr.spec.11.12.1. Wait until either req’s done flag is set or this’s timeout is not 0 and this’s timeout milliseconds have passed since now.
1913 : 3 : // xhr.spec.11.12.2. If req’s done flag is unset, then set this’s timed out flag and terminate this’s fetch controller.
1914 : 3 :
1915 [ + ]: 263 : if(requestData['timeout']){
1916 [ + ]: 3 : setTimeout(() => {
1917 : 3 : if(requestData['readyState'] !== xhr.DONE){
1918 : 3 : requestData['timeoutFlag'] = true;
1919 : 3 : handleError.call(xhr);
1920 : 3 : }
1921 : 3 : }, requestData['timeout']);
1922 : 3 : }
1923 [ + ]: 263 : }
1924 : 228 : // xhr.spec.12. Otherwise, if this’s synchronous flag is set:
1925 : 228 : else
1926 : 228 : {
1927 : 228 : // xhr.spec.12.1. Let <processedResponse> be false.
1928 : 228 : // xhr.spec.12.2. Let <processResponseConsumeBody>, given a <response> and <nullOrFailureOrBytes>, be these steps:
1929 : 228 : // xhr.spec.12.2.1. If <nullOrFailureOrBytes> is not failure, then set this’s response to response.
1930 : 228 : // xhr.spec.12.2.2. If <nullOrFailureOrBytes> is a byte sequence, then append nullOrFailureOrBytes to this’s received bytes.
1931 : 228 : // xhr.spec.12.2.3. Set <processedResponse> to true.
1932 : 228 :
1933 : 228 : // xhr.spec.12.3. Set this’s fetch controller to the result of fetching req with <processResponseConsumeBody> set to processResponseConsumeBody
1934 : 228 : // and useParallelQueue set to true.
1935 : 228 : // xhr.spec.12.4. Let now be the present time.
1936 : 228 : // xhr.spec.12.5. Pause until either processedResponse is true or this’s timeout is not 0 and this’s timeout milliseconds have passed since now.
1937 : 228 : // xhr.spec.12.6. If processedResponse is false, then set this’s timed out flag and terminate this’s fetch controller.
1938 : 228 : // xhr.spec.12.7. Report timing for this’s fetch controller given the current global object.
1939 : 228 : // xhr.spec.12.8. Run handle response end-of-body for this.
1940 : 228 :
1941 : 228 : const contentFile = '.whatwg-xhr-content-' + process.pid;
1942 : 228 : const syncFile = '.whatwg-xhr-sync-' + process.pid;
1943 : 228 : const filename = __filename.replace(/\\/g, '\\\\');
1944 : 228 :
1945 : 228 : fs.writeFileSync(syncFile, '', 'utf8');
1946 : 228 :
1947 : 228 : let defaultHeadersObj = defaultHeaders();
1948 : 228 :
1949 [ + ]: 228 : for(let name in defaultHeadersObj){
1950 [ + ]: 684 : if(req_headers[name] && req_headers[name] === defaultHeadersObj[name]){
1951 : 681 : delete req_headers[name];
1952 : 681 : delete req_headersCase[name.toLowerCase()];
1953 : 681 : }
1954 : 684 : }
1955 : 228 :
1956 : 228 : let headersJSON = JSON.stringify(req_headers);
1957 : 228 :
1958 [ + ]: 228 : let bodyFlag = body !== null && body !== '';
1959 : 228 :
1960 : 228 : const execString = `
1961 : 228 : (async () => {
1962 : 228 : let fs = await import('node:fs');
1963 : 228 : let { XMLHttpRequest } = await import('file:${filename}');
1964 : 228 :
1965 : 228 : let xhr = new XMLHttpRequest;
1966 : 228 :
1967 : 228 : let requestData = {};
1968 : 228 :
1969 : 228 : xhr.onload = () => {
1970 : 228 : requestData['error'] = null;
1971 : 228 :
1972 : 228 : requestData['status'] = xhr.status;
1973 : 228 : requestData['statusText'] = xhr.statusText;
1974 : 228 :
1975 : 228 : requestData['response' ] = xhr.response;
1976 : 228 : requestData['responseText' ] = xhr.responseText;
1977 : 228 : requestData['responseType' ] = xhr.responseType;
1978 : 228 : requestData['responseURL' ] = xhr.responseURL;
1979 : 228 : requestData['responseXML' ] = xhr.responseXML;
1980 : 228 : requestData['responseHeaders'] = xhr.getAllResponseHeaders();
1981 : 228 : };
1982 : 228 :
1983 : 228 : xhr.onerror = (e) => {
1984 : 228 : requestData['error'] = e;
1985 : 228 : }
1986 : 228 :
1987 : 228 : xhr.onloadend = () => {
1988 : 228 : fs.writeFileSync('${contentFile}', JSON.stringify(requestData, null, 2), 'utf8');
1989 : 228 : fs.unlinkSync('${syncFile}');
1990 : 228 : }
1991 : 228 :
1992 : 228 : xhr.open('${req_method}', '${req_url}');
1993 : 228 :
1994 : 228 : let headers = JSON.parse(process.argv[1]);
1995 : 228 : for(let k in headers){
1996 : 228 : xhr.setRequestHeader(k, headers[k]);
1997 : 228 : }
1998 : 228 :
1999 : 228 : if(!${bodyFlag})
2000 : 228 : xhr.send();
2001 : 228 :
2002 : 228 : let body = Buffer.alloc(0);
2003 : 228 :
2004 : 228 : process.stdin.on('data', (chunk) => {
2005 : 228 : body = Buffer.concat([body, chunk]);
2006 : 228 : xhr.send(body);
2007 : 228 : });
2008 : 228 : })();
2009 : 228 : `;
2010 : 228 :
2011 : 228 : let syncProc = spawn(process.argv[0], ['-e', execString, headersJSON]);
2012 : 228 :
2013 [ + ][ + ]: 228 : if(bodyFlag && requestData['requestBody'].source instanceof Blob){
2014 : 10 :
2015 : 10 : requestData['requestBody'].source = readBlobSync(requestData['requestBody'].source);
2016 : 10 : syncProc.stdin.write(requestData['requestBody'].source);
2017 [ + ]: 10 : }
2018 : 218 : else
2019 [ + ][ + ]: 218 : if(bodyFlag && requestData['requestBody'].source instanceof FormData){
2020 : 3 :
2021 : 3 : let buffer = Buffer.alloc(0);
2022 : 3 : if(requestData['requestBody'].blobs){
2023 : 3 : for(let part of requestData['requestBody'].blobs){
2024 : 3 : if(part instanceof Blob){
2025 : 3 : buffer = Buffer.concat([buffer, readBlobSync(part)]);
2026 : 3 : }
2027 : 3 : else
2028 : 3 : if(part instanceof Uint8Array){
2029 : 3 : buffer = Buffer.concat([buffer, part]);
2030 : 3 : }
2031 : 3 : }
2032 : 3 : }
2033 : 3 :
2034 : 3 : requestData['requestBody'].source = buffer;
2035 : 3 : syncProc.stdin.write(requestData['requestBody'].source);
2036 [ + ]: 3 : }
2037 : 215 : else
2038 [ + ]: 215 : if(bodyFlag){
2039 : 36 : syncProc.stdin.write(requestData['requestBody'].source);
2040 : 36 : }
2041 : 228 :
2042 : 228 : /* node:coverage disable */
2043 : : syncProc.stderr.on('data', (chunk) => {
2044 : : let msg = chunk.toString();
2045 : :
2046 : : setResponseToNetworkError.call(xhr);
2047 : : handleError.call(xhr, new Error(msg));
2048 : :
2049 : : process.exit(1);
2050 : : });
2051 : : /* node:coverage enable */
2052 : 228 :
2053 [ + ]: 228 : while(fs.existsSync(syncFile));
2054 : 228 : syncProc.stdin.end();
2055 : 228 : processResult();
2056 : 228 :
2057 [ + ]: 228 : function processResult(){
2058 : 228 :
2059 [ - ]: 228 : let _requestData = JSON.parse(fs.readFileSync(contentFile, 'utf8')) || {};
2060 : 228 : fs.unlinkSync(contentFile);
2061 : 228 :
2062 [ + ]: 228 : if(_requestData['status']){
2063 : 212 :
2064 : 212 : let response = {};
2065 : 212 :
2066 : 212 : requestData['status'] = response.statusCode = _requestData['status'];
2067 : 212 : requestData['statusText'] = response.statusMessage = _requestData['statusText'];
2068 : 212 :
2069 : 212 : requestData['responseType'] = _requestData['responseType'];
2070 : 212 : requestData['responseURL' ] = _requestData['responseURL' ];
2071 : 212 : requestData['responseXML' ] = _requestData['responseXML' ];
2072 : 212 :
2073 : 212 : response.url = _requestData['responseURL'];
2074 : 212 :
2075 : 212 : _requestData['responseHeaders'] = _requestData['responseHeaders'].split('\r\n');
2076 : 212 : _requestData['responseHeaders'].pop();
2077 : 212 :
2078 : 212 : response.headers = {};
2079 [ + ]: 212 : for(let header of _requestData['responseHeaders']){
2080 : 1062 : let [name, value] = header.split('\x3A\x20');
2081 : 1062 : response.headers[name] = value;
2082 : 1062 : }
2083 : 212 :
2084 : 212 : response.rawHeaders = [];
2085 [ + ]: 212 : for(let k in response.headers){
2086 : 1062 : response.rawHeaders.push(k);
2087 : 1062 : response.rawHeaders.push(response.headers[k]);
2088 : 1062 : }
2089 : 212 :
2090 [ + ]: 212 : if(_requestData['response']){
2091 : 109 : let chunk = Buffer.from(_requestData['response']);
2092 : 109 : requestData['receivedBytes'] = Buffer.concat([requestData['receivedBytes'], chunk]);
2093 : 109 : }
2094 : 212 :
2095 : 212 : requestData['response'] = response;
2096 : 212 :
2097 : 212 : // xhr.spec.11.9.11.7. Set xhr’s state to done.
2098 : 212 : requestData['readyState'] = xhr.DONE;
2099 : 212 :
2100 : 212 : // xhr.spec.11.9.11.8. Unset xhr’s send() flag.
2101 : 212 : requestData['sendFlag'] = false;
2102 : 212 :
2103 : 212 : // xhr.spec.11.9.11.9. Fire an event named readystatechange at xhr.
2104 : 212 : xhr.dispatchEvent(new Event('readystatechange'));
2105 : 212 :
2106 : 212 : // xhr.spec.11.9.11.10. Fire a progress event named "load" at xhr with transmitted and length.
2107 : 212 : xhr.dispatchEvent(new ProgressEvent('load', {loaded: requestData['receivedBytes'].length, total: requestData['receivedBytes'].length}));
2108 : 212 :
2109 : 212 : // xhr.spec.11.9.11.11. Fire a progress event named loadend at xhr with transmitted and length.
2110 : 212 : xhr.dispatchEvent(new ProgressEvent('loadend', {loaded: requestData['receivedBytes'].length, total: requestData['receivedBytes'].length}));
2111 [ + ]: 212 : }
2112 : 16 : else{
2113 : 16 : // xhr.spec.11.9.12.1. Set this’s response to a network error.
2114 : 16 : setResponseToNetworkError.call(xhr);
2115 : 16 :
2116 : 16 : // xhr.spec.11.9.12.2 Run handle errors for this.
2117 : 16 : handleError.call(xhr);
2118 : 16 : }
2119 : 228 : }
2120 : 228 : }
2121 : 229 : }; // @endof method XMLHttpRequest.prototype.send
2122 : 229 :
2123 : 229 : /**
2124 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2125 : 229 : * url: https://xhr.spec.whatwg.org/#the-getresponseheader()-method
2126 : 229 : *
2127 : 229 : * client.getResponseHeader(name)
2128 : 229 : * The getResponseHeader(name) method steps are to return the result of getting name from this’s response’s header list.
2129 : 229 : *
2130 : 229 : * @method getResponseHeader
2131 : 229 : * @instance
2132 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
2133 : 229 : * @param {string} name - The name of the header whose text value is required.
2134 : 229 : * @return {string|null} The header text value or null.
2135 : 229 : * @desc XMLHttpRequest method that returns the string containing the text of a particular header's value.
2136 : 229 : */
2137 [ + ]: 229 : XMLHttpRequest.prototype.getResponseHeader = function getResponseHeader(name){
2138 : 263 :
2139 : 263 : // @author. validate the number of arguments
2140 : 263 : if(arguments.length < 1)
2141 [ - ]: 263 : throw new TypeError(`XMLHttpRequest.getResponseHeader: At least 1 argument required, but only ${arguments.length} passed`); // firefox 102
2142 : 263 :
2143 : 263 : const requestData = xmlHttpRequest.get(this);
2144 : 263 :
2145 [ + ][ + ]: 263 : if(!requestData['response']) return null;
2146 : 248 :
2147 : 248 : if(typeof name === 'symbol')
2148 [ - ][ + ]: 263 : throw new TypeError(`can't convert symbol to string`);
2149 : 248 :
2150 : 248 : let headerName = String(name).toLowerCase();
2151 : 248 : let responseHeaders = {};
2152 : 248 :
2153 : 248 : for(let headerName in requestData['response'].headers)
2154 [ + ][ + ]: 263 : responseHeaders[headerName.toLowerCase()] = requestData['response'].headers[headerName];
2155 : 248 :
2156 [ + ]: 263 : if(headerName in responseHeaders && !headerName.startsWith('set-cookie'))
2157 [ + ][ + ]: 263 : return responseHeaders[headerName];
2158 : 31 :
2159 : 31 : return null;
2160 : 31 :
2161 : 229 : }; // @endof method XMLHttpRequest.prototype.getResponseHeader
2162 : 229 :
2163 : 229 : /**
2164 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2165 : 229 : * url: https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method
2166 : 229 : *
2167 : 229 : * client.getAllResponseHeaders()
2168 : 229 : * The getAllResponseHeaders() method steps are:
2169 : 229 : * 1. Let <output> be an empty byte sequence.
2170 : 229 : * 2. Let <initialHeaders> be the result of running <sort and combine> with this’s response’s header list.
2171 : 229 : * 3. Let <headers> be the result of sorting initialHeaders in ascending order, with a being less than b if a’s name is legacy-uppercased-byte less than b’s name.
2172 : 229 : * Note: Unfortunately, this is needed for compatibility with deployed content.
2173 : 229 : * 4. For each <header> in <headers>, append <header>’s name, followed by a 0x3A 0x20 byte pair, followed by <header>’s value,
2174 : 229 : * followed by a 0x0D 0x0A byte pair, to <output>.
2175 : 229 : * 5. Return <output>.
2176 : 229 : *
2177 : 229 : * @method getAllResponseHeaders
2178 : 229 : * @instance
2179 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
2180 : 229 : * @return {string|null} A string representing all of the response's headers, or null if no response has been received.
2181 : 229 : * @desc XMLHttpRequest method that returns all the response headers, separated by CRLF, as a string, or null if no response.
2182 : 229 : */
2183 [ + ]: 229 : XMLHttpRequest.prototype.getAllResponseHeaders = function getAllResponseHeaders(){
2184 : 239 :
2185 : 239 : const requestData = xmlHttpRequest.get(this);
2186 : 239 :
2187 [ + ][ + ]: 239 : if(!requestData['response']) return '';
2188 : 18 :
2189 : 18 : // xhr.spec.1. Let <output> be an empty byte sequence.
2190 : 18 : let output = '';
2191 : 18 :
2192 : 18 : // xhr.spec.2. Let <initialHeaders> be the result of running <sort and combine>...
2193 : 18 : let initialHeaders = requestData['response'].headers;
2194 : 18 :
2195 : 18 : // xhr.spec.3. Let <headers> be the result of sorting initialHeaders in ascending order...
2196 : 18 : let headers = Object.entries(initialHeaders);
2197 : 18 :
2198 [ + ]: 18 : headers.sort((a, b) => {
2199 : 1979 : if(a[0].toUpperCase() > b[0].toUpperCase())
2200 [ + ][ + ][ + ]: 1979 : return 1;
2201 : 8 : else
2202 : 8 : return -1;
2203 : 18 : });
2204 : 18 :
2205 : 18 : // xhr.spec.4. For each <header> in <headers>, append <header>’s name, followed by a 0x3A 0x20 byte pair, followed by <header>’s value,
2206 : 18 : // followed by a 0x0D 0x0A byte pair, to output.
2207 : 18 :
2208 [ + ]: 239 : for(let header of headers){
2209 : 1142 : if(!header[0].toLowerCase().startsWith('set-cookie'))
2210 [ + ]: 1142 : output += header[0] + '\x3A' + '\x20' + header[1] + '\x0D' + '\x0A';
2211 [ + ]: 1142 : }
2212 : 18 : return output;
2213 : 18 :
2214 : 229 : }; // @endof method XMLHttpRequest.prototype.getAllResponseHeaders
2215 : 229 :
2216 : 229 : /**
2217 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2218 : 229 : * url: https://xhr.spec.whatwg.org/#the-overridemimetype()-method
2219 : 229 : *
2220 : 229 : * client.overrideMimeType(mime)
2221 : 229 : * Acts as if the `Content-Type` header value for a response is mime. (It does not change the header.)
2222 : 229 : * Throws an "InvalidStateError" DOMException if state is loading or done.
2223 : 229 : *
2224 : 229 : * The overrideMimeType(mime) method steps are:
2225 : 229 : * 1. If this’s state is loading or done, then throw an "InvalidStateError" DOMException.
2226 : 229 : * 2. Set this’s override MIME type to the result of parsing mime.
2227 : 229 : * 3. If this’s override MIME type is failure, then set this’s override MIME type to application/octet-stream.
2228 : 229 : *
2229 : 229 : * @method overrideMimeType
2230 : 229 : * @instance
2231 : 229 : * @memberof module:whatwg-xhr.XMLHttpRequest
2232 : 229 : * @param {string} mime - MIME type to use instead of the one specified by the server.
2233 : 229 : * @desc XMLHttpRequest method that specifies a MIME type other than the one provided by the server.
2234 : 229 : * @requires module:whatwg-misc.parseMIMEType
2235 : 229 : * @requires module:whatwg-misc.serializeAMimeType
2236 : 229 : */
2237 [ + ]: 229 : XMLHttpRequest.prototype.overrideMimeType = function overrideMimeType(mime){
2238 : 6 :
2239 : 6 : // @author. validate the number of arguments
2240 : 6 : if(arguments.length < 1)
2241 [ - ]: 6 : throw new TypeError(`XMLHttpRequest.overrideMimeType: At least 1 argument required, but only 0 passed`); // firefox 102
2242 : 6 :
2243 : 6 : const requestData = xmlHttpRequest.get(this);
2244 : 6 :
2245 : 6 : // xhr.spec.1. If this’s state is loading or done, then throw an "InvalidStateError" DOMException.
2246 [ + ]: 6 : if(requestData['readyState'] === this.LOADING || requestData['readyState'] === this.DONE)
2247 [ + ][ + ]: 6 : throw new DOMException(`XMLHttpRequest.overrideMimeType: Cannot call 'overrideMimeType()' on XMLHttpRequest after 'send()' (when its state is LOADING or DONE).`, 'InvalidStateError'); // firefox 102
2248 : 5 :
2249 : 5 : let parsedMime = parseMIMEType(String(mime));
2250 : 5 :
2251 [ + ]: 6 : if(parsedMime !== 'failure'){
2252 : 2 : // xhr.spec.2. Set this’s override MIME type to the result of parsing mime.
2253 : 2 : requestData['overrideMimeType'] = serializeAMimeType(parsedMime);
2254 [ + ]: 2 : }
2255 : 3 : else{
2256 : 3 : // xhr.spec.3. If this’s override MIME type is failure, then set this’s override MIME type to application/octet-stream.
2257 : 3 : requestData['overrideMimeType'] = 'application/octet-stream';
2258 : 3 : }
2259 : 229 : }; // @endof method XMLHttpRequest.prototype.overrideMimeType
2260 : 229 :
2261 : 229 : /**
2262 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2263 : 229 : * url: https://xhr.spec.whatwg.org/#interface-progressevent
2264 : 229 : *
2265 : 229 : * interface ProgressEvent : Event {
2266 : 229 : * constructor(DOMString type, optional ProgressEventInit eventInitDict = {});
2267 : 229 : *
2268 : 229 : * readonly attribute boolean lengthComputable;
2269 : 229 : * readonly attribute unsigned long long loaded;
2270 : 229 : * readonly attribute unsigned long long total;
2271 : 229 : * };
2272 : 229 : *
2273 : 229 : * dictionary ProgressEventInit : EventInit {
2274 : 229 : * boolean lengthComputable = false;
2275 : 229 : * unsigned long long loaded = 0;
2276 : 229 : * unsigned long long total = 0;
2277 : 229 : * };
2278 : 229 : *
2279 : 229 : * @class ProgressEvent
2280 : 229 : * @extends module:whatwg-xhr~Event
2281 : 229 : * @static
2282 : 229 : * @desc Interface that represents events measuring progress of an underlying process, like an HTTP request for an XMLHttpRequest client.
2283 : 229 : */
2284 : 229 : export class ProgressEvent extends Event{
2285 : 229 :
2286 : 229 : /**
2287 : 229 : * @method constructor
2288 : 229 : * @instance
2289 : 229 : * @memberof module:whatwg-xhr.ProgressEvent
2290 : 229 : * @param {string} type - A case-sensitive name of the event.
2291 : 229 : * @param {object} eventInitDict - (Optional) An object that, in addition to the properties defined in Event(), can have lengthComputable, loaded and total.
2292 : 229 : * @desc Constructs a new ProfressEvent object.
2293 : 229 : */
2294 [ + ]: 229 : constructor(type, eventInitDict = {}){
2295 : 2380 : if(arguments.length < 1)
2296 [ - ]: 2380 : throw new TypeError(`ProgressEvent constructor: At least 1 argument required, but only 0 passed`); // firefox 102
2297 : 2380 :
2298 : 2380 : const progressEventData = {};
2299 : 2380 :
2300 : 2380 : progressEventData['type'] = String(type);
2301 : 2380 : progressEventData['lengthComputable'] = false;
2302 : 2380 : progressEventData['loaded'] = 0;
2303 : 2380 : progressEventData['total'] = 0;
2304 : 2380 :
2305 : 2380 : if(eventInitDict !== undefined && eventInitDict !== null){
2306 [ - ]: 2380 : if(typeof eventInitDict !== 'object' && typeof eventInitDict !== 'function')
2307 [ - ]: 2380 : throw new TypeError(`ProgressEvent constructor: Value can't be converted to a dictionary.`); // firefox 102
2308 : 2380 :
2309 : 2380 : if(eventInitDict.loaded !== undefined)
2310 [ + ][ - ]: 2380 : progressEventData['loaded'] = isNaN(eventInitDict.loaded) ? 0 : Number(eventInitDict.loaded);
2311 : 2380 :
2312 : 2380 : if(eventInitDict.total !== undefined)
2313 [ + ][ - ]: 2380 : progressEventData['total'] = isNaN(eventInitDict.total) ? 0 : Number(eventInitDict.total);
2314 : 2380 :
2315 : 2380 : if(eventInitDict.lengthComputable !== undefined)
2316 [ + ][ + ]: 2380 : progressEventData['lengthComputable'] = Boolean(eventInitDict.lengthComputable);
2317 : 1437 : else
2318 : 1437 : progressEventData['lengthComputable'] = progressEventData['total'] !== 0;
2319 : 2380 :
2320 : 2380 : let eventInit = {
2321 : 2380 : bubbles: Boolean(eventInitDict['bubbles']),
2322 : 2380 : cancelable: Boolean(eventInitDict['cancelable']),
2323 : 2380 : composed: Boolean(eventInitDict['composed'])
2324 : 2380 : };
2325 : 2380 :
2326 : 2380 : super(type, eventInit);
2327 [ - ]: 2380 : }
2328 : 0 : else
2329 : 0 : super(type);
2330 : 2380 :
2331 : 2380 : // Map any ProgressEvent object created to an associated data object.
2332 : 2380 : progressEvent.set(this, progressEventData);
2333 : 2380 : }
2334 : 229 :
2335 : 229 : /**
2336 : 229 : * @member {boolean} lengthComputable
2337 : 229 : * @memberof module:whatwg-xhr.ProgressEvent
2338 : 229 : * @readonly
2339 : 229 : * @default false
2340 : 229 : * @instance
2341 : 229 : * @desc A boolean value indicating if the total work to be done, and the amount of work already done, by the underlying process is calculable.
2342 : 229 : */
2343 [ + ]: 229 : get lengthComputable(){ return progressEvent.get(this)['lengthComputable']; }
2344 : 229 :
2345 : 229 : /**
2346 : 229 : * @member {number} loaded
2347 : 229 : * @memberof module:whatwg-xhr.ProgressEvent
2348 : 229 : * @readonly
2349 : 229 : * @default 0
2350 : 229 : * @instance
2351 : 229 : * @desc A number representing the amount of work already performed by the underlying process.
2352 : 229 : */
2353 [ + ]: 229 : get loaded(){ return progressEvent.get(this)['loaded']; }
2354 : 229 :
2355 : 229 : /**
2356 : 229 : * @member {number} total
2357 : 229 : * @memberof module:whatwg-xhr.ProgressEvent
2358 : 229 : * @readonly
2359 : 229 : * @default 0
2360 : 229 : * @instance
2361 : 229 : * @desc A number representing the total amount of work that the underlying process is in the progress of performing.
2362 : 229 : */
2363 [ + ]: 229 : get total(){ return progressEvent.get(this)['total']; }
2364 : 229 :
2365 : 229 : } // @endOf class ProgressEvent extends Event
2366 : 229 :
2367 : 229 : Object.defineProperty(ProgressEvent.prototype, 'lengthComputable', {enumerable: true});
2368 : 229 : Object.defineProperty(ProgressEvent.prototype, 'loaded', {enumerable: true});
2369 : 229 : Object.defineProperty(ProgressEvent.prototype, 'total', {enumerable: true});
2370 : 229 :
2371 : 229 : Object.defineProperty(ProgressEvent.prototype, Symbol.toStringTag, {value: 'ProgressEvent', writable: false, enumerable: false, configurable: true});
2372 : 229 :
2373 : 229 : /**
2374 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2375 : 229 : * url: https://xhr.spec.whatwg.org/#formdata
2376 : 229 : *
2377 : 229 : * interface FormData {
2378 : 229 : * constructor(optional HTMLFormElement form, optional HTMLElement? submitter = null);
2379 : 229 : *
2380 : 229 : * undefined append(USVString name, USVString value);
2381 : 229 : * undefined append(USVString name, Blob blobValue, optional USVString filename);
2382 : 229 : * undefined delete(USVString name);
2383 : 229 : *
2384 : 229 : * FormDataEntryValue? get(USVString name);
2385 : 229 : * sequence<FormDataEntryValue> getAll(USVString name);
2386 : 229 : *
2387 : 229 : * boolean has(USVString name);
2388 : 229 : * undefined set(USVString name, USVString value);
2389 : 229 : * undefined set(USVString name, Blob blobValue, optional USVString filename);
2390 : 229 : *
2391 : 229 : * iterable<USVString, FormDataEntryValue>;
2392 : 229 : * };
2393 : 229 : *
2394 : 229 : * @class FormData
2395 : 229 : * @static
2396 : 229 : * @desc An interface that provides a way to construct a set of key/value pairs representing form fields and their values.
2397 : 229 : */
2398 : 229 : export class FormData{
2399 : 229 : /**
2400 : 229 : * @method constructor
2401 : 229 : * @instance
2402 : 229 : * @memberof module:whatwg-xhr.FormData
2403 : 229 : * @param {FormData} form - (Optional) The FormData object will be populated with the form's current keys/values.
2404 : 229 : * @param {string|Blob} value - (Optional) A submit button that is a member of the form.
2405 : 229 : * @desc Constructs a new FormData object.
2406 : 229 : */
2407 [ + ]: 229 : constructor(form, submitter){
2408 : 41 :
2409 [ - ]: 41 : if(globalThis.HTMLFormElement){
2410 : 0 : /* node:coverage disable *
2411 : 0 :
2412 : 0 : if(form === null || typeof form !== 'object')
2413 : 0 : throw new TypeError('FormData constructor: Argument 1 is not an object.'); // firefox 102
2414 : 0 :
2415 : 0 : if(!(form instanceof globalThis.HTMLFormElement))
2416 : 0 : throw new TypeError(' FormData constructor: Argument 1 does not implement interface HTMLFormElement.'); // firefox 102
2417 : 0 :
2418 : 0 : const entryList = [];
2419 : 0 : formData.set(this, entryList);
2420 : 0 :
2421 : 0 : ** node:coverage enable */
2422 : 0 : }
2423 : 41 : else
2424 [ + ][ + ]: 41 : if(arguments.length === 0 || form === undefined){
2425 : 39 : const entryList = [];
2426 : 39 :
2427 : 39 : // Map any FormData object created to an associated data object.
2428 : 39 : formData.set(this, entryList);
2429 [ + ]: 39 : }
2430 : 2 : else
2431 : 2 : if(form !== undefined){
2432 : 2 : throw new TypeError('FormData constructor: Argument 1 could not be converted to: undefined.'); // node.js
2433 : 2 : }
2434 : 41 : }
2435 : 229 :
2436 : 229 : /**
2437 : 229 : * @method append
2438 : 229 : * @instance
2439 : 229 : * @memberof module:whatwg-xhr.FormData
2440 : 229 : * @param {string} name - The name of the field whose data is contained in value.
2441 : 229 : * @param {string|Blob} value - The field's value, can be a string or Blob, otherwise the value is converted to a string.
2442 : 229 : * @param {string} filename - (Optional) The filename reported to the server (a string), when a Blob or File is passed as the second parameter.
2443 : 229 : * @desc Appends a new value onto an existing key inside a FormData object, or adds the key if it does not already exist.
2444 : 229 : */
2445 [ + ]: 229 : append(name, value, filename = undefined){
2446 : 51 :
2447 [ - ]: 51 : if(globalThis.HTMLFormElement){
2448 : 0 : /* node:coverage disable *
2449 : 0 :
2450 : 0 : if(arguments.length < 2)
2451 : 0 : throw new TypeError(`FormData.append: ${arguments.length} is not a valid argument count for any overload.`); // firefox 102
2452 : 0 : else
2453 : 0 : if(arguments.length === 3){
2454 : 0 : if(typeof value !== 'object')
2455 : 0 : throw new TypeError(`FormData.append: Argument 2 is not an object.`); // firefox 102
2456 : 0 : else
2457 : 0 : if(!(value instanceof Blob))
2458 : 0 : throw new TypeError(`FormData.append: Argument 2 does not implement interface Blob.`); // firefox 102
2459 : 0 : }
2460 : 0 :
2461 : 0 : ** node:coverage enable */
2462 : 0 : }
2463 : 51 : else
2464 : 51 : if(arguments.length === 0)
2465 [ - ]: 51 : throw new TypeError(`FormData.append: 2 arguments required, but 0 found.`); // node.js
2466 : 51 : else
2467 : 51 : if(arguments.length === 1)
2468 [ - ]: 51 : throw new TypeError(`FormData.append: 2 arguments required, but only 1 found.`); // node.js
2469 : 51 : else
2470 [ + ]: 51 : if(arguments.length === 3 && !(value instanceof Blob))
2471 [ - ]: 51 : throw new TypeError(`Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'`); // node.js
2472 : 51 :
2473 : 51 : name = String(name);
2474 : 51 :
2475 [ + ][ + ]: 51 : value = value instanceof Blob ? value : String(value);
2476 [ + ][ + ]: 51 : filename = arguments.length === 3 ? String(filename) : undefined;
2477 : 51 :
2478 : 51 : const entryList = formData.get(this);
2479 : 51 :
2480 : 51 : const entry = makeEntry(name, value, filename);
2481 : 51 : entryList.push(entry);
2482 : 51 : }
2483 : 229 :
2484 : 229 : /**
2485 : 229 : * @method delete
2486 : 229 : * @instance
2487 : 229 : * @memberof module:whatwg-xhr.FormData
2488 : 229 : * @param {string} name - The name of the key to be deleted.
2489 : 229 : * @desc Deletes a key and its value(s) from a FormData object.
2490 : 229 : */
2491 [ + ]: 229 : delete(name){
2492 : 7 : // validate the arguments
2493 : 7 : if(arguments.length < 1)
2494 [ - ]: 7 : throw new TypeError(`FormData.delete: At least 1 argument required, but only ${arguments.length} passed`);
2495 : 7 :
2496 : 7 : const entryList = formData.get(this);
2497 : 7 : for(let i = 0; i < entryList.length; i++)
2498 [ + ]: 7 : if(entryList[i][name] !== undefined)
2499 [ + ]: 21 : entryList.splice(i--, 1);
2500 : 7 : }
2501 : 229 :
2502 : 229 : /**
2503 : 229 : * @method get
2504 : 229 : * @instance
2505 : 229 : * @memberof module:whatwg-xhr.FormData
2506 : 229 : * @param {string} name - The name of the key to be retrieved.
2507 : 229 : * @return {string|Blob} Form data entry value.
2508 : 229 : * @desc Returns the first value associated with a given key from within a FormData object.
2509 : 229 : */
2510 [ + ]: 229 : get(name){
2511 : 28 : // validate the arguments
2512 : 28 : if(arguments.length < 1)
2513 [ - ]: 28 : throw new TypeError(`FormData.delete: At least 1 argument required, but only ${arguments.length} passed`);
2514 : 28 :
2515 : 28 : const entryList = formData.get(this);
2516 : 28 : for(let i = 0; i < entryList.length; i++)
2517 [ + ]: 28 : if(entryList[i][name] !== undefined)
2518 [ + ][ + ]: 37 : return entryList[i][name];
2519 : 4 :
2520 : 4 : return null;
2521 : 28 : }
2522 : 229 :
2523 : 229 : /**
2524 : 229 : * @method getAll
2525 : 229 : * @instance
2526 : 229 : * @memberof module:whatwg-xhr.FormData
2527 : 229 : * @param {string} name - The name of the key to be retrieved.
2528 : 229 : * @return {Array} An array of values whose key matches the specified name, otherwise, an empty list.
2529 : 229 : * @desc Returns all the values associated with a given key from within a FormData object.
2530 : 229 : */
2531 [ + ]: 229 : getAll(name){
2532 : 3 : // validate the arguments
2533 : 3 : if(arguments.length < 1)
2534 [ - ]: 3 : throw new TypeError(`FormData.delete: At least 1 argument required, but only ${arguments.length} passed`);
2535 : 3 :
2536 : 3 : const result = [];
2537 : 3 : const entryList = formData.get(this);
2538 : 3 : for(let i = 0; i < entryList.length; i++)
2539 [ + ]: 3 : if(entryList[i][name] !== undefined)
2540 [ + ]: 4 : result.push(entryList[i][name]);
2541 : 3 :
2542 : 3 : return result;
2543 : 3 : }
2544 : 229 :
2545 : 229 : /**
2546 : 229 : * @method has
2547 : 229 : * @instance
2548 : 229 : * @memberof module:whatwg-xhr.FormData
2549 : 229 : * @param {string} name - The name of the key to be retrieved.
2550 : 229 : * @return {boolean} true if a key of FormData matches the specified name, otherwise, false.
2551 : 229 : * @desc Returns whether a FormData object contains a certain key.
2552 : 229 : */
2553 [ + ]: 229 : has(name){
2554 : 3 : // validate the arguments
2555 : 3 : if(arguments.length < 1)
2556 [ - ]: 3 : throw new TypeError(`FormData.delete: At least 1 argument required, but only ${arguments.length} passed`);
2557 : 3 :
2558 : 3 : const entryList = formData.get(this);
2559 : 3 : for(let i = 0; i < entryList.length; i++)
2560 : 3 : if(entryList[i][name] !== undefined)
2561 [ + ][ + ]: 3 : return true;
2562 : 2 :
2563 : 2 : return false;
2564 : 3 : }
2565 : 229 :
2566 : 229 : /**
2567 : 229 : * @method set
2568 : 229 : * @instance
2569 : 229 : * @memberof module:whatwg-xhr.FormData
2570 : 229 : * @param {string} name - The name of the field whose data is contained in value.
2571 : 229 : * @param {string|Blob} value - The field's value, can be a string or Blob, otherwise the value is converted to a string.
2572 : 229 : * @param {string} filename - (Optional) The filename reported to the server (a string), when a Blob or File is passed as the second parameter.
2573 : 229 : * @desc Sets a new value for an existing key inside a FormData object, or adds the key/value if it does not already exist.
2574 : 229 : */
2575 [ + ]: 229 : set(name, value, filename){
2576 : 15 :
2577 : 15 : if(arguments.length === 0)
2578 [ - ]: 15 : throw new TypeError(`FormData.set: 2 arguments required, but 0 found.`); // node.js
2579 : 15 :
2580 : 15 : if(arguments.length === 1)
2581 [ - ]: 15 : throw new TypeError(`FormData.set: 2 arguments required, but only 1 found.`); // node.js
2582 : 15 :
2583 [ + ]: 15 : if(arguments.length === 3 && !(value instanceof Blob))
2584 [ - ]: 15 : throw new TypeError(`Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'`); // node.js
2585 : 15 :
2586 : 15 : name = String(name);
2587 : 15 :
2588 [ + ][ + ]: 15 : value = value instanceof Blob ? value : String(value);
2589 [ + ][ + ]: 15 : filename = arguments.length === 3 ? String(filename) : undefined;
2590 : 15 :
2591 : 15 : const entryList = formData.get(this);
2592 : 15 : for(let i = 0; i < entryList.length; i++)
2593 [ + ]: 15 : if(entryList[i][name] !== undefined)
2594 [ + ]: 13 : entryList.splice(i, 1);
2595 : 15 :
2596 : 15 : const entry = makeEntry(name, value, filename);
2597 : 15 : entryList.push(entry);
2598 : 15 : }
2599 : 229 :
2600 [ + ]: 229 : entries(){
2601 : 1 : const entryList = formData.get(this);
2602 : 1 :
2603 : 1 : let i = 0;
2604 : 1 : let iterator = {
2605 [ + ]: 1 : next(){
2606 [ + ]: 6 : return i < entryList.length ?
2607 [ + ]: 6 : { value: Object.entries(entryList[i++])[0], done: false} :
2608 : 6 : { value: undefined, done: true};
2609 : 1 : },
2610 : 1 : };
2611 : 1 : Object.defineProperty(iterator, Symbol.toStringTag, { value: 'FormData Iterator', writable: false, enumerable: false, configurable: true});
2612 : 1 :
2613 : 1 : const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
2614 : 1 : Object.setPrototypeOf(iterator, esIteratorPrototype);
2615 : 1 :
2616 : 1 : return Object.create(iterator);
2617 : 1 : }
2618 : 229 :
2619 [ + ]: 229 : keys(){
2620 : 1 : const entryList = formData.get(this);
2621 : 1 :
2622 : 1 : let i = 0;
2623 : 1 : let iterator = {
2624 [ + ]: 1 : next(){
2625 [ + ]: 6 : return i < entryList.length ?
2626 [ + ]: 6 : { value: Object.keys(entryList[i++])[0], done: false} :
2627 : 6 : { value: undefined, done: true};
2628 : 1 : },
2629 : 1 : };
2630 : 1 : Object.defineProperty(iterator, Symbol.toStringTag, { value: 'FormData Iterator', writable: false, enumerable: false, configurable: true});
2631 : 1 :
2632 : 1 : const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
2633 : 1 : Object.setPrototypeOf(iterator, esIteratorPrototype);
2634 : 1 :
2635 : 1 : return Object.create(iterator);
2636 : 1 : }
2637 : 229 :
2638 [ + ]: 229 : values(){
2639 : 1 : const entryList = formData.get(this);
2640 : 1 :
2641 : 1 : let i = 0;
2642 : 1 : let iterator = {
2643 [ + ]: 1 : next(){
2644 [ + ]: 6 : return i < entryList.length ?
2645 [ + ]: 6 : { value: Object.values(entryList[i++])[0], done: false} :
2646 : 6 : { value: undefined, done: true};
2647 : 1 : },
2648 : 1 : };
2649 : 1 : Object.defineProperty(iterator, Symbol.toStringTag, { value: 'FormData Iterator', writable: false, enumerable: false, configurable: true});
2650 : 1 :
2651 : 1 : const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
2652 : 1 : Object.setPrototypeOf(iterator, esIteratorPrototype);
2653 : 1 :
2654 : 1 : return Object.create(iterator);
2655 : 1 : }
2656 : 229 :
2657 : 229 : } // @endof class FormData
2658 : 229 :
2659 : 229 : Object.defineProperty(FormData.prototype, Symbol.toStringTag, { value: 'FormData', writable: false, enumerable: false, configurable: true});
2660 : 229 : Object.defineProperty(FormData.prototype, Symbol.iterator, {
2661 [ + ]: 229 : value: function entries(){
2662 : 12 : const entryList = formData.get(this);
2663 : 12 :
2664 : 12 : let i = 0;
2665 : 12 :
2666 : 12 : let iterator = {
2667 [ + ]: 12 : next(){
2668 [ + ]: 31 : return i < entryList.length ?
2669 [ + ]: 31 : { value: Object.entries(entryList[i++])[0], done: false} :
2670 : 31 : { value: undefined, done: true};
2671 : 12 : },
2672 : 12 : };
2673 : 12 : Object.defineProperty(iterator, Symbol.toStringTag, { value: 'FormData Iterator', writable: false, enumerable: false, configurable: true});
2674 : 12 :
2675 : 12 : return Object.create(iterator);
2676 : 229 : },
2677 : 229 : writable: false, enumerable: false, configurable: true
2678 : 229 : });
2679 : 229 :
2680 : 229 : /**
2681 : 229 : * HTML Living Standard — Last Updated 7 January 2025
2682 : 229 : * url: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
2683 : 229 : *
2684 : 229 : * To create an entry given a string name, a string or Blob object value, and optionally a scalar value string filename:
2685 : 229 : * 1. Set name to the result of converting name into a scalar value string.
2686 : 229 : * 2. If value is a string, then set value to the result of converting value into a scalar value string.
2687 : 229 : * 3. Otherwise:
2688 : 229 : * 1. If value is not a File object, then set value to a new File object, representing the same bytes, whose name attribute value is "blob".
2689 : 229 : * 2. If filename is given, then set value to a new File object, representing the same bytes, whose name attribute is filename.
2690 : 229 : * 4. Return an entry whose name is name and whose value is value.
2691 : 229 : * Note: These operations will create a new File object if either filename is given or the passed Blob is not a File object.
2692 : 229 : * In those cases, the identity of the passed Blob object is not kept.
2693 : 229 : *
2694 : 229 : * function makeEntry
2695 : 229 : * @param {string} name
2696 : 229 : * @param {string|Blob} value
2697 : 229 : * @param {?string} filename
2698 : 229 : * @return {object} Return an entry whose name is name and whose value is value.
2699 : 229 : * @desc To create an entry given a string name, a string or Blob object value, and optionally a scalar value string filename.
2700 : 229 : * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
2701 : 229 : */
2702 [ + ]: 66 : function makeEntry(name, value, filename) {
2703 : 66 :
2704 : 66 : // html.spec.1. Set name to the result of converting name into a scalar value string.
2705 : 66 : name = Buffer.from(name).toString('utf8');
2706 : 66 :
2707 : 66 : // html.spec.2. If value is a string, then set value to the result of converting value into a scalar value string.
2708 [ + ]: 66 : if(typeof value === 'string'){
2709 : 55 : value = Buffer.from(value).toString('utf8')
2710 [ + ]: 55 : }
2711 : 11 : // html.spec.3. Otherwise:
2712 : 11 : else {
2713 : 11 : // html.spec.3.1. If value is not a File object, then set value to a new File object,
2714 : 11 : // representing the same bytes, whose name attribute value is "blob".
2715 [ + ]: 11 : if(!(value instanceof File)){
2716 : 8 : value = new File([value], 'blob', { type: value.type });
2717 : 8 : }
2718 : 11 :
2719 : 11 : // html.spec.3.2. If filename is given, then set value to a new File object,
2720 : 11 : // representing the same bytes, whose name attribute is filename.
2721 [ + ]: 11 : if(filename !== undefined){
2722 : 6 : const options = {
2723 : 6 : type: value.type,
2724 : 6 : lastModified: value.lastModified
2725 : 6 : };
2726 : 6 :
2727 : 6 : value = new File([value], filename, options);
2728 : 6 : }
2729 : 11 : }
2730 : 66 :
2731 : 66 : // html.spec.4. Return an entry whose name is name and whose value is value.
2732 : 66 : return { [name]: value };
2733 : 66 : }
2734 : 229 :
2735 : 229 : /**
2736 : 229 : * function readBlobSync
2737 : 229 : * @param {Blob|File} blob
2738 : 229 : * @return {Buffer} Return a buffer object with the contents of the Blob or File given.
2739 : 229 : * @desc Reads the contents of the Blob or File object synchronously, thus blocking the Node.js event loop and further JavaScript execution until return.
2740 : 229 : */
2741 [ + ]: 10 : function readBlobSync(blob){
2742 : 10 :
2743 : 10 : const contentBlob = '.whatwg-xhr-contentBlob-' + process.pid;
2744 : 10 : const syncBlob = '.whatwg-xhr-syncBlob-' + process.pid;
2745 : 10 :
2746 : 10 : fs.writeFileSync(syncBlob, '', 'utf8');
2747 : 10 :
2748 : 10 : let threadCode = `\
2749 : 10 : const fs = require('node:fs');
2750 : 10 : const threads = require('node:worker_threads');
2751 : 10 :
2752 : 10 : threads.parentPort.once('message', (data) => {
2753 : 10 : data.arrayBuffer()
2754 : 10 : .then(arrayBuffer => Buffer.from(arrayBuffer))
2755 : 10 : .then(buf => {
2756 : 10 : fs.writeFileSync('${contentBlob}', buf);
2757 : 10 : fs.unlinkSync('${syncBlob}');
2758 : 10 : });
2759 : 10 : });`;
2760 : 10 :
2761 : 10 : let worker = new threads.Worker(threadCode, {eval: true});
2762 : 10 :
2763 : 10 : worker.postMessage(blob);
2764 : 10 :
2765 [ + ]: 10 : while(fs.existsSync(syncBlob));
2766 : 10 : let buffer = fs.readFileSync(contentBlob);
2767 : 10 : fs.unlinkSync(contentBlob);
2768 : 10 :
2769 : 10 : return buffer;
2770 : 10 : }
2771 : 229 :
2772 : 229 : /**
2773 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2774 : 229 : * url: https://xhr.spec.whatwg.org/#handle-errors
2775 : 229 : *
2776 : 229 : * To handle errors for an XMLHttpRequest object xhr, run these steps:
2777 : 229 : * 1. If xhr’s send() flag is unset, then return.
2778 : 229 : * 2. If xhr’s timed out flag is set, then run the request error steps for xhr, "timeout", and "TimeoutError" DOMException.
2779 : 229 : * 3. Otherwise, if xhr’s response’s aborted flag is set, run the request error steps for xhr, "abort", and "AbortError" DOMException.
2780 : 229 : * 4. Otherwise, if xhr’s response is a network error, then run the request error steps for xhr, error, and "NetworkError" DOMException.
2781 : 229 : *
2782 : 229 : * @func handleError
2783 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
2784 : 229 : * @param {object} Error object.
2785 : 229 : * @desc Handle errors for an XMLHttpRequest object, as given by {@link https://xhr.spec.whatwg.org/#handle-errors XHR specs}.
2786 : 229 : */
2787 [ + ]: 959 : function handleError(error){
2788 : 959 :
2789 : 959 : const requestData = xmlHttpRequest.get(this);
2790 : 959 :
2791 : 959 : // xhr.spec.1. If xhr’s send() flag is unset, then return.
2792 [ + ][ + ]: 959 : if(!requestData['sendFlag']){
2793 : 1 : return;
2794 [ + ]: 1 : }
2795 : 1 :
2796 : 1 : // xhr.spec.2. If xhr’s timed out flag is set, then run the request error steps for xhr, "timeout", and "TimeoutError" DOMException.
2797 [ + ]: 959 : if(requestData['timeoutFlag']){
2798 : 3 : requestErrorSteps.call(this, 'timeout', new DOMException('Timeout error', 'TimeoutError'));
2799 [ + ]: 3 : }
2800 : 457 : else
2801 : 457 : // xhr.spec.3. If xhr’s response’s aborted flag is set, run the request error steps for xhr, "abort", and "AbortError" DOMException.
2802 [ + ][ - ]: 457 : if(requestData['response'] && requestData['_responseAbort']){
2803 : 0 : requestErrorSteps.call(this, 'abort', new DOMException('Abort error', 'AbortError'));
2804 : 0 : }
2805 : 457 : else
2806 : 457 : // xhr.spec.4. if xhr’s response is a network error, then run the request error steps for xhr, "error", and "NetworkError" DOMException.
2807 [ + ]: 457 : if(requestData['response'] === ''){
2808 : 38 : requestErrorSteps.call(this, 'error', new DOMException('Network error', 'NetworkError'));
2809 : 38 : }
2810 : 959 :
2811 : 229 : } // @endof function handleError
2812 : 229 :
2813 : 229 : /**
2814 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2815 : 229 : * url: https://xhr.spec.whatwg.org/#request-error-steps
2816 : 229 : *
2817 : 229 : * The request error steps for an XMLHttpRequest object <xhr>, <event>, and optionally <exception> are:
2818 : 229 : * 1. Set <xhr>’s state to done.
2819 : 229 : * 2. Unset <xhr>’s send() flag.
2820 : 229 : * 3. Set <xhr>’s response to a network error.
2821 : 229 : * 4. If <xhr>’s synchronous flag is set, then throw <exception>.
2822 : 229 : * 5. Fire an event named readystatechange at <xhr>.
2823 : 229 : * Note: At this point it is clear that <xhr>’s synchronous flag is unset.
2824 : 229 : * 6. If <xhr>’s upload complete flag is unset, then:
2825 : 229 : * 1. Set <xhr>’s upload complete flag.
2826 : 229 : * 2. If <xhr>’s upload listener flag is set, then:
2827 : 229 : * 1. Fire a progress event named <event> at <xhr>’s upload object with 0 and 0.
2828 : 229 : * 2. Fire a progress event named 'loadend' at <xhr>’s upload object with 0 and 0.
2829 : 229 : * 7. Fire a progress event named <event> at <xhr> with 0 and 0.
2830 : 229 : * 8. Fire a progress event named 'loadend' at <xhr> with 0 and 0.
2831 : 229 : *
2832 : 229 : * @func requestErrorSteps
2833 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
2834 : 229 : * @param {string} Event type
2835 : 229 : * @param {object|string} Exception
2836 : 229 : * @desc Request error steps for an XMLHttpRequest object <xhr>, <event>, and optionally <exception>, as given by {@link https://xhr.spec.whatwg.org/#request-error-steps XHR specs}.
2837 : 229 : */
2838 [ + ]: 54 : function requestErrorSteps(event, exception){
2839 : 54 :
2840 : 54 : const xhr = this;
2841 : 54 : const requestData = xmlHttpRequest.get(xhr);
2842 : 54 :
2843 : 54 : // xhr.spec.1. Set <xhr>’s state to done.
2844 : 54 : requestData['readyState'] = xhr.DONE;
2845 : 54 :
2846 : 54 : // xhr.spec.2. Unset <xhr>’s send() flag.
2847 : 54 : requestData['sendFlag'] = false;
2848 : 54 :
2849 : 54 : // xhr.spec.3. Set <xhr>’s response to a network error.
2850 : 54 : setResponseToNetworkError.call(xhr);
2851 : 54 :
2852 : 54 : // xhr.spec.4. If <xhr>’s synchronous flag is set, then throw <exception>.
2853 [ + ]: 54 : if(!requestData['async']){
2854 : 16 : throw exception;
2855 [ + ]: 16 : }
2856 : 22 :
2857 : 22 : // xhr.spec.5. Fire an event named readystatechange at <xhr>.
2858 : 22 : xhr.dispatchEvent(new Event('readystatechange'));
2859 : 22 :
2860 : 22 : // xhr.spec.6. If <xhr>’s upload complete flag is unset, then:
2861 [ + ]: 54 : if(!requestData['uploadCompleteFlag']){
2862 : 5 :
2863 : 5 : // xhr.spec.6.1. Set <xhr>’s upload complete flag.
2864 : 5 : requestData['uploadCompleteFlag'] = true;
2865 : 5 :
2866 : 5 : // xhr.spec.6.2. If <xhr>’s upload listener flag is set, then:
2867 : 5 : if(requestData['uploadListenerFlag']){
2868 : 5 : const upload = requestData['upload'];
2869 : 5 :
2870 : 5 : // xhr.spec.6.2.1. Fire a progress event named <event> at <xhr>’s upload object with 0 and 0.
2871 : 5 : upload.dispatchEvent(new ProgressEvent(event, {loaded: 0, total: 0}));
2872 : 5 :
2873 : 5 : // xhr.spec.6.2.2. Fire a progress event named 'loadend' at <xhr>’s upload object with 0 and 0.
2874 : 5 : upload.dispatchEvent(new ProgressEvent('loadend', {loaded: 0, total: 0}));
2875 : 5 : }
2876 [ + ]: 5 : }
2877 : 22 :
2878 : 22 : // xhr.spec.7. Fire a progress event named <event> at <xhr> with 0 and 0.
2879 : 22 : xhr.dispatchEvent(new ProgressEvent(event, {loaded: 0, total: 0}));
2880 : 22 :
2881 : 22 : // xhr.spec.7. Fire a progress event named 'loadend' at <xhr> with 0 and 0.
2882 : 22 : xhr.dispatchEvent(new ProgressEvent('loadend', {loaded: 0, total: 0}));
2883 : 22 :
2884 : 229 : } // @endof function requestErrorSteps
2885 : 229 :
2886 : 229 : /**
2887 : 229 : * Fetch Living Standard — Last Updated 17 June 2024
2888 : 229 : * url: https://fetch.spec.whatwg.org/#concept-network-error
2889 : 229 : *
2890 : 229 : * A network error is a response whose type is "error", status is 0, status message is the empty byte sequence, header list is « »,
2891 : 229 : * body is null, and body info is a new response body info.
2892 : 229 : *
2893 : 229 : * @func setResponseToNetworkError
2894 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
2895 : 229 : * @desc Network error is a response whose type is "error", as defined by {@link https://fetch.spec.whatwg.org/#concept-network-error Fetch specs}.
2896 : 229 : */
2897 [ + ]: 868 : function setResponseToNetworkError(){
2898 : 868 :
2899 : 868 : const requestData = xmlHttpRequest.get(this);
2900 : 868 :
2901 [ + ]: 868 : if(requestData['fetchController']){
2902 : 31 : requestData['fetchController'].destroy();
2903 : 31 : requestData['fetchController'] = null;
2904 : 31 : }
2905 : 868 :
2906 : 868 : requestData['response'] = '';
2907 : 868 :
2908 : 868 : // requestData['status'] = 0;
2909 : 868 : // requestData['statusText'] = '';
2910 : 868 :
2911 : 868 : requestData['requestHeaders' ] = {};
2912 : 868 : requestData['requestHeadersCase'] = {};
2913 : 868 :
2914 : 868 : requestData['receivedBytes'] = Buffer.alloc(0);
2915 : 868 : requestData['_responseBody'] = null;
2916 : 868 :
2917 : 229 : } // @endof function setResponseToNetworkError
2918 : 229 :
2919 : 229 : /*
2920 : 229 : * XMLHttpRequest Living Standard — Last Updated 20 February 2023
2921 : 229 : * url: https://xhr.spec.whatwg.org/#response-body
2922 : 229 : *
2923 : 229 : * 3.6.6. Response body
2924 : 229 : *
2925 : 229 : * To get a response MIME type for an XMLHttpRequest object xhr, run these steps:
2926 : 229 : * 1. Let mimeType be the result of <extracting a MIME type> from xhr’s response’s header list.
2927 : 229 : * 2. If mimeType is failure, then set mimeType to text/xml.
2928 : 229 : * 3. Return mimeType.
2929 : 229 : *
2930 : 229 : * To get a final MIME type for an XMLHttpRequest object xhr, run these steps:
2931 : 229 : * 1. If xhr’s <override MIME type> is null, return the result of get a response MIME type for xhr.
2932 : 229 : * 2. Return xhr’s override MIME type.
2933 : 229 : *
2934 : 229 : * To get a final encoding for an XMLHttpRequest object xhr, run these steps:
2935 : 229 : * 1. Let label be null.
2936 : 229 : * 2. Let responseMIME be the result of <get a response MIME type> for xhr.
2937 : 229 : * 3. If responseMIME’s parameters["charset"] exists, then set label to it.
2938 : 229 : * 4. If xhr’s override MIME type’s parameters["charset"] exists, then set label to it.
2939 : 229 : * 5. If label is null, then return null.
2940 : 229 : * 6. Let encoding be the result of getting an encoding from label.
2941 : 229 : * 7. If encoding is failure, then return null.
2942 : 229 : * 8. Return encoding.
2943 : 229 : * The above steps intentionally do not use the get a final MIME type as it would not be web compatible.
2944 : 229 : *
2945 : 229 : * To set a document response for an XMLHttpRequest object xhr, run these steps:
2946 : 229 : * 1. If xhr’s response’s body is null, then return.
2947 : 229 : * 2. Let finalMIME be the result of get a final MIME type for xhr.
2948 : 229 : * 3. If finalMIME is not an HTML MIME type or an XML MIME type, then return.
2949 : 229 : * 4. If xhr’s response type is the empty string and finalMIME is an HTML MIME type, then return.
2950 : 229 : * Note: This is restricted to xhr’s response type being "document" in order to prevent breaking legacy content.
2951 : 229 : * 5. If finalMIME is an HTML MIME type, then:
2952 : 229 : * 1. Let charset be the result of get a final encoding for xhr.
2953 : 229 : * 2. If charset is null, prescan the first 1024 bytes of xhr’s received bytes and if that does not terminate
2954 : 229 : * unsuccessfully then let charset be the return value.
2955 : 229 : * 3. If charset is null, then set charset to UTF-8.
2956 : 229 : * 4. Let document be a document that represents the result parsing xhr’s received bytes
2957 : 229 : * following the rules set forth in the HTML Standard for an HTML parser with scripting
2958 : 229 : * disabled and a known definite encoding charset. [HTML]
2959 : 229 : * 5. Flag document as an HTML document.
2960 : 229 : * 6. Otherwise, let document be a document that represents the result of running the XML parser
2961 : 229 : * with XML scripting support disabled on xhr’s received bytes.
2962 : 229 : * If that fails (unsupported character encoding, namespace well-formedness error, etc.),
2963 : 229 : * then return null. [HTML]
2964 : 229 : * Note: Resources referenced will not be loaded and no associated XSLT will be applied.
2965 : 229 : * 7. If charset is null, then set charset to UTF-8.
2966 : 229 : * 8. Set document’s encoding to charset.
2967 : 229 : * 9. Set document’s content type to finalMIME.
2968 : 229 : * 10. Set document’s URL to xhr’s response’s URL.
2969 : 229 : * 11. Set document’s origin to xhr’s relevant settings object’s origin.
2970 : 229 : * 12. Set xhr’s response object to document.
2971 : 229 : *
2972 : 229 : * To get a text response for an XMLHttpRequest object xhr, run these steps:
2973 : 229 : * 1. If xhr’s response’s body is null, then return the empty string.
2974 : 229 : * 2. Let charset be the result of get a final encoding for xhr.
2975 : 229 : * 3. If xhr’s response type is the empty string, charset is null, and the result of get a final MIME type for xhr is an XML MIME type,
2976 : 229 : * then use the rules set forth in the XML specifications to determine the encoding. Let charset be the determined encoding. [XML] [XML-NAMES]
2977 : 229 : * This is restricted to xhr’s response type being the empty string to keep the non-legacy response type value "text" simple.
2978 : 229 : * 4. If charset is null, then set charset to UTF-8.
2979 : 229 : * 5. Return the result of running decode on xhr’s received bytes using fallback encoding charset.
2980 : 229 : * Authors are strongly encouraged to always encode their resources using UTF-8.
2981 : 229 : */
2982 : 229 :
2983 : 229 : /**
2984 : 229 : * @func getResponseMimeType
2985 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
2986 : 229 : * @return {string} MIME type.
2987 : 229 : * @desc Get a response MIME type for an XMLHttpRequest object xhr, as given by {@link https://xhr.spec.whatwg.org/#response-body XHR specs}.
2988 : 229 : * @requires module:helper-content-type.parse
2989 : 229 : */
2990 [ + ]: 908 : function getResponseMimeType(){
2991 : 908 :
2992 : 908 : const requestData = xmlHttpRequest.get(this);
2993 : 908 : const response = requestData['response'];
2994 : 908 :
2995 : 908 : let mimeType = '';
2996 : 908 :
2997 : 908 : // xhr.spec.1. Let mimeType be the result of <extracting a MIME type> from xhr’s response’s header list.
2998 : 908 : try{
2999 : 908 : let obj = contentTypeParse(response.headers['content-type']);
3000 : 908 : mimeType = response.headers['content-type'];
3001 : 908 : }
3002 : 908 : // xhr.spec.2. If mimeType is failure, then set mimeType to text/xml.
3003 [ + ]: 908 : catch(e){
3004 : 48 : mimeType = 'text/xml';
3005 : 48 : }
3006 : 908 :
3007 : 908 : // xhr.spec.3. Return mimeType.
3008 : 908 : return mimeType;
3009 : 908 :
3010 : 229 : } // @endof function getResponseMimeType
3011 : 229 :
3012 : 229 : /**
3013 : 229 : * @func getFinalMimeType
3014 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
3015 : 229 : * @return {string} MIME type.
3016 : 229 : * @desc Get a final MIME type for an XMLHttpRequest object xhr, as given by {@link https://xhr.spec.whatwg.org/#response-body XHR specs}.
3017 : 229 : */
3018 [ + ]: 496 : function getFinalMimeType(){
3019 : 496 :
3020 : 496 : const requestData = xmlHttpRequest.get(this);
3021 : 496 :
3022 : 496 : // xhr.spec.1. If xhr’s <override MIME type> is null, return the result of get a response MIME type for xhr.
3023 : 496 : if(requestData['overrideMimeType'] === null)
3024 [ + ][ + ]: 496 : return getResponseMimeType.call(this);
3025 : 1 :
3026 : 1 : // xhr.spec.2. Return xhr’s override MIME type.
3027 : 1 : return requestData['overrideMimeType'];
3028 : 1 :
3029 : 229 : } // @endof function getFinalMimeType
3030 : 229 :
3031 : 229 : /**
3032 : 229 : * @func getFinalEncoding
3033 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
3034 : 229 : * @return {string|null} Final encoding.
3035 : 229 : * @desc Get a final encoding for an XMLHttpRequest object xhr, as given by {@link https://xhr.spec.whatwg.org/#response-body XHR specs}.
3036 : 229 : * @requires module:helper-content-type.parse
3037 : 229 : */
3038 [ + ]: 413 : function getFinalEncoding(){
3039 : 413 :
3040 : 413 : const requestData = xmlHttpRequest.get(this);
3041 : 413 :
3042 : 413 : // xhr.spec.1. Let label be null.
3043 : 413 : let label = null;
3044 : 413 :
3045 : 413 : // xhr.spec.2. Let responseMIME be the result of <get a response MIME type> for xhr.
3046 : 413 : let responseMIME = getResponseMimeType.call(this);
3047 : 413 :
3048 : 413 : let obj = null;
3049 : 413 : // xhr.spec.3. If responseMIME’s parameters["charset"] exists, then set label to it.
3050 : 413 : try{
3051 : 413 : obj = contentTypeParse(responseMIME);
3052 : 413 : if(obj.parameters && obj.parameters.charset)
3053 [ + ]: 413 : label = obj.parameters.charset;
3054 [ - ]: 413 : }catch(e){}
3055 : 413 :
3056 : 413 : // xhr.spec.4. If xhr’s override MIME type’s parameters["charset"] exists, then set label to it.
3057 [ + ]: 413 : if(requestData['overrideMimeType']){
3058 : 4 : try{
3059 : 4 : obj = contentTypeParse(requestData['overrideMimeType']);
3060 : 4 : if(obj.parameters && obj.parameters.charset)
3061 [ + ]: 4 : label = obj.parameters.charset;
3062 : 4 : }catch(e){}
3063 : 4 : }
3064 : 413 :
3065 : 413 : // xhr.spec.5. If label is null, then return null.
3066 [ + ][ + ]: 413 : if(label === null) return null;
3067 : 17 :
3068 : 17 : // xhr.spec.6. Let encoding be the result of getting an encoding from label.
3069 : 17 : let encoding = whatwgEncoding.labelToName(label);
3070 : 17 :
3071 : 17 : // xhr.spec.7. If encoding is failure, then return null.
3072 : 17 : // xhr.spec.8. Return encoding.
3073 : 17 : return encoding;
3074 : 17 :
3075 : 229 : } // @endof function getFinalEncoding
3076 : 229 :
3077 : 229 : /**
3078 : 229 : * @func setDocumentResponse
3079 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
3080 : 229 : * @desc Set a document response for an XMLHttpRequest object xhr, as given by {@link https://xhr.spec.whatwg.org/#response-body XHR specs}.
3081 : 229 : * @requires module:helper-content-type.parse
3082 : 229 : */
3083 [ + ]: 216 : function setDocumentResponse(){
3084 : 216 :
3085 : 216 : const requestData = xmlHttpRequest.get(this);
3086 : 216 :
3087 : 216 : // xhr.spec.1. If xhr’s response’s body is null, then return.
3088 [ + ]: 216 : if(requestData['receivedBytes'].length === 0) return;
3089 : 2 :
3090 : 2 : // xhr.spec.2. Let <finalMIME> be the result of get a final MIME type for xhr.
3091 : 2 : let finalMIME = getFinalMimeType.call(this);
3092 : 2 : let obj;
3093 : 2 :
3094 : 2 : try{
3095 : 2 : obj = contentTypeParse(finalMIME);
3096 : 2 :
3097 : 2 : // xhr.spec.3. If <finalMIME> is not an HTML MIME type or an XML MIME type, then return.
3098 : 2 : if(
3099 : 2 : obj.type !== 'text/xml' &&
3100 [ + ]: 216 : obj.type !== 'application/xml' &&
3101 [ + ]: 216 : !obj.type.endsWith('+xml') &&
3102 : 2 : obj.type !== 'text/html'
3103 : 216 : )
3104 [ + ]: 216 : return;
3105 : 1 :
3106 : 1 : // xhr.spec.4. If xhr’s response type is the empty string and <finalMIME> is an HTML MIME type, then return.
3107 : 216 : if(obj.type === 'text/html' && this.responseType === '')
3108 [ + ]: 216 : return;
3109 : 216 :
3110 : 216 : // xhr.spec.5. If finalMIME is an HTML MIME type, then:
3111 [ - ]: 216 : if(obj.type === 'text/html'){
3112 : 0 : /* node:coverage disable */
3113 : :
3114 : : // xhr.spec.5.1. Let <charset> be the result of get a final encoding for xhr.
3115 : : let charset = getFinalEncoding.call(this);
3116 : :
3117 : : // xhr.spec.5.2. If <charset> is null, prescan the first 1024 bytes of xhr’s received bytes and if that does not terminate
3118 : : // unsuccessfully then let <charset> be the return value.
3119 : : if(charset === null){
3120 : : charset = htmlEncodingSniffer(requestData['receivedBytes'].subarray(0, 1024));
3121 : : }
3122 : :
3123 : : // xhr.spec.5.3. If <charset> is null, then set <charset> to UTF-8.
3124 : : if(charset === null) charset = 'UTF-8';
3125 : :
3126 : : // xhr.spec.5.4. Let <document> be a document that represents the result parsing xhr’s received bytes
3127 : : // following the rules set forth in the HTML Standard for an HTML parser with scripting
3128 : : // disabled and a known definite encoding charset. [HTML]
3129 : : // xhr.spec.5.5. Flag document as an HTML document.
3130 : :
3131 : : // @todo...
3132 : :
3133 : : /* node:coverage enable */
3134 : 0 : }
3135 : 216 : else{
3136 : 216 : // xhr.spec.6. Otherwise, let document be a document that represents the result of running the XML parser
3137 : 216 : // with XML scripting support disabled on xhr’s received bytes.
3138 : 216 : // If that fails (unsupported character encoding, namespace well-formedness error, etc.),
3139 : 216 : // then return null. [HTML]
3140 : 216 : // Note: Resources referenced will not be loaded and no associated XSLT will be applied.
3141 : 216 :
3142 : 216 : // @todo...
3143 : 216 : }
3144 [ - ]: 216 : }catch(e){
3145 : 0 : return;
3146 : 0 : }
3147 : 216 :
3148 : 216 : // xhr.spec.7. If charset is null, then set charset to UTF-8.
3149 : 216 : // xhr.spec.8. Set document’s encoding to charset.
3150 : 216 : // xhr.spec.9. Set document’s content type to finalMIME.
3151 : 216 : // xhr.spec.10. Set document’s URL to xhr’s response’s URL.
3152 : 216 : // xhr.spec.11. Set document’s origin to xhr’s relevant settings object’s origin.
3153 : 216 :
3154 : 216 : // @todo...
3155 : 216 :
3156 : 216 : // xhr.spec.12. Set xhr’s response object to document.
3157 : 216 : requestData['responseObject'] = null;
3158 : 216 :
3159 : 229 : } // @endof function setDocumentResponse
3160 : 229 :
3161 : 229 : /**
3162 : 229 : * @func getTextResponse
3163 : 229 : * @this module:whatwg-xhr.XMLHttpRequest
3164 : 229 : * @return {string} Text response.
3165 : 229 : * @desc Get a text for an XMLHttpRequest object xhr, as given by {@link https://xhr.spec.whatwg.org/#response-body XHR specs}.
3166 : 229 : * @requires module:helper-content-type.parse
3167 : 229 : * @requires module:helper-xml-encoding-sniffer.xmlEncodingSniffer
3168 : 229 : */
3169 [ + ]: 723 : function getTextResponse(){
3170 : 723 :
3171 : 723 : const requestData = xmlHttpRequest.get(this);
3172 : 723 :
3173 : 723 : // xhr.spec.1. If xhr’s response’s body is null, then return the empty string.
3174 : 723 : // @author. if(requestData['_responseBody'] === null)
3175 [ + ]: 723 : if(requestData['receivedBytes'].length === 0){
3176 : 104 : return '';
3177 [ + ]: 104 : }
3178 : 195 :
3179 : 195 : // xhr.spec.2. Let charset be the result of get a final encoding for xhr.
3180 : 195 : let charset = getFinalEncoding.call(this);
3181 : 195 :
3182 : 195 : // xhr.spec.3. If xhr’s response type is the empty string, charset is null, and the result of get a final MIME type for xhr is an XML MIME type,
3183 : 195 : // then use the rules set forth in the XML specifications to determine the encoding. Let charset be the determined encoding. [XML] [XML-NAMES]
3184 : 195 : // This is restricted to xhr’s response type being the empty string to keep the non-legacy response type value "text" simple.
3185 [ + ][ + ]: 723 : if(requestData['responseType'] === '' && charset === null){
3186 : 165 : let finalMimeType = getFinalMimeType.call(this);
3187 : 165 : try{
3188 : 165 : let obj = contentTypeParse(finalMimeType);
3189 [ + ][ + ][ + ]: 165 : if(obj.type === 'text/xml' || obj.type === 'application/xml' || obj.type.endsWith('+xml')){
3190 : 29 : // @todo. use the rules set forth in the XML specifications to determine the encoding. Let charset be the determined encoding.
3191 : 29 : charset = xmlEncodingSniffer(requestData['receivedBytes']);
3192 : 29 : }
3193 [ - ]: 165 : }catch(e){}
3194 [ + ]: 165 : }
3195 : 195 :
3196 : 195 : // xhr.spec.4. If charset is null, then set charset to UTF-8.
3197 [ + ][ + ]: 723 : if(charset === null) charset = 'UTF-8';
3198 : 195 :
3199 : 195 : // xhr.spec.5. Return the result of running decode on xhr’s received bytes using fallback encoding charset.
3200 : 195 : return whatwgEncoding.decode(requestData['receivedBytes'], charset);
3201 : 195 :
3202 : 229 : } // @endof function getTextResponse
3203 : 229 :
3204 : 229 : /**
3205 : 229 : * @class ClientRequestData
3206 : 229 : * @desc Emulates the node http.ClientRequest for processing data url's.
3207 : 229 : */
3208 : 229 : class ClientRequestData{
3209 : 229 :
3210 : 229 : /**
3211 : 229 : * @method constructor
3212 : 229 : * @instance
3213 : 229 : * @memberof module:whatwg-xhr~ClientRequestData
3214 : 229 : * @param {object} response - Emulates the http.IncomingMessage object.
3215 : 229 : * @return {object} ClientRequestData object.
3216 : 229 : * @desc Constructs a new ClientRequestData object.
3217 : 229 : */
3218 [ + ]: 229 : constructor(response){
3219 : 13 : this.response = response;
3220 : 13 : }
3221 : 229 :
3222 : 229 : /**
3223 : 229 : * @method destroy
3224 : 229 : * @instance
3225 : 229 : * @memberof module:whatwg-xhr~ClientRequestData
3226 : 229 : * @return {object} ClientRequestData object.
3227 : 229 : * @desc Emulates the node http.ClientRequest destroy() method.
3228 : 229 : */
3229 [ + ]: 229 : destroy(){
3230 : 2 : return this;
3231 : 2 : }
3232 : 229 :
3233 : 229 : /**
3234 : 229 : * @method end
3235 : 229 : * @instance
3236 : 229 : * @memberof module:whatwg-xhr~ClientRequestData
3237 : 229 : * @param {function} callback - Callback function to invoke.
3238 : 229 : * @return {object} ClientRequestData object.
3239 : 229 : * @desc Emulates the node http.ClientRequest end() method.
3240 : 229 : */
3241 [ + ]: 229 : end(callback){
3242 : 13 : if(typeof callback === 'function')
3243 [ - ]: 13 : callback();
3244 : 13 :
3245 : 13 : return this;
3246 : 13 : }
3247 : 229 :
3248 : 229 : /**
3249 : 229 : * @method on
3250 : 229 : * @instance
3251 : 229 : * @memberof module:whatwg-xhr~ClientRequestData
3252 : 229 : * @param {string} type - The event to invoke.
3253 : 229 : * @param {function} callback - Callback function to invoke.
3254 : 229 : * @return {object} ClientRequestData object.
3255 : 229 : * @desc Emulates the node http.ClientRequest on() method.
3256 : 229 : */
3257 [ + ]: 229 : on(type, callback){
3258 [ + ]: 39 : if(type === 'response' && typeof callback === 'function')
3259 [ + ][ + ]: 39 : callback(this.response);
3260 : 26 : else
3261 [ + ][ + ]: 26 : if(type === 'error' && typeof callback === 'function' && this.response.statusCode === 0)
3262 [ + ]: 26 : callback(new ProgressEvent('error'));
3263 : 39 :
3264 : 39 : return this;
3265 : 39 : }
3266 : 229 : } // @endof class ClientRequestData
3267 : 229 :
3268 : 229 : /**
3269 : 229 : * @func requestDataURL
3270 : 229 : * @param {object} options - Options passed to the node http.request function.
3271 : 229 : * @return {object} ClientRequestData object.
3272 : 229 : * @desc Emulates the node http.request function for processing data url's.
3273 : 229 : * @requires module:whatwg-misc.dataURLProcessor
3274 : 229 : * @requires module:whatwg-misc.serializeAMimeType
3275 : 229 : */
3276 [ + ]: 13 : function requestDataURL(options){
3277 : 13 :
3278 : 13 : let urlStruct = null;
3279 : 13 : let responseDataURL = null;
3280 : 13 :
3281 : 13 : try{
3282 : 13 : urlStruct = dataURLProcessor(new URL(options.url));
3283 [ - ]: 13 : }catch(e){
3284 : 0 : urlStruct = 'failure';
3285 : 0 : }
3286 : 13 :
3287 [ + ]: 13 : if(urlStruct !== 'failure'){
3288 : 11 :
3289 : 11 : if(options.method.toUpperCase() === 'HEAD')
3290 [ + ][ + ]: 11 : responseDataURL = Readable.from(Buffer.alloc(0));
3291 : 10 : else
3292 : 10 : responseDataURL = Readable.from(Buffer.from(urlStruct.body));
3293 : 11 :
3294 : 11 : responseDataURL.statusCode = 200;
3295 : 11 : responseDataURL.statusMessage = 'OK';
3296 : 11 : responseDataURL.url = options.url;
3297 : 11 :
3298 : 11 : responseDataURL.headers = {
3299 : 11 : 'Content-Type': serializeAMimeType(urlStruct.mimeType),
3300 : 11 : }
3301 [ + ]: 11 : }
3302 : 1 : else{
3303 : 1 : responseDataURL = Readable.from(Buffer.alloc(0));
3304 : 1 :
3305 : 1 : responseDataURL.statusCode = 0;
3306 : 1 : responseDataURL.statusMessage = '';
3307 : 1 :
3308 : 1 : responseDataURL.headers = {};
3309 : 1 : }
3310 : 13 :
3311 : 13 : return new ClientRequestData(responseDataURL);
3312 : 13 :
3313 : 229 : } // @endof function requestDataURL
|