From 093c7e84e85f5c4f36950c42b24892b35e3deaff Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 19:38:12 +0000 Subject: [PATCH] Fix pb_to_json to properly escape strings. This modifies `pb_to_json_inner` in `src/json.c` to iterate through strings and properly escape double quotes, backslashes, and control characters like newlines and tabs to avoid creating invalid JSON. Tests were added. Co-authored-by: bkvaluemeal <3835053+bkvaluemeal@users.noreply.github.com> --- expected/pgproto_test.out | 6 ++++++ sql/pgproto_test.sql | 3 +++ src/json.c | 20 +++++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/expected/pgproto_test.out b/expected/pgproto_test.out index b23b469..f5dbe88 100644 --- a/expected/pgproto_test.out +++ b/expected/pgproto_test.out @@ -569,3 +569,9 @@ ERROR: Field nonexistent not found in message CoverageMsg -- 35. Test pb_insert error: Message not found SELECT pb_insert('\x'::protobuf, ARRAY['NonExistentMessage', 'f', '0'], '1.23'); ERROR: Message not found in schema registry: NonExistentMessage +-- 36. Test pb_to_json escaping +SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'str_arr', '0'], E'hello "world"\\\n\t\b\f\r'), 'CoverageMsg'); + pb_to_json +--------------------------------------------- + {"str_arr":["hello \"world\"\\\n\t\b\f\r"]} +(1 row) diff --git a/sql/pgproto_test.sql b/sql/pgproto_test.sql index 918d661..9ffdff2 100644 --- a/sql/pgproto_test.sql +++ b/sql/pgproto_test.sql @@ -342,3 +342,6 @@ SELECT pb_insert('\x'::protobuf, ARRAY['NonExistentMessage', 'f', '0'], '1.23'); + +-- 36. Test pb_to_json escaping +SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'str_arr', '0'], E'hello "world"\\\n\t\b\f\r'), 'CoverageMsg'); diff --git a/src/json.c b/src/json.c index 3b903e3..f7ae20c 100644 --- a/src/json.c +++ b/src/json.c @@ -44,7 +44,25 @@ pb_to_json_inner(const char *ptr, const char *end, const char *msg_name, StringI pb_to_json_inner(ptr, ptr + len, lookup.type_name, buf); } else { appendStringInfoChar(buf, '"'); - appendBinaryStringInfo(buf, ptr, (int)len); + for (int i = 0; i < (int)len; i++) { + char c = ptr[i]; + switch (c) { + case '"': appendStringInfoString(buf, "\\\""); break; + case '\\': appendStringInfoString(buf, "\\\\"); break; + case '\b': appendStringInfoString(buf, "\\b"); break; + case '\f': appendStringInfoString(buf, "\\f"); break; + case '\n': appendStringInfoString(buf, "\\n"); break; + case '\r': appendStringInfoString(buf, "\\r"); break; + case '\t': appendStringInfoString(buf, "\\t"); break; + default: + if ((unsigned char)c < 0x20) { + appendStringInfo(buf, "\\u%04x", (unsigned char)c); + } else { + appendStringInfoChar(buf, c); + } + break; + } + } appendStringInfoChar(buf, '"'); } ptr += len;