LCOV - code coverage report
Current view: top level - tests/unit - test_imap_util.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 154 154
Test Date: 2026-05-07 15:53:07 Functions: 100.0 % 1 1

            Line data    Source code
       1              : #include "test_helpers.h"
       2              : #include "imap_util.h"
       3              : #include "raii.h"
       4              : #include <string.h>
       5              : #include <stdlib.h>
       6              : 
       7            1 : void test_imap_util(void) {
       8              : 
       9              :     /* Pure ASCII — no encoding, output identical to input */
      10              :     {
      11            2 :         RAII_STRING char *r = imap_utf7_decode("INBOX");
      12            1 :         ASSERT(r != NULL, "imap_utf7_decode: ASCII should not return NULL");
      13            1 :         ASSERT(strcmp(r, "INBOX") == 0, "ASCII passthrough mismatch");
      14              :     }
      15              : 
      16              :     /* Literal ampersand: "&-" → "&" */
      17              :     {
      18            2 :         RAII_STRING char *r = imap_utf7_decode("foo&-bar");
      19            1 :         ASSERT(r != NULL, "imap_utf7_decode: literal & should not return NULL");
      20            1 :         ASSERT(strcmp(r, "foo&bar") == 0, "Literal & decoding mismatch");
      21              :     }
      22              : 
      23              :     /* Single accented character: é = U+00E9 → "&AOk-" */
      24              :     {
      25            2 :         RAII_STRING char *r = imap_utf7_decode("&AOk-");
      26            1 :         ASSERT(r != NULL, "imap_utf7_decode: single char should not return NULL");
      27            1 :         ASSERT(strcmp(r, "\xC3\xA9") == 0, "é (U+00E9) decoding mismatch");
      28              :     }
      29              : 
      30              :     /* Hungarian pangram: árvíztűrőtükörfúrógép */
      31              :     {
      32              :         /* Modified UTF-7 encoded version of the pangram */
      33            1 :         const char *encoded =
      34              :             "&AOE-rv&AO0-zt&AXE-r&AVE-t&APw-k&APY-rf&APo-r&APM-g&AOk-p";
      35            2 :         RAII_STRING char *r = imap_utf7_decode(encoded);
      36            1 :         ASSERT(r != NULL, "imap_utf7_decode: pangram should not return NULL");
      37            1 :         ASSERT(strcmp(r, "\xC3\xA1"          /* á */
      38              :                         "rv"
      39              :                         "\xC3\xAD"           /* í */
      40              :                         "zt"
      41              :                         "\xC5\xB1"           /* ű */
      42              :                         "r"
      43              :                         "\xC5\x91"           /* ő */
      44              :                         "t"
      45              :                         "\xC3\xBC"           /* ü */
      46              :                         "k"
      47              :                         "\xC3\xB6"           /* ö */
      48              :                         "rf"
      49              :                         "\xC3\xBA"           /* ú */
      50              :                         "r"
      51              :                         "\xC3\xB3"           /* ó */
      52              :                         "g"
      53              :                         "\xC3\xA9"           /* é */
      54              :                         "p") == 0,
      55              :                "Hungarian pangram decoding mismatch");
      56              :     }
      57              : 
      58              :     /* Mixed ASCII and encoded segments (folder path) */
      59              :     {
      60            2 :         RAII_STRING char *r = imap_utf7_decode("INBOX.&AOk-rtes&AO0-t&AOk-s");
      61            1 :         ASSERT(r != NULL, "imap_utf7_decode: mixed path should not return NULL");
      62            1 :         ASSERT(strcmp(r, "INBOX."
      63              :                          "\xC3\xA9"   /* é */
      64              :                          "rtes"
      65              :                          "\xC3\xAD"   /* í */
      66              :                          "t"
      67              :                          "\xC3\xA9"   /* é */
      68              :                          "s") == 0,
      69              :                "Mixed folder path decoding mismatch");
      70              :     }
      71              : 
      72              :     /* Empty string */
      73              :     {
      74            2 :         RAII_STRING char *r = imap_utf7_decode("");
      75            1 :         ASSERT(r != NULL, "imap_utf7_decode: empty string should not return NULL");
      76            1 :         ASSERT(strcmp(r, "") == 0, "Empty string result mismatch");
      77              :     }
      78              : 
      79              :     /* NULL input */
      80              :     {
      81            1 :         char *r = imap_utf7_decode(NULL);
      82            1 :         ASSERT(r == NULL, "imap_utf7_decode: NULL input should return NULL");
      83              :     }
      84              : 
      85              :     /* mod64 '+' (62) and ',' (63) characters: U+FBF0 → "&+,A-" */
      86              :     {
      87              :         /* UTF-16BE: FB F0 → 6-bit groups: 111110(+) 111111(,) 000000(A) */
      88            2 :         RAII_STRING char *r = imap_utf7_decode("&+,A-");
      89            1 :         ASSERT(r != NULL, "imap_utf7_decode: '+' ',' in base64 should not return NULL");
      90            1 :         ASSERT(strcmp(r, "\xEF\xAF\xB0") == 0, "U+FBF0 via '+' and ',' decoding mismatch");
      91              :     }
      92              : 
      93              :     /* Invalid character in base64 run: covers mod64_value() return -1 path */
      94              :     {
      95            2 :         RAII_STRING char *r = imap_utf7_decode("&!-");
      96            1 :         ASSERT(r != NULL, "imap_utf7_decode: invalid base64 char should not return NULL");
      97            1 :         ASSERT(strcmp(r, "") == 0, "Invalid base64 segment should produce empty output");
      98              :     }
      99              : 
     100              :     /* ASCII codepoint via encoded path: U+0041 'A' → "&AEE-"
     101              :      * Tests utf8_encode() cp < 0x80 branch (1-byte output). */
     102              :     {
     103              :         /* UTF-16BE: 00 41 → base64: A(0) E(4) E(4), decodes to 0x00 0x41 = U+0041 */
     104            2 :         RAII_STRING char *r = imap_utf7_decode("&AEE-");
     105            1 :         ASSERT(r != NULL, "imap_utf7_decode: ASCII-via-encoding should not return NULL");
     106            1 :         ASSERT(strcmp(r, "A") == 0, "U+0041 via encoded path mismatch");
     107              :     }
     108              : 
     109              :     /* CJK 3-byte UTF-8: U+4E2D (中) → "&Ti0-"
     110              :      * Tests utf8_encode() 0x800 <= cp < 0x10000 branch. */
     111              :     {
     112              :         /* UTF-16BE: 4E 2D → base64: T(19) i(34) 0(52) */
     113            2 :         RAII_STRING char *r = imap_utf7_decode("&Ti0-");
     114            1 :         ASSERT(r != NULL, "imap_utf7_decode: CJK should not return NULL");
     115            1 :         ASSERT(strcmp(r, "\xE4\xB8\xAD") == 0, "U+4E2D (middle) CJK decoding mismatch");
     116              :     }
     117              : 
     118              :     /* UTF-16BE surrogate pair: U+10000 (𐀀) → "&2ADcAA-"
     119              :      * Tests utf8_encode() cp >= 0x10000 branch and surrogate-pair reassembly. */
     120              :     {
     121              :         /* High=0xD800, Low=0xDC00; UTF-16BE: D8 00 DC 00 → 2ADcAA */
     122            2 :         RAII_STRING char *r = imap_utf7_decode("&2ADcAA-");
     123            1 :         ASSERT(r != NULL, "imap_utf7_decode: surrogate pair should not return NULL");
     124            1 :         ASSERT(strcmp(r, "\xF0\x90\x80\x80") == 0, "U+10000 surrogate pair decoding mismatch");
     125              :     }
     126              : 
     127              :     /* Unpaired high surrogate followed by non-surrogate: covers cp=unit pass-through path */
     128              :     {
     129              :         /* UTF-16BE: D8 00 (high surrogate) 00 41 (U+0041, not a low surrogate)
     130              :          * → base64: 2AAAQQ; result is U+D800 (invalid UTF-8) + 'A' */
     131            2 :         RAII_STRING char *r = imap_utf7_decode("&2AAAQQ-");
     132            1 :         ASSERT(r != NULL, "imap_utf7_decode: unpaired high surrogate must not crash");
     133            1 :         ASSERT(strcmp(r, "\xED\xA0\x80" "A") == 0,
     134              :                "Unpaired high surrogate pass-through mismatch");
     135              :     }
     136              : 
     137              :     /* ── imap_utf7_encode tests ──────────────────────────────────────────── */
     138              : 
     139              :     /* NULL input → NULL */
     140              :     {
     141            1 :         char *r = imap_utf7_encode(NULL);
     142            1 :         ASSERT(r == NULL, "imap_utf7_encode: NULL input returns NULL");
     143              :     }
     144              : 
     145              :     /* Empty string → empty string */
     146              :     {
     147            2 :         RAII_STRING char *r = imap_utf7_encode("");
     148            1 :         ASSERT(r != NULL, "imap_utf7_encode: empty string must not return NULL");
     149            1 :         ASSERT(strcmp(r, "") == 0, "imap_utf7_encode: empty string round-trip");
     150              :     }
     151              : 
     152              :     /* Pure ASCII passthrough (no encoding needed) */
     153              :     {
     154            2 :         RAII_STRING char *r = imap_utf7_encode("INBOX");
     155            1 :         ASSERT(r != NULL, "imap_utf7_encode: ASCII must not return NULL");
     156            1 :         ASSERT(strcmp(r, "INBOX") == 0, "imap_utf7_encode: ASCII passthrough");
     157              :     }
     158              : 
     159              :     /* Literal '&' → "&-" */
     160              :     {
     161            2 :         RAII_STRING char *r = imap_utf7_encode("foo&bar");
     162            1 :         ASSERT(r != NULL, "imap_utf7_encode: '&' must not return NULL");
     163            1 :         ASSERT(strcmp(r, "foo&-bar") == 0, "imap_utf7_encode: literal & escaping");
     164              :     }
     165              : 
     166              :     /* Roundtrip: encode then decode → original UTF-8 string */
     167              :     /* é (U+00E9, 2-byte UTF-8) */
     168              :     {
     169            2 :         RAII_STRING char *encoded = imap_utf7_encode("\xC3\xA9");
     170            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: é must not return NULL");
     171            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     172            1 :         ASSERT(decoded != NULL, "imap_utf7_encode/decode: é roundtrip must not be NULL");
     173            1 :         ASSERT(strcmp(decoded, "\xC3\xA9") == 0, "imap_utf7_encode/decode: é roundtrip");
     174              :     }
     175              : 
     176              :     /* CJK U+4E2D (中, 3-byte UTF-8) roundtrip */
     177              :     {
     178            2 :         RAII_STRING char *encoded = imap_utf7_encode("\xE4\xB8\xAD");
     179            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: CJK must not return NULL");
     180            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     181            1 :         ASSERT(decoded != NULL, "imap_utf7_encode/decode: CJK roundtrip must not be NULL");
     182            1 :         ASSERT(strcmp(decoded, "\xE4\xB8\xAD") == 0, "imap_utf7_encode/decode: CJK roundtrip");
     183              :     }
     184              : 
     185              :     /* Supplementary plane U+10000 (4-byte UTF-8) roundtrip (surrogate pair) */
     186              :     {
     187            2 :         RAII_STRING char *encoded = imap_utf7_encode("\xF0\x90\x80\x80");
     188            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: U+10000 must not return NULL");
     189            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     190            1 :         ASSERT(decoded != NULL, "imap_utf7_encode/decode: U+10000 roundtrip must not be NULL");
     191            1 :         ASSERT(strcmp(decoded, "\xF0\x90\x80\x80") == 0,
     192              :                "imap_utf7_encode/decode: U+10000 surrogate-pair roundtrip");
     193              :     }
     194              : 
     195              :     /* Mixed ASCII and non-ASCII (folder path with accented chars) */
     196              :     {
     197              :         /* "INBOX.étest" — é is U+00E9 */
     198            2 :         RAII_STRING char *encoded = imap_utf7_encode("INBOX.\xC3\xA9test");
     199            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: mixed path must not return NULL");
     200            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     201            1 :         ASSERT(decoded != NULL, "imap_utf7_encode/decode: mixed path must not be NULL");
     202            1 :         ASSERT(strcmp(decoded, "INBOX.\xC3\xA9test") == 0,
     203              :                "imap_utf7_encode/decode: mixed path roundtrip");
     204              :     }
     205              : 
     206              :     /* Multiple non-ASCII chars in a single run */
     207              :     {
     208              :         /* "éü" — two accented chars back to back, one encoded run */
     209            2 :         RAII_STRING char *encoded = imap_utf7_encode("\xC3\xA9\xC3\xBC");
     210            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: two-char run must not return NULL");
     211            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     212            1 :         ASSERT(decoded != NULL, "imap_utf7_encode/decode: two-char run roundtrip must not be NULL");
     213            1 :         ASSERT(strcmp(decoded, "\xC3\xA9\xC3\xBC") == 0,
     214              :                "imap_utf7_encode/decode: two-char run roundtrip");
     215              :     }
     216              : 
     217              :     /* Non-ASCII immediately followed by '&' (edge: run flush then '&' escape) */
     218              :     {
     219              :         /* "é&" */
     220            2 :         RAII_STRING char *encoded = imap_utf7_encode("\xC3\xA9&");
     221            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: non-ASCII+'&' must not return NULL");
     222            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     223            1 :         ASSERT(decoded != NULL,
     224              :                "imap_utf7_encode/decode: non-ASCII+'&' roundtrip must not be NULL");
     225            1 :         ASSERT(strcmp(decoded, "\xC3\xA9&") == 0,
     226              :                "imap_utf7_encode/decode: non-ASCII+'&' roundtrip");
     227              :     }
     228              : 
     229              :     /* Invalid UTF-8 continuation byte at start (0x80) → replaced with U+FFFD */
     230              :     {
     231              :         /* 0x80 is a bare continuation byte — decoder maps it to U+FFFD */
     232            2 :         RAII_STRING char *encoded = imap_utf7_encode("\x80");
     233            1 :         ASSERT(encoded != NULL, "imap_utf7_encode: invalid UTF-8 must not return NULL");
     234              :         /* encoded should be a non-ASCII run; decode it back and compare with U+FFFD */
     235            2 :         RAII_STRING char *decoded = imap_utf7_decode(encoded);
     236            1 :         ASSERT(decoded != NULL,
     237              :                "imap_utf7_encode/decode: invalid UTF-8 decoded must not be NULL");
     238              :         /* U+FFFD in UTF-8 is EF BF BD */
     239            1 :         ASSERT(strcmp(decoded, "\xEF\xBF\xBD") == 0,
     240              :                "imap_utf7_encode: invalid UTF-8 byte encodes as U+FFFD");
     241              :     }
     242              : 
     243              :     /* Known encode value: é → "&AOk-" (same as decode test, verify direction) */
     244              :     {
     245            2 :         RAII_STRING char *r = imap_utf7_encode("\xC3\xA9");
     246            1 :         ASSERT(r != NULL, "imap_utf7_encode: é known value must not be NULL");
     247            1 :         ASSERT(strcmp(r, "&AOk-") == 0, "imap_utf7_encode: é encodes to &AOk-");
     248              :     }
     249              : 
     250              :     /* Known encode value: ASCII 'A' is passed through (not encoded) */
     251              :     {
     252            2 :         RAII_STRING char *r = imap_utf7_encode("A");
     253            1 :         ASSERT(r != NULL, "imap_utf7_encode: 'A' must not be NULL");
     254            1 :         ASSERT(strcmp(r, "A") == 0, "imap_utf7_encode: printable ASCII 'A' passthrough");
     255              :     }
     256              : 
     257              :     /* Printable ASCII boundaries: space (0x20) and tilde (0x7E) pass through */
     258              :     {
     259            2 :         RAII_STRING char *r = imap_utf7_encode(" ~");
     260            1 :         ASSERT(r != NULL, "imap_utf7_encode: space+tilde must not be NULL");
     261            1 :         ASSERT(strcmp(r, " ~") == 0, "imap_utf7_encode: space+tilde passthrough");
     262              :     }
     263              : 
     264              :     /* ── imap_uid_set_expand tests ──────────────────────────────────── */
     265              : 
     266              :     /* NULL input → count 0, NULL array */
     267              :     {
     268            1 :         char (*uids)[17] = NULL;
     269            1 :         int cnt = 0;
     270            1 :         int rc = imap_uid_set_expand(NULL, &uids, &cnt);
     271            1 :         ASSERT(rc == 0,     "uid_set_expand: NULL set rc=0");
     272            1 :         ASSERT(cnt == 0,    "uid_set_expand: NULL set count=0");
     273            1 :         ASSERT(uids == NULL, "uid_set_expand: NULL set array=NULL");
     274              :     }
     275              : 
     276              :     /* Empty string → count 0 */
     277              :     {
     278            1 :         char (*uids)[17] = NULL;
     279            1 :         int cnt = 0;
     280            1 :         int rc = imap_uid_set_expand("", &uids, &cnt);
     281            1 :         ASSERT(rc == 0,  "uid_set_expand: empty set rc=0");
     282            1 :         ASSERT(cnt == 0, "uid_set_expand: empty set count=0");
     283            1 :         free(uids);
     284              :     }
     285              : 
     286              :     /* Single UID */
     287              :     {
     288            1 :         char (*uids)[17] = NULL;
     289            1 :         int cnt = 0;
     290            1 :         int rc = imap_uid_set_expand("5", &uids, &cnt);
     291            1 :         ASSERT(rc == 0,   "uid_set_expand: single UID rc=0");
     292            1 :         ASSERT(cnt == 1,  "uid_set_expand: single UID count=1");
     293            1 :         ASSERT(strcmp(uids[0], "0000000000000005") == 0,
     294              :                "uid_set_expand: single UID value");
     295            1 :         free(uids);
     296              :     }
     297              : 
     298              :     /* Contiguous range 1:3 → 3 entries */
     299              :     {
     300            1 :         char (*uids)[17] = NULL;
     301            1 :         int cnt = 0;
     302            1 :         int rc = imap_uid_set_expand("1:3", &uids, &cnt);
     303            1 :         ASSERT(rc == 0,   "uid_set_expand: range rc=0");
     304            1 :         ASSERT(cnt == 3,  "uid_set_expand: range count=3");
     305            1 :         ASSERT(strcmp(uids[0], "0000000000000001") == 0, "uid_set_expand: range[0]");
     306            1 :         ASSERT(strcmp(uids[2], "0000000000000003") == 0, "uid_set_expand: range[2]");
     307            1 :         free(uids);
     308              :     }
     309              : 
     310              :     /* Multiple comma-separated UIDs */
     311              :     {
     312            1 :         char (*uids)[17] = NULL;
     313            1 :         int cnt = 0;
     314            1 :         int rc = imap_uid_set_expand("1,3,7", &uids, &cnt);
     315            1 :         ASSERT(rc == 0,   "uid_set_expand: multi-uid rc=0");
     316            1 :         ASSERT(cnt == 3,  "uid_set_expand: multi-uid count=3");
     317            1 :         ASSERT(strcmp(uids[1], "0000000000000003") == 0, "uid_set_expand: multi-uid[1]");
     318            1 :         free(uids);
     319              :     }
     320              : 
     321              :     /* Mix of single and range: "2,5:7" → 4 entries */
     322              :     {
     323            1 :         char (*uids)[17] = NULL;
     324            1 :         int cnt = 0;
     325            1 :         int rc = imap_uid_set_expand("2,5:7", &uids, &cnt);
     326            1 :         ASSERT(rc == 0,  "uid_set_expand: mixed rc=0");
     327            1 :         ASSERT(cnt == 4, "uid_set_expand: mixed count=4");
     328            1 :         free(uids);
     329              :     }
     330              : 
     331              :     /* Large range that triggers realloc (initial cap=32) */
     332              :     {
     333            1 :         char (*uids)[17] = NULL;
     334            1 :         int cnt = 0;
     335            1 :         int rc = imap_uid_set_expand("1:40", &uids, &cnt);
     336            1 :         ASSERT(rc == 0,   "uid_set_expand: large range rc=0");
     337            1 :         ASSERT(cnt == 40, "uid_set_expand: large range count=40");
     338            1 :         free(uids);
     339              :     }
     340              : 
     341              :     /* Non-numeric input → parser stops immediately → 0 UIDs */
     342              :     {
     343            1 :         char (*uids)[17] = NULL;
     344            1 :         int cnt = 0;
     345            1 :         int rc = imap_uid_set_expand("abc", &uids, &cnt);
     346            1 :         ASSERT(rc == 0,  "uid_set_expand: non-numeric rc=0");
     347            1 :         ASSERT(cnt == 0, "uid_set_expand: non-numeric count=0");
     348            1 :         free(uids);
     349              :     }
     350              : 
     351              :     /* Malformed range "5:" → treats hi=lo */
     352              :     {
     353            1 :         char (*uids)[17] = NULL;
     354            1 :         int cnt = 0;
     355            1 :         int rc = imap_uid_set_expand("5:", &uids, &cnt);
     356            1 :         ASSERT(rc == 0,  "uid_set_expand: malformed range rc=0");
     357            1 :         ASSERT(cnt == 1, "uid_set_expand: malformed range count=1");
     358            1 :         ASSERT(strcmp(uids[0], "0000000000000005") == 0,
     359              :                "uid_set_expand: malformed range value");
     360            1 :         free(uids);
     361              :     }
     362              : }
        

Generated by: LCOV version 2.0-1