@sproutboat/toolchain 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/acquire.test.ts +7 -1
- package/src/patch.test.ts +22 -0
- package/src/patch.ts +53 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- afd054c: Fix (baronunread/sproutboat#172): a standalone/native-fetch response body,
|
|
8
|
+
header name, or header value whose code points all fit one byte (any Latin-1-
|
|
9
|
+
range string — most non-astral JS strings) reached the wire as raw Latin-1
|
|
10
|
+
bytes instead of UTF-8, corrupting any non-ASCII text regardless of the
|
|
11
|
+
declared `charset`. `patchRenderJs` now encodes Porffor's `bytestring`
|
|
12
|
+
representation the same way its `string` (UTF-16) branch already did. Plain
|
|
13
|
+
ASCII output is unaffected (identical in both encodings).
|
|
14
|
+
|
|
3
15
|
## 0.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
package/src/acquire.test.ts
CHANGED
|
@@ -18,7 +18,13 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
18
18
|
"runtime/index.js": "fixture:runtime/index.js\n",
|
|
19
19
|
"compiler/render.js":
|
|
20
20
|
"void porf_native_fetch_runtime_init(void) {\n signal(SIGPIPE, SIG_IGN);\n porf_init(0, NULL);\n}\n" +
|
|
21
|
-
"f64 porf_native_fetch_get_port(void) {\nfixture:compiler/render.js\n"
|
|
21
|
+
"f64 porf_native_fetch_get_port(void) {\nfixture:compiler/render.js\n" +
|
|
22
|
+
" if (value.type == ${TYPES.bytestring}) {\n" +
|
|
23
|
+
" const u32 ptr = (u32)value.val;\n" +
|
|
24
|
+
" *out_buf = (const char*)(MEM + ptr + 4);\n" +
|
|
25
|
+
" *out_len = (size_t)*(u32*)(MEM + ptr);\n" +
|
|
26
|
+
" return 0;\n" +
|
|
27
|
+
" }\n",
|
|
22
28
|
"compiler/index.js": " '-xc', '-', '-c',\n uSocketsArchive,\n '-lm'\n",
|
|
23
29
|
"compiler/uwebsockets.js":
|
|
24
30
|
"static const size_t REQUEST_BODY_MAX_BYTES = 1024u * 1024u;\n" +
|
package/src/patch.test.ts
CHANGED
|
@@ -17,6 +17,23 @@ const RENDER = `void porf_native_fetch_runtime_init(void) {
|
|
|
17
17
|
f64 porf_native_fetch_get_port(void) {
|
|
18
18
|
return __porffor_native_fetch_port.val;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
int porf_native_fetch_read_value(jsval value, const char** out_buf, size_t* out_len, char** out_owned) {
|
|
22
|
+
if (!out_buf || !out_len || !out_owned) return -1;
|
|
23
|
+
|
|
24
|
+
*out_buf = NULL;
|
|
25
|
+
*out_len = 0;
|
|
26
|
+
*out_owned = NULL;
|
|
27
|
+
|
|
28
|
+
if (value.type == \${TYPES.bytestring}) {
|
|
29
|
+
const u32 ptr = (u32)value.val;
|
|
30
|
+
*out_buf = (const char*)(MEM + ptr + 4);
|
|
31
|
+
*out_len = (size_t)*(u32*)(MEM + ptr);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return -1;
|
|
36
|
+
}
|
|
20
37
|
`;
|
|
21
38
|
|
|
22
39
|
// Porffor's compiler/uwebsockets.js, trimmed to the parts the patch touches:
|
|
@@ -126,6 +143,11 @@ test("#165: render.js routes console output to stderr, unbuffered, idempotently"
|
|
|
126
143
|
// The $PORT edit still lands too.
|
|
127
144
|
expect(once).toContain('getenv("PORT")');
|
|
128
145
|
|
|
146
|
+
// #172: the bytestring branch encodes instead of copying raw bytes.
|
|
147
|
+
expect(once).toContain("sproutboat #172");
|
|
148
|
+
expect(once).toContain("(char)(0xc0 | (c >> 6));");
|
|
149
|
+
expect(once).not.toContain("*out_buf = (const char*)(MEM + ptr + 4);");
|
|
150
|
+
|
|
129
151
|
await patchRenderJs(root);
|
|
130
152
|
expect(await readFile(join(root, "compiler/render.js"), "utf8")).toBe(once);
|
|
131
153
|
} finally {
|
package/src/patch.ts
CHANGED
|
@@ -46,6 +46,47 @@ const RENDER_EDITS = [
|
|
|
46
46
|
[CONSOLE_MARKER, CONSOLE_ANCHOR, CONSOLE_INJECT, "console output sink (#165)"],
|
|
47
47
|
] as const;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* baronunread/sproutboat#172 — `porf_native_fetch_read_value`'s `bytestring`
|
|
51
|
+
* branch (Porffor's Latin-1-range string representation) copies the raw code
|
|
52
|
+
* units straight onto the wire, as if they were already UTF-8 bytes. Any unit
|
|
53
|
+
* >= 0x80 — any non-ASCII Latin-1 character — reaches the client corrupt
|
|
54
|
+
* instead of UTF-8 encoded. Response bodies and header values both go through
|
|
55
|
+
* this function, so both are affected; the sibling `string` (UTF-16) branch a
|
|
56
|
+
* few lines down already encodes correctly, this makes the `bytestring` one
|
|
57
|
+
* do the same, one byte in instead of two.
|
|
58
|
+
*/
|
|
59
|
+
const BYTESTRING_ANCHOR =
|
|
60
|
+
" if (value.type == ${TYPES.bytestring}) {\n" +
|
|
61
|
+
" const u32 ptr = (u32)value.val;\n" +
|
|
62
|
+
" *out_buf = (const char*)(MEM + ptr + 4);\n" +
|
|
63
|
+
" *out_len = (size_t)*(u32*)(MEM + ptr);\n" +
|
|
64
|
+
" return 0;\n" +
|
|
65
|
+
" }";
|
|
66
|
+
const BYTESTRING_INJECT =
|
|
67
|
+
" if (value.type == ${TYPES.bytestring}) {\n" +
|
|
68
|
+
" // sproutboat #172: units are Latin-1 code points, not UTF-8 bytes -- encode them\n" +
|
|
69
|
+
" const u32 ptr = (u32)value.val;\n" +
|
|
70
|
+
" const size_t len = (size_t)*(u32*)(MEM + ptr);\n" +
|
|
71
|
+
" const unsigned char* units = (const unsigned char*)(MEM + ptr + 4);\n" +
|
|
72
|
+
" char* utf8 = (char*)malloc(len * 2);\n" +
|
|
73
|
+
" if (!utf8 && len > 0) return -1;\n" +
|
|
74
|
+
" size_t out_len_local = 0;\n" +
|
|
75
|
+
" for (size_t i = 0; i < len; i++) {\n" +
|
|
76
|
+
" unsigned char c = units[i];\n" +
|
|
77
|
+
" if (c < 0x80) utf8[out_len_local++] = (char)c;\n" +
|
|
78
|
+
" else {\n" +
|
|
79
|
+
" utf8[out_len_local++] = (char)(0xc0 | (c >> 6));\n" +
|
|
80
|
+
" utf8[out_len_local++] = (char)(0x80 | (c & 0x3f));\n" +
|
|
81
|
+
" }\n" +
|
|
82
|
+
" }\n" +
|
|
83
|
+
" *out_buf = utf8;\n" +
|
|
84
|
+
" *out_len = out_len_local;\n" +
|
|
85
|
+
" *out_owned = utf8;\n" +
|
|
86
|
+
" return 0;\n" +
|
|
87
|
+
" }";
|
|
88
|
+
const BYTESTRING_MARKER = "sproutboat #172";
|
|
89
|
+
|
|
49
90
|
// #15 — no `--port` flag: Porffor's native-fetch entry point calls
|
|
50
91
|
// `porf_init(0, NULL)` (see porf_native_fetch_runtime_init in render.js), so a
|
|
51
92
|
// native-fetch binary never sees argv at all. A standalone binary takes its
|
|
@@ -244,6 +285,18 @@ export async function patchRenderJs(root: string): Promise<void> {
|
|
|
244
285
|
src = src.slice(0, at + anchor.length) + inject + src.slice(at + anchor.length);
|
|
245
286
|
changed = true;
|
|
246
287
|
}
|
|
288
|
+
|
|
289
|
+
if (!src.includes(BYTESTRING_MARKER)) {
|
|
290
|
+
if (!src.includes(BYTESTRING_ANCHOR)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
"could not patch Porffor for bytestring UTF-8 encoding (#172): anchor not found in " +
|
|
293
|
+
`${file}. Porffor's native-fetch renderer changed — check patches/UPSTREAM.md.`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
src = src.replace(BYTESTRING_ANCHOR, BYTESTRING_INJECT);
|
|
297
|
+
changed = true;
|
|
298
|
+
}
|
|
299
|
+
|
|
247
300
|
if (changed) await writeFile(file, src);
|
|
248
301
|
}
|
|
249
302
|
|