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