LCOV - code coverage report
Current view: top level - libemail/src/core - json_util.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.0 % 200 196
Test Date: 2026-05-07 15:53:07 Functions: 100.0 % 8 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        41343 : static const char *skip_ws(const char *p) {
      10        41421 :     while (*p && isspace((unsigned char)*p)) p++;
      11        41343 :     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         5579 : static const char *skip_value(const char *p) {
      19         5579 :     p = skip_ws(p);
      20         5579 :     if (!*p) return NULL;
      21              : 
      22         5579 :     if (*p == '"') {
      23              :         /* String: advance past closing quote, handling escapes */
      24         2728 :         p++;
      25        43264 :         while (*p && *p != '"') {
      26        40536 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      27        40536 :             p++;
      28              :         }
      29         2728 :         return *p == '"' ? p + 1 : NULL;
      30              :     }
      31         2851 :     if (*p == '{') {
      32              :         /* Object: match braces */
      33         2073 :         int depth = 1; p++;
      34        19438 :         while (*p && depth > 0) {
      35        17365 :             if (*p == '{') depth++;
      36        17364 :             else if (*p == '}') depth--;
      37        15290 :             else if (*p == '"') {
      38         8673 :                 p++;
      39        91524 :                 while (*p && *p != '"') {
      40        82851 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      41        82851 :                     p++;
      42              :                 }
      43         8673 :                 if (!*p) return NULL;
      44              :             }
      45        17365 :             p++;
      46              :         }
      47         2073 :         return depth == 0 ? p : NULL;
      48              :     }
      49          778 :     if (*p == '[') {
      50              :         /* Array: match brackets */
      51          713 :         int depth = 1; p++;
      52        39550 :         while (*p && depth > 0) {
      53        38837 :             if (*p == '[') depth++;
      54        38834 :             else if (*p == ']') depth--;
      55        38118 :             else if (*p == '"') {
      56        15720 :                 p++;
      57       175325 :                 while (*p && *p != '"') {
      58       159605 :                     if (*p == '\\') { p++; if (!*p) return NULL; }
      59       159605 :                     p++;
      60              :                 }
      61        15720 :                 if (!*p) return NULL;
      62              :             }
      63        38837 :             p++;
      64              :         }
      65          713 :         return depth == 0 ? p : NULL;
      66              :     }
      67              :     /* Number, true, false, null: advance past alphanumeric + signs */
      68          188 :     while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
      69           65 :                   *p == '+' || *p == '-'))
      70          123 :         p++;
      71           65 :     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         3740 : static const char *find_key(const char *p, const char *key) {
      80         3740 :     size_t klen = strlen(key);
      81         3740 :     p = skip_ws(p);
      82              : 
      83         7247 :     while (*p && *p != '}') {
      84              :         /* Expect a quoted key */
      85         7195 :         if (*p != '"') return NULL;
      86         7195 :         p++;
      87         7195 :         const char *ks = p;
      88        40206 :         while (*p && *p != '"') {
      89        33011 :             if (*p == '\\') { p++; if (!*p) return NULL; }
      90        33011 :             p++;
      91              :         }
      92         7195 :         if (!*p) return NULL;
      93         7195 :         size_t found_len = (size_t)(p - ks);
      94         7195 :         int match = (found_len == klen && memcmp(ks, key, klen) == 0);
      95         7195 :         p++; /* past closing quote */
      96              : 
      97         7195 :         p = skip_ws(p);
      98         7195 :         if (*p != ':') return NULL;
      99         7195 :         p++;
     100         7195 :         p = skip_ws(p);
     101              : 
     102         7195 :         if (match) return p;
     103              : 
     104              :         /* Skip the value */
     105         3507 :         p = skip_value(p);
     106         3507 :         if (!p) return NULL;
     107         3507 :         p = skip_ws(p);
     108         3507 :         if (*p == ',') p++;
     109         3507 :         p = skip_ws(p);
     110              :     }
     111           52 :     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         4008 : static char *unescape(const char *src, size_t len) {
     119         4008 :     char *buf = malloc(len + 1);
     120         4008 :     if (!buf) return NULL;
     121         4008 :     size_t out = 0;
     122       237740 :     for (size_t i = 0; i < len; i++) {
     123       233732 :         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       233718 :             buf[out++] = src[i];
     152              :         }
     153              :     }
     154         4008 :     buf[out] = '\0';
     155         4008 :     return buf;
     156              : }
     157              : 
     158              : /* ── Public API ─────────────────────────────────────────────────────── */
     159              : 
     160         3024 : char *json_get_string(const char *json, const char *key) {
     161         3024 :     if (!json || !key) return NULL;
     162              : 
     163         3022 :     const char *p = skip_ws(json);
     164         3022 :     if (*p != '{') return NULL;
     165         3022 :     p++;
     166              : 
     167         3022 :     const char *val = find_key(p, key);
     168         3022 :     if (!val || *val != '"') return NULL;
     169              : 
     170         2988 :     val++; /* past opening quote */
     171         2988 :     const char *end = val;
     172       230955 :     while (*end && *end != '"') {
     173       227967 :         if (*end == '\\') { end++; if (!*end) return NULL; }
     174       227967 :         end++;
     175              :     }
     176         2988 :     if (!*end) return NULL;
     177              : 
     178         2988 :     return unescape(val, (size_t)(end - val));
     179              : }
     180              : 
     181            6 : int json_get_int(const char *json, const char *key, int *out) {
     182            6 :     if (!json || !key || !out) return -1;
     183              : 
     184            5 :     const char *p = skip_ws(json);
     185            5 :     if (*p != '{') return -1;
     186            5 :     p++;
     187              : 
     188            5 :     const char *val = find_key(p, key);
     189            5 :     if (!val) return -1;
     190              : 
     191              :     /* Accept number or quoted number */
     192            4 :     if (*val == '"') {
     193            1 :         val++;
     194              :         char *end;
     195            1 :         long v = strtol(val, &end, 10);
     196            1 :         if (end == val) return -1;
     197            1 :         *out = (int)v;
     198            1 :         return 0;
     199              :     }
     200            3 :     if (*val == '-' || isdigit((unsigned char)*val)) {
     201              :         char *end;
     202            2 :         long v = strtol(val, &end, 10);
     203            2 :         if (end == val) return -1;
     204            2 :         *out = (int)v;
     205            2 :         return 0;
     206              :     }
     207            1 :     return -1;
     208              : }
     209              : 
     210          618 : int json_get_string_array(const char *json, const char *key,
     211              :                           char ***out, int *count_out) {
     212          618 :     if (!json || !key || !out || !count_out) return -1;
     213          618 :     *out = NULL;
     214          618 :     *count_out = 0;
     215              : 
     216          618 :     const char *p = skip_ws(json);
     217          618 :     if (*p != '{') return -1;
     218          618 :     p++;
     219              : 
     220          618 :     const char *val = find_key(p, key);
     221          618 :     if (!val || *val != '[') return -1;
     222              : 
     223          617 :     val++; /* past '[' */
     224          617 :     val = skip_ws(val);
     225              : 
     226          617 :     int cap = 8;
     227          617 :     char **arr = malloc((size_t)cap * sizeof(char *));
     228          617 :     if (!arr) return -1;
     229          617 :     int count = 0;
     230              : 
     231         1637 :     while (*val && *val != ']') {
     232         1020 :         if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
     233         1020 :         val++; /* past opening quote */
     234         1020 :         const char *end = val;
     235         6793 :         while (*end && *end != '"') {
     236         5773 :             if (*end == '\\') { end++; if (!*end) goto fail; }
     237         5773 :             end++;
     238              :         }
     239         1020 :         if (!*end) goto fail;
     240              : 
     241         1020 :         char *s = unescape(val, (size_t)(end - val));
     242         1020 :         if (!s) goto fail;
     243              : 
     244         1020 :         if (count >= cap) {
     245            2 :             cap *= 2;
     246            2 :             char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
     247            2 :             if (!tmp) { free(s); goto fail; }
     248            2 :             arr = tmp;
     249              :         }
     250         1020 :         arr[count++] = s;
     251              : 
     252         1020 :         val = end + 1; /* past closing quote */
     253         1020 :         val = skip_ws(val);
     254         1020 :         if (*val == ',') val++;
     255         1020 :         val = skip_ws(val);
     256              :     }
     257              : 
     258          617 :     *out = arr;
     259          617 :     *count_out = count;
     260          617 :     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           95 : int json_foreach_object(const char *json, const char *key,
     269              :                         JsonObjectCb cb, void *ctx) {
     270           95 :     if (!json || !key || !cb) return -1;
     271              : 
     272           95 :     const char *p = skip_ws(json);
     273           95 :     if (*p != '{') return -1;
     274           95 :     p++;
     275              : 
     276           95 :     const char *val = find_key(p, key);
     277           95 :     if (!val || *val != '[') return -1;
     278              : 
     279           79 :     val++; /* past '[' */
     280           79 :     val = skip_ws(val);
     281           79 :     int index = 0;
     282              : 
     283         2151 :     while (*val && *val != ']') {
     284         2072 :         if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
     285              : 
     286              :         /* Find the extent of this object */
     287         2072 :         const char *obj_start = val;
     288         2072 :         const char *obj_end = skip_value(val);
     289         2072 :         if (!obj_end) return -1;
     290              : 
     291              :         /* Create a NUL-terminated copy for the callback */
     292         2072 :         size_t obj_len = (size_t)(obj_end - obj_start);
     293         2072 :         char *obj_copy = malloc(obj_len + 1);
     294         2072 :         if (!obj_copy) return -1;
     295         2072 :         memcpy(obj_copy, obj_start, obj_len);
     296         2072 :         obj_copy[obj_len] = '\0';
     297              : 
     298         2072 :         cb(obj_copy, index, ctx);
     299         2072 :         free(obj_copy);
     300         2072 :         index++;
     301              : 
     302         2072 :         val = skip_ws(obj_end);
     303         2072 :         if (*val == ',') val++;
     304         2072 :         val = skip_ws(val);
     305              :     }
     306              : 
     307           79 :     return index;
     308              : }
        

Generated by: LCOV version 2.0-1