handle escaping xml data

This commit is contained in:
Emil Lerch 2025-07-12 17:50:10 -07:00
parent eb16d2396a
commit 7a5d10bc82
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -5,6 +5,19 @@ const zeit = @import("zeit");
const Release = @import("main.zig").Release; const Release = @import("main.zig").Release;
fn escapeXml(writer: anytype, input: []const u8) !void {
for (input) |char| {
switch (char) {
'<' => try writer.writeAll("&lt;"),
'>' => try writer.writeAll("&gt;"),
'&' => try writer.writeAll("&amp;"),
'"' => try writer.writeAll("&quot;"),
'\'' => try writer.writeAll("&apos;"),
else => try writer.writeByte(char),
}
}
}
pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 { pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
var buffer = ArrayList(u8).init(allocator); var buffer = ArrayList(u8).init(allocator);
defer buffer.deinit(); defer buffer.deinit();
@ -32,13 +45,37 @@ pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
// Add entries // Add entries
for (releases) |release| { for (releases) |release| {
try writer.writeAll("<entry>\n"); try writer.writeAll("<entry>\n");
try writer.print(" <title>{s} - {s}</title>\n", .{ release.repo_name, release.tag_name });
try writer.print(" <link href=\"{s}\"/>\n", .{release.html_url}); try writer.writeAll(" <title>");
try writer.print(" <id>{s}</id>\n", .{release.html_url}); try escapeXml(writer, release.repo_name);
try writer.print(" <updated>{s}</updated>\n", .{release.published_at}); try writer.writeAll(" - ");
try writer.print(" <author><name>{s}</name></author>\n", .{release.provider}); try escapeXml(writer, release.tag_name);
try writer.print(" <summary>{s}</summary>\n", .{release.description}); try writer.writeAll("</title>\n");
try writer.print(" <category term=\"{s}\"/>\n", .{release.provider});
try writer.writeAll(" <link href=\"");
try escapeXml(writer, release.html_url);
try writer.writeAll("\"/>\n");
try writer.writeAll(" <id>");
try escapeXml(writer, release.html_url);
try writer.writeAll("</id>\n");
try writer.writeAll(" <updated>");
try escapeXml(writer, release.published_at);
try writer.writeAll("</updated>\n");
try writer.writeAll(" <author><name>");
try escapeXml(writer, release.provider);
try writer.writeAll("</name></author>\n");
try writer.writeAll(" <summary>");
try escapeXml(writer, release.description);
try writer.writeAll("</summary>\n");
try writer.writeAll(" <category term=\"");
try escapeXml(writer, release.provider);
try writer.writeAll("\"/>\n");
try writer.writeAll("</entry>\n"); try writer.writeAll("</entry>\n");
} }
@ -47,6 +84,22 @@ pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
return buffer.toOwnedSlice(); return buffer.toOwnedSlice();
} }
test "XML escaping" {
const allocator = std.testing.allocator;
var buffer = ArrayList(u8).init(allocator);
defer buffer.deinit();
const input = "Test <tag> & \"quotes\" & 'apostrophes'";
try escapeXml(buffer.writer(), input);
const result = try buffer.toOwnedSlice();
defer allocator.free(result);
const expected = "Test &lt;tag&gt; &amp; &quot;quotes&quot; &amp; &apos;apostrophes&apos;";
try std.testing.expectEqualStrings(expected, result);
}
test "Atom feed generation" { test "Atom feed generation" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
@ -68,3 +121,31 @@ test "Atom feed generation" {
try std.testing.expect(std.mem.indexOf(u8, atom_content, "v1.0.0") != null); try std.testing.expect(std.mem.indexOf(u8, atom_content, "v1.0.0") != null);
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<feed xmlns=\"http://www.w3.org/2005/Atom\">") != null); try std.testing.expect(std.mem.indexOf(u8, atom_content, "<feed xmlns=\"http://www.w3.org/2005/Atom\">") != null);
} }
test "Atom feed with special characters" {
const allocator = std.testing.allocator;
const releases = [_]Release{
Release{
.repo_name = "test/repo<script>",
.tag_name = "v1.0.0 & more",
.published_at = "2024-01-01T00:00:00Z",
.html_url = "https://github.com/test/repo/releases/tag/v1.0.0",
.description = "Test \"release\" with <special> chars & symbols",
.provider = "github",
},
};
const atom_content = try generateFeed(allocator, &releases);
defer allocator.free(atom_content);
// Verify special characters are properly escaped
try std.testing.expect(std.mem.indexOf(u8, atom_content, "&lt;script&gt;") != null);
try std.testing.expect(std.mem.indexOf(u8, atom_content, "&amp; more") != null);
try std.testing.expect(std.mem.indexOf(u8, atom_content, "&quot;release&quot;") != null);
try std.testing.expect(std.mem.indexOf(u8, atom_content, "&lt;special&gt;") != null);
// Verify raw special characters are not present
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<script>") == null);
try std.testing.expect(std.mem.indexOf(u8, atom_content, "\"release\"") == null);
}