fix(js): match Chromium data URL handling
This commit is contained in:
1 parent
0c4740a86a
commit
f84120591e
2 files changed
+97
-31
No files matched your search
@@ -154,15 +154,17 @@ async fn dynamic_data_scripts_execute_before_chained_load_handlers() {
|
||||
window.__dataScriptState = state;
|
||||
|
||||
var a = document.createElement('script');
|
||||
a.src = 'data:text/javascript,window.__dataScriptState.aExec=true';
|
||||
a.src = 'data:,window.__dataScriptState.aExec=true';
|
||||
a.onload = function () {
|
||||
state.aLoad = true;
|
||||
var b = document.createElement('script');
|
||||
b.src = 'data:application/javascript,' + encodeURIComponent('window.__dataScriptState.bExec=true');
|
||||
b.src = 'data:text/html,' + encodeURIComponent('window.__dataScriptState.bExec=true');
|
||||
b.onload = function () {
|
||||
state.bLoad = true;
|
||||
var c = document.createElement('script');
|
||||
c.src = 'data:text/javascript;base64,' + btoa('window.__dataScriptState.cExec=true');
|
||||
c.src = 'data:text/javascript;base64,' +
|
||||
btoa('window.__dataScriptState.cExec=true').replace(/=+$/, '') +
|
||||
'#ignored-fragment';
|
||||
c.onload = function () { state.cLoad = true; };
|
||||
document.head.appendChild(c);
|
||||
};
|
||||
@@ -196,3 +198,53 @@ async fn dynamic_data_scripts_execute_before_chained_load_handlers() {
|
||||
"data URL script bodies must execute before each chained load handler"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn invalid_dynamic_data_script_fires_error_not_load() {
|
||||
let mut ctx = CdpContext::new();
|
||||
let page_id = ctx.create_page();
|
||||
let session_id = "session-1";
|
||||
ctx.sessions.insert(session_id.to_string(), page_id);
|
||||
|
||||
cdp(
|
||||
&mut ctx,
|
||||
1,
|
||||
"Page.navigate",
|
||||
json!({"url": "data:text/html,<html><head></head><body></body></html>", "waitUntil": "load"}),
|
||||
session_id,
|
||||
)
|
||||
.await;
|
||||
|
||||
cdp(
|
||||
&mut ctx,
|
||||
2,
|
||||
"Runtime.callFunctionOn",
|
||||
json!({
|
||||
"functionDeclaration": r#"function () {
|
||||
window.__invalidDataScript = { error: false, load: false };
|
||||
var script = document.createElement('script');
|
||||
script.src = 'data:text/javascript;base64,!';
|
||||
script.onerror = function () { window.__invalidDataScript.error = true; };
|
||||
script.onload = function () { window.__invalidDataScript.load = true; };
|
||||
document.head.appendChild(script);
|
||||
}"#,
|
||||
"awaitPromise": true,
|
||||
}),
|
||||
session_id,
|
||||
)
|
||||
.await;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
let result = cdp(
|
||||
&mut ctx,
|
||||
3,
|
||||
"Runtime.evaluate",
|
||||
json!({
|
||||
"expression": "JSON.stringify(window.__invalidDataScript)",
|
||||
"returnByValue": true,
|
||||
}),
|
||||
session_id,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(result["result"]["value"], r#"{"error":true,"load":false}"#);
|
||||
}
|
||||
Vendored
+42
-28
@@ -164,35 +164,49 @@ let _fpSeed = 0;
|
||||
let __dynScriptQueue = [];
|
||||
let __dynScriptBusy = false;
|
||||
function _decodeDataScriptUrl(url) {
|
||||
let parsed;
|
||||
try { parsed = new URL(url); }
|
||||
catch(e) { throw new TypeError('Invalid dynamic script data URL'); }
|
||||
|
||||
const path = parsed.pathname;
|
||||
const comma = path.indexOf(',');
|
||||
if (comma < 0) throw new TypeError('Invalid dynamic script data URL');
|
||||
|
||||
const parts = path.slice(0, comma).split(';');
|
||||
const mime = parts.shift().trim().toLowerCase();
|
||||
const isJavaScript = mime === 'application/ecmascript' ||
|
||||
mime === 'application/javascript' || mime === 'application/x-ecmascript' ||
|
||||
mime === 'application/x-javascript' || mime === 'text/ecmascript' ||
|
||||
mime === 'text/javascript' || /^text\/javascript1\.[0-5]$/.test(mime) ||
|
||||
mime === 'text/jscript' || mime === 'text/livescript' ||
|
||||
mime === 'text/x-ecmascript' || mime === 'text/x-javascript';
|
||||
if (!isJavaScript) throw new TypeError('Unsupported dynamic script data URL MIME type');
|
||||
|
||||
let payload = path.slice(comma + 1);
|
||||
try { payload = decodeURIComponent(payload); }
|
||||
catch(e) { throw new TypeError('Invalid dynamic script data URL encoding'); }
|
||||
|
||||
const isBase64 = parts.some((part) => part.trim().toLowerCase() === 'base64');
|
||||
if (!isBase64) return payload;
|
||||
const clean = payload.replace(/[\r\n\s]/g, '');
|
||||
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(clean)) {
|
||||
throw new TypeError('Invalid dynamic script data URL base64');
|
||||
const comma = url.indexOf(',');
|
||||
if (!url.startsWith('data:') || comma < 5) {
|
||||
throw new TypeError('Invalid dynamic script data URL');
|
||||
}
|
||||
return new TextDecoder().decode(_base64ToUint8Array(clean));
|
||||
|
||||
const meta = url.slice(5, comma);
|
||||
const fragment = url.indexOf('#', comma + 1);
|
||||
const payload = url.slice(comma + 1, fragment < 0 ? url.length : fragment);
|
||||
if (meta.split(';').some(part => part.toLowerCase() === 'base64')) {
|
||||
let encoded = payload.replace(/[\r\n\t\f ]/g, '');
|
||||
const remainder = encoded.length % 4;
|
||||
if (remainder === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded) || /=/.test(encoded.slice(0, -2))) {
|
||||
throw new TypeError('Invalid dynamic script data URL base64');
|
||||
}
|
||||
if (remainder > 0) encoded += '='.repeat(4 - remainder);
|
||||
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(encoded)) {
|
||||
throw new TypeError('Invalid dynamic script data URL base64');
|
||||
}
|
||||
return new TextDecoder().decode(_base64ToUint8Array(encoded));
|
||||
}
|
||||
|
||||
const bytes = [];
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
const code = payload.charCodeAt(i);
|
||||
if (code === 0x25 && i + 2 < payload.length) {
|
||||
const hi = _hexv(payload.charCodeAt(i + 1));
|
||||
const lo = _hexv(payload.charCodeAt(i + 2));
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
bytes.push(hi * 16 + lo);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (code < 0x80) {
|
||||
bytes.push(code);
|
||||
} else {
|
||||
const character = String.fromCodePoint(payload.codePointAt(i));
|
||||
if (character.length === 2) i++;
|
||||
const encoded = new TextEncoder().encode(character);
|
||||
for (let j = 0; j < encoded.length; j++) bytes.push(encoded[j]);
|
||||
}
|
||||
}
|
||||
return new TextDecoder().decode(new Uint8Array(bytes));
|
||||
}
|
||||
async function __processDynScriptQueue() {
|
||||
if (__dynScriptBusy) return;
|
||||
|
||||
Reference in new issue
Block a user