size: 2 KiB

const std = @import("std");
const t = std.testing;

pub fn escapeAlloc(arena: std.mem.Allocator, text: []const u8) ![]const u8 {
    var w = std.Io.Writer.Allocating.init(arena);
    try escape(&w.writer, text);
    return w.written();
}

pub fn escape(out: *std.Io.Writer, text: []const u8) !void {
    for (text) |char| {
        switch (char) {
            '&' => try out.writeAll("&"),
            '"' => try out.writeAll("""),
            '\'' => try out.writeAll("'"),
            '<' => try out.writeAll("&lt;"),
            '>' => try out.writeAll("&gt;"),
            else => try out.writeByte(char),
        }
    }
}

test "escape" {
    var buf: [4096]u8 = undefined;
    inline for (.{
        .{
            "<script>alert(\"jello\")</script>",
            "&lt;script&gt;alert(&quot;jello&quot;)&lt;/script&gt;",
        },
        .{
            "&\"'<>",
            "&amp;&quot;&#39;&lt;&gt;",
        },
    }) |case| {
        const input = case[0];
        const expected = case[1];
        var w = std.Io.Writer.fixed(&buf);
        try escape(&w, input);
        try t.expectEqualStrings(expected, w.buffered());
    }
}

pub fn percentEncode(out: *std.Io.Writer, text: []const u8) !void {
    for (text) |char| {
        switch (char) {
            ':' => try out.writeAll("%3A"),
            '/' => try out.writeAll("%2F"),
            '?' => try out.writeAll("%3F"),
            '#' => try out.writeAll("%23"),
            '[' => try out.writeAll("%5B"),
            ']' => try out.writeAll("%5D"),
            '@' => try out.writeAll("%40"),
            '!' => try out.writeAll("%21"),
            '$' => try out.writeAll("%24"),
            '&' => try out.writeAll("%26"),
            '\'' => try out.writeAll("%27"),
            '(' => try out.writeAll("%28"),
            ')' => try out.writeAll("%29"),
            '*' => try out.writeAll("%2A"),
            '+' => try out.writeAll("%2B"),
            ',' => try out.writeAll("%2C"),
            ';' => try out.writeAll("%3B"),
            '=' => try out.writeAll("%3D"),
            '%' => try out.writeAll("%25"),
            ' ' => try out.writeAll("%20"),
            else => try out.writeByte(char),
        }
    }
}

pub fn percentEncodeAlloc(arena: std.mem.Allocator, text: []const u8) ![]const u8 {
    var w = std.Io.Writer.Allocating.init(arena);
    try percentEncode(&w.writer, text);
    return w.written();
}