happy-dom 7.7.2 → 7.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of happy-dom might be problematic. Click here for more details.
- package/README.md +62 -27
- package/lib/async-task-manager/AsyncTaskManager.d.ts +3 -8
- package/lib/async-task-manager/AsyncTaskManager.js +21 -24
- package/lib/async-task-manager/AsyncTaskManager.js.map +1 -1
- package/lib/event/IEventListener.d.ts +1 -1
- package/lib/exception/DOMExceptionNameEnum.d.ts +4 -1
- package/lib/exception/DOMExceptionNameEnum.js +3 -0
- package/lib/exception/DOMExceptionNameEnum.js.map +1 -1
- package/lib/fetch/FetchHandler.d.ts +3 -2
- package/lib/fetch/FetchHandler.js +31 -3
- package/lib/fetch/FetchHandler.js.map +1 -1
- package/lib/fetch/RequestInfo.d.ts +5 -0
- package/lib/fetch/RequestInfo.js +3 -0
- package/lib/fetch/RequestInfo.js.map +1 -0
- package/lib/fetch/ResourceFetchHandler.d.ts +2 -2
- package/lib/fetch/ResourceFetchHandler.js +9 -8
- package/lib/fetch/ResourceFetchHandler.js.map +1 -1
- package/lib/index.d.ts +1 -2
- package/lib/index.js +3 -4
- package/lib/index.js.map +1 -1
- package/lib/location/Location.d.ts +2 -1
- package/lib/location/Location.js +4 -7
- package/lib/location/Location.js.map +1 -1
- package/lib/location/RelativeURL.d.ts +3 -1
- package/lib/location/RelativeURL.js +2 -11
- package/lib/location/RelativeURL.js.map +1 -1
- package/lib/nodes/document/Document.d.ts +12 -2
- package/lib/nodes/document/Document.js +16 -2
- package/lib/nodes/document/Document.js.map +1 -1
- package/lib/nodes/document/IDocument.d.ts +2 -0
- package/lib/nodes/html-link-element/HTMLLinkElement.js +5 -2
- package/lib/nodes/html-link-element/HTMLLinkElement.js.map +1 -1
- package/lib/nodes/html-script-element/HTMLScriptElement.js +1 -1
- package/lib/nodes/html-script-element/HTMLScriptElement.js.map +1 -1
- package/lib/nodes/html-script-element/ScriptUtility.js +4 -0
- package/lib/nodes/html-script-element/ScriptUtility.js.map +1 -1
- package/lib/window/IHappyDOMSettings.d.ts +9 -0
- package/lib/window/IHappyDOMSettings.js +3 -0
- package/lib/window/IHappyDOMSettings.js.map +1 -0
- package/lib/window/IWindow.d.ts +15 -6
- package/lib/window/Window.d.ts +18 -3
- package/lib/window/Window.js +26 -4
- package/lib/window/Window.js.map +1 -1
- package/lib/xml-http-request/XMLHttpRequest.d.ts +196 -0
- package/lib/xml-http-request/XMLHttpRequest.js +777 -0
- package/lib/xml-http-request/XMLHttpRequest.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestCertificate.d.ts +5 -0
- package/lib/xml-http-request/XMLHttpRequestCertificate.js +55 -0
- package/lib/xml-http-request/XMLHttpRequestCertificate.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestEventTarget.d.ts +15 -0
- package/lib/xml-http-request/XMLHttpRequestEventTarget.js +23 -0
- package/lib/xml-http-request/XMLHttpRequestEventTarget.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestReadyStateEnum.d.ts +8 -0
- package/lib/xml-http-request/XMLHttpRequestReadyStateEnum.js +12 -0
- package/lib/xml-http-request/XMLHttpRequestReadyStateEnum.js.map +1 -0
- package/lib/xml-http-request/XMLHttpRequestUpload.d.ts +6 -0
- package/lib/xml-http-request/XMLHttpRequestUpload.js +13 -0
- package/lib/xml-http-request/XMLHttpRequestUpload.js.map +1 -0
- package/lib/xml-http-request/XMLHttpResponseTypeEnum.d.ts +8 -0
- package/lib/xml-http-request/XMLHttpResponseTypeEnum.js +12 -0
- package/lib/xml-http-request/XMLHttpResponseTypeEnum.js.map +1 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.d.ts +15 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.js +55 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.js.map +1 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestURLUtility.d.ts +35 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestURLUtility.js +62 -0
- package/lib/xml-http-request/utilities/XMLHttpRequestURLUtility.js.map +1 -0
- package/package.json +2 -3
- package/src/async-task-manager/AsyncTaskManager.ts +24 -26
- package/src/event/IEventListener.ts +1 -1
- package/src/exception/DOMExceptionNameEnum.ts +4 -1
- package/src/fetch/FetchHandler.ts +40 -4
- package/src/fetch/RequestInfo.ts +6 -0
- package/src/fetch/ResourceFetchHandler.ts +10 -8
- package/src/index.ts +1 -2
- package/src/location/Location.ts +3 -3
- package/src/location/RelativeURL.ts +3 -14
- package/src/nodes/document/Document.ts +18 -2
- package/src/nodes/document/IDocument.ts +2 -0
- package/src/nodes/html-link-element/HTMLLinkElement.ts +7 -2
- package/src/nodes/html-script-element/HTMLScriptElement.ts +1 -1
- package/src/nodes/html-script-element/ScriptUtility.ts +8 -0
- package/src/window/IHappyDOMSettings.ts +9 -0
- package/src/window/IWindow.ts +15 -6
- package/src/window/Window.ts +36 -5
- package/src/xml-http-request/XMLHttpRequest.ts +998 -0
- package/src/xml-http-request/XMLHttpRequestCertificate.ts +52 -0
- package/src/xml-http-request/XMLHttpRequestEventTarget.ts +17 -0
- package/src/xml-http-request/XMLHttpRequestReadyStateEnum.ts +9 -0
- package/src/xml-http-request/XMLHttpRequestUpload.ts +6 -0
- package/src/xml-http-request/XMLHttpResponseTypeEnum.ts +9 -0
- package/src/xml-http-request/utilities/XMLHttpRequestSyncRequestScriptBuilder.ts +53 -0
- package/src/xml-http-request/utilities/XMLHttpRequestURLUtility.ts +64 -0
- package/lib/location/URL.d.ts +0 -53
- package/lib/location/URL.js +0 -96
- package/lib/location/URL.js.map +0 -1
- package/src/location/URL.ts +0 -102
@@ -0,0 +1,998 @@
|
|
1
|
+
import FS from 'fs';
|
2
|
+
import ChildProcess from 'child_process';
|
3
|
+
import HTTP from 'http';
|
4
|
+
import HTTPS from 'https';
|
5
|
+
import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget';
|
6
|
+
import XMLHttpRequestReadyStateEnum from './XMLHttpRequestReadyStateEnum';
|
7
|
+
import Event from '../event/Event';
|
8
|
+
import IDocument from '../nodes/document/IDocument';
|
9
|
+
import Blob from '../file/Blob';
|
10
|
+
import RelativeURL from '../location/RelativeURL';
|
11
|
+
import XMLHttpRequestUpload from './XMLHttpRequestUpload';
|
12
|
+
import DOMException from '../exception/DOMException';
|
13
|
+
import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum';
|
14
|
+
import { UrlObject } from 'url';
|
15
|
+
import XMLHttpRequestURLUtility from './utilities/XMLHttpRequestURLUtility';
|
16
|
+
import ProgressEvent from '../event/events/ProgressEvent';
|
17
|
+
import XMLHttpResponseTypeEnum from './XMLHttpResponseTypeEnum';
|
18
|
+
import XMLHttpRequestCertificate from './XMLHttpRequestCertificate';
|
19
|
+
import XMLHttpRequestSyncRequestScriptBuilder from './utilities/XMLHttpRequestSyncRequestScriptBuilder';
|
20
|
+
|
21
|
+
// These headers are not user setable.
|
22
|
+
// The following are allowed but banned in the spec:
|
23
|
+
// * User-agent
|
24
|
+
const FORBIDDEN_REQUEST_HEADERS = [
|
25
|
+
'accept-charset',
|
26
|
+
'accept-encoding',
|
27
|
+
'access-control-request-headers',
|
28
|
+
'access-control-request-method',
|
29
|
+
'connection',
|
30
|
+
'content-length',
|
31
|
+
'content-transfer-encoding',
|
32
|
+
'cookie',
|
33
|
+
'cookie2',
|
34
|
+
'date',
|
35
|
+
'expect',
|
36
|
+
'host',
|
37
|
+
'keep-alive',
|
38
|
+
'origin',
|
39
|
+
'referer',
|
40
|
+
'te',
|
41
|
+
'trailer',
|
42
|
+
'transfer-encoding',
|
43
|
+
'upgrade',
|
44
|
+
'via'
|
45
|
+
];
|
46
|
+
|
47
|
+
// These request methods are not allowed
|
48
|
+
const FORBIDDEN_REQUEST_METHODS = ['TRACE', 'TRACK', 'CONNECT'];
|
49
|
+
|
50
|
+
/**
|
51
|
+
* XMLHttpRequest.
|
52
|
+
*
|
53
|
+
* Based on:
|
54
|
+
* https://github.com/mjwwit/node-XMLHttpRequest/blob/master/lib/XMLHttpRequest.js
|
55
|
+
*/
|
56
|
+
export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
|
57
|
+
// Owner document is set by a sub-class in the Window constructor
|
58
|
+
public static _ownerDocument: IDocument = null;
|
59
|
+
|
60
|
+
// Constants
|
61
|
+
public static UNSENT = XMLHttpRequestReadyStateEnum.unsent;
|
62
|
+
public static OPENED = XMLHttpRequestReadyStateEnum.opened;
|
63
|
+
public static HEADERS_RECEIVED = XMLHttpRequestReadyStateEnum.headersRecieved;
|
64
|
+
public static LOADING = XMLHttpRequestReadyStateEnum.loading;
|
65
|
+
public static DONE = XMLHttpRequestReadyStateEnum.done;
|
66
|
+
|
67
|
+
// Public properties
|
68
|
+
public upload: XMLHttpRequestUpload = new XMLHttpRequestUpload();
|
69
|
+
|
70
|
+
// Private properties
|
71
|
+
private readonly _ownerDocument: IDocument = null;
|
72
|
+
private _state: {
|
73
|
+
incommingMessage: HTTP.IncomingMessage | { headers: string[]; statusCode: number };
|
74
|
+
response: ArrayBuffer | Blob | IDocument | object | string;
|
75
|
+
responseType: XMLHttpResponseTypeEnum | '';
|
76
|
+
responseText: string;
|
77
|
+
responseXML: IDocument;
|
78
|
+
responseURL: string;
|
79
|
+
readyState: XMLHttpRequestReadyStateEnum;
|
80
|
+
asyncRequest: HTTP.ClientRequest;
|
81
|
+
asyncTaskID: number;
|
82
|
+
requestHeaders: object;
|
83
|
+
status: number;
|
84
|
+
statusText: string;
|
85
|
+
send: boolean;
|
86
|
+
error: boolean;
|
87
|
+
aborted: boolean;
|
88
|
+
} = {
|
89
|
+
incommingMessage: null,
|
90
|
+
response: null,
|
91
|
+
responseType: '',
|
92
|
+
responseText: '',
|
93
|
+
responseXML: null,
|
94
|
+
responseURL: '',
|
95
|
+
readyState: XMLHttpRequestReadyStateEnum.unsent,
|
96
|
+
asyncRequest: null,
|
97
|
+
asyncTaskID: null,
|
98
|
+
requestHeaders: {},
|
99
|
+
status: null,
|
100
|
+
statusText: null,
|
101
|
+
send: false,
|
102
|
+
error: false,
|
103
|
+
aborted: false
|
104
|
+
};
|
105
|
+
|
106
|
+
private _settings: {
|
107
|
+
method: string;
|
108
|
+
url: string;
|
109
|
+
async: boolean;
|
110
|
+
user: string;
|
111
|
+
password: string;
|
112
|
+
} = {
|
113
|
+
method: null,
|
114
|
+
url: null,
|
115
|
+
async: true,
|
116
|
+
user: null,
|
117
|
+
password: null
|
118
|
+
};
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Constructor.
|
122
|
+
*/
|
123
|
+
constructor() {
|
124
|
+
super();
|
125
|
+
this._ownerDocument = XMLHttpRequest._ownerDocument;
|
126
|
+
}
|
127
|
+
|
128
|
+
/**
|
129
|
+
* Returns the status.
|
130
|
+
*
|
131
|
+
* @returns Status.
|
132
|
+
*/
|
133
|
+
public get status(): number {
|
134
|
+
return this._state.status;
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Returns the status text.
|
139
|
+
*
|
140
|
+
* @returns Status text.
|
141
|
+
*/
|
142
|
+
public get statusText(): string {
|
143
|
+
return this._state.statusText;
|
144
|
+
}
|
145
|
+
|
146
|
+
/**
|
147
|
+
* Returns the response URL.
|
148
|
+
*
|
149
|
+
* @returns Response URL.
|
150
|
+
*/
|
151
|
+
public get responseURL(): string {
|
152
|
+
return this._state.responseURL;
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Returns the ready state.
|
157
|
+
*
|
158
|
+
* @returns Ready state.
|
159
|
+
*/
|
160
|
+
public get readyState(): XMLHttpRequestReadyStateEnum {
|
161
|
+
return this._state.readyState;
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Get the response text.
|
166
|
+
*
|
167
|
+
* @throws {DOMException} If the response type is not text or empty.
|
168
|
+
* @returns The response text.
|
169
|
+
*/
|
170
|
+
public get responseText(): string {
|
171
|
+
if (this.responseType === XMLHttpResponseTypeEnum.text || this.responseType === '') {
|
172
|
+
return this._state.responseText;
|
173
|
+
}
|
174
|
+
throw new DOMException(
|
175
|
+
`Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was '${this.responseType}').`,
|
176
|
+
DOMExceptionNameEnum.invalidStateError
|
177
|
+
);
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Get the responseXML.
|
182
|
+
*
|
183
|
+
* @throws {DOMException} If the response type is not text or empty.
|
184
|
+
* @returns Response XML.
|
185
|
+
*/
|
186
|
+
public get responseXML(): IDocument {
|
187
|
+
if (this.responseType === XMLHttpResponseTypeEnum.document || this.responseType === '') {
|
188
|
+
return this._state.responseXML;
|
189
|
+
}
|
190
|
+
throw new DOMException(
|
191
|
+
`Failed to read the 'responseXML' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'document' (was '${this.responseType}').`,
|
192
|
+
DOMExceptionNameEnum.invalidStateError
|
193
|
+
);
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Set response type.
|
198
|
+
*
|
199
|
+
* @param type Response type.
|
200
|
+
* @throws {DOMException} If the state is not unsent or opened.
|
201
|
+
* @throws {DOMException} If the request is synchronous.
|
202
|
+
*/
|
203
|
+
public set responseType(type: XMLHttpResponseTypeEnum | '') {
|
204
|
+
// ResponseType can only be set when the state is unsent or opened.
|
205
|
+
if (
|
206
|
+
this.readyState !== XMLHttpRequestReadyStateEnum.opened &&
|
207
|
+
this.readyState !== XMLHttpRequestReadyStateEnum.unsent
|
208
|
+
) {
|
209
|
+
throw new DOMException(
|
210
|
+
`Failed to set the 'responseType' property on 'XMLHttpRequest': The object's state must be OPENED or UNSENT.`,
|
211
|
+
DOMExceptionNameEnum.invalidStateError
|
212
|
+
);
|
213
|
+
}
|
214
|
+
// Sync requests can only have empty string or 'text' as response type.
|
215
|
+
if (!this._settings.async) {
|
216
|
+
throw new DOMException(
|
217
|
+
`Failed to set the 'responseType' property on 'XMLHttpRequest': The response type cannot be changed for synchronous requests made from a document.`,
|
218
|
+
DOMExceptionNameEnum.invalidStateError
|
219
|
+
);
|
220
|
+
}
|
221
|
+
this._state.responseType = type;
|
222
|
+
}
|
223
|
+
|
224
|
+
/**
|
225
|
+
* Get response Type.
|
226
|
+
*
|
227
|
+
* @returns Response type.
|
228
|
+
*/
|
229
|
+
public get responseType(): XMLHttpResponseTypeEnum | '' {
|
230
|
+
return this._state.responseType;
|
231
|
+
}
|
232
|
+
|
233
|
+
/**
|
234
|
+
* Opens the connection.
|
235
|
+
*
|
236
|
+
* @param method Connection method (eg GET, POST).
|
237
|
+
* @param url URL for the connection.
|
238
|
+
* @param [async=true] Asynchronous connection.
|
239
|
+
* @param [user] Username for basic authentication (optional).
|
240
|
+
* @param [password] Password for basic authentication (optional).
|
241
|
+
*/
|
242
|
+
public open(method: string, url: string, async = true, user?: string, password?: string): void {
|
243
|
+
this.abort();
|
244
|
+
|
245
|
+
this._state.aborted = false;
|
246
|
+
this._state.error = false;
|
247
|
+
|
248
|
+
const upperMethod = method.toUpperCase();
|
249
|
+
|
250
|
+
// Check for valid request method
|
251
|
+
if (FORBIDDEN_REQUEST_METHODS.includes(upperMethod)) {
|
252
|
+
throw new DOMException('Request method not allowed', DOMExceptionNameEnum.securityError);
|
253
|
+
}
|
254
|
+
|
255
|
+
// Check responseType.
|
256
|
+
if (!async && !!this.responseType && this.responseType !== XMLHttpResponseTypeEnum.text) {
|
257
|
+
throw new DOMException(
|
258
|
+
`Failed to execute 'open' on 'XMLHttpRequest': Synchronous requests from a document must not set a response type.`,
|
259
|
+
DOMExceptionNameEnum.invalidAccessError
|
260
|
+
);
|
261
|
+
}
|
262
|
+
|
263
|
+
this._settings = {
|
264
|
+
method: upperMethod,
|
265
|
+
url: url,
|
266
|
+
async: async,
|
267
|
+
user: user || null,
|
268
|
+
password: password || null
|
269
|
+
};
|
270
|
+
|
271
|
+
this._setState(XMLHttpRequestReadyStateEnum.opened);
|
272
|
+
}
|
273
|
+
|
274
|
+
/**
|
275
|
+
* Sets a header for the request.
|
276
|
+
*
|
277
|
+
* @param header Header name
|
278
|
+
* @param value Header value
|
279
|
+
* @returns Header added.
|
280
|
+
*/
|
281
|
+
public setRequestHeader(header: string, value: string): boolean {
|
282
|
+
if (this.readyState !== XMLHttpRequestReadyStateEnum.opened) {
|
283
|
+
throw new DOMException(
|
284
|
+
`Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.`,
|
285
|
+
DOMExceptionNameEnum.invalidStateError
|
286
|
+
);
|
287
|
+
}
|
288
|
+
|
289
|
+
const lowerHeader = header.toLowerCase();
|
290
|
+
|
291
|
+
if (FORBIDDEN_REQUEST_HEADERS.includes(lowerHeader)) {
|
292
|
+
return false;
|
293
|
+
}
|
294
|
+
|
295
|
+
if (this._state.send) {
|
296
|
+
throw new DOMException(
|
297
|
+
`Failed to execute 'setRequestHeader' on 'XMLHttpRequest': Request is in progress.`,
|
298
|
+
DOMExceptionNameEnum.invalidStateError
|
299
|
+
);
|
300
|
+
}
|
301
|
+
|
302
|
+
this._state.requestHeaders[lowerHeader] = value;
|
303
|
+
|
304
|
+
return true;
|
305
|
+
}
|
306
|
+
|
307
|
+
/**
|
308
|
+
* Gets a header from the server response.
|
309
|
+
*
|
310
|
+
* @param header header Name of header to get.
|
311
|
+
* @returns string Text of the header or null if it doesn't exist.
|
312
|
+
*/
|
313
|
+
public getResponseHeader(header: string): string {
|
314
|
+
const lowerHeader = header.toLowerCase();
|
315
|
+
|
316
|
+
// Cookie headers are excluded for security reasons as per spec.
|
317
|
+
if (
|
318
|
+
typeof header === 'string' &&
|
319
|
+
header !== 'set-cookie' &&
|
320
|
+
header !== 'set-cookie2' &&
|
321
|
+
this.readyState > XMLHttpRequestReadyStateEnum.opened &&
|
322
|
+
this._state.incommingMessage.headers[lowerHeader] &&
|
323
|
+
!this._state.error
|
324
|
+
) {
|
325
|
+
return this._state.incommingMessage.headers[lowerHeader];
|
326
|
+
}
|
327
|
+
|
328
|
+
return null;
|
329
|
+
}
|
330
|
+
|
331
|
+
/**
|
332
|
+
* Gets all the response headers.
|
333
|
+
*
|
334
|
+
* @returns A string with all response headers separated by CR+LF.
|
335
|
+
*/
|
336
|
+
public getAllResponseHeaders(): string {
|
337
|
+
if (this.readyState < XMLHttpRequestReadyStateEnum.headersRecieved || this._state.error) {
|
338
|
+
return '';
|
339
|
+
}
|
340
|
+
|
341
|
+
const result = [];
|
342
|
+
|
343
|
+
for (const name of Object.keys(this._state.incommingMessage.headers)) {
|
344
|
+
// Cookie headers are excluded for security reasons as per spec.
|
345
|
+
if (name !== 'set-cookie' && name !== 'set-cookie2') {
|
346
|
+
result.push(`${name}: ${this._state.incommingMessage.headers[name]}`);
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
return result.join('\r\n');
|
351
|
+
}
|
352
|
+
|
353
|
+
/**
|
354
|
+
* Sends the request to the server.
|
355
|
+
*
|
356
|
+
* @param data Optional data to send as request body.
|
357
|
+
*/
|
358
|
+
public send(data?: string): void {
|
359
|
+
if (this.readyState != XMLHttpRequestReadyStateEnum.opened) {
|
360
|
+
throw new DOMException(
|
361
|
+
`Failed to execute 'send' on 'XMLHttpRequest': Connection must be opened before send() is called.`,
|
362
|
+
DOMExceptionNameEnum.invalidStateError
|
363
|
+
);
|
364
|
+
}
|
365
|
+
|
366
|
+
if (this._state.send) {
|
367
|
+
throw new DOMException(
|
368
|
+
`Failed to execute 'send' on 'XMLHttpRequest': Send has already been called.`,
|
369
|
+
DOMExceptionNameEnum.invalidStateError
|
370
|
+
);
|
371
|
+
}
|
372
|
+
|
373
|
+
const { location } = this._ownerDocument.defaultView;
|
374
|
+
|
375
|
+
const url = RelativeURL.getAbsoluteURL(location, this._settings.url);
|
376
|
+
|
377
|
+
// Security check.
|
378
|
+
if (url.protocol === 'http:' && location.protocol === 'https:') {
|
379
|
+
throw new DOMException(
|
380
|
+
`Mixed Content: The page at '${location.href}' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint '${url.href}'. This request has been blocked; the content must be served over HTTPS.`,
|
381
|
+
DOMExceptionNameEnum.securityError
|
382
|
+
);
|
383
|
+
}
|
384
|
+
|
385
|
+
// Load files off the local filesystem (file://)
|
386
|
+
if (XMLHttpRequestURLUtility.isLocal(url)) {
|
387
|
+
if (!this._ownerDocument.defaultView.happyDOM.settings.enableFileSystemHttpRequests) {
|
388
|
+
throw new DOMException(
|
389
|
+
'File system is disabled by default for security reasons. To enable it, set the "window.happyDOM.settings.enableFileSystemHttpRequests" option to true.',
|
390
|
+
DOMExceptionNameEnum.securityError
|
391
|
+
);
|
392
|
+
}
|
393
|
+
|
394
|
+
if (this._settings.method !== 'GET') {
|
395
|
+
throw new DOMException(
|
396
|
+
'Failed to send local file system request. Only "GET" method is supported for local file system requests.',
|
397
|
+
DOMExceptionNameEnum.notSupportedError
|
398
|
+
);
|
399
|
+
}
|
400
|
+
|
401
|
+
if (this._settings.async) {
|
402
|
+
this._sendLocalAsyncRequest(url).catch((error) => this._onError(error));
|
403
|
+
} else {
|
404
|
+
this._sendLocalSyncRequest(url);
|
405
|
+
}
|
406
|
+
return;
|
407
|
+
}
|
408
|
+
|
409
|
+
// TODO: CORS check.
|
410
|
+
|
411
|
+
const host = XMLHttpRequestURLUtility.getHost(url);
|
412
|
+
const ssl = XMLHttpRequestURLUtility.isSSL(url);
|
413
|
+
|
414
|
+
// Default to port 80. If accessing localhost on another port be sure
|
415
|
+
// To use http://localhost:port/path
|
416
|
+
const port = Number(url.port) || (ssl ? 443 : 80);
|
417
|
+
// Add query string if one is used
|
418
|
+
const uri = url.pathname + (url.search ? url.search : '');
|
419
|
+
|
420
|
+
// Set the Host header or the server may reject the request
|
421
|
+
this._state.requestHeaders['host'] = host;
|
422
|
+
if (!((ssl && port === 443) || port === 80)) {
|
423
|
+
this._state.requestHeaders['host'] += ':' + url.port;
|
424
|
+
}
|
425
|
+
|
426
|
+
// Set Basic Auth if necessary
|
427
|
+
if (this._settings.user) {
|
428
|
+
this._settings.password ??= '';
|
429
|
+
const authBuffer = Buffer.from(this._settings.user + ':' + this._settings.password);
|
430
|
+
this._state.requestHeaders['authorization'] = 'Basic ' + authBuffer.toString('base64');
|
431
|
+
}
|
432
|
+
// Set the Content-Length header if method is POST
|
433
|
+
switch (this._settings.method) {
|
434
|
+
case 'GET':
|
435
|
+
case 'HEAD':
|
436
|
+
data = null;
|
437
|
+
break;
|
438
|
+
case 'POST':
|
439
|
+
this._state.requestHeaders['content-type'] ??= 'text/plain;charset=UTF-8';
|
440
|
+
if (data) {
|
441
|
+
this._state.requestHeaders['content-length'] = Buffer.isBuffer(data)
|
442
|
+
? data.length
|
443
|
+
: Buffer.byteLength(data);
|
444
|
+
} else {
|
445
|
+
this._state.requestHeaders['content-length'] = 0;
|
446
|
+
}
|
447
|
+
break;
|
448
|
+
|
449
|
+
default:
|
450
|
+
break;
|
451
|
+
}
|
452
|
+
|
453
|
+
const options: HTTPS.RequestOptions = {
|
454
|
+
host: host,
|
455
|
+
port: port,
|
456
|
+
path: uri,
|
457
|
+
method: this._settings.method,
|
458
|
+
headers: { ...this._getDefaultRequestHeaders(), ...this._state.requestHeaders },
|
459
|
+
agent: false,
|
460
|
+
rejectUnauthorized: true,
|
461
|
+
key: ssl ? XMLHttpRequestCertificate.key : null,
|
462
|
+
cert: ssl ? XMLHttpRequestCertificate.cert : null
|
463
|
+
};
|
464
|
+
|
465
|
+
// Reset error flag
|
466
|
+
this._state.error = false;
|
467
|
+
|
468
|
+
// Handle async requests
|
469
|
+
if (this._settings.async) {
|
470
|
+
this._sendAsyncRequest(options, ssl, data).catch((error) => this._onError(error));
|
471
|
+
} else {
|
472
|
+
this._sendSyncRequest(options, ssl, data);
|
473
|
+
}
|
474
|
+
}
|
475
|
+
|
476
|
+
/**
|
477
|
+
* Aborts a request.
|
478
|
+
*/
|
479
|
+
public abort(): void {
|
480
|
+
if (this._state.asyncRequest) {
|
481
|
+
this._state.asyncRequest.destroy();
|
482
|
+
this._state.asyncRequest = null;
|
483
|
+
}
|
484
|
+
|
485
|
+
this._state.status = null;
|
486
|
+
this._state.statusText = null;
|
487
|
+
this._state.requestHeaders = {};
|
488
|
+
this._state.responseText = '';
|
489
|
+
this._state.responseXML = null;
|
490
|
+
this._state.aborted = true;
|
491
|
+
this._state.error = true;
|
492
|
+
|
493
|
+
if (
|
494
|
+
this.readyState !== XMLHttpRequestReadyStateEnum.unsent &&
|
495
|
+
(this.readyState !== XMLHttpRequestReadyStateEnum.opened || this._state.send) &&
|
496
|
+
this.readyState !== XMLHttpRequestReadyStateEnum.done
|
497
|
+
) {
|
498
|
+
this._state.send = false;
|
499
|
+
this._setState(XMLHttpRequestReadyStateEnum.done);
|
500
|
+
}
|
501
|
+
this._state.readyState = XMLHttpRequestReadyStateEnum.unsent;
|
502
|
+
|
503
|
+
if (this._state.asyncTaskID !== null) {
|
504
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
/**
|
509
|
+
* Changes readyState and calls onreadystatechange.
|
510
|
+
*
|
511
|
+
* @param state
|
512
|
+
*/
|
513
|
+
private _setState(state: XMLHttpRequestReadyStateEnum): void {
|
514
|
+
if (
|
515
|
+
this.readyState === state ||
|
516
|
+
(this.readyState === XMLHttpRequestReadyStateEnum.unsent && this._state.aborted)
|
517
|
+
) {
|
518
|
+
return;
|
519
|
+
}
|
520
|
+
|
521
|
+
this._state.readyState = state;
|
522
|
+
|
523
|
+
if (
|
524
|
+
this._settings.async ||
|
525
|
+
this.readyState < XMLHttpRequestReadyStateEnum.opened ||
|
526
|
+
this.readyState === XMLHttpRequestReadyStateEnum.done
|
527
|
+
) {
|
528
|
+
this.dispatchEvent(new Event('readystatechange'));
|
529
|
+
}
|
530
|
+
|
531
|
+
if (this.readyState === XMLHttpRequestReadyStateEnum.done) {
|
532
|
+
let fire: Event;
|
533
|
+
|
534
|
+
if (this._state.aborted) {
|
535
|
+
fire = new Event('abort');
|
536
|
+
} else if (this._state.error) {
|
537
|
+
fire = new Event('error');
|
538
|
+
} else {
|
539
|
+
fire = new Event('load');
|
540
|
+
}
|
541
|
+
|
542
|
+
this.dispatchEvent(fire);
|
543
|
+
this.dispatchEvent(new Event('loadend'));
|
544
|
+
}
|
545
|
+
}
|
546
|
+
|
547
|
+
/**
|
548
|
+
* Default request headers.
|
549
|
+
*
|
550
|
+
* @returns Default request headers.
|
551
|
+
*/
|
552
|
+
private _getDefaultRequestHeaders(): { [key: string]: string } {
|
553
|
+
const { location, navigator, document } = this._ownerDocument.defaultView;
|
554
|
+
|
555
|
+
return {
|
556
|
+
accept: '*/*',
|
557
|
+
referer: location.href,
|
558
|
+
'user-agent': navigator.userAgent,
|
559
|
+
cookie: document._cookie.getCookiesString(location, false)
|
560
|
+
};
|
561
|
+
}
|
562
|
+
|
563
|
+
/**
|
564
|
+
* Sends a synchronous request.
|
565
|
+
*
|
566
|
+
* @param options
|
567
|
+
* @param ssl
|
568
|
+
* @param data
|
569
|
+
*/
|
570
|
+
private _sendSyncRequest(options: HTTPS.RequestOptions, ssl: boolean, data?: string): void {
|
571
|
+
const scriptString = XMLHttpRequestSyncRequestScriptBuilder.getScript(options, ssl, data);
|
572
|
+
|
573
|
+
// Start the other Node Process, executing this string
|
574
|
+
const content = ChildProcess.execFileSync(process.argv[0], ['-e', scriptString], {
|
575
|
+
encoding: 'buffer',
|
576
|
+
maxBuffer: 1024 * 1024 * 1024 // TODO: Consistent buffer size: 1GB.
|
577
|
+
});
|
578
|
+
|
579
|
+
// If content length is 0, then there was an error
|
580
|
+
if (!content.length) {
|
581
|
+
throw new DOMException('Synchronous request failed', DOMExceptionNameEnum.networkError);
|
582
|
+
}
|
583
|
+
|
584
|
+
const { error, data: response } = JSON.parse(content.toString());
|
585
|
+
|
586
|
+
if (error) {
|
587
|
+
this._onError(error);
|
588
|
+
}
|
589
|
+
|
590
|
+
if (response) {
|
591
|
+
this._state.incommingMessage = {
|
592
|
+
statusCode: response.statusCode,
|
593
|
+
headers: response.headers
|
594
|
+
};
|
595
|
+
this._state.status = response.statusCode;
|
596
|
+
this._state.statusText = response.statusMessage;
|
597
|
+
// Sync responseType === ''
|
598
|
+
this._state.response = response.text;
|
599
|
+
this._state.responseText = response.text;
|
600
|
+
this._state.responseXML = null;
|
601
|
+
this._state.responseURL = RelativeURL.getAbsoluteURL(
|
602
|
+
this._ownerDocument.defaultView.location,
|
603
|
+
this._settings.url
|
604
|
+
).href;
|
605
|
+
// Set Cookies.
|
606
|
+
this._setCookies(this._state.incommingMessage.headers);
|
607
|
+
// Redirect.
|
608
|
+
if (
|
609
|
+
this._state.incommingMessage.statusCode === 301 ||
|
610
|
+
this._state.incommingMessage.statusCode === 302 ||
|
611
|
+
this._state.incommingMessage.statusCode === 303 ||
|
612
|
+
this._state.incommingMessage.statusCode === 307
|
613
|
+
) {
|
614
|
+
const redirectUrl = RelativeURL.getAbsoluteURL(
|
615
|
+
this._ownerDocument.defaultView.location,
|
616
|
+
this._state.incommingMessage.headers['location']
|
617
|
+
);
|
618
|
+
ssl = redirectUrl.protocol === 'https:';
|
619
|
+
this._settings.url = redirectUrl.href;
|
620
|
+
// Recursive call.
|
621
|
+
this._sendSyncRequest(
|
622
|
+
Object.assign(options, {
|
623
|
+
host: redirectUrl.host,
|
624
|
+
path: redirectUrl.pathname + (redirectUrl.search ?? ''),
|
625
|
+
port: redirectUrl.port || (ssl ? 443 : 80),
|
626
|
+
method: this._state.incommingMessage.statusCode === 303 ? 'GET' : this._settings.method,
|
627
|
+
headers: Object.assign(options.headers, {
|
628
|
+
referer: redirectUrl.origin,
|
629
|
+
host: redirectUrl.host
|
630
|
+
})
|
631
|
+
}),
|
632
|
+
ssl,
|
633
|
+
data
|
634
|
+
);
|
635
|
+
}
|
636
|
+
|
637
|
+
this._setState(XMLHttpRequestReadyStateEnum.done);
|
638
|
+
}
|
639
|
+
}
|
640
|
+
|
641
|
+
/**
|
642
|
+
* Sends an async request.
|
643
|
+
*
|
644
|
+
* @param options
|
645
|
+
* @param ssl
|
646
|
+
* @param data
|
647
|
+
*/
|
648
|
+
private _sendAsyncRequest(
|
649
|
+
options: HTTPS.RequestOptions,
|
650
|
+
ssl: boolean,
|
651
|
+
data?: string
|
652
|
+
): Promise<void> {
|
653
|
+
return new Promise((resolve) => {
|
654
|
+
// Starts async task in Happy DOM
|
655
|
+
this._state.asyncTaskID = this._ownerDocument.defaultView.happyDOM.asyncTaskManager.startTask(
|
656
|
+
this.abort.bind(this)
|
657
|
+
);
|
658
|
+
|
659
|
+
// Use the proper protocol
|
660
|
+
const sendRequest = ssl ? HTTPS.request : HTTP.request;
|
661
|
+
|
662
|
+
// Request is being sent, set send flag
|
663
|
+
this._state.send = true;
|
664
|
+
|
665
|
+
// As per spec, this is called here for historical reasons.
|
666
|
+
this.dispatchEvent(new Event('readystatechange'));
|
667
|
+
|
668
|
+
// Create the request
|
669
|
+
this._state.asyncRequest = sendRequest(
|
670
|
+
<object>options,
|
671
|
+
async (response: HTTP.IncomingMessage) => {
|
672
|
+
await this._onAsyncResponse(response, options, ssl, data);
|
673
|
+
|
674
|
+
resolve();
|
675
|
+
|
676
|
+
// Ends async task in Happy DOM
|
677
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(
|
678
|
+
this._state.asyncTaskID
|
679
|
+
);
|
680
|
+
}
|
681
|
+
).on('error', (error: Error) => {
|
682
|
+
this._onError(error);
|
683
|
+
resolve();
|
684
|
+
|
685
|
+
// Ends async task in Happy DOM
|
686
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
687
|
+
});
|
688
|
+
|
689
|
+
// Node 0.4 and later won't accept empty data. Make sure it's needed.
|
690
|
+
if (data) {
|
691
|
+
this._state.asyncRequest.write(data);
|
692
|
+
}
|
693
|
+
|
694
|
+
this._state.asyncRequest.end();
|
695
|
+
|
696
|
+
this.dispatchEvent(new Event('loadstart'));
|
697
|
+
});
|
698
|
+
}
|
699
|
+
|
700
|
+
/**
|
701
|
+
* Handles an async response.
|
702
|
+
*
|
703
|
+
* @param options Options.
|
704
|
+
* @param ssl SSL.
|
705
|
+
* @param data Data.
|
706
|
+
* @param response Response.
|
707
|
+
* @returns Promise.
|
708
|
+
*/
|
709
|
+
private _onAsyncResponse(
|
710
|
+
response: HTTP.IncomingMessage,
|
711
|
+
options: HTTPS.RequestOptions,
|
712
|
+
ssl: boolean,
|
713
|
+
data?: string
|
714
|
+
): Promise<void> {
|
715
|
+
return new Promise((resolve) => {
|
716
|
+
// Set response var to the response we got back
|
717
|
+
// This is so it remains accessable outside this scope
|
718
|
+
this._state.incommingMessage = response;
|
719
|
+
|
720
|
+
// Set Cookies
|
721
|
+
this._setCookies(this._state.incommingMessage.headers);
|
722
|
+
|
723
|
+
// Check for redirect
|
724
|
+
// @TODO Prevent looped redirects
|
725
|
+
if (
|
726
|
+
this._state.incommingMessage.statusCode === 301 ||
|
727
|
+
this._state.incommingMessage.statusCode === 302 ||
|
728
|
+
this._state.incommingMessage.statusCode === 303 ||
|
729
|
+
this._state.incommingMessage.statusCode === 307
|
730
|
+
) {
|
731
|
+
// TODO: redirect url protocol change.
|
732
|
+
// Change URL to the redirect location
|
733
|
+
this._settings.url = this._state.incommingMessage.headers.location;
|
734
|
+
// Parse the new URL.
|
735
|
+
const redirectUrl = RelativeURL.getAbsoluteURL(
|
736
|
+
this._ownerDocument.defaultView.location,
|
737
|
+
this._settings.url
|
738
|
+
);
|
739
|
+
this._settings.url = redirectUrl.href;
|
740
|
+
ssl = redirectUrl.protocol === 'https:';
|
741
|
+
// Issue the new request
|
742
|
+
this._sendAsyncRequest(
|
743
|
+
{
|
744
|
+
...options,
|
745
|
+
host: redirectUrl.hostname,
|
746
|
+
port: redirectUrl.port,
|
747
|
+
path: redirectUrl.pathname + (redirectUrl.search ?? ''),
|
748
|
+
method: this._state.incommingMessage.statusCode === 303 ? 'GET' : this._settings.method,
|
749
|
+
headers: { ...options.headers, referer: redirectUrl.origin, host: redirectUrl.host }
|
750
|
+
},
|
751
|
+
ssl,
|
752
|
+
data
|
753
|
+
);
|
754
|
+
// @TODO Check if an XHR event needs to be fired here
|
755
|
+
return;
|
756
|
+
}
|
757
|
+
|
758
|
+
if (this._state.incommingMessage && this._state.incommingMessage.setEncoding) {
|
759
|
+
this._state.incommingMessage.setEncoding('utf-8');
|
760
|
+
}
|
761
|
+
|
762
|
+
this._setState(XMLHttpRequestReadyStateEnum.headersRecieved);
|
763
|
+
this._state.status = this._state.incommingMessage.statusCode;
|
764
|
+
this._state.statusText = this._state.incommingMessage.statusMessage;
|
765
|
+
|
766
|
+
// Initialize response.
|
767
|
+
let tempResponse = Buffer.from(new Uint8Array(0));
|
768
|
+
|
769
|
+
this._state.incommingMessage.on('data', (chunk: Uint8Array) => {
|
770
|
+
// Make sure there's some data
|
771
|
+
if (chunk) {
|
772
|
+
tempResponse = Buffer.concat([tempResponse, Buffer.from(chunk)]);
|
773
|
+
}
|
774
|
+
// Don't emit state changes if the connection has been aborted.
|
775
|
+
if (this._state.send) {
|
776
|
+
this._setState(XMLHttpRequestReadyStateEnum.loading);
|
777
|
+
}
|
778
|
+
|
779
|
+
const contentLength = Number(this._state.incommingMessage.headers['content-length']);
|
780
|
+
this.dispatchEvent(
|
781
|
+
new ProgressEvent('progress', {
|
782
|
+
lengthComputable: isNaN(contentLength) ? false : true,
|
783
|
+
loaded: tempResponse.length,
|
784
|
+
total: isNaN(contentLength) ? 0 : contentLength
|
785
|
+
})
|
786
|
+
);
|
787
|
+
});
|
788
|
+
|
789
|
+
this._state.incommingMessage.on('end', () => {
|
790
|
+
if (this._state.send) {
|
791
|
+
// The sendFlag needs to be set before setState is called. Otherwise, if we are chaining callbacks
|
792
|
+
// There can be a timing issue (the callback is called and a new call is made before the flag is reset).
|
793
|
+
this._state.send = false;
|
794
|
+
|
795
|
+
// Set response according to responseType.
|
796
|
+
const { response, responseXML, responseText } = this._parseResponseData(tempResponse);
|
797
|
+
this._state.response = response;
|
798
|
+
this._state.responseXML = responseXML;
|
799
|
+
this._state.responseText = responseText;
|
800
|
+
this._state.responseURL = RelativeURL.getAbsoluteURL(
|
801
|
+
this._ownerDocument.defaultView.location,
|
802
|
+
this._settings.url
|
803
|
+
).href;
|
804
|
+
// Discard the 'end' event if the connection has been aborted
|
805
|
+
this._setState(XMLHttpRequestReadyStateEnum.done);
|
806
|
+
}
|
807
|
+
|
808
|
+
resolve();
|
809
|
+
});
|
810
|
+
|
811
|
+
this._state.incommingMessage.on('error', (error) => {
|
812
|
+
this._onError(error);
|
813
|
+
resolve();
|
814
|
+
});
|
815
|
+
});
|
816
|
+
}
|
817
|
+
|
818
|
+
/**
|
819
|
+
* Sends a local file system async request.
|
820
|
+
*
|
821
|
+
* @param url URL.
|
822
|
+
* @returns Promise.
|
823
|
+
*/
|
824
|
+
private async _sendLocalAsyncRequest(url: UrlObject): Promise<void> {
|
825
|
+
this._state.asyncTaskID = this._ownerDocument.defaultView.happyDOM.asyncTaskManager.startTask(
|
826
|
+
this.abort.bind(this)
|
827
|
+
);
|
828
|
+
|
829
|
+
let data: Buffer;
|
830
|
+
|
831
|
+
try {
|
832
|
+
data = await FS.promises.readFile(decodeURI(url.pathname.slice(1)));
|
833
|
+
} catch (error) {
|
834
|
+
this._onError(error);
|
835
|
+
}
|
836
|
+
|
837
|
+
const dataLength = data.length;
|
838
|
+
|
839
|
+
this._setState(XMLHttpRequestReadyStateEnum.loading);
|
840
|
+
this.dispatchEvent(
|
841
|
+
new ProgressEvent('progress', {
|
842
|
+
lengthComputable: true,
|
843
|
+
loaded: dataLength,
|
844
|
+
total: dataLength
|
845
|
+
})
|
846
|
+
);
|
847
|
+
|
848
|
+
if (data) {
|
849
|
+
this._parseLocalRequestData(data);
|
850
|
+
}
|
851
|
+
|
852
|
+
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
|
853
|
+
}
|
854
|
+
|
855
|
+
/**
|
856
|
+
* Sends a local file system synchronous request.
|
857
|
+
*
|
858
|
+
* @param url URL.
|
859
|
+
*/
|
860
|
+
private _sendLocalSyncRequest(url: UrlObject): void {
|
861
|
+
let data: Buffer;
|
862
|
+
try {
|
863
|
+
data = FS.readFileSync(decodeURI(url.pathname.slice(1)));
|
864
|
+
} catch (error) {
|
865
|
+
this._onError(error);
|
866
|
+
}
|
867
|
+
|
868
|
+
if (data) {
|
869
|
+
this._parseLocalRequestData(data);
|
870
|
+
}
|
871
|
+
}
|
872
|
+
|
873
|
+
/**
|
874
|
+
* Parses local request data.
|
875
|
+
*
|
876
|
+
* @param data Data.
|
877
|
+
*/
|
878
|
+
private _parseLocalRequestData(data: Buffer): void {
|
879
|
+
this._state.status = 200;
|
880
|
+
this._state.statusText = 'OK';
|
881
|
+
|
882
|
+
const { response, responseXML, responseText } = this._parseResponseData(data);
|
883
|
+
this._state.response = response;
|
884
|
+
this._state.responseXML = responseXML;
|
885
|
+
this._state.responseText = responseText;
|
886
|
+
this._state.responseURL = RelativeURL.getAbsoluteURL(
|
887
|
+
this._ownerDocument.defaultView.location,
|
888
|
+
this._settings.url
|
889
|
+
).href;
|
890
|
+
|
891
|
+
this._setState(XMLHttpRequestReadyStateEnum.done);
|
892
|
+
}
|
893
|
+
|
894
|
+
/**
|
895
|
+
* Returns response based to the "responseType" property.
|
896
|
+
*
|
897
|
+
* @param data Data.
|
898
|
+
* @returns Parsed response.
|
899
|
+
*/
|
900
|
+
private _parseResponseData(data: Buffer): {
|
901
|
+
response: ArrayBuffer | Blob | IDocument | object | string;
|
902
|
+
responseText: string;
|
903
|
+
responseXML: IDocument;
|
904
|
+
} {
|
905
|
+
switch (this.responseType) {
|
906
|
+
case XMLHttpResponseTypeEnum.arraybuffer:
|
907
|
+
// See: https://github.com/jsdom/jsdom/blob/c3c421c364510e053478520500bccafd97f5fa39/lib/jsdom/living/helpers/binary-data.js
|
908
|
+
const newAB = new ArrayBuffer(data.length);
|
909
|
+
const view = new Uint8Array(newAB);
|
910
|
+
view.set(data);
|
911
|
+
return {
|
912
|
+
response: view,
|
913
|
+
responseText: null,
|
914
|
+
responseXML: null
|
915
|
+
};
|
916
|
+
case XMLHttpResponseTypeEnum.blob:
|
917
|
+
try {
|
918
|
+
return {
|
919
|
+
response: new this._ownerDocument.defaultView.Blob([new Uint8Array(data)], {
|
920
|
+
type: this.getResponseHeader('content-type') || ''
|
921
|
+
}),
|
922
|
+
responseText: null,
|
923
|
+
responseXML: null
|
924
|
+
};
|
925
|
+
} catch (e) {
|
926
|
+
return { response: null, responseText: null, responseXML: null };
|
927
|
+
}
|
928
|
+
case XMLHttpResponseTypeEnum.document:
|
929
|
+
const window = this._ownerDocument.defaultView;
|
930
|
+
const happyDOMSettings = window.happyDOM.settings;
|
931
|
+
let response: IDocument;
|
932
|
+
|
933
|
+
// Temporary disables unsecure features.
|
934
|
+
window.happyDOM.settings = {
|
935
|
+
...happyDOMSettings,
|
936
|
+
enableFileSystemHttpRequests: false,
|
937
|
+
disableJavaScriptEvaluation: true,
|
938
|
+
disableCSSFileLoading: true,
|
939
|
+
disableJavaScriptFileLoading: true
|
940
|
+
};
|
941
|
+
|
942
|
+
const domParser = new window.DOMParser();
|
943
|
+
|
944
|
+
try {
|
945
|
+
response = domParser.parseFromString(data.toString(), 'text/xml');
|
946
|
+
} catch (e) {
|
947
|
+
return { response: null, responseText: null, responseXML: null };
|
948
|
+
}
|
949
|
+
|
950
|
+
// Restores unsecure features.
|
951
|
+
window.happyDOM.settings = happyDOMSettings;
|
952
|
+
|
953
|
+
return { response, responseText: null, responseXML: response };
|
954
|
+
case XMLHttpResponseTypeEnum.json:
|
955
|
+
try {
|
956
|
+
return {
|
957
|
+
response: JSON.parse(data.toString()),
|
958
|
+
responseText: null,
|
959
|
+
responseXML: null
|
960
|
+
};
|
961
|
+
} catch (e) {
|
962
|
+
return { response: null, responseText: null, responseXML: null };
|
963
|
+
}
|
964
|
+
case XMLHttpResponseTypeEnum.text:
|
965
|
+
case '':
|
966
|
+
default:
|
967
|
+
return {
|
968
|
+
response: data.toString(),
|
969
|
+
responseText: data.toString(),
|
970
|
+
responseXML: null
|
971
|
+
};
|
972
|
+
}
|
973
|
+
}
|
974
|
+
|
975
|
+
/**
|
976
|
+
* Set Cookies from response headers.
|
977
|
+
*
|
978
|
+
* @param headers String array.
|
979
|
+
*/
|
980
|
+
private _setCookies(headers: string[] | HTTP.IncomingHttpHeaders): void {
|
981
|
+
for (const cookie of [...(headers['set-cookie'] ?? []), ...(headers['set-cookie2'] ?? [])]) {
|
982
|
+
this._ownerDocument.defaultView.document._cookie.setCookiesString(cookie);
|
983
|
+
}
|
984
|
+
}
|
985
|
+
|
986
|
+
/**
|
987
|
+
* Called when an error is encountered to deal with it.
|
988
|
+
*
|
989
|
+
* @param error Error.
|
990
|
+
*/
|
991
|
+
private _onError(error: Error | string): void {
|
992
|
+
this._state.status = 0;
|
993
|
+
this._state.statusText = error.toString();
|
994
|
+
this._state.responseText = error instanceof Error ? error.stack : '';
|
995
|
+
this._state.error = true;
|
996
|
+
this._setState(XMLHttpRequestReadyStateEnum.done);
|
997
|
+
}
|
998
|
+
}
|