@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/toolchain",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -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