jplist: Improve numerical value parsing without copying data to stack buffer
Instead of calling strtoll() and atof(), the code now parses the numerical
values directly to handle cases of non-0-terminated string data.
The floating point value parsing is probably not ideal, but sufficient for
our purposes.
diff --git a/src/jplist.c b/src/jplist.c
index 65eb528..1629f59 100644
--- a/src/jplist.c
+++ b/src/jplist.c
@@ -423,6 +423,26 @@
int count;
} jsmntok_info_t;
+static long long parse_decimal(const char* str, const char* str_end, char** endp)
+{
+ long long x = 0;
+ int is_neg = 0;
+ *endp = (char*)str;
+
+ if (str[0] == '-') {
+ is_neg = 1;
+ (*endp)++;
+ }
+ while (*endp < str_end && isdigit(**endp)) {
+ x = x * 10 + (**endp - '0');
+ (*endp)++;
+ }
+ if (is_neg) {
+ x = -x;
+ }
+ return x;
+}
+
static plist_t parse_primitive(const char* js, jsmntok_info_t* ti, int* index)
{
if (ti->tokens[*index].type != JSMN_PRIMITIVE) {
@@ -441,27 +461,51 @@
plist_data_t data = plist_new_plist_data();
data->type = PLIST_NULL;
val = plist_new_node(data);
- } else if (str_val[0] == '-' || isdigit(str_val[0])) {
- char* endp = NULL;
- char cbuf[48];
- size_t maxlen = str_end-str_val;
- if (maxlen >= sizeof(cbuf)) maxlen = sizeof(cbuf)-1;
- strncpy(cbuf, str_val, maxlen);
- cbuf[maxlen] = '\0';
- long long intpart = strtoll(cbuf, &endp, 10);
- endp = (char*)str_val + (endp-&cbuf[0]);
+ } else if (isdigit(str_val[0]) || (str_val[0] == '-' && str_end > str_val && isdigit(str_val[1]))) {
+ char* endp = (char*)str_val;
+ long long intpart = parse_decimal(str_val, str_end, &endp);
if (endp >= str_end) {
/* integer */
val = plist_new_uint((uint64_t)intpart);
- } else if (*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) {
- /* float */
- char* fendp = endp+1;
- while (isdigit(*fendp) && fendp < str_end) fendp++;
- if ((fendp > endp+1 && fendp >= str_end) || (fendp+2 < str_end && (*fendp == 'e' || *fendp == 'E') && (*(fendp+1) == '+' || *(fendp+1) == '-') && isdigit(*(fendp+2)))) {
- double dval = atof(cbuf);
- val = plist_new_real(dval);
- } else {
- PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js));
+ } else if ((*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) || ((*endp == 'e' || *endp == 'E') && endp < str_end && (isdigit(*(endp+1)) || ((*(endp+1) == '-') && endp+1 < str_end && isdigit(*(endp+2)))))) {
+ /* floating point */
+ double dval = (double)intpart;
+ char* fendp = endp;
+ int err = 0;
+ do {
+ if (*endp == '.') {
+ fendp++;
+ int is_neg = (str_val[0] == '-');
+ double frac = 0;
+ double p = 0.1;
+ while (isdigit(*fendp) && fendp < str_end) {
+ frac = frac + (*fendp - '0') * p;
+ p *= 0.1;
+ fendp++;
+ }
+ if (is_neg) {
+ dval -= frac;
+ } else {
+ dval += frac;
+ }
+ }
+ if (fendp >= str_end) {
+ break;
+ }
+ if (fendp+1 < str_end && (*fendp == 'e' || *fendp == 'E') && (isdigit(*(fendp+1)) || ((*(fendp+1) == '-') && fendp+2 < str_end && isdigit(*(fendp+2))))) {
+ double exp = (double)parse_decimal(fendp+1, str_end, &fendp);
+ dval = dval * pow(10, exp);
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js));
+ err++;
+ }
+ } while (0);
+ if (!err) {
+ if (isinf(dval) || isnan(dval)) {
+ PLIST_JSON_ERR("%s: unrepresentable floating point value at offset %d when parsing numerical value\n", __func__, (int)(str_val - js));
+ } else {
+ val = plist_new_real(dval);
+ }
}
} else {
PLIST_JSON_ERR("%s: invalid character at offset %d when parsing numerical value\n", __func__, (int)(endp - js));