From 354422206545584d333c1e0ff76823456dcdc31f Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Mon, 21 Nov 2022 00:54:03 -0800 Subject: [PATCH] Percent encode/decode header values --- src/http/request/parser.zig | 34 +++++++++++++++++++++++++++++++--- src/http/server/response.zig | 22 ++++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/http/request/parser.zig b/src/http/request/parser.zig index 57fae33..6ffba12 100644 --- a/src/http/request/parser.zig +++ b/src/http/request/parser.zig @@ -102,16 +102,44 @@ fn parseHeaders(allocator: std.mem.Allocator, reader: anytype) !Fields { error.StreamTooLong => return error.HeaderLineTooLong, else => return err, }; - const line = std.mem.trimRight(u8, full_line, "\r"); + const line = if (full_line.len != 0 and full_line[full_line.len - 1] == '\r') + full_line[0 .. full_line.len - 1] + else + full_line; if (line.len == 0) break; const name = std.mem.sliceTo(line, ':'); if (!isTokenValid(name)) return error.BadRequest; if (name.len == line.len) return error.BadRequest; - const value = std.mem.trim(u8, line[name.len + 1 ..], " \t"); + const encoded_value = line[name.len + 1 ..]; + const decoded_value = blk: { + var ii: usize = 0; + var io: usize = 0; + while (ii < encoded_value.len) : ({ + ii += 1; + io += 1; + }) { + switch (encoded_value[ii]) { + '\r', '\n', 0 => return error.BadRequest, + else => {}, + } - try headers.append(name, value); + if (encoded_value[ii] == '%') { + if (encoded_value.len < ii + 2) return error.BadRequest; + + const ch_buf = [2]u8{ encoded_value[ii + 1], encoded_value[ii + 2] }; + encoded_value[io] = try std.fmt.parseInt(u8, &ch_buf, 16); + ii += 2; + } else { + encoded_value[io] = encoded_value[ii]; + } + } + break :blk encoded_value[0..io]; + }; + + const val = std.mem.trim(u8, decoded_value, " \t"); + try headers.append(name, val); } return headers; diff --git a/src/http/server/response.zig b/src/http/server/response.zig index 0da7ab0..fdbe9cc 100644 --- a/src/http/server/response.zig +++ b/src/http/server/response.zig @@ -39,21 +39,31 @@ fn writeStatusLine(writer: anytype, status: Status) !void { fn writeFields(writer: anytype, headers: *const Fields) !void { var iter = headers.iterator(); while (iter.next()) |header| { - for (header.value_ptr.*) |ch| { - if (ch == '\r' or ch == '\n') @panic("newlines not yet supported in headers"); - } - if (std.ascii.eqlIgnoreCase("Set-Cookie", header.key_ptr.*)) continue; - try writer.print("{s}: {s}\r\n", .{ header.key_ptr.*, header.value_ptr.* }); + try writer.print("{s}: {s}\r\n", .{ header.key_ptr.*, percentEncode(header.value_ptr.*) }); } var cookie_iter = headers.getList("Set-Cookie"); while (cookie_iter.next()) |cookie| { - try writer.print("Set-Cookie: {s}\r\n", .{cookie}); + try writer.print("Set-Cookie: {s}\r\n", .{percentEncode(cookie)}); } } +const PercentEncode = struct { + str: []const u8, + + pub fn format(v: PercentEncode, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + for (v.str) |ch| switch (ch) { + ' ', '\t', 0x21...0x7e, 0x80...0xff => try writer.writeByte(ch), + else => try std.fmt.format(writer, "%{x:0>2}", .{ch}), + }; + } +}; +fn percentEncode(str: []const u8) PercentEncode { + return PercentEncode{ .str = str }; +} + fn writeChunk(writer: anytype, contents: []const u8) @TypeOf(writer).Error!void { try writer.print("{x}\r\n", .{contents.len}); try writer.writeAll(contents);