LCOV - code coverage report
Current view: top level - libemail/src/core - json_util.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 77.6 % 219 170
Test Date: 2026-08-21 10:11:30 Functions: 88.9 % 9 8

            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        58451 : static const char *skip_ws(const char *p) {
      10        58451 :     while (*p && isspace((unsigned char)*p)) p++;
      11        58451 :     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         7827 : static const char *skip_value(const char *p) {
      19         7827 :     p = skip_ws(p);
      20         7827 :     if (!*p) return NULL;
      21              : 
      22         7827 :     if (*p == '"') {
      23              :         /* String: advance past closing quote, handling escapes */
      24         3958 :         p++;
      25        60167 :         while (*p && *p != '"') {
      26        56209 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      27        56209 :             p++;
      28              :         }
      29         3958 :         return *p == '"' ? p + 1 : NULL;
      30              :     }
      31         3869 :     if (*p == '{') {
      32              :         /* Object: match braces */
      33         2740 :         int depth = 1; p++;
      34        27104 :         while (*p && depth > 0) {
      35        24364 :             if (*p == '{') depth++;
      36        24358 :             else if (*p == '}') depth--;
      37        21612 :             else if (*p == '"') {
      38        12171 :                 p++;
      39       118572 :                 while (*p && *p != '"') {
      40       106401 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      41       106401 :                     p++;
      42              :                 }
      43        12171 :                 if (!*p) return NULL;
      44              :             }
      45        24364 :             p++;
      46              :         }
      47         2740 :         return depth == 0 ? p : NULL;
      48              :     }
      49         1129 :     if (*p == '[') {
      50              :         /* Array: match brackets */
      51          982 :         int depth = 1; p++;
      52        46856 :         while (*p && depth > 0) {
      53        45874 :             if (*p == '[') depth++;
      54        45865 :             else if (*p == ']') depth--;
      55        44874 :             else if (*p == '"') {
      56        18625 :                 p++;
      57       207122 :                 while (*p && *p != '"') {
      58       188497 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      59       188497 :                     p++;
      60              :                 }
      61        18625 :                 if (!*p) return NULL;
      62              :             }
      63        45874 :             p++;
      64              :         }
      65          982 :         return depth == 0 ? p : NULL;
      66              :     }
      67              :     /* Number, true, false, null: advance past alphanumeric + signs */
      68          356 :     while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
      69          147 :                   *p == '+' || *p == '-'))
      70          209 :         p++;
      71          147 :     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         5292 : static const char *find_key(const char *p, const char *key) {
      80         5292 :     size_t klen = strlen(key);
      81         5292 :     p = skip_ws(p);
      82              : 
      83        10379 :     while (*p && *p != '}') {
      84              :         /* Expect a quoted key */
      85        10307 :         if (*p != '"') return NULL;
      86        10307 :         p++;
      87        10307 :         const char *ks = p;
      88        58019 :         while (*p && *p != '"') {
      89        47712 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      90        47712 :             p++;
      91              :         }
      92        10307 :         if (!*p) return NULL;
      93        10307 :         size_t found_len = (size_t)(p - ks);
      94        10307 :         int match = (found_len == klen && memcmp(ks, key, klen) == 0);
      95        10307 :         p++; /* past closing quote */
      96              : 
      97        10307 :         p = skip_ws(p);
      98        10307 :         if (*p != ':') return NULL;
      99        10307 :         p++;
     100        10307 :         p = skip_ws(p);
     101              : 
     102        10307 :         if (match) return p;
     103              : 
     104              :         /* Skip the value */
     105         5087 :         p = skip_value(p);
     106         5087 :         if (!p) return NULL;
     107         5087 :         p = skip_ws(p);
     108         5087 :         if (*p == ',') p++;
     109         5087 :         p = skip_ws(p);
     110              :     }
     111           72 :     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         5652 : static char *unescape(const char *src, size_t len) {
     119         5652 :     char *buf = malloc(len + 1);
     120         5652 :     if (!buf) return NULL;
     121         5652 :     size_t out = 0;
     122       307943 :     for (size_t i = 0; i < len; i++) {
     123       302291 :         if (src[i] == '\\' && i + 1 < len) {
     124            0 :             i++;
     125            0 :             switch (src[i]) {
     126            0 :             case '"':  buf[out++] = '"';  break;
     127            0 :             case '\\': buf[out++] = '\\'; break;
     128            0 :             case '/':  buf[out++] = '/';  break;
     129            0 :             case 'n':  buf[out++] = '\n'; break;
     130            0 :             case 'r':  buf[out++] = '\r'; break;
     131            0 :             case 't':  buf[out++] = '\t'; break;
     132            0 :             case 'b':  buf[out++] = '\b'; break;
     133            0 :             case 'f':  buf[out++] = '\f'; break;
     134            0 :             case 'u':
     135              :                 /* \uXXXX — emit '?' for non-ASCII, decode ASCII range */
     136            0 :                 if (i + 4 < len) {
     137            0 :                     char hex[5] = {src[i+1], src[i+2], src[i+3], src[i+4], 0};
     138            0 :                     unsigned long cp = strtoul(hex, NULL, 16);
     139            0 :                     if (cp < 0x80)
     140            0 :                         buf[out++] = (char)cp;
     141              :                     else
     142            0 :                         buf[out++] = '?'; /* non-ASCII BMP placeholder */
     143            0 :                     i += 4;
     144              :                 }
     145            0 :                 break;
     146            0 :             default:
     147            0 :                 buf[out++] = src[i];
     148            0 :                 break;
     149              :             }
     150              :         } else {
     151       302291 :             buf[out++] = src[i];
     152              :         }
     153              :     }
     154         5652 :     buf[out] = '\0';
     155         5652 :     return buf;
     156              : }
     157              : 
     158              : /* ── Public API ─────────────────────────────────────────────────────── */
     159              : 
     160            2 : char *json_get_nested_string(const char *json,
     161              :                               const char *outer_key, const char *inner_key) {
     162            2 :     if (!json || !outer_key || !inner_key) return NULL;
     163              : 
     164            2 :     const char *p = skip_ws(json);
     165            2 :     if (*p != '{') return NULL;
     166            2 :     p++;
     167              : 
     168            2 :     const char *outer_val = find_key(p, outer_key);
     169            2 :     if (!outer_val) return NULL;
     170            2 :     outer_val = skip_ws(outer_val);
     171            2 :     if (*outer_val != '{') return NULL;
     172            2 :     outer_val++; /* inside the sub-object */
     173              : 
     174            2 :     const char *inner_val = find_key(outer_val, inner_key);
     175            2 :     if (!inner_val || *inner_val != '"') return NULL;
     176            2 :     inner_val++; /* past opening quote */
     177              : 
     178            2 :     const char *end = inner_val;
     179           34 :     while (*end && *end != '"') {
     180           32 :         if (*end == '\\') { end++; if (!*end) return NULL; }
     181           32 :         end++;
     182              :     }
     183            2 :     if (!*end) return NULL;
     184              : 
     185            2 :     return unescape(inner_val, (size_t)(end - inner_val));
     186              : }
     187              : 
     188         4317 : char *json_get_string(const char *json, const char *key) {
     189         4317 :     if (!json || !key) return NULL;
     190              : 
     191         4317 :     const char *p = skip_ws(json);
     192         4317 :     if (*p != '{') return NULL;
     193         4317 :     p++;
     194              : 
     195         4317 :     const char *val = find_key(p, key);
     196         4317 :     if (!val || *val != '"') return NULL;
     197              : 
     198         4248 :     val++; /* past opening quote */
     199         4248 :     const char *end = val;
     200       298651 :     while (*end && *end != '"') {
     201       294403 :         if (*end == '\\') { end++; if (!*end) return NULL; }
     202       294403 :         end++;
     203              :     }
     204         4248 :     if (!*end) return NULL;
     205              : 
     206         4248 :     return unescape(val, (size_t)(end - val));
     207              : }
     208              : 
     209            0 : int json_get_int(const char *json, const char *key, int *out) {
     210            0 :     if (!json || !key || !out) return -1;
     211              : 
     212            0 :     const char *p = skip_ws(json);
     213            0 :     if (*p != '{') return -1;
     214            0 :     p++;
     215              : 
     216            0 :     const char *val = find_key(p, key);
     217            0 :     if (!val) return -1;
     218              : 
     219              :     /* Accept number or quoted number */
     220            0 :     if (*val == '"') {
     221            0 :         val++;
     222              :         char *end;
     223            0 :         long v = strtol(val, &end, 10);
     224            0 :         if (end == val) return -1;
     225            0 :         *out = (int)v;
     226            0 :         return 0;
     227              :     }
     228            0 :     if (*val == '-' || isdigit((unsigned char)*val)) {
     229              :         char *end;
     230            0 :         long v = strtol(val, &end, 10);
     231            0 :         if (end == val) return -1;
     232            0 :         *out = (int)v;
     233            0 :         return 0;
     234              :     }
     235            0 :     return -1;
     236              : }
     237              : 
     238          816 : int json_get_string_array(const char *json, const char *key,
     239              :                           char ***out, int *count_out) {
     240          816 :     if (!json || !key || !out || !count_out) return -1;
     241          816 :     *out = NULL;
     242          816 :     *count_out = 0;
     243              : 
     244          816 :     const char *p = skip_ws(json);
     245          816 :     if (*p != '{') return -1;
     246          816 :     p++;
     247              : 
     248          816 :     const char *val = find_key(p, key);
     249          816 :     if (!val || *val != '[') return -1;
     250              : 
     251          816 :     val++; /* past '[' */
     252          816 :     val = skip_ws(val);
     253              : 
     254          816 :     int cap = 8;
     255          816 :     char **arr = malloc((size_t)cap * sizeof(char *));
     256          816 :     if (!arr) return -1;
     257          816 :     int count = 0;
     258              : 
     259         2218 :     while (*val && *val != ']') {
     260         1402 :         if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
     261         1402 :         val++; /* past opening quote */
     262         1402 :         const char *end = val;
     263         9258 :         while (*end && *end != '"') {
     264         7856 :             if (*end == '\\') { end++; if (!*end) goto fail; }
     265         7856 :             end++;
     266              :         }
     267         1402 :         if (!*end) goto fail;
     268              : 
     269         1402 :         char *s = unescape(val, (size_t)(end - val));
     270         1402 :         if (!s) goto fail;
     271              : 
     272         1402 :         if (count >= cap) {
     273            0 :             cap *= 2;
     274            0 :             char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
     275            0 :             if (!tmp) { free(s); goto fail; }
     276            0 :             arr = tmp;
     277              :         }
     278         1402 :         arr[count++] = s;
     279              : 
     280         1402 :         val = end + 1; /* past closing quote */
     281         1402 :         val = skip_ws(val);
     282         1402 :         if (*val == ',') val++;
     283         1402 :         val = skip_ws(val);
     284              :     }
     285              : 
     286          816 :     *out = arr;
     287          816 :     *count_out = count;
     288          816 :     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          155 : int json_foreach_object(const char *json, const char *key,
     297              :                         JsonObjectCb cb, void *ctx) {
     298          155 :     if (!json || !key || !cb) return -1;
     299              : 
     300          155 :     const char *p = skip_ws(json);
     301          155 :     if (*p != '{') return -1;
     302          155 :     p++;
     303              : 
     304          155 :     const char *val = find_key(p, key);
     305          155 :     if (!val || *val != '[') return -1;
     306              : 
     307          152 :     val++; /* past '[' */
     308          152 :     val = skip_ws(val);
     309          152 :     int index = 0;
     310              : 
     311         2892 :     while (*val && *val != ']') {
     312         2740 :         if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
     313              : 
     314              :         /* Find the extent of this object */
     315         2740 :         const char *obj_start = val;
     316         2740 :         const char *obj_end = skip_value(val);
     317         2740 :         if (!obj_end) return -1;
     318              : 
     319              :         /* Create a NUL-terminated copy for the callback */
     320         2740 :         size_t obj_len = (size_t)(obj_end - obj_start);
     321         2740 :         char *obj_copy = malloc(obj_len + 1);
     322         2740 :         if (!obj_copy) return -1;
     323         2740 :         memcpy(obj_copy, obj_start, obj_len);
     324         2740 :         obj_copy[obj_len] = '\0';
     325              : 
     326         2740 :         cb(obj_copy, index, ctx);
     327         2740 :         free(obj_copy);
     328         2740 :         index++;
     329              : 
     330         2740 :         val = skip_ws(obj_end);
     331         2740 :         if (*val == ',') val++;
     332         2740 :         val = skip_ws(val);
     333              :     }
     334              : 
     335          152 :     return index;
     336              : }
        

Generated by: LCOV version 2.0-1