LCOV - code coverage report
Current view: top level - libemail/src/core - json_util.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.2 % 219 215
Test Date: 2026-08-21 10:11:28 Functions: 100.0 % 9 9

            Line data    Source code
       1              : #include "json_util.h"
       2              : #include <stdlib.h>
       3              : #include <string.h>
       4              : #include <ctype.h>
       5              : 
       6              : /* ── Internal helpers ───────────────────────────────────────────────── */
       7              : 
       8              : /** Skip whitespace, return pointer to first non-ws char. */
       9        60157 : static const char *skip_ws(const char *p) {
      10        60269 :     while (*p && isspace((unsigned char)*p)) p++;
      11        60157 :     return p;
      12              : }
      13              : 
      14              : /**
      15              :  * Skip a JSON value starting at *p (string, number, object, array,
      16              :  * true/false/null).  Returns pointer past the value, or NULL on error.
      17              :  */
      18         8011 : static const char *skip_value(const char *p) {
      19         8011 :     p = skip_ws(p);
      20         8011 :     if (!*p) return NULL;
      21              : 
      22         8011 :     if (*p == '"') {
      23              :         /* String: advance past closing quote, handling escapes */
      24         4026 :         p++;
      25        60775 :         while (*p && *p != '"') {
      26        56749 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      27        56749 :             p++;
      28              :         }
      29         4026 :         return *p == '"' ? p + 1 : NULL;
      30              :     }
      31         3985 :     if (*p == '{') {
      32              :         /* Object: match braces */
      33         2798 :         int depth = 1; p++;
      34        27701 :         while (*p && depth > 0) {
      35        24903 :             if (*p == '{') depth++;
      36        24884 :             else if (*p == '}') depth--;
      37        22067 :             else if (*p == '"') {
      38        12404 :                 p++;
      39       120093 :                 while (*p && *p != '"') {
      40       107689 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      41       107689 :                     p++;
      42              :                 }
      43        12404 :                 if (!*p) return NULL;
      44              :             }
      45        24903 :             p++;
      46              :         }
      47         2798 :         return depth == 0 ? p : NULL;
      48              :     }
      49         1187 :     if (*p == '[') {
      50              :         /* Array: match brackets */
      51         1020 :         int depth = 1; p++;
      52        47347 :         while (*p && depth > 0) {
      53        46327 :             if (*p == '[') depth++;
      54        46315 :             else if (*p == ']') depth--;
      55        45283 :             else if (*p == '"') {
      56        18801 :                 p++;
      57       208435 :                 while (*p && *p != '"') {
      58       189634 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      59       189634 :                     p++;
      60              :                 }
      61        18801 :                 if (!*p) return NULL;
      62              :             }
      63        46327 :             p++;
      64              :         }
      65         1020 :         return depth == 0 ? p : NULL;
      66              :     }
      67              :     /* Number, true, false, null: advance past alphanumeric + signs */
      68          396 :     while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
      69          167 :                   *p == '+' || *p == '-'))
      70          229 :         p++;
      71          167 :     return p;
      72              : }
      73              : 
      74              : /**
      75              :  * Find a key at the current object nesting level.
      76              :  * p must point inside a '{' ... '}' block (past the opening '{').
      77              :  * Returns pointer to the value (after the ':' and whitespace), or NULL.
      78              :  */
      79         5490 : static const char *find_key(const char *p, const char *key) {
      80         5490 :     size_t klen = strlen(key);
      81         5490 :     p = skip_ws(p);
      82              : 
      83        10706 :     while (*p && *p != '}') {
      84              :         /* Expect a quoted key */
      85        10615 :         if (*p != '"') return NULL;
      86        10615 :         p++;
      87        10615 :         const char *ks = p;
      88        60041 :         while (*p && *p != '"') {
      89        49426 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      90        49426 :             p++;
      91              :         }
      92        10615 :         if (!*p) return NULL;
      93        10615 :         size_t found_len = (size_t)(p - ks);
      94        10615 :         int match = (found_len == klen && memcmp(ks, key, klen) == 0);
      95        10615 :         p++; /* past closing quote */
      96              : 
      97        10615 :         p = skip_ws(p);
      98        10615 :         if (*p != ':') return NULL;
      99        10615 :         p++;
     100        10615 :         p = skip_ws(p);
     101              : 
     102        10615 :         if (match) return p;
     103              : 
     104              :         /* Skip the value */
     105         5216 :         p = skip_value(p);
     106         5216 :         if (!p) return NULL;
     107         5216 :         p = skip_ws(p);
     108         5216 :         if (*p == ',') p++;
     109         5216 :         p = skip_ws(p);
     110              :     }
     111           91 :     return NULL;
     112              : }
     113              : 
     114              : /**
     115              :  * Unescape a JSON string from src[0..len-1] into a heap-allocated buffer.
     116              :  * Handles: \\, \", \/, \n, \r, \t, \b, \f, \uXXXX (BMP only, as ASCII ?).
     117              :  */
     118         5825 : static char *unescape(const char *src, size_t len) {
     119         5825 :     char *buf = malloc(len + 1);
     120         5825 :     if (!buf) return NULL;
     121         5825 :     size_t out = 0;
     122       311314 :     for (size_t i = 0; i < len; i++) {
     123       305489 :         if (src[i] == '\\' && i + 1 < len) {
     124           14 :             i++;
     125           14 :             switch (src[i]) {
     126            2 :             case '"':  buf[out++] = '"';  break;
     127            1 :             case '\\': buf[out++] = '\\'; break;
     128            1 :             case '/':  buf[out++] = '/';  break;
     129            2 :             case 'n':  buf[out++] = '\n'; break;
     130            1 :             case 'r':  buf[out++] = '\r'; break;
     131            2 :             case 't':  buf[out++] = '\t'; break;
     132            1 :             case 'b':  buf[out++] = '\b'; break;
     133            1 :             case 'f':  buf[out++] = '\f'; break;
     134            2 :             case 'u':
     135              :                 /* \uXXXX — emit '?' for non-ASCII, decode ASCII range */
     136            2 :                 if (i + 4 < len) {
     137            2 :                     char hex[5] = {src[i+1], src[i+2], src[i+3], src[i+4], 0};
     138            2 :                     unsigned long cp = strtoul(hex, NULL, 16);
     139            2 :                     if (cp < 0x80)
     140            1 :                         buf[out++] = (char)cp;
     141              :                     else
     142            1 :                         buf[out++] = '?'; /* non-ASCII BMP placeholder */
     143            2 :                     i += 4;
     144              :                 }
     145            2 :                 break;
     146            1 :             default:
     147            1 :                 buf[out++] = src[i];
     148            1 :                 break;
     149              :             }
     150              :         } else {
     151       305475 :             buf[out++] = src[i];
     152              :         }
     153              :     }
     154         5825 :     buf[out] = '\0';
     155         5825 :     return buf;
     156              : }
     157              : 
     158              : /* ── Public API ─────────────────────────────────────────────────────── */
     159              : 
     160            6 : char *json_get_nested_string(const char *json,
     161              :                               const char *outer_key, const char *inner_key) {
     162            6 :     if (!json || !outer_key || !inner_key) return NULL;
     163              : 
     164            6 :     const char *p = skip_ws(json);
     165            6 :     if (*p != '{') return NULL;
     166            6 :     p++;
     167              : 
     168            6 :     const char *outer_val = find_key(p, outer_key);
     169            6 :     if (!outer_val) return NULL;
     170            6 :     outer_val = skip_ws(outer_val);
     171            6 :     if (*outer_val != '{') return NULL;
     172            6 :     outer_val++; /* inside the sub-object */
     173              : 
     174            6 :     const char *inner_val = find_key(outer_val, inner_key);
     175            6 :     if (!inner_val || *inner_val != '"') return NULL;
     176            6 :     inner_val++; /* past opening quote */
     177              : 
     178            6 :     const char *end = inner_val;
     179          102 :     while (*end && *end != '"') {
     180           96 :         if (*end == '\\') { end++; if (!*end) return NULL; }
     181           96 :         end++;
     182              :     }
     183            6 :     if (!*end) return NULL;
     184              : 
     185            6 :     return unescape(inner_val, (size_t)(end - inner_val));
     186              : }
     187              : 
     188         4456 : char *json_get_string(const char *json, const char *key) {
     189         4456 :     if (!json || !key) return NULL;
     190              : 
     191         4454 :     const char *p = skip_ws(json);
     192         4454 :     if (*p != '{') return NULL;
     193         4454 :     p++;
     194              : 
     195         4454 :     const char *val = find_key(p, key);
     196         4454 :     if (!val || *val != '"') return NULL;
     197              : 
     198         4369 :     val++; /* past opening quote */
     199         4369 :     const char *end = val;
     200       301632 :     while (*end && *end != '"') {
     201       297263 :         if (*end == '\\') { end++; if (!*end) return NULL; }
     202       297263 :         end++;
     203              :     }
     204         4369 :     if (!*end) return NULL;
     205              : 
     206         4369 :     return unescape(val, (size_t)(end - val));
     207              : }
     208              : 
     209            6 : int json_get_int(const char *json, const char *key, int *out) {
     210            6 :     if (!json || !key || !out) return -1;
     211              : 
     212            5 :     const char *p = skip_ws(json);
     213            5 :     if (*p != '{') return -1;
     214            5 :     p++;
     215              : 
     216            5 :     const char *val = find_key(p, key);
     217            5 :     if (!val) return -1;
     218              : 
     219              :     /* Accept number or quoted number */
     220            4 :     if (*val == '"') {
     221            1 :         val++;
     222              :         char *end;
     223            1 :         long v = strtol(val, &end, 10);
     224            1 :         if (end == val) return -1;
     225            1 :         *out = (int)v;
     226            1 :         return 0;
     227              :     }
     228            3 :     if (*val == '-' || isdigit((unsigned char)*val)) {
     229              :         char *end;
     230            2 :         long v = strtol(val, &end, 10);
     231            2 :         if (end == val) return -1;
     232            2 :         *out = (int)v;
     233            2 :         return 0;
     234              :     }
     235            1 :     return -1;
     236              : }
     237              : 
     238          833 : int json_get_string_array(const char *json, const char *key,
     239              :                           char ***out, int *count_out) {
     240          833 :     if (!json || !key || !out || !count_out) return -1;
     241          833 :     *out = NULL;
     242          833 :     *count_out = 0;
     243              : 
     244          833 :     const char *p = skip_ws(json);
     245          833 :     if (*p != '{') return -1;
     246          833 :     p++;
     247              : 
     248          833 :     const char *val = find_key(p, key);
     249          833 :     if (!val || *val != '[') return -1;
     250              : 
     251          832 :     val++; /* past '[' */
     252          832 :     val = skip_ws(val);
     253              : 
     254          832 :     int cap = 8;
     255          832 :     char **arr = malloc((size_t)cap * sizeof(char *));
     256          832 :     if (!arr) return -1;
     257          832 :     int count = 0;
     258              : 
     259         2282 :     while (*val && *val != ']') {
     260         1450 :         if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
     261         1450 :         val++; /* past opening quote */
     262         1450 :         const char *end = val;
     263         9588 :         while (*end && *end != '"') {
     264         8138 :             if (*end == '\\') { end++; if (!*end) goto fail; }
     265         8138 :             end++;
     266              :         }
     267         1450 :         if (!*end) goto fail;
     268              : 
     269         1450 :         char *s = unescape(val, (size_t)(end - val));
     270         1450 :         if (!s) goto fail;
     271              : 
     272         1450 :         if (count >= cap) {
     273            2 :             cap *= 2;
     274            2 :             char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
     275            2 :             if (!tmp) { free(s); goto fail; }
     276            2 :             arr = tmp;
     277              :         }
     278         1450 :         arr[count++] = s;
     279              : 
     280         1450 :         val = end + 1; /* past closing quote */
     281         1450 :         val = skip_ws(val);
     282         1450 :         if (*val == ',') val++;
     283         1450 :         val = skip_ws(val);
     284              :     }
     285              : 
     286          832 :     *out = arr;
     287          832 :     *count_out = count;
     288          832 :     return 0;
     289              : 
     290            0 : fail:
     291            0 :     for (int i = 0; i < count; i++) free(arr[i]);
     292            0 :     free(arr);
     293            0 :     return -1;
     294              : }
     295              : 
     296          186 : int json_foreach_object(const char *json, const char *key,
     297              :                         JsonObjectCb cb, void *ctx) {
     298          186 :     if (!json || !key || !cb) return -1;
     299              : 
     300          186 :     const char *p = skip_ws(json);
     301          186 :     if (*p != '{') return -1;
     302          186 :     p++;
     303              : 
     304          186 :     const char *val = find_key(p, key);
     305          186 :     if (!val || *val != '[') return -1;
     306              : 
     307          182 :     val++; /* past '[' */
     308          182 :     val = skip_ws(val);
     309          182 :     int index = 0;
     310              : 
     311         2977 :     while (*val && *val != ']') {
     312         2795 :         if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
     313              : 
     314              :         /* Find the extent of this object */
     315         2795 :         const char *obj_start = val;
     316         2795 :         const char *obj_end = skip_value(val);
     317         2795 :         if (!obj_end) return -1;
     318              : 
     319              :         /* Create a NUL-terminated copy for the callback */
     320         2795 :         size_t obj_len = (size_t)(obj_end - obj_start);
     321         2795 :         char *obj_copy = malloc(obj_len + 1);
     322         2795 :         if (!obj_copy) return -1;
     323         2795 :         memcpy(obj_copy, obj_start, obj_len);
     324         2795 :         obj_copy[obj_len] = '\0';
     325              : 
     326         2795 :         cb(obj_copy, index, ctx);
     327         2795 :         free(obj_copy);
     328         2795 :         index++;
     329              : 
     330         2795 :         val = skip_ws(obj_end);
     331         2795 :         if (*val == ',') val++;
     332         2795 :         val = skip_ws(val);
     333              :     }
     334              : 
     335          182 :     return index;
     336              : }
        

Generated by: LCOV version 2.0-1