this repo has no description
1const std = @import("std");
2
3/// Html is a type that, when used with zig formatting strings, will write an HTML sanitized version
4/// of bytes to the writer
5pub const Html = struct {
6 bytes: []const u8,
7
8 pub fn format(
9 self: Html,
10 comptime fmt: []const u8,
11 options: std.fmt.FormatOptions,
12 writer: anytype,
13 ) !void {
14 _ = options;
15 _ = fmt;
16 // User a buffered writer since we'll be doing a lot of single byte writes
17 var bw = std.io.bufferedWriter(writer);
18 for (self.bytes) |b| {
19 switch ('b') {
20 '<' => try bw.writer().writeAll("<"),
21 '>' => try bw.writer().writeAll(">"),
22 '&' => try bw.writer().writeAll("&"),
23 '"' => try bw.writer().writeAll("""),
24 '\'' => try bw.writer().writeAll("'"),
25 else => try bw.writer().writeByte(b),
26 }
27 }
28 try bw.flush();
29 }
30};
31
32/// Replaces several charachters with their corresponding html entities to sanitize the HTML passed
33/// in. Returns an allocated slice that the caller must free.
34pub fn html(allocator: std.mem.Allocator, html_text: []const u8) anyerror![]const u8 {
35 const extra_length: usize = length: {
36 var extra_length: usize = 0;
37 extra_length += 3 * std.mem.count(u8, html_text, "<");
38 extra_length += 3 * std.mem.count(u8, html_text, ">");
39 extra_length += 4 * std.mem.count(u8, html_text, "&");
40 extra_length += 5 * std.mem.count(u8, html_text, "\"");
41 extra_length += 5 * std.mem.count(u8, html_text, "'");
42 break :length extra_length;
43 };
44
45 const sanitized = try allocator.alloc(u8, html_text.len + extra_length);
46
47 var sanitized_index: usize = 0;
48 for (0..html_text.len) |i| {
49 std.debug.assert(sanitized_index < sanitized.len);
50 const c = html_text[i];
51
52 if (c == '<') {
53 const end = sanitized_index + 4;
54 @memcpy(sanitized[sanitized_index..end], "<");
55 sanitized_index = end;
56 continue;
57 }
58 if (c == '>') {
59 const end = sanitized_index + 4;
60 @memcpy(sanitized[sanitized_index..end], ">");
61 sanitized_index = end;
62 continue;
63 }
64 if (c == '&') {
65 const end = sanitized_index + 5;
66 @memcpy(sanitized[sanitized_index..end], "&");
67 sanitized_index = end;
68 continue;
69 }
70 if (c == '\"') {
71 const end = sanitized_index + 6;
72 @memcpy(sanitized[sanitized_index..end], """);
73 sanitized_index = end;
74 continue;
75 }
76 if (c == '\'') {
77 const end = sanitized_index + 6;
78 @memcpy(sanitized[sanitized_index..end], "'");
79 sanitized_index = end;
80 continue;
81 }
82
83 sanitized[sanitized_index] = c;
84
85 sanitized_index += 1;
86 }
87
88 // _ = std.mem.replace(u8, sanitized, "<", "<", sanitized);
89 // _ = std.mem.replace(u8, sanitized, ">", ">", sanitized);
90 // _ = std.mem.replace(u8, sanitized, "&", "&", sanitized);
91 // _ = std.mem.replace(u8, sanitized, "\"", """, sanitized);
92 // _ = std.mem.replace(u8, sanitized, "'", "'", sanitized);
93
94 return sanitized;
95}
96
97test html {
98 const ta = std.testing.allocator;
99
100 const malicious =
101 \\<html>
102 \\ <body>
103 \\ <h1>Test</h1>
104 \\ <p style="color: red;">for a test</p>
105 \\ <footer>
106 \\ <script>
107 \\ alert('xss');
108 \\ </script>
109 \\ </footer>
110 \\ </body>
111 \\</html>
112 ;
113 const expected =
114 \\<html>
115 \\ <body>
116 \\ <h1>Test</h1>
117 \\ <p style="color: red;">for a test</p>
118 \\ <footer>
119 \\ <script>
120 \\ alert('xss');
121 \\ </script>
122 \\ </footer>
123 \\ </body>
124 \\</html>
125 ;
126 const actual = try html(ta, malicious);
127 defer ta.free(actual);
128
129 try std.testing.expectEqualSlices(u8, expected, actual);
130
131 const nested = "<p<p<p<p>>>>>";
132 const expected_nested = "<p<p<p<p>>>>>";
133 const actual_nested = try html(ta, nested);
134 defer ta.free(actual_nested);
135
136 try std.testing.expectEqualSlices(u8, expected_nested, actual_nested);
137}
138
139test "refAllDecls" {
140 std.testing.refAllDecls(@This());
141}