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