Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#783@patch: Fixes XMLHttpRequest response encoding error. #784

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/happy-dom/package.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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');
}
}
Original file line number Diff line number Diff line change
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