Skip to content

Commit b69ea96

Browse files
authored
Fixed non-conformance in JSON parsing for empty strings in numeric fields. (#19259)
Ruby & PHP will raise a warning, but will also raise a ParseError in the next minor release. PiperOrigin-RevId: 696138522
1 parent fb8ee79 commit b69ea96

File tree

11 files changed

+1738
-1500
lines changed

11 files changed

+1738
-1500
lines changed

php/ext/google/protobuf/message.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -722,12 +722,20 @@ PHP_METHOD(Message, mergeFromJsonString) {
722722
}
723723

724724
upb_Status_Clear(&status);
725-
if (!upb_JsonDecode(data, data_len, intern->msg, intern->desc->msgdef,
726-
DescriptorPool_GetSymbolTable(), options, arena,
727-
&status)) {
728-
zend_throw_exception_ex(NULL, 0, "Error occurred during parsing: %s",
729-
upb_Status_ErrorMessage(&status));
730-
return;
725+
int result = upb_JsonDecodeDetectingNonconformance(
726+
data, data_len, intern->msg, intern->desc->msgdef,
727+
DescriptorPool_GetSymbolTable(), options, arena, &status);
728+
729+
switch (result) {
730+
case kUpb_JsonDecodeResult_Ok:
731+
break;
732+
case kUpb_JsonDecodeResult_OkWithEmptyStringNumerics:
733+
zend_error(E_USER_WARNING, "%s", upb_Status_ErrorMessage(&status));
734+
return;
735+
case kUpb_JsonDecodeResult_Error:
736+
zend_throw_exception_ex(NULL, 0, "Error occurred during parsing: %s",
737+
upb_Status_ErrorMessage(&status));
738+
return;
731739
}
732740
}
733741

php/ext/google/protobuf/php-upb.c

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ typedef struct {
523523
upb_Arena* arena; /* TODO: should we have a tmp arena for tmp data? */
524524
const upb_DefPool* symtab;
525525
int depth;
526+
int result;
526527
upb_Status* status;
527528
jmp_buf err;
528529
int line;
@@ -1152,6 +1153,16 @@ static int64_t jsondec_strtoint64(jsondec* d, upb_StringView str) {
11521153
return ret;
11531154
}
11541155

1156+
static void jsondec_checkempty(jsondec* d, upb_StringView str,
1157+
const upb_FieldDef* f) {
1158+
if (str.size != 0) return;
1159+
d->result = kUpb_JsonDecodeResult_OkWithEmptyStringNumerics;
1160+
upb_Status_SetErrorFormat(d->status,
1161+
"Empty string is not a valid number (field: %s). "
1162+
"This will be an error in a future version.",
1163+
upb_FieldDef_FullName(f));
1164+
}
1165+
11551166
/* Primitive value types ******************************************************/
11561167

11571168
/* Parse INT32 or INT64 value. */
@@ -1173,6 +1184,7 @@ static upb_MessageValue jsondec_int(jsondec* d, const upb_FieldDef* f) {
11731184
}
11741185
case JD_STRING: {
11751186
upb_StringView str = jsondec_string(d);
1187+
jsondec_checkempty(d, str, f);
11761188
val.int64_val = jsondec_strtoint64(d, str);
11771189
break;
11781190
}
@@ -1210,6 +1222,7 @@ static upb_MessageValue jsondec_uint(jsondec* d, const upb_FieldDef* f) {
12101222
}
12111223
case JD_STRING: {
12121224
upb_StringView str = jsondec_string(d);
1225+
jsondec_checkempty(d, str, f);
12131226
val.uint64_val = jsondec_strtouint64(d, str);
12141227
break;
12151228
}
@@ -1238,14 +1251,26 @@ static upb_MessageValue jsondec_double(jsondec* d, const upb_FieldDef* f) {
12381251
break;
12391252
case JD_STRING:
12401253
str = jsondec_string(d);
1241-
if (jsondec_streql(str, "NaN")) {
1254+
if (str.size == 0) {
1255+
jsondec_checkempty(d, str, f);
1256+
val.double_val = 0.0;
1257+
} else if (jsondec_streql(str, "NaN")) {
12421258
val.double_val = NAN;
12431259
} else if (jsondec_streql(str, "Infinity")) {
12441260
val.double_val = INFINITY;
12451261
} else if (jsondec_streql(str, "-Infinity")) {
12461262
val.double_val = -INFINITY;
12471263
} else {
1248-
val.double_val = strtod(str.data, NULL);
1264+
char* end;
1265+
val.double_val = strtod(str.data, &end);
1266+
if (end != str.data + str.size) {
1267+
d->result = kUpb_JsonDecodeResult_OkWithEmptyStringNumerics;
1268+
upb_Status_SetErrorFormat(
1269+
d->status,
1270+
"Non-number characters in quoted number (field: %s). "
1271+
"This will be an error in a future version.",
1272+
upb_FieldDef_FullName(f));
1273+
}
12491274
}
12501275
break;
12511276
default:
@@ -1987,10 +2012,10 @@ static void jsondec_wellknown(jsondec* d, upb_Message* msg,
19872012
}
19882013
}
19892014

1990-
static bool upb_JsonDecoder_Decode(jsondec* const d, upb_Message* const msg,
1991-
const upb_MessageDef* const m) {
2015+
static int upb_JsonDecoder_Decode(jsondec* const d, upb_Message* const msg,
2016+
const upb_MessageDef* const m) {
19922017
UPB_ASSERT(!upb_Message_IsFrozen(msg));
1993-
if (UPB_SETJMP(d->err)) return false;
2018+
if (UPB_SETJMP(d->err)) return kUpb_JsonDecodeResult_Error;
19942019

19952020
jsondec_tomsg(d, msg, m);
19962021

@@ -1999,16 +2024,19 @@ static bool upb_JsonDecoder_Decode(jsondec* const d, upb_Message* const msg,
19992024
jsondec_consumews(d);
20002025

20012026
if (d->ptr == d->end) {
2002-
return true;
2027+
return d->result;
20032028
} else {
20042029
jsondec_seterrmsg(d, "unexpected trailing characters");
2005-
return false;
2030+
return kUpb_JsonDecodeResult_Error;
20062031
}
20072032
}
20082033

2009-
bool upb_JsonDecode(const char* buf, size_t size, upb_Message* msg,
2010-
const upb_MessageDef* m, const upb_DefPool* symtab,
2011-
int options, upb_Arena* arena, upb_Status* status) {
2034+
int upb_JsonDecodeDetectingNonconformance(const char* buf, size_t size,
2035+
upb_Message* msg,
2036+
const upb_MessageDef* m,
2037+
const upb_DefPool* symtab,
2038+
int options, upb_Arena* arena,
2039+
upb_Status* status) {
20122040
UPB_ASSERT(!upb_Message_IsFrozen(msg));
20132041
jsondec d;
20142042

@@ -2021,6 +2049,7 @@ bool upb_JsonDecode(const char* buf, size_t size, upb_Message* msg,
20212049
d.status = status;
20222050
d.options = options;
20232051
d.depth = 64;
2052+
d.result = kUpb_JsonDecodeResult_Ok;
20242053
d.line = 1;
20252054
d.line_begin = d.ptr;
20262055
d.debug_field = NULL;

0 commit comments

Comments
 (0)