diff --git a/src/atom.zig b/src/atom.zig
index e44d552..cfa6085 100644
--- a/src/atom.zig
+++ b/src/atom.zig
@@ -5,6 +5,19 @@ const zeit = @import("zeit");
const Release = @import("main.zig").Release;
+fn escapeXml(writer: anytype, input: []const u8) !void {
+ for (input) |char| {
+ switch (char) {
+ '<' => try writer.writeAll("<"),
+ '>' => try writer.writeAll(">"),
+ '&' => try writer.writeAll("&"),
+ '"' => try writer.writeAll("""),
+ '\'' => try writer.writeAll("'"),
+ else => try writer.writeByte(char),
+ }
+ }
+}
+
pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
var buffer = ArrayList(u8).init(allocator);
defer buffer.deinit();
@@ -32,13 +45,37 @@ pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
// Add entries
for (releases) |release| {
try writer.writeAll("\n");
- try writer.print(" {s} - {s}\n", .{ release.repo_name, release.tag_name });
- try writer.print(" \n", .{release.html_url});
- try writer.print(" {s}\n", .{release.html_url});
- try writer.print(" {s}\n", .{release.published_at});
- try writer.print(" {s}\n", .{release.provider});
- try writer.print(" {s}\n", .{release.description});
- try writer.print(" \n", .{release.provider});
+
+ try writer.writeAll(" ");
+ try escapeXml(writer, release.repo_name);
+ try writer.writeAll(" - ");
+ try escapeXml(writer, release.tag_name);
+ try writer.writeAll("\n");
+
+ try writer.writeAll(" \n");
+
+ try writer.writeAll(" ");
+ try escapeXml(writer, release.html_url);
+ try writer.writeAll("\n");
+
+ try writer.writeAll(" ");
+ try escapeXml(writer, release.published_at);
+ try writer.writeAll("\n");
+
+ try writer.writeAll(" ");
+ try escapeXml(writer, release.provider);
+ try writer.writeAll("\n");
+
+ try writer.writeAll(" ");
+ try escapeXml(writer, release.description);
+ try writer.writeAll("\n");
+
+ try writer.writeAll(" \n");
+
try writer.writeAll("\n");
}
@@ -47,6 +84,22 @@ pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
return buffer.toOwnedSlice();
}
+test "XML escaping" {
+ const allocator = std.testing.allocator;
+
+ var buffer = ArrayList(u8).init(allocator);
+ defer buffer.deinit();
+
+ const input = "Test & \"quotes\" & 'apostrophes'";
+ try escapeXml(buffer.writer(), input);
+
+ const result = try buffer.toOwnedSlice();
+ defer allocator.free(result);
+
+ const expected = "Test <tag> & "quotes" & 'apostrophes'";
+ try std.testing.expectEqualStrings(expected, result);
+}
+
test "Atom feed generation" {
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, "") != null);
}
+
+test "Atom feed with special characters" {
+ const allocator = std.testing.allocator;
+
+ const releases = [_]Release{
+ Release{
+ .repo_name = "test/repo