Skip to content

Commit 1dde156

Browse files
JasonGrossclaude
andcommitted
Make the web page's argv string encoding a faithful round trip
Fixes scrutineer finding #2519. In fiat-html/main.js, splitUnescapedSpaces and joinWithEscaping convert between the "Input String" text box and the argv array passed to the worker. The decoder used U+0000 and U+0001 as internal placeholders and dropped empty strings, so `splitUnescapedSpaces(joinWithEscaping(a))` was not `a` for arguments containing those characters or for empty arguments. When the page was opened via a crafted `?argv=...&interactive` link, the text box was filled with the lossy re-encoding while synthesis ran on the original argv, so the visitor saw one command line and ran another. Rewrite the decoder as a character-by-character parser with no placeholder characters. `\ ` and `\\` keep their meaning, `\"` is a literal quote, and a bare `""` token is the empty argument; any other backslash is kept literally, as before. The encoder emits `""` for an empty argument and escapes the quotes of an argument that is literally `""`. With the pair an exact inverse, the displayed string always decodes to the argv that ran. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Rbn2gww3MGhvrh52fNjpD
1 parent 5691ca0 commit 1dde156

1 file changed

Lines changed: 27 additions & 10 deletions

File tree

fiat-html/main.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,40 @@ document.addEventListener('DOMContentLoaded', function () {
2020
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
2121
const isMacOrIOS = /Macintosh|MacIntel|MacPPC|Mac68K|iPhone|iPad|iPod/.test(navigator.platform);
2222

23+
// Format of the "Input String" text box: arguments are separated by
24+
// unescaped spaces; `\ ` is a literal space, `\\` a literal backslash,
25+
// `\"` a literal double quote, and a bare `""` token is an empty argument.
26+
// Any other backslash is kept literally. splitUnescapedSpaces and
27+
// joinWithEscaping must be exact inverses of each other, so that the
28+
// command line shown in the box is the one that actually runs.
2329
function splitUnescapedSpaces(input) {
24-
return input
25-
.replace(/\\\\/g, '\u0000') // Temporarily replace \\ with a placeholder
26-
.replace(/\\ /g, '\u0001') // Temporarily replace escaped spaces with a placeholder
27-
.split(/ +/) // Split by spaces
28-
.filter(s => s)
29-
.map(s => s
30-
.replace(/\u0000/g, '\\') // Restore backslashes
31-
.replace(/\u0001/g, ' ') // Restore spaces
32-
);
30+
const args = [];
31+
let current = null; // null between arguments
32+
for (let i = 0; i < input.length; i++) {
33+
const c = input[i], next = input[i + 1];
34+
if (c === ' ') {
35+
if (current !== null) args.push(current);
36+
current = null;
37+
} else if (c === '\\' && (next === ' ' || next === '\\' || next === '"')) {
38+
current = (current || '') + next;
39+
i++;
40+
} else if (c === '"' && current === null && next === '"' && (i + 2 >= input.length || input[i + 2] === ' ')) {
41+
args.push('');
42+
i++;
43+
} else {
44+
current = (current || '') + c;
45+
}
46+
}
47+
if (current !== null) args.push(current);
48+
return args;
3349
}
3450

3551
function joinWithEscaping(inputArray) {
3652
return inputArray
37-
.map(s => s
53+
.map(s => s === '' ? '""' : s
3854
.replace(/\\/g, '\\\\') // Escape backslashes
3955
.replace(/ /g, '\\ ') // Escape spaces
56+
.replace(/^""$/, '\\"\\"') // A literal "" must not read as the empty argument
4057
)
4158
.join(' ');
4259
}

0 commit comments

Comments
 (0)