]> git.ipfire.org Git - thirdparty/json-c.git/commitdiff
fix locale-dependent strtod in json_object_get_double 930/head
authordxbjavid <dxbjavid@gmail.com>
Mon, 15 Jun 2026 18:23:53 +0000 (23:53 +0530)
committerdxbjavid <dxbjavid@gmail.com>
Mon, 15 Jun 2026 18:23:53 +0000 (23:53 +0530)
json_object.c
tests/test_locale.c

index db79d6eb5d7c734853a4f2145cb68309e602929b..e03a888dcb27a5df1eec0897b9358eb16cb5f4f0 100644 (file)
@@ -21,6 +21,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#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;
        }
 }
index 79f6c2c6bd8b1f1026b8565b220aea63f1dfd841..efbd42514212b56d002f75dea45af820d879587b 100644 (file)
@@ -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