Skip to content

Commit

Permalink
Merge branch 'master' into task/785-add-document-title
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 authored Feb 25, 2023
2 parents c6afb80 + 9bcd5fd commit fad50de
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 13 deletions.
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
21 changes: 20 additions & 1 deletion packages/happy-dom/src/xml-parser/XMLParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import HTMLLinkElement from '../nodes/html-link-element/HTMLLinkElement';
import IDocumentFragment from '../nodes/document-fragment/IDocumentFragment';
import PlainTextElements from '../config/PlainTextElements';

const CONDITION_COMMENT_REGEXP =
/<!(--)?\[if (!|le|lt|lte|gt|gte|\(.*\)|&|\|| |IE|WindowsEdition|Contoso|true|false|\d+\.?(\d+)?|)*\]>/gi;
const CONDITION_COMMENT_END_REGEXP = /<!\[endif\](--)?>/gi;
const MARKUP_REGEXP = /<(\/?)([a-z][-.0-9_a-z]*)\s*([^<>]*?)(\/?)>/gi;
const COMMENT_REGEXP = /<!--(.*?)-->|<([!?])([^>]*)>/gi;
const DOCUMENT_TYPE_ATTRIBUTE_REGEXP = /"([^"]+)"/gm;
Expand Down Expand Up @@ -56,7 +59,23 @@ export default class XMLParser {
if (parentTagName && PlainTextElements.includes(parentTagName)) {
parent.appendChild(document.createTextNode(text));
} else {
this.appendTextAndCommentNodes(document, parent, text);
let condCommMatch;
let condCommEndMatch;
const condCommRegexp = new RegExp(CONDITION_COMMENT_REGEXP, 'gi');
const condCommEndRegexp = new RegExp(CONDITION_COMMENT_END_REGEXP, 'gi');
// @Refer: https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/?redirectedfrom=MSDN
if (
isStartTag &&
(condCommMatch = condCommRegexp.exec(text)) &&
condCommMatch[0] &&
(condCommEndMatch = condCommEndRegexp.exec(data.substring(markupRegexp.lastIndex))) &&
condCommEndMatch[0]
) {
markupRegexp.lastIndex += condCommEndRegexp.lastIndex;
continue;
} else {
this.appendTextAndCommentNodes(document, parent, text);
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions packages/happy-dom/test/xml-parser/XMLParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,5 +410,58 @@ describe('XMLParser', () => {
const root4 = XMLParser.parse(window.document, <string>(<unknown>false));
expect(new XMLSerializer().serializeToString(root4)).toBe('false');
});

it('Parses conditional comments', () => {
const testHTML = [
'<!--[if IE 8]>\n' + '<p>Welcome to Internet Explorer 8.</p>\n' + '<![endif]-->',

'<!--[if gte IE 7]>\n' +
'<script>\n' +
' alert("Congratulations! You are running Internet Explorer 7 or a later version of Internet Explorer.");\n' +
'</script>\n' +
'<p>Thank you for closing the message box.</p>\n' +
'<![endif]-->',

'<!--[if IE 5]>\n' +
'<p>Welcome to any incremental version of Internet Explorer 5!</p>\n' +
'<![endif]-->',

'<!--[if IE 5.0000]>\n' + '<p>Welcome to Internet Explorer 5.0!</p>\n' + '<![endif]-->',

'<!--[if WindowsEdition 1]>\n' +
'<p>You are using Windows Ultimate Edition.</p>\n' +
'<![endif]-->',

'<!--[if lt Contoso 2]>\n' +
'<p>Your version of the Contoso control is out of date; Please update to the latest.</p>\n' +
'<![endif]-->',

'<!DOCTYPE html><html lang="en">\n' +
'<head>\n' +
' <meta charset="UTF-8">\n' +
' <title>Title</title>\n' +
'</head>\n' +
'<body>\n' +
'<!--[if lt IE 9]>\n' +
"<script>window.location = 'browser.htm';</script>\n" +
'<![endif]-->\n' +
'\n' +
'\n' +
'<script>\n' +
" const node = document.createElement('a');\n" +
" node.href = 'http://www.google.com';\n" +
" node.target = '_blank';\n" +
" node.innerHTML = 'google';\n" +
' window.document.body.appendChild(node);\n' +
'</script>\n' +
'</body>\n' +
'</html>\n'
];

for (const html of testHTML) {
const root = XMLParser.parse(window.document, html);
expect(new XMLSerializer().serializeToString(root)).toBe(html);
}
});
});
});

0 comments on commit fad50de

Please sign in to comment.