Skip to content

Commit

Permalink
Merge pull request #784 from Mas0nShi/783-fixes-xmlhttprequest-respon…
Browse files Browse the repository at this point in the history
…se-encoding

#783@patch: Fixes XMLHttpRequest response encoding error.
  • Loading branch information
capricorn86 committed Feb 24, 2023
2 parents 2cb3f15 + 7e1b6fd commit 9bcd5fd
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/happy-dom/package.json
Expand Up @@ -47,7 +47,8 @@
"node-fetch": "^2.x.x",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^2.0.0",
"whatwg-mimetype": "^3.0.0"
"whatwg-mimetype": "^3.0.0",
"iconv-lite": "^0.6.3"
},
"devDependencies": {
"@types/he": "^1.1.2",
Expand Down
39 changes: 29 additions & 10 deletions packages/happy-dom/src/xml-http-request/XMLHttpRequest.ts
Expand Up @@ -17,6 +17,7 @@ import ProgressEvent from '../event/events/ProgressEvent';
import XMLHttpResponseTypeEnum from './XMLHttpResponseTypeEnum';
import XMLHttpRequestCertificate from './XMLHttpRequestCertificate';
import XMLHttpRequestSyncRequestScriptBuilder from './utilities/XMLHttpRequestSyncRequestScriptBuilder';
import iconv from 'iconv-lite';

// These headers are not user setable.
// The following are allowed but banned in the spec:
Expand Down Expand Up @@ -46,6 +47,8 @@ const FORBIDDEN_REQUEST_HEADERS = [

// These request methods are not allowed
const FORBIDDEN_REQUEST_METHODS = ['TRACE', 'TRACK', 'CONNECT'];
// Match Content-Type header charset
const CONTENT_TYPE_ENCODING_REGEXP = /charset=([^;]*)/i;

/**
* XMLHttpRequest.
Expand Down Expand Up @@ -595,8 +598,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
this._state.status = response.statusCode;
this._state.statusText = response.statusMessage;
// Sync responseType === ''
this._state.response = response.text;
this._state.responseText = response.text;
this._state.response = this._decodeResponseText(Buffer.from(response.data, 'base64'));
this._state.responseText = this._state.response;
this._state.responseXML = null;
this._state.responseURL = RelativeURL.getAbsoluteURL(
this._ownerDocument.defaultView.location,
Expand Down Expand Up @@ -755,10 +758,6 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
return;
}

if (this._state.incommingMessage && this._state.incommingMessage.setEncoding) {
this._state.incommingMessage.setEncoding('utf-8');
}

this._setState(XMLHttpRequestReadyStateEnum.headersRecieved);
this._state.status = this._state.incommingMessage.statusCode;
this._state.statusText = this._state.incommingMessage.statusMessage;
Expand All @@ -779,7 +778,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
const contentLength = Number(this._state.incommingMessage.headers['content-length']);
this.dispatchEvent(
new ProgressEvent('progress', {
lengthComputable: isNaN(contentLength) ? false : true,
lengthComputable: !isNaN(contentLength),
loaded: tempResponse.length,
total: isNaN(contentLength) ? 0 : contentLength
})
Expand Down Expand Up @@ -954,7 +953,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
case XMLHttpResponseTypeEnum.json:
try {
return {
response: JSON.parse(data.toString()),
response: JSON.parse(this._decodeResponseText(data)),
responseText: null,
responseXML: null
};
Expand All @@ -964,9 +963,10 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
case XMLHttpResponseTypeEnum.text:
case '':
default:
const responseText = this._decodeResponseText(data);
return {
response: data.toString(),
responseText: data.toString(),
response: responseText,
responseText: responseText,
responseXML: null
};
}
Expand Down Expand Up @@ -995,4 +995,23 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
this._state.error = true;
this._setState(XMLHttpRequestReadyStateEnum.done);
}

/**
* Decodes response text.
*
* @param data Data.
* @returns Decoded text.
**/
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.
}
const charset = contextTypeEncodingRegexp.exec(contentType);
// Default utf-8
return iconv.decode(data, charset ? charset[1] : 'utf-8');
}
}
Expand Up @@ -23,7 +23,6 @@ export default class XMLHttpRequestSyncRequestScriptBuilder {
const request = sendRequest(options, (response) => {
let responseText = '';
let responseData = Buffer.alloc(0);
response.setEncoding('utf8');
response.on('data', (chunk) => {
responseText += chunk;
responseData = Buffer.concat([responseData, Buffer.from(chunk)]);
Expand Down

0 comments on commit 9bcd5fd

Please sign in to comment.