Skip to content

Commit 843a962

Browse files
justrachclaude
andcommitted
fix: properly escape plain string values in GET/scan/search JSON responses
Values that aren't JSON objects/arrays/strings (e.g. plain text) were emitted unquoted via {s}, producing invalid JSON. Now plain values are quoted and special chars (quotes, backslashes, newlines) are escaped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3fec28f commit 843a962

1 file changed

Lines changed: 42 additions & 12 deletions

File tree

src/server.zig

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,20 @@ fn handleGet(srv: *Server, tenant_id: []const u8, col_name: []const u8, key: []c
481481
const HEADER_RESERVE = 256;
482482
var resp = getRespBuf();
483483
var fbs = std.io.fixedBufferStream(resp[HEADER_RESERVE..]);
484-
std.fmt.format(fbs.writer(),
485-
"{{\"doc_id\":{d},\"key\":\"{s}\",\"version\":{d},\"value\":{s}}}",
486-
.{ d.header.doc_id, d.key, d.header.version,
487-
if (d.value.len > 0) d.value else "{}" }) catch {};
484+
const val = if (d.value.len > 0) d.value else "{}";
485+
const is_json = val.len > 0 and (val[0] == '{' or val[0] == '[' or val[0] == '"');
486+
const w = fbs.writer();
487+
if (is_json) {
488+
std.fmt.format(w, "{{\"doc_id\":{d},\"key\":\"{s}\",\"version\":{d},\"value\":{s}}}", .{ d.header.doc_id, d.key, d.header.version, val }) catch {};
489+
} else {
490+
std.fmt.format(w, "{{\"doc_id\":{d},\"key\":\"{s}\",\"version\":{d},\"value\":\"", .{ d.header.doc_id, d.key, d.header.version }) catch {};
491+
for (val) |ch| {
492+
if (ch == '"' or ch == '\\') w.writeByte('\\') catch {};
493+
if (ch == '\n') { w.writeAll("\\n") catch {}; continue; }
494+
w.writeByte(ch) catch {};
495+
}
496+
w.writeAll("\"}") catch {};
497+
}
488498
const body_len = fbs.pos;
489499

490500
// Now write headers into the reserved space at the front
@@ -544,10 +554,19 @@ fn handleScan(srv: *Server, tenant_id: []const u8, col_name: []const u8, query_s
544554
.{ tenant_id, col_name, result.docs.len }) catch {};
545555
for (result.docs, 0..) |d, i| {
546556
if (i > 0) w.writeByte(',') catch {};
547-
std.fmt.format(w,
548-
"{{\"doc_id\":{d},\"key\":\"{s}\",\"version\":{d},\"value\":{s}}}",
549-
.{ d.header.doc_id, d.key, d.header.version,
550-
if (d.value.len > 0) d.value else "{}" }) catch {};
557+
const val = if (d.value.len > 0) d.value else "{}";
558+
const is_json = val.len > 0 and (val[0] == '{' or val[0] == '[' or val[0] == '"');
559+
if (is_json) {
560+
std.fmt.format(w, "{{\"doc_id\":{d},\"key\":\"{s}\",\"version\":{d},\"value\":{s}}}", .{ d.header.doc_id, d.key, d.header.version, val }) catch {};
561+
} else {
562+
std.fmt.format(w, "{{\"doc_id\":{d},\"key\":\"{s}\",\"version\":{d},\"value\":\"", .{ d.header.doc_id, d.key, d.header.version }) catch {};
563+
for (val) |ch| {
564+
if (ch == '"' or ch == '\\') w.writeByte('\\') catch {};
565+
if (ch == '\n') { w.writeAll("\\n") catch {}; continue; }
566+
w.writeByte(ch) catch {};
567+
}
568+
w.writeAll("\"}") catch {};
569+
}
551570
}
552571
w.writeAll("]}") catch {};
553572
return ok(getBodyBuf()[0..fbs.pos]);
@@ -584,10 +603,21 @@ fn handleSearch(srv: *Server, tenant_id: []const u8, col_name: []const u8, query
584603
.{ result.docs.len, result.candidate_paths.len, col.docCount(), result.total_files }) catch {};
585604
for (result.docs, 0..) |d, i| {
586605
if (i > 0) w.writeByte(',') catch {};
587-
std.fmt.format(w,
588-
"{{\"doc_id\":{d},\"key\":\"{s}\",\"value\":{s}}}",
589-
.{ d.header.doc_id, d.key,
590-
if (d.value.len > 0) d.value else "{}" }) catch {};
606+
// Output value as valid JSON — objects/arrays as-is, strings quoted
607+
const val = if (d.value.len > 0) d.value else "{}";
608+
const is_json = val.len > 0 and (val[0] == '{' or val[0] == '[' or val[0] == '"');
609+
if (is_json) {
610+
std.fmt.format(w, "{{\"doc_id\":{d},\"key\":\"{s}\",\"value\":{s}}}", .{ d.header.doc_id, d.key, val }) catch {};
611+
} else {
612+
w.writeAll("{\"doc_id\":") catch {};
613+
std.fmt.format(w, "{d},\"key\":\"{s}\",\"value\":\"", .{ d.header.doc_id, d.key }) catch {};
614+
for (val) |ch| {
615+
if (ch == '"' or ch == '\\') w.writeByte('\\') catch {};
616+
if (ch == '\n') { w.writeAll("\\n") catch {}; continue; }
617+
w.writeByte(ch) catch {};
618+
}
619+
w.writeAll("\"}") catch {};
620+
}
591621
}
592622
w.writeAll("]}") catch {};
593623
return ok(getBodyBuf()[0..fbs.pos]);

0 commit comments

Comments
 (0)