LCOV - code coverage report
Current view: top level - libemail/src/core - json_util.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 75.5 % 200 151
Test Date: 2026-05-07 15:53:08 Functions: 87.5 % 8 7

            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        39646 : static const char *skip_ws(const char *p) {
      10        39646 :     while (*p && isspace((unsigned char)*p)) p++;
      11        39646 :     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         5393 : static const char *skip_value(const char *p) {
      19         5393 :     p = skip_ws(p);
      20         5393 :     if (!*p) return NULL;
      21              : 
      22         5393 :     if (*p == '"') {
      23              :         /* String: advance past closing quote, handling escapes */
      24         2655 :         p++;
      25        42604 :         while (*p && *p != '"') {
      26        39949 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      27        39949 :             p++;
      28              :         }
      29         2655 :         return *p == '"' ? p + 1 : NULL;
      30              :     }
      31         2738 :     if (*p == '{') {
      32              :         /* Object: match braces */
      33         2018 :         int depth = 1; p++;
      34        18954 :         while (*p && depth > 0) {
      35        16936 :             if (*p == '{') depth++;
      36        16936 :             else if (*p == '}') depth--;
      37        14918 :             else if (*p == '"') {
      38         8468 :                 p++;
      39        90276 :                 while (*p && *p != '"') {
      40        81808 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      41        81808 :                     p++;
      42              :                 }
      43         8468 :                 if (!*p) return NULL;
      44              :             }
      45        16936 :             p++;
      46              :         }
      47         2018 :         return depth == 0 ? p : NULL;
      48              :     }
      49          720 :     if (*p == '[') {
      50              :         /* Array: match brackets */
      51          675 :         int depth = 1; p++;
      52        39071 :         while (*p && depth > 0) {
      53        38396 :             if (*p == '[') depth++;
      54        38396 :             else if (*p == ']') depth--;
      55        37721 :             else if (*p == '"') {
      56        15550 :                 p++;
      57       174060 :                 while (*p && *p != '"') {
      58       158510 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      59       158510 :                     p++;
      60              :                 }
      61        15550 :                 if (!*p) return NULL;
      62              :             }
      63        38396 :             p++;
      64              :         }
      65          675 :         return depth == 0 ? p : NULL;
      66              :     }
      67              :     /* Number, true, false, null: advance past alphanumeric + signs */
      68          148 :     while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
      69           45 :                   *p == '+' || *p == '-'))
      70          103 :         p++;
      71           45 :     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         3547 : static const char *find_key(const char *p, const char *key) {
      80         3547 :     size_t klen = strlen(key);
      81         3547 :     p = skip_ws(p);
      82              : 
      83         6922 :     while (*p && *p != '}') {
      84              :         /* Expect a quoted key */
      85         6889 :         if (*p != '"') return NULL;
      86         6889 :         p++;
      87         6889 :         const char *ks = p;
      88        38204 :         while (*p && *p != '"') {
      89        31315 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      90        31315 :             p++;
      91              :         }
      92         6889 :         if (!*p) return NULL;
      93         6889 :         size_t found_len = (size_t)(p - ks);
      94         6889 :         int match = (found_len == klen && memcmp(ks, key, klen) == 0);
      95         6889 :         p++; /* past closing quote */
      96              : 
      97         6889 :         p = skip_ws(p);
      98         6889 :         if (*p != ':') return NULL;
      99         6889 :         p++;
     100         6889 :         p = skip_ws(p);
     101              : 
     102         6889 :         if (match) return p;
     103              : 
     104              :         /* Skip the value */
     105         3375 :         p = skip_value(p);
     106         3375 :         if (!p) return NULL;
     107         3375 :         p = skip_ws(p);
     108         3375 :         if (*p == ',') p++;
     109         3375 :         p = skip_ws(p);
     110              :     }
     111           33 :     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         3835 : static char *unescape(const char *src, size_t len) {
     119         3835 :     char *buf = malloc(len + 1);
     120         3835 :     if (!buf) return NULL;
     121         3835 :     size_t out = 0;
     122       234369 :     for (size_t i = 0; i < len; i++) {
     123       230534 :         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       230534 :             buf[out++] = src[i];
     152              :         }
     153              :     }
     154         3835 :     buf[out] = '\0';
     155         3835 :     return buf;
     156              : }
     157              : 
     158              : /* ── Public API ─────────────────────────────────────────────────────── */
     159              : 
     160         2881 : char *json_get_string(const char *json, const char *key) {
     161         2881 :     if (!json || !key) return NULL;
     162              : 
     163         2881 :     const char *p = skip_ws(json);
     164         2881 :     if (*p != '{') return NULL;
     165         2881 :     p++;
     166              : 
     167         2881 :     const char *val = find_key(p, key);
     168         2881 :     if (!val || *val != '"') return NULL;
     169              : 
     170         2863 :     val++; /* past opening quote */
     171         2863 :     const char *end = val;
     172       227906 :     while (*end && *end != '"') {
     173       225043 :         if (*end == '\\') { end++; if (!*end) return NULL; }
     174       225043 :         end++;
     175              :     }
     176         2863 :     if (!*end) return NULL;
     177              : 
     178         2863 :     return unescape(val, (size_t)(end - val));
     179              : }
     180              : 
     181            0 : int json_get_int(const char *json, const char *key, int *out) {
     182            0 :     if (!json || !key || !out) return -1;
     183              : 
     184            0 :     const char *p = skip_ws(json);
     185            0 :     if (*p != '{') return -1;
     186            0 :     p++;
     187              : 
     188            0 :     const char *val = find_key(p, key);
     189            0 :     if (!val) return -1;
     190              : 
     191              :     /* Accept number or quoted number */
     192            0 :     if (*val == '"') {
     193            0 :         val++;
     194              :         char *end;
     195            0 :         long v = strtol(val, &end, 10);
     196            0 :         if (end == val) return -1;
     197            0 :         *out = (int)v;
     198            0 :         return 0;
     199              :     }
     200            0 :     if (*val == '-' || isdigit((unsigned char)*val)) {
     201              :         char *end;
     202            0 :         long v = strtol(val, &end, 10);
     203            0 :         if (end == val) return -1;
     204            0 :         *out = (int)v;
     205            0 :         return 0;
     206              :     }
     207            0 :     return -1;
     208              : }
     209              : 
     210          601 : int json_get_string_array(const char *json, const char *key,
     211              :                           char ***out, int *count_out) {
     212          601 :     if (!json || !key || !out || !count_out) return -1;
     213          601 :     *out = NULL;
     214          601 :     *count_out = 0;
     215              : 
     216          601 :     const char *p = skip_ws(json);
     217          601 :     if (*p != '{') return -1;
     218          601 :     p++;
     219              : 
     220          601 :     const char *val = find_key(p, key);
     221          601 :     if (!val || *val != '[') return -1;
     222              : 
     223          601 :     val++; /* past '[' */
     224          601 :     val = skip_ws(val);
     225              : 
     226          601 :     int cap = 8;
     227          601 :     char **arr = malloc((size_t)cap * sizeof(char *));
     228          601 :     if (!arr) return -1;
     229          601 :     int count = 0;
     230              : 
     231         1573 :     while (*val && *val != ']') {
     232          972 :         if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
     233          972 :         val++; /* past opening quote */
     234          972 :         const char *end = val;
     235         6463 :         while (*end && *end != '"') {
     236         5491 :             if (*end == '\\') { end++; if (!*end) goto fail; }
     237         5491 :             end++;
     238              :         }
     239          972 :         if (!*end) goto fail;
     240              : 
     241          972 :         char *s = unescape(val, (size_t)(end - val));
     242          972 :         if (!s) goto fail;
     243              : 
     244          972 :         if (count >= cap) {
     245            0 :             cap *= 2;
     246            0 :             char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
     247            0 :             if (!tmp) { free(s); goto fail; }
     248            0 :             arr = tmp;
     249              :         }
     250          972 :         arr[count++] = s;
     251              : 
     252          972 :         val = end + 1; /* past closing quote */
     253          972 :         val = skip_ws(val);
     254          972 :         if (*val == ',') val++;
     255          972 :         val = skip_ws(val);
     256              :     }
     257              : 
     258          601 :     *out = arr;
     259          601 :     *count_out = count;
     260          601 :     return 0;
     261              : 
     262            0 : fail:
     263            0 :     for (int i = 0; i < count; i++) free(arr[i]);
     264            0 :     free(arr);
     265            0 :     return -1;
     266              : }
     267              : 
     268           65 : int json_foreach_object(const char *json, const char *key,
     269              :                         JsonObjectCb cb, void *ctx) {
     270           65 :     if (!json || !key || !cb) return -1;
     271              : 
     272           65 :     const char *p = skip_ws(json);
     273           65 :     if (*p != '{') return -1;
     274           65 :     p++;
     275              : 
     276           65 :     const char *val = find_key(p, key);
     277           65 :     if (!val || *val != '[') return -1;
     278              : 
     279           50 :     val++; /* past '[' */
     280           50 :     val = skip_ws(val);
     281           50 :     int index = 0;
     282              : 
     283         2068 :     while (*val && *val != ']') {
     284         2018 :         if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
     285              : 
     286              :         /* Find the extent of this object */
     287         2018 :         const char *obj_start = val;
     288         2018 :         const char *obj_end = skip_value(val);
     289         2018 :         if (!obj_end) return -1;
     290              : 
     291              :         /* Create a NUL-terminated copy for the callback */
     292         2018 :         size_t obj_len = (size_t)(obj_end - obj_start);
     293         2018 :         char *obj_copy = malloc(obj_len + 1);
     294         2018 :         if (!obj_copy) return -1;
     295         2018 :         memcpy(obj_copy, obj_start, obj_len);
     296         2018 :         obj_copy[obj_len] = '\0';
     297              : 
     298         2018 :         cb(obj_copy, index, ctx);
     299         2018 :         free(obj_copy);
     300         2018 :         index++;
     301              : 
     302         2018 :         val = skip_ws(obj_end);
     303         2018 :         if (*val == ',') val++;
     304         2018 :         val = skip_ws(val);
     305              :     }
     306              : 
     307           50 :     return index;
     308              : }
        

Generated by: LCOV version 2.0-1