sx-http: fix </script escaping in serialize_value, inline component-defs
escape_sx_string now escapes </ as <\\/ inside SX string literals, matching Python's serialize() behavior. This prevents the HTML parser from matching </script> inside component-defs strings while keeping the SX valid for the client parser. Component-defs back to inline <script data-components> (reverts external endpoint approach). init-sx triggers client render when sx-root is empty. Geography page: fully rendered with header, nav, content, styling. Header island hydration warning (Undefined symbol: let) is a pre-existing WASM kernel issue, not related to the HTTP server. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,14 +25,20 @@ open Sx_types
|
||||
|
||||
(** Escape a string for embedding in an SX string literal. *)
|
||||
let escape_sx_string s =
|
||||
let buf = Buffer.create (String.length s + 16) in
|
||||
String.iter (function
|
||||
let len = String.length s in
|
||||
let buf = Buffer.create (len + 16) in
|
||||
for i = 0 to len - 1 do
|
||||
match s.[i] with
|
||||
| '"' -> Buffer.add_string buf "\\\""
|
||||
| '\\' -> Buffer.add_string buf "\\\\"
|
||||
| '\n' -> Buffer.add_string buf "\\n"
|
||||
| '\r' -> Buffer.add_string buf "\\r"
|
||||
| '\t' -> Buffer.add_string buf "\\t"
|
||||
| c -> Buffer.add_char buf c) s;
|
||||
| '<' when i + 1 < len && s.[i + 1] = '/' ->
|
||||
(* Escape </ as <\\/ to prevent HTML parser matching </script> *)
|
||||
Buffer.add_string buf "<\\\\/"
|
||||
| c -> Buffer.add_char buf c
|
||||
done;
|
||||
Buffer.contents buf
|
||||
|
||||
(** Serialize a value to SX text (for io-request args). *)
|
||||
@@ -1619,14 +1625,10 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
| _ -> ()
|
||||
) env.bindings;
|
||||
let raw_defs = Buffer.contents buf in
|
||||
(* Don't inline component-defs — serve from /static/sx-components.sx.
|
||||
Inlining breaks because SX source can contain literal </script> which
|
||||
the HTML parser uses to close the tag prematurely. *)
|
||||
let component_defs = "" in
|
||||
(* Cache the raw defs for the /static/sx-components.sx endpoint *)
|
||||
Hashtbl.replace static_cache "/static/sx-components.sx"
|
||||
(Printf.sprintf "HTTP/1.1 200 OK\r\nContent-Type: text/sx; charset=utf-8\r\nContent-Length: %d\r\nCache-Control: public, max-age=31536000, immutable\r\n\r\n%s"
|
||||
(String.length raw_defs) raw_defs);
|
||||
(* Component-defs are inlined in <script type="text/sx">.
|
||||
The escape_sx_string function handles </ → <\\/ inside string
|
||||
literals, preventing the HTML parser from matching </script>. *)
|
||||
let component_defs = raw_defs in
|
||||
let component_hash = Digest.string component_defs |> Digest.to_hex in
|
||||
(* Compute file hashes for cache busting *)
|
||||
let sx_js_hash = file_hash (static_dir ^ "/scripts/sx-browser.js") in
|
||||
|
||||
Reference in New Issue
Block a user