Make real number encoding and decoding work under all locales
The decimal point '.' is changed to locale's decimal point before/after JSON conversion to make C standard library's locale-specific string conversion functions work correctly. All the tests now call setlocale(LC_ALL, "") on startup to use the locale set in the environment. Fixes GH-32.
This commit is contained in:
@@ -12,6 +12,7 @@ libjansson_la_SOURCES = \
|
||||
pack_unpack.c \
|
||||
strbuffer.c \
|
||||
strbuffer.h \
|
||||
strconv.c \
|
||||
utf.c \
|
||||
utf.h \
|
||||
value.c
|
||||
|
||||
20
src/dump.c
20
src/dump.c
@@ -196,26 +196,12 @@ static int do_dump(const json_t *json, size_t flags, int depth,
|
||||
{
|
||||
char buffer[MAX_REAL_STR_LENGTH];
|
||||
int size;
|
||||
double value = json_real_value(json);
|
||||
|
||||
size = snprintf(buffer, MAX_REAL_STR_LENGTH, "%.17g",
|
||||
json_real_value(json));
|
||||
if(size >= MAX_REAL_STR_LENGTH)
|
||||
size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value);
|
||||
if(size < 0)
|
||||
return -1;
|
||||
|
||||
/* Make sure there's a dot or 'e' in the output. Otherwise
|
||||
a real is converted to an integer when decoding */
|
||||
if(strchr(buffer, '.') == NULL &&
|
||||
strchr(buffer, 'e') == NULL)
|
||||
{
|
||||
if(size + 2 >= MAX_REAL_STR_LENGTH) {
|
||||
/* No space to append ".0" */
|
||||
return -1;
|
||||
}
|
||||
buffer[size] = '.';
|
||||
buffer[size + 1] = '0';
|
||||
size += 2;
|
||||
}
|
||||
|
||||
return dump(buffer, size, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
|
||||
#define JSON_INTEGER_IS_LONG_LONG @json_have_long_long@
|
||||
|
||||
/* If locale.h and localeconv() are available, define to 1,
|
||||
otherwise to 0. */
|
||||
#define JSON_HAVE_LOCALECONV @json_have_localeconv@
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
|
||||
#define JSON_INTEGER_IS_LONG_LONG 1
|
||||
|
||||
/* If locale.h and localeconv() are available, define to 1,
|
||||
otherwise to 0. */
|
||||
#define JSON_HAVE_LOCALECONV 1
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stddef.h>
|
||||
#include "jansson.h"
|
||||
#include "hashtable.h"
|
||||
#include "strbuffer.h"
|
||||
|
||||
#define container_of(ptr_, type_, member_) \
|
||||
((type_ *)((char *)ptr_ - offsetof(type_, member_)))
|
||||
@@ -83,6 +84,10 @@ void jsonp_error_set(json_error_t *error, int line, int column,
|
||||
void jsonp_error_vset(json_error_t *error, int line, int column,
|
||||
size_t position, const char *msg, va_list ap);
|
||||
|
||||
/* Locale independent string<->double conversions */
|
||||
int jsonp_strtod(strbuffer_t *strbuffer, double *out);
|
||||
int jsonp_dtostr(char *buffer, size_t size, double value);
|
||||
|
||||
/* Wrappers for custom memory functions */
|
||||
void* jsonp_malloc(size_t size);
|
||||
void jsonp_free(void *ptr);
|
||||
|
||||
@@ -524,12 +524,7 @@ static int lex_scan_number(lex_t *lex, int c, json_error_t *error)
|
||||
|
||||
lex_unget_unsave(lex, c);
|
||||
|
||||
saved_text = strbuffer_value(&lex->saved_text);
|
||||
errno = 0;
|
||||
value = strtod(saved_text, &end);
|
||||
assert(end == saved_text + lex->saved_text.length);
|
||||
|
||||
if(errno == ERANGE && value != 0) {
|
||||
if(jsonp_strtod(&lex->saved_text, &value)) {
|
||||
error_set(error, lex, "real number overflow");
|
||||
goto out;
|
||||
}
|
||||
|
||||
108
src/strconv.c
Normal file
108
src/strconv.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "jansson_private.h"
|
||||
#include "strbuffer.h"
|
||||
|
||||
#if JSON_HAVE_LOCALECONV
|
||||
#include <locale.h>
|
||||
|
||||
/*
|
||||
- This code assumes that the decimal separator is exactly one
|
||||
character.
|
||||
|
||||
- If setlocale() is called by another thread between the call to
|
||||
localeconv() and the call to sprintf() or strtod(), the result may
|
||||
be wrong. setlocale() is not thread-safe and should not be used
|
||||
this way. Multi-threaded programs should use uselocale() instead.
|
||||
*/
|
||||
|
||||
static void to_locale(strbuffer_t *strbuffer)
|
||||
{
|
||||
const char *point;
|
||||
char *pos;
|
||||
|
||||
point = localeconv()->decimal_point;
|
||||
if(*point == '.') {
|
||||
/* No conversion needed */
|
||||
return;
|
||||
}
|
||||
|
||||
pos = strstr(strbuffer->value, ".");
|
||||
if(pos)
|
||||
*pos = *point;
|
||||
}
|
||||
|
||||
static void from_locale(char *buffer)
|
||||
{
|
||||
const char *point;
|
||||
char *pos;
|
||||
|
||||
point = localeconv()->decimal_point;
|
||||
if(*point == '.') {
|
||||
/* No conversion needed */
|
||||
return;
|
||||
}
|
||||
|
||||
pos = strstr(buffer, point);
|
||||
if(pos)
|
||||
*pos = '.';
|
||||
}
|
||||
#endif
|
||||
|
||||
int jsonp_strtod(strbuffer_t *strbuffer, double *out)
|
||||
{
|
||||
double value;
|
||||
char *end;
|
||||
|
||||
#if JSON_HAVE_LOCALECONV
|
||||
to_locale(strbuffer);
|
||||
#endif
|
||||
|
||||
errno = 0;
|
||||
value = strtod(strbuffer->value, &end);
|
||||
assert(end == strbuffer->value + strbuffer->length);
|
||||
|
||||
if(errno == ERANGE && value != 0) {
|
||||
/* Overflow */
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int jsonp_dtostr(char *buffer, size_t size, double value)
|
||||
{
|
||||
int ret;
|
||||
size_t length;
|
||||
|
||||
ret = snprintf(buffer, size, "%.17g", value);
|
||||
if(ret < 0)
|
||||
return -1;
|
||||
|
||||
length = (size_t)ret;
|
||||
if(length >= size)
|
||||
return -1;
|
||||
|
||||
#if JSON_HAVE_LOCALECONV
|
||||
from_locale(buffer);
|
||||
#endif
|
||||
|
||||
/* Make sure there's a dot or 'e' in the output. Otherwise
|
||||
a real is converted to an integer when decoding */
|
||||
if(strchr(buffer, '.') == NULL &&
|
||||
strchr(buffer, 'e') == NULL)
|
||||
{
|
||||
if(length + 2 >= size) {
|
||||
/* No space to append ".0" */
|
||||
return -1;
|
||||
}
|
||||
buffer[length] = '.';
|
||||
buffer[length + 1] = '0';
|
||||
length += 2;
|
||||
}
|
||||
|
||||
return (int)length;
|
||||
}
|
||||
Reference in New Issue
Block a user