From: dxbjavid Date: Mon, 15 Jun 2026 18:23:53 +0000 (+0530) Subject: fix locale-dependent strtod in json_object_get_double X-Git-Tag: json-c-0.19-20260627~5^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F930%2Fhead;p=thirdparty%2Fjson-c.git fix locale-dependent strtod in json_object_get_double --- diff --git a/json_object.c b/json_object.c index db79d6e..e03a888 100644 --- a/json_object.c +++ b/json_object.c @@ -21,6 +21,9 @@ #include #include #include +#ifdef HAVE_LOCALE_H +#include +#endif /* HAVE_LOCALE_H */ #include "arraylist.h" #include "debug.h" @@ -1381,12 +1384,42 @@ double json_object_get_double(const struct json_object *jso) } case json_type_boolean: return JC_BOOL_C(jso)->c_boolean; case json_type_string: + { + const char *cstr = get_string_component(jso); + const char *parse = cstr; + char *radixconv = NULL; +#ifdef HAVE_LOCALE_H + /* A json_type_string holds the value with a '.' radix, as it + * appears in JSON, but strtod() honours the current locale. In a + * locale whose decimal point is not '.' the '.' is treated as a + * stray character, so e.g. "19.95" is read as 0. Parse a copy + * with the radix translated so the value comes back unchanged. */ + const char *decimal_point = localeconv()->decimal_point; + if (decimal_point && decimal_point[0] != '.' && decimal_point[1] == '\0') + { + const char *dot = strchr(cstr, '.'); + if (dot) + { + size_t slen = strlen(cstr); + radixconv = (char *)malloc(slen + 1); + if (radixconv == NULL) + { + errno = ENOMEM; + return 0.0; + } + memcpy(radixconv, cstr, slen + 1); + radixconv[dot - cstr] = decimal_point[0]; + parse = radixconv; + } + } +#endif errno = 0; - cdouble = strtod(get_string_component(jso), &errPtr); + cdouble = strtod(parse, &errPtr); /* if conversion stopped at the first character, return 0.0 */ - if (errPtr == get_string_component(jso)) + if (errPtr == parse) { + free(radixconv); errno = EINVAL; return 0.0; } @@ -1398,6 +1431,7 @@ double json_object_get_double(const struct json_object *jso) */ if (*errPtr != '\0') { + free(radixconv); errno = EINVAL; return 0.0; } @@ -1415,7 +1449,9 @@ double json_object_get_double(const struct json_object *jso) */ if ((HUGE_VAL == cdouble || -HUGE_VAL == cdouble) && (ERANGE == errno)) cdouble = 0.0; + free(radixconv); return cdouble; + } default: errno = EINVAL; return 0.0; } } diff --git a/tests/test_locale.c b/tests/test_locale.c index 79f6c2c..efbd425 100644 --- a/tests/test_locale.c +++ b/tests/test_locale.c @@ -40,6 +40,18 @@ int main(int argc, char **argv) if (strcmp(buf1, buf2) != 0) printf("ERROR: Original locale not restored \"%s\" != \"%s\"", buf1, buf2); + // json_object_get_double() on a json_type_string must not depend on the + // locale's decimal point. Under a comma-radix locale such as de_DE the + // '.' used to be treated as a stray char, so "19.95" came back as 0. + // (If the de_DE locale isn't installed we're still in "C" here, where + // this passes anyway, so the check is safe to always run.) + { + json_object *numstr = json_object_new_string("19.95"); + double d = json_object_get_double(numstr); + assert(d > 19.94 && d < 19.96); + json_object_put(numstr); + } + #ifdef HAVE_SETLOCALE setlocale(LC_NUMERIC, "C"); #endif