╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │395 const overlay = try generateOverlay(allocator, &root, &session); │ │396 defer allocator.free(overlay); │ │397 │ │398 - // serialize the snapshot so the wasm side can parse it back without │ │399 - // making a second request. base64-encoded so the json can be embedded │ │400 - // safely inside the host html. │ │398 + // serialize the snapshot so the wasm side can parse it back without making │ │399 + // a second request. it's embedded raw in a <script type="application/json">│ │400 + // block, whose content is raw text terminated only by "</script". in json, │ │401 + // '<' appears solely inside string values, so escaping every '<' (and '>' │ │402 + // for good measure) to its \uXXXX form yields equivalent json that can't │ │403 + // break out of the tag. │ │404 var json: std.Io.Writer.Allocating = .init(allocator); │ │405 defer json.deinit(); │ │406 try std.json.Stringify.value(snapshot, .{}, &json.writer); │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │407 │ │405 - const b64 = std.base64.standard.Encoder; │ │406 - const json_b64 = try allocator.alloc(u8, b64.calcSize(json.written().len));│ │407 - defer allocator.free(json_b64); │ │408 - _ = b64.encode(json_b64, json.written()); │ │408 + var json_escaped: std.ArrayList(u8) = .empty; │ │409 + defer json_escaped.deinit(allocator); │ │410 + for (json.written()) |c| switch (c) { │ │411 + '<' => try json_escaped.appendSlice(allocator, "\\u003c"), │ │412 + '>' => try json_escaped.appendSlice(allocator, "\\u003e"), │ │413 + else => try json_escaped.append(allocator, c), │ │414 + }; │ │415 │ │416 var out: std.ArrayList(u8) = .empty; │ │417 errdefer out.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │420 for (&[_]struct { needle: []const u8, replacement: []const u8 }{ │ │421 .{ .needle = "{{{ HAXY_HTML }}}", .replacement = content }, │ │422 .{ .needle = "{{{ HAXY_OVERLAY }}}", .replacement = overlay }, │ │417 - .{ .needle = "{{{ HAXY_JSON }}}", .replacement = json_b64 }, │ │423 + .{ .needle = "{{{ HAXY_JSON }}}", .replacement = json_escaped.items }, │ │424 }) |sub| { │ │425 const idx = std.mem.indexOfPos(u8, template, cursor, sub.needle) orelse return error.MissingTemplateToken;│ │426 try out.appendSlice(allocator, template[cursor..idx]); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯