diff --git a/src/main.zig b/src/main.zig --- a/src/main.zig +++ b/src/main.zig @@ -591,7 +591,8 @@ } fn handleGetRepoStatus(stream: std.net.Stream, query: []const u8, persist: *event_log_mod.DiskPersist) void { - const did = queryParam(query, "did") orelse { + var did_buf: [256]u8 = undefined; + const did = queryParamDecoded(query, "did", &did_buf) orelse { httpRespondJson(stream, "400 Bad Request", "{\"error\":\"BadRequest\",\"message\":\"did parameter required\"}"); return; }; @@ -651,7 +652,8 @@ } fn handleGetLatestCommit(stream: std.net.Stream, query: []const u8, persist: *event_log_mod.DiskPersist) void { - const did = queryParam(query, "did") orelse { + var did_buf: [256]u8 = undefined; + const did = queryParamDecoded(query, "did", &did_buf) orelse { httpRespondJson(stream, "400 Bad Request", "{\"error\":\"BadRequest\",\"message\":\"did parameter required\"}"); return; }; @@ -736,7 +738,8 @@ return; } - const cursor_did = queryParam(query, "cursor"); + var cursor_buf: [256]u8 = undefined; + const cursor_did = queryParamDecoded(query, "cursor", &cursor_buf); // scan collection index var did_buf: [65536]u8 = undefined; @@ -783,6 +786,56 @@ } } return null; +} + +/// like queryParam but percent-decodes the value into buf. +/// returns null if the param is missing, or a slice into buf with the decoded value. +fn queryParamDecoded(query: []const u8, name: []const u8, buf: []u8) ?[]const u8 { + const raw = queryParam(query, name) orelse return null; + var i: usize = 0; + var out: usize = 0; + while (i < raw.len) { + if (raw[i] == '%' and i + 2 < raw.len) { + const hi = hexVal(raw[i + 1]) orelse { + if (out >= buf.len) return null; + buf[out] = raw[i]; + out += 1; + i += 1; + continue; + }; + const lo = hexVal(raw[i + 2]) orelse { + if (out >= buf.len) return null; + buf[out] = raw[i]; + out += 1; + i += 1; + continue; + }; + if (out >= buf.len) return null; + buf[out] = (hi << 4) | lo; + out += 1; + i += 3; + } else if (raw[i] == '+') { + if (out >= buf.len) return null; + buf[out] = ' '; + out += 1; + i += 1; + } else { + if (out >= buf.len) return null; + buf[out] = raw[i]; + out += 1; + i += 1; + } + } + return buf[0..out]; +} + +fn hexVal(c: u8) ?u4 { + return switch (c) { + '0'...'9' => @intCast(c - '0'), + 'a'...'f' => @intCast(c - 'a' + 10), + 'A'...'F' => @intCast(c - 'A' + 10), + else => null, + }; } fn httpRespondJson(stream: std.net.Stream, status: []const u8, body: []const u8) void {