From 474c8eb82e776bfac804338247045b11fa389d8d Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Wed, 2 Feb 2022 04:38:16 +0100 Subject: 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. --- src/jplist.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 18 deletions(-) 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 @@ typedef struct { 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 @@ static plist_t parse_primitive(const char* js, jsmntok_info_t* ti, int* index) 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)); -- cgit v1.1-32-gdbae