Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Mas0nShi committed Feb 25, 2023
1 parent 13bcfe7 commit 6763d52
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 18 deletions.
60 changes: 43 additions & 17 deletions packages/happy-dom/src/xml-http-request/XMLHttpRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
// Private properties
private readonly _ownerDocument: IDocument = null;
private _state: {
incommingMessage: HTTP.IncomingMessage | { headers: string[]; statusCode: number };
incommingMessage: HTTP.IncomingMessage | { headers: object; statusCode: number };
response: ArrayBuffer | Blob | IDocument | object | string;
responseType: XMLHttpResponseTypeEnum | '';
responseText: string;
Expand Down Expand Up @@ -146,6 +146,15 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
return this._state.statusText;
}

/**
* Returns the response.
*
* @returns Response.
*/
public get response(): ArrayBuffer | Blob | IDocument | object | string {
return this._state.response;
}

/**
* Returns the response URL.
*
Expand Down Expand Up @@ -597,7 +606,11 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
};
this._state.status = response.statusCode;
this._state.statusText = response.statusMessage;
// Although it will immediately be set to loading,
// According to the spec, the state should be headersRecieved first.
this._setState(XMLHttpRequestReadyStateEnum.headersRecieved);
// Sync responseType === ''
this._setState(XMLHttpRequestReadyStateEnum.loading);
this._state.response = this._decodeResponseText(Buffer.from(response.data, 'base64'));
this._state.responseText = this._state.response;
this._state.responseXML = null;
Expand Down Expand Up @@ -758,9 +771,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
return;
}

this._setState(XMLHttpRequestReadyStateEnum.headersRecieved);
this._state.status = this._state.incommingMessage.statusCode;
this._state.statusText = this._state.incommingMessage.statusMessage;
this._setState(XMLHttpRequestReadyStateEnum.headersRecieved);

// Initialize response.
let tempResponse = Buffer.from(new Uint8Array(0));
Expand Down Expand Up @@ -835,6 +848,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {

const dataLength = data.length;

// @TODO: set state headersRecieved first.
this._setState(XMLHttpRequestReadyStateEnum.loading);
this.dispatchEvent(
new ProgressEvent('progress', {
Expand All @@ -845,9 +859,10 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
);

if (data) {
this._parseLocalRequestData(data);
this._parseLocalRequestData(url, data);
}

this._setState(XMLHttpRequestReadyStateEnum.done);
this._ownerDocument.defaultView.happyDOM.asyncTaskManager.endTask(this._state.asyncTaskID);
}

Expand All @@ -863,19 +878,33 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
} catch (error) {
this._onError(error);
}

// @TODO: set state headersRecieved first.
this._setState(XMLHttpRequestReadyStateEnum.loading);
if (data) {
this._parseLocalRequestData(data);
this._parseLocalRequestData(url, data);
}

this._setState(XMLHttpRequestReadyStateEnum.done);
}

/**
* Parses local request data.
*
* @param url URL.
* @param data Data.
*/
private _parseLocalRequestData(data: Buffer): void {
this._state.status = 200;
private _parseLocalRequestData(url: UrlObject, data: Buffer): void {
// Manually set the response headers.
this._state.incommingMessage = {
statusCode: 200,
headers: {
'content-length': data.length,
'content-type': XMLHttpRequestURLUtility.getMimeTypeFromExt(url)
// @TODO: 'last-modified':
}
};

this._state.status = this._state.incommingMessage.statusCode;
this._state.statusText = 'OK';

const { response, responseXML, responseText } = this._parseResponseData(data);
Expand Down Expand Up @@ -941,7 +970,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
const domParser = new window.DOMParser();

try {
response = domParser.parseFromString(data.toString(), 'text/xml');
response = domParser.parseFromString(this._decodeResponseText(data), 'text/xml');
} catch (e) {
return { response: null, responseText: null, responseXML: null };
}
Expand Down Expand Up @@ -975,9 +1004,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
/**
* Set Cookies from response headers.
*
* @param headers String array.
* @param headers Headers.
*/
private _setCookies(headers: string[] | HTTP.IncomingHttpHeaders): void {
private _setCookies(headers: object | HTTP.IncomingHttpHeaders): void {
for (const cookie of [...(headers['set-cookie'] ?? []), ...(headers['set-cookie2'] ?? [])]) {
this._ownerDocument.defaultView.document._cookie.setCookiesString(cookie);
}
Expand All @@ -1004,14 +1033,11 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
**/
private _decodeResponseText(data: Buffer): string {
const contextTypeEncodingRegexp = new RegExp(CONTENT_TYPE_ENCODING_REGEXP, 'gi');
let contentType;
if (this._state.incommingMessage && this._state.incommingMessage.headers) {
contentType = this._state.incommingMessage.headers['content-type']; // For remote requests (http/https).
} else {
contentType = this._state.requestHeaders['content-type']; // For local requests or unpredictable remote requests.
}
// Compatibility with file:// protocol or unpredictable http request.
const contentType =
this.getResponseHeader('content-type') ?? this._state.requestHeaders['content-type'];
const charset = contextTypeEncodingRegexp.exec(contentType);
// Default utf-8
// Default encoding: utf-8.
return iconv.decode(data, charset ? charset[1] : 'utf-8');
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,78 @@
import { URL } from 'url';
import { URL, UrlObject } from 'url';
import Path from 'path';

// MIME type.
const LOCAL_MIME_TYPES = {
json: 'application/json',
xml: 'application/xml',
html: 'text/html',
text: 'text/plain',
xhtml: 'application/xhtml+xml',
xht: 'application/xhtml+xml',
xsl: 'application/xml',
xslt: 'application/xml',
rss: 'application/rss+xml',
atom: 'application/atom+xml',
yaml: 'application/x-yaml',
pdf: 'application/pdf',
zip: 'application/zip',
gzip: 'application/gzip',
rar: 'application/x-rar-compressed',
'7z': 'application/x-7z-compressed',
exe: 'application/x-msdownload',
csv: 'text/csv',
ics: 'text/calendar',
rtf: 'text/rtf',
js: 'application/javascript',
css: 'text/css',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
bmp: 'image/bmp',
ico: 'image/x-icon',
tiff: 'image/tiff',
tif: 'image/tiff',
svg: 'image/svg+xml',
svgz: 'image/svg+xml',
webp: 'image/webp',
wav: 'audio/wav',
webm: 'video/webm',
mp4: 'video/mp4',
mpeg: 'video/mpeg',
mpg: 'video/mpeg',
mov: 'video/quicktime',
avi: 'video/x-msvideo',
flv: 'video/x-flv',
mkv: 'video/x-matroska',
mka: 'audio/x-matroska',
m3u: 'audio/x-mpegurl',
m3u8: 'application/x-mpegURL',
pls: 'audio/x-scpls',
flac: 'audio/x-flac',
ogg: 'audio/ogg',
oga: 'audio/ogg',
ogv: 'video/ogg',
ogx: 'application/ogg',
opus: 'audio/opus',
spx: 'audio/ogg',
swf: 'application/x-shockwave-flash',
woff: 'font/woff',
woff2: 'font/woff2',
ttf: 'font/ttf',
eot: 'application/vnd.ms-fontobject',
otf: 'font/otf',
sfnt: 'application/font-sfnt',
bin: 'application/octet-stream',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
odt: 'application/vnd.oasis.opendocument.text',
ods: 'application/vnd.oasis.opendocument.spreadsheet'
};

/**
* URL utility.
Expand Down Expand Up @@ -61,4 +135,13 @@ export default class XMLHttpRequestURLUtility {
return null;
}
}

/**
*
* @param url
*/
public static getMimeTypeFromExt(url: UrlObject): string {
const ext = Path.extname(url.pathname);
return LOCAL_MIME_TYPES[ext] || 'application/octet-stream';
}
}

0 comments on commit 6763d52

Please sign in to comment.