LCOV - code coverage report
Current view: top level - src - main_ro.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 95.8 % 260 249
Test Date: 2026-05-07 15:53:08 Functions: 100.0 % 11 11

            Line data    Source code
       1              : /**
       2              :  * @file main_ro.c
       3              :  * @brief Entry point for email-cli-ro — read-only, non-interactive CLI.
       4              :  *
       5              :  * email-cli-ro is a strict subset of email-cli:
       6              :  *   - All output is batch/non-interactive (no TUI, no pager prompts).
       7              :  *   - No write operations (no SMTP, no IMAP flag changes, no cron writes).
       8              :  *   - No setup wizard — configuration must already exist.
       9              :  *   - Safe to give to AI agents: there is no code path that sends email.
      10              :  *
      11              :  * Supported commands: list, show, folders, attachments, save-attachment, help.
      12              :  */
      13              : 
      14              : #include <stdio.h>
      15              : #include <stdlib.h>
      16              : #include <string.h>
      17              : #include <ctype.h>
      18              : #include <locale.h>
      19              : #include "config_store.h"
      20              : #include "email_service.h"
      21              : #include "platform/terminal.h"
      22              : #include "platform/path.h"
      23              : #include "raii.h"
      24              : #include "logger.h"
      25              : #include "local_store.h"
      26              : #include "fs_util.h"
      27              : #include "config.h"
      28              : 
      29              : #define BATCH_DEFAULT_LIMIT 100
      30              : 
      31              : /* ── Help pages ──────────────────────────────────────────────────────── */
      32              : 
      33            3 : static void help_general(void) {
      34            3 :     printf(
      35              :         "Usage: email-cli-ro [<account>] <command> [options]\n"
      36              :         "\n"
      37              :         "Read-only email CLI. All output is non-interactive (batch mode).\n"
      38              :         "Safe for use by AI agents: no send or write operations are available.\n"
      39              :         "\n"
      40              :         "  <account>  Email address of the account to use (e.g. user@example.com).\n"
      41              :         "             Required when multiple accounts are configured.\n"
      42              :         "             Alternative: --account <email>.\n"
      43              :         "\n"
      44              :         "Reading:\n"
      45              :         "  list                       List messages in the configured mailbox\n"
      46              :         "  show <uid>                 Display the full content of a message\n"
      47              :         "  list-folders               List available IMAP folders / Gmail labels\n"
      48              :         "  list-labels                List all labels (Gmail) or folders (IMAP)\n"
      49              :         "  list-attachments <uid>     List attachments in a message\n"
      50              :         "  save-attachment <uid> <filename> [dir]\n"
      51              :         "                             Save an attachment to disk\n"
      52              :         "\n"
      53              :         "Account management:\n"
      54              :         "  list-accounts              List all configured accounts\n"
      55              :         "\n"
      56              :         "Help:\n"
      57              :         "  help [command]             Show this help, or detailed help for a command\n"
      58              :         "\n"
      59              :         "Run 'email-cli-ro help <command>' for more information.\n"
      60              :         "For write operations (send, mark-read, add-label, etc.) use 'email-cli'.\n"
      61              :     );
      62            3 : }
      63              : 
      64            1 : static void help_list(void) {
      65            1 :     printf(
      66              :         "Usage: email-cli-ro [<account>] list [options]\n"
      67              :         "\n"
      68              :         "Lists messages in the configured mailbox.\n"
      69              :         "Shows unread (UNSEEN) messages by default; use --all for everything.\n"
      70              :         "\n"
      71              :         "Options:\n"
      72              :         "  --all                    Show all messages (not just unread).\n"
      73              :         "  --folder <name>          IMAP: use <name> instead of the configured folder.\n"
      74              :         "  --label  <id-or-name>    Gmail: filter by label (alias for --folder).\n"
      75              :         "  --limit <n>              Show at most <n> messages (default: %d).\n"
      76              :         "  --offset <n>             Start listing from the <n>-th message (1-based).\n"
      77              :         "\n"
      78              :         "Gmail notes:\n"
      79              :         "  Use 'email-cli-ro list-labels' to see available labels and their IDs.\n"
      80              :         "  Predefined labels: INBOX, SENT, DRAFT, SPAM, TRASH, STARRED, IMPORTANT.\n"
      81              :         "\n"
      82              :         "Examples (IMAP):\n"
      83              :         "  email-cli-ro list\n"
      84              :         "  email-cli-ro list --folder INBOX.Sent --limit 50\n"
      85              :         "\n"
      86              :         "Examples (Gmail):\n"
      87              :         "  email-cli-ro list\n"
      88              :         "  email-cli-ro list --label INBOX\n"
      89              :         "  email-cli-ro list --label SENT\n"
      90              :         "  email-cli-ro user@gmail.com list --label Label_42\n",
      91              :         BATCH_DEFAULT_LIMIT
      92              :     );
      93            1 : }
      94              : 
      95            3 : static void help_show(void) {
      96            3 :     printf(
      97              :         "Usage: email-cli-ro show <uid>\n"
      98              :         "\n"
      99              :         "Displays the full content of the message identified by <uid>.\n"
     100              :         "\n"
     101              :         "  <uid>   Numeric IMAP UID shown by 'email-cli-ro list'\n"
     102              :         "\n"
     103              :         "The message is fetched from the server on first access and stored\n"
     104              :         "locally at ~/.local/share/email-cli/messages/<folder>/<uid>.eml.\n"
     105              :         "Subsequent reads are served from the local store.\n"
     106              :     );
     107            3 : }
     108              : 
     109            1 : static void help_folders(void) {
     110            1 :     printf(
     111              :         "Usage: email-cli-ro list-folders [options]\n"
     112              :         "\n"
     113              :         "Lists all available IMAP folders on the server.\n"
     114              :         "\n"
     115              :         "Options:\n"
     116              :         "  --tree    Render the folder hierarchy as a tree.\n"
     117              :         "\n"
     118              :         "Examples:\n"
     119              :         "  email-cli-ro list-folders\n"
     120              :         "  email-cli-ro list-folders --tree\n"
     121              :     );
     122            1 : }
     123              : 
     124            3 : static void help_attachments(void) {
     125            3 :     printf(
     126              :         "Usage: email-cli-ro list-attachments <uid>\n"
     127              :         "\n"
     128              :         "Lists all attachments in the message identified by <uid>.\n"
     129              :         "Prints one line per attachment: filename and decoded size.\n"
     130              :         "\n"
     131              :         "  <uid>   Numeric IMAP UID shown by 'email-cli-ro list'\n"
     132              :         "\n"
     133              :         "Examples:\n"
     134              :         "  email-cli-ro list-attachments 42\n"
     135              :     );
     136            3 : }
     137              : 
     138            3 : static void help_save_attachment(void) {
     139            3 :     printf(
     140              :         "Usage: email-cli-ro save-attachment <uid> <filename> [dir]\n"
     141              :         "\n"
     142              :         "Saves the named attachment from message <uid> to disk.\n"
     143              :         "\n"
     144              :         "  <uid>       Numeric IMAP UID shown by 'email-cli-ro list'\n"
     145              :         "  <filename>  Exact attachment filename shown by 'email-cli-ro list-attachments'\n"
     146              :         "  [dir]       Destination directory (default: ~/Downloads or ~)\n"
     147              :         "\n"
     148              :         "Examples:\n"
     149              :         "  email-cli-ro save-attachment 42 report.pdf\n"
     150              :         "  email-cli-ro save-attachment 42 report.pdf /tmp\n"
     151              :     );
     152            3 : }
     153              : 
     154            2 : static void help_list_labels(void) {
     155            2 :     printf(
     156              :         "Usage: email-cli-ro list-labels\n"
     157              :         "\n"
     158              :         "List all available labels (Gmail) or folders (IMAP).\n"
     159              :         "For Gmail, shows both the display name and the label ID.\n"
     160              :         "\n"
     161              :         "Examples:\n"
     162              :         "  email-cli-ro list-labels\n"
     163              :     );
     164            2 : }
     165              : 
     166            2 : static void help_list_accounts(void) {
     167            2 :     printf(
     168              :         "Usage: email-cli-ro list-accounts\n"
     169              :         "\n"
     170              :         "List all configured accounts with their type and server.\n"
     171              :         "\n"
     172              :         "Examples:\n"
     173              :         "  email-cli-ro list-accounts\n"
     174              :     );
     175            2 : }
     176              : 
     177              : /* ── Helpers ─────────────────────────────────────────────────────────── */
     178              : 
     179           21 : static int parse_uid(const char *s, char uid_out[17]) {
     180           21 :     if (!s || !*s) return -1;
     181              :     /* Accept 16-character hex strings directly (Gmail message IDs shown by list). */
     182           21 :     if (strlen(s) == 16) {
     183           12 :         int all_hex = 1;
     184          204 :         for (int i = 0; i < 16; i++) {
     185          192 :             if (!isxdigit((unsigned char)s[i])) { all_hex = 0; break; }
     186              :         }
     187           12 :         if (all_hex) {
     188           12 :             memcpy(uid_out, s, 16);
     189           12 :             uid_out[16] = '\0';
     190           12 :             return 0;
     191              :         }
     192              :     }
     193              :     /* Accept positive decimal integers (IMAP UIDs). */
     194              :     char *end;
     195            9 :     unsigned long v = strtoul(s, &end, 10);
     196            9 :     if (*end != '\0' || v == 0 || v > 4294967295UL) return -1;
     197            6 :     snprintf(uid_out, 17, "%016lu", v);
     198            6 :     return 0;
     199              : }
     200              : 
     201            2 : static void unknown_option(const char *cmd, const char *opt) {
     202            2 :     fprintf(stderr, "Unknown option '%s' for '%s'.\n", opt, cmd);
     203            2 :     fprintf(stderr, "Run 'email-cli-ro help %s' for usage.\n", cmd);
     204            2 : }
     205              : 
     206              : /* ── Entry point ─────────────────────────────────────────────────────── */
     207              : 
     208              : #ifndef EMAIL_CLI_VERSION
     209              : #define EMAIL_CLI_VERSION "unknown"
     210              : #endif
     211              : 
     212           76 : int main(int argc, char *argv[]) {
     213           76 :     setlocale(LC_ALL, "");
     214              : 
     215           76 :     if (argc >= 2 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)) {
     216            1 :         printf("email-cli-ro %s\n", EMAIL_CLI_VERSION);
     217            1 :         return EXIT_SUCCESS;
     218              :     }
     219              : 
     220              :     /* 1. Determine cache directory for logs */
     221           75 :     const char *cache_base = platform_cache_dir();
     222           75 :     if (!cache_base) {
     223            0 :         fprintf(stderr, "Fatal: Could not determine cache directory.\n");
     224            0 :         return EXIT_FAILURE;
     225              :     }
     226              : 
     227           75 :     RAII_STRING char *log_dir  = NULL;
     228           75 :     RAII_STRING char *log_file = NULL;
     229          150 :     if (asprintf(&log_dir,  "%s/email-cli/logs", cache_base) == -1 ||
     230           75 :         asprintf(&log_file, "%s/session.log", log_dir)        == -1) {
     231            0 :         fprintf(stderr, "Fatal: Memory allocation failed.\n");
     232            0 :         return EXIT_FAILURE;
     233              :     }
     234              : 
     235              :     /* 2. Account + command detection (mirrors main.c logic).
     236              :      *    Supported forms:
     237              :      *      email-cli-ro [<account>] <command> [options]
     238              :      *      email-cli-ro --account <email> <command> [options]  */
     239           75 :     const char *account_arg = NULL;
     240           75 :     int account_arg_idx = -1;
     241              : 
     242              :     /* Pass A: scan for --account flag anywhere in args */
     243          279 :     for (int i = 1; i < argc; i++) {
     244          204 :         if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
     245            2 :             account_arg = argv[++i]; continue;
     246              :         }
     247              :     }
     248              : 
     249              :     /* Pass B: if no --account flag, check whether first positional arg is an email */
     250           75 :     if (!account_arg) {
     251           74 :         for (int i = 1; i < argc; i++) {
     252           72 :             if (argv[i][0] == '-') {
     253            1 :                 if (strcmp(argv[i], "--account") == 0) i++;
     254            1 :                 continue;
     255              :             }
     256           71 :             if (strchr(argv[i], '@')) {
     257           24 :                 account_arg = argv[i];
     258           24 :                 account_arg_idx = i;
     259              :             }
     260           71 :             break;
     261              :         }
     262              :     }
     263              : 
     264              :     /* Command: first non-flag, non-account arg */
     265           75 :     const char *cmd = NULL;
     266           75 :     int cmd_idx = 0;
     267          118 :     for (int i = 1; i < argc; i++) {
     268          116 :         if (strcmp(argv[i], "--help") == 0) continue;
     269          115 :         if (strcmp(argv[i], "--account") == 0) { i++; continue; }
     270          113 :         if (strcmp(argv[i], "--batch") == 0) continue; /* no-op: always batch */
     271           97 :         if (i == account_arg_idx) continue;
     272           73 :         cmd = argv[i]; cmd_idx = i; break;
     273              :     }
     274              : 
     275              :     /* --help anywhere: treat as "help <cmd>" */
     276          273 :     for (int i = 1; i < argc; i++) {
     277          206 :         if (strcmp(argv[i], "--help") == 0) {
     278            8 :             if (cmd && strcmp(cmd, "--help") != 0) {
     279            7 :                 if (strcmp(cmd, "list")            == 0) { help_list();            return EXIT_SUCCESS; }
     280            6 :                 if (strcmp(cmd, "show")            == 0) { help_show();            return EXIT_SUCCESS; }
     281            5 :                 if (strcmp(cmd, "list-folders")         == 0) { help_folders();         return EXIT_SUCCESS; }
     282            4 :                 if (strcmp(cmd, "list-attachments")     == 0) { help_attachments();     return EXIT_SUCCESS; }
     283            3 :                 if (strcmp(cmd, "save-attachment") == 0) { help_save_attachment(); return EXIT_SUCCESS; }
     284            2 :                 if (strcmp(cmd, "list-labels")     == 0) { help_list_labels();     return EXIT_SUCCESS; }
     285            1 :                 if (strcmp(cmd, "list-accounts")   == 0) { help_list_accounts();   return EXIT_SUCCESS; }
     286              :             }
     287            1 :             help_general();
     288            1 :             return EXIT_SUCCESS;
     289              :         }
     290              :     }
     291              : 
     292           67 :     if (cmd && strcmp(cmd, "help") == 0) {
     293            7 :         const char *topic = NULL;
     294            7 :         for (int i = cmd_idx + 1; i < argc; i++) { topic = argv[i]; break; }
     295            7 :         if (topic) {
     296            6 :             if (strcmp(topic, "list")            == 0) { help_list();            return EXIT_SUCCESS; }
     297            6 :             if (strcmp(topic, "show")            == 0) { help_show();            return EXIT_SUCCESS; }
     298            5 :             if (strcmp(topic, "list-folders")         == 0) { help_folders();         return EXIT_SUCCESS; }
     299            5 :             if (strcmp(topic, "list-attachments")     == 0) { help_attachments();     return EXIT_SUCCESS; }
     300            4 :             if (strcmp(topic, "save-attachment") == 0) { help_save_attachment(); return EXIT_SUCCESS; }
     301            3 :             if (strcmp(topic, "list-labels")     == 0) { help_list_labels();     return EXIT_SUCCESS; }
     302            2 :             if (strcmp(topic, "list-accounts")   == 0) { help_list_accounts();   return EXIT_SUCCESS; }
     303            1 :             fprintf(stderr, "Unknown command '%s'.\n", topic);
     304            1 :             fprintf(stderr, "Run 'email-cli-ro help' for available commands.\n");
     305            1 :             return EXIT_FAILURE;
     306              :         }
     307            1 :         help_general();
     308            1 :         return EXIT_SUCCESS;
     309              :     }
     310              : 
     311           60 :     if (!cmd) {
     312            1 :         help_general();
     313            1 :         return EXIT_SUCCESS;
     314              :     }
     315              : 
     316              :     /* 3. Initialize logger */
     317           59 :     if (fs_mkdir_p(log_dir, 0700) != 0)
     318            0 :         fprintf(stderr, "Warning: Could not create log directory %s\n", log_dir);
     319           59 :     if (logger_init(log_file, LOG_DEBUG) != 0)
     320            0 :         fprintf(stderr, "Warning: Logging system failed to initialize.\n");
     321           59 :     logger_log(LOG_INFO, "--- email-cli-ro starting (cmd: %s) ---", cmd);
     322              : 
     323              :     /* 4. Load configuration — no wizard: must already exist */
     324           59 :     Config *cfg = NULL;
     325           59 :     if (strcmp(cmd, "list-accounts") != 0) {
     326           56 :         if (account_arg) {
     327           26 :             cfg = config_load_account(account_arg);
     328           26 :             if (!cfg) {
     329            1 :                 fprintf(stderr,
     330              :                         "Error: Account '%s' not found.\n"
     331              :                         "Run 'email-cli-ro list-accounts' to list configured accounts.\n",
     332              :                         account_arg);
     333            1 :                 logger_close();
     334            1 :                 return EXIT_FAILURE;
     335              :             }
     336              :         } else {
     337           30 :             int count = 0;
     338           30 :             AccountEntry *list = config_list_accounts(&count);
     339           30 :             if (count == 1) {
     340           29 :                 cfg = list[0].cfg; list[0].cfg = NULL;
     341           29 :                 config_free_account_list(list, count);
     342            1 :             } else if (count > 1) {
     343            1 :                 fprintf(stderr, "Multiple accounts configured. Specify which to use:\n");
     344            3 :                 for (int i = 0; i < count; i++)
     345            4 :                     fprintf(stderr, "  email-cli-ro %s %s\n",
     346            2 :                             list[i].name ? list[i].name : "?", cmd ? cmd : "");
     347            1 :                 fprintf(stderr, "Run 'email-cli-ro list-accounts' for the full list.\n");
     348            1 :                 config_free_account_list(list, count);
     349            1 :                 logger_close();
     350            1 :                 return EXIT_FAILURE;
     351              :             } else {
     352            0 :                 config_free_account_list(list, count);
     353            0 :                 fprintf(stderr,
     354              :                         "Error: No configuration found.\n"
     355              :                         "Run 'email-cli' once to complete the setup wizard.\n");
     356            0 :                 logger_close();
     357            0 :                 return EXIT_FAILURE;
     358              :             }
     359              :         }
     360              :     }
     361              : 
     362              :     /* 5. Initialize local store */
     363           57 :     if (cfg && local_store_init(cfg->host, cfg->user) != 0)
     364            0 :         logger_log(LOG_WARN, "Failed to initialize local store for %s", cfg->host);
     365              : 
     366              :     /* 6. Dispatch — batch mode only (pager = 0) */
     367           57 :     int result = -1;
     368              : 
     369           57 :     if (strcmp(cmd, "list") == 0) {
     370           21 :         EmailListOpts opts = {0, NULL, BATCH_DEFAULT_LIMIT, 0, 0, {0}, {0}};
     371           21 :         int ok = 1;
     372           47 :         for (int i = cmd_idx + 1; i < argc && ok; i++) {
     373           26 :             if (strcmp(argv[i], "--batch") == 0) {
     374              :                 /* accepted as no-op: email-cli-ro is always batch mode */
     375           22 :             } else if (strcmp(argv[i], "--all") == 0) {
     376            6 :                 opts.all = 1;
     377           16 :             } else if (strcmp(argv[i], "--folder") == 0 ||
     378           14 :                        strcmp(argv[i], "--label")  == 0) {
     379            3 :                 if (i + 1 >= argc) {
     380            1 :                     fprintf(stderr, "Error: %s requires a name.\n", argv[i]);
     381            1 :                     ok = 0;
     382              :                 } else {
     383            2 :                     opts.folder = argv[++i];
     384              :                 }
     385           13 :             } else if (strcmp(argv[i], "--limit") == 0) {
     386            9 :                 if (i + 1 >= argc) {
     387            1 :                     fprintf(stderr, "Error: --limit requires a number.\n");
     388            1 :                     ok = 0;
     389              :                 } else {
     390              :                     char *end;
     391            8 :                     long v = strtol(argv[++i], &end, 10);
     392            8 :                     if (*end != '\0' || v <= 0) {
     393            1 :                         fprintf(stderr, "Error: --limit must be a positive integer.\n");
     394            1 :                         ok = 0;
     395              :                     } else {
     396            7 :                         opts.limit = (int)v;
     397              :                     }
     398              :                 }
     399            4 :             } else if (strcmp(argv[i], "--offset") == 0) {
     400            3 :                 if (i + 1 >= argc) {
     401            1 :                     fprintf(stderr, "Error: --offset requires a number.\n");
     402            1 :                     ok = 0;
     403              :                 } else {
     404              :                     char *end;
     405            2 :                     long v = strtol(argv[++i], &end, 10);
     406            2 :                     if (*end != '\0' || v < 1) {
     407            1 :                         fprintf(stderr, "Error: --offset must be a positive integer.\n");
     408            1 :                         ok = 0;
     409              :                     } else {
     410            1 :                         opts.offset = (int)v;
     411              :                     }
     412              :                 }
     413              :             } else {
     414            1 :                 unknown_option("list", argv[i]);
     415            1 :                 ok = 0;
     416              :             }
     417              :         }
     418           21 :         if (ok) result = email_service_list(cfg, &opts);
     419              : 
     420           36 :     } else if (strcmp(cmd, "show") == 0) {
     421           11 :         const char *uid_str = NULL;
     422           11 :         for (int i = cmd_idx + 1; i < argc; i++) {
     423           10 :             if (strcmp(argv[i], "--batch") == 0) continue; /* no-op */
     424           10 :             uid_str = argv[i]; break;
     425              :         }
     426           11 :         if (!uid_str) {
     427            1 :             fprintf(stderr, "Error: 'show' requires a UID argument.\n");
     428            1 :             help_show();
     429              :         } else {
     430              :             char uid[17];
     431           10 :             if (parse_uid(uid_str, uid) != 0)
     432            1 :                 fprintf(stderr,
     433              :                         "Error: UID must be a positive integer (got '%s').\n",
     434              :                         uid_str);
     435              :             else
     436            9 :                 result = email_service_read(cfg, uid, 0, BATCH_DEFAULT_LIMIT);
     437              :         }
     438              : 
     439           25 :     } else if (strcmp(cmd, "list-folders") == 0) {
     440            4 :         int tree = 0, ok = 1;
     441            8 :         for (int i = cmd_idx + 1; i < argc && ok; i++) {
     442            4 :             if (strcmp(argv[i], "--batch") == 0) { /* no-op */
     443            2 :             } else if (strcmp(argv[i], "--tree") == 0)
     444            1 :                 tree = 1;
     445            1 :             else { unknown_option("list-folders", argv[i]); ok = 0; }
     446              :         }
     447            4 :         if (ok) result = email_service_list_folders(cfg, tree);
     448              : 
     449           21 :     } else if (strcmp(cmd, "list-attachments") == 0) {
     450            7 :         const char *uid_str = NULL;
     451            7 :         for (int i = cmd_idx + 1; i < argc; i++) {
     452            6 :             if (strcmp(argv[i], "--batch") == 0) continue;
     453            6 :             uid_str = argv[i]; break;
     454              :         }
     455            7 :         if (!uid_str) {
     456            1 :             fprintf(stderr, "Error: 'list-attachments' requires a UID argument.\n");
     457            1 :             help_attachments();
     458              :         } else {
     459              :             char uid[17];
     460            6 :             if (parse_uid(uid_str, uid) != 0)
     461            1 :                 fprintf(stderr,
     462              :                         "Error: UID must be a positive integer (got '%s').\n",
     463              :                         uid_str);
     464              :             else
     465            5 :                 result = email_service_list_attachments(cfg, uid);
     466              :         }
     467              : 
     468           14 :     } else if (strcmp(cmd, "save-attachment") == 0) {
     469            6 :         const char *uid_str  = NULL;
     470            6 :         const char *filename = NULL;
     471            6 :         const char *outdir   = NULL;
     472            6 :         int argn = 0;
     473           20 :         for (int i = cmd_idx + 1; i < argc; i++) {
     474           14 :             if (strcmp(argv[i], "--batch") == 0) continue;
     475           14 :             if (argn == 0)      { uid_str  = argv[i]; argn++; }
     476            9 :             else if (argn == 1) { filename = argv[i]; argn++; }
     477            4 :             else if (argn == 2) { outdir   = argv[i]; argn++; }
     478              :         }
     479            6 :         if (!uid_str || !filename) {
     480            1 :             fprintf(stderr,
     481              :                     "Error: 'save-attachment' requires a UID and a filename.\n");
     482            1 :             help_save_attachment();
     483              :         } else {
     484              :             char uid[17];
     485            5 :             if (parse_uid(uid_str, uid) != 0)
     486            1 :                 fprintf(stderr,
     487              :                         "Error: UID must be a positive integer (got '%s').\n",
     488              :                         uid_str);
     489              :             else
     490            4 :                 result = email_service_save_attachment(cfg, uid, filename, outdir);
     491              :         }
     492              : 
     493            8 :     } else if (strcmp(cmd, "list-labels") == 0) {
     494            3 :         result = email_service_list_labels(cfg);
     495              : 
     496            5 :     } else if (strcmp(cmd, "list-accounts") == 0) {
     497            3 :         int count = 0;
     498            3 :         AccountEntry *accs = config_list_accounts(&count);
     499            3 :         if (count == 0) {
     500            1 :             printf("No accounts configured.\n");
     501            1 :             result = 0;
     502              :         } else {
     503            2 :             printf("%-40s  %-8s  %s\n", "Account", "Type", "Server");
     504            2 :             printf("%-40s  %-8s  %s\n",
     505              :                    "----------------------------------------",
     506              :                    "--------",
     507              :                    "----------------------------");
     508            5 :             for (int i = 0; i < count; i++) {
     509            3 :                 const char *type   = (accs[i].cfg && accs[i].cfg->gmail_mode) ? "Gmail" : "IMAP";
     510            3 :                 const char *server = accs[i].cfg ? (accs[i].cfg->host ? accs[i].cfg->host : "-") : "-";
     511            3 :                 printf("%-40s  %-8s  %s\n",
     512            3 :                        accs[i].name ? accs[i].name : "?",
     513              :                        type, server);
     514              :             }
     515            2 :             config_free_account_list(accs, count);
     516            2 :             result = 0;
     517              :         }
     518              : 
     519              :     } else {
     520              :         /* Check if the command is a write-only command blocked in ro mode */
     521              :         static const char *ro_blocked[] = {
     522              :             "mark-read", "mark-unread", "mark-starred", "remove-starred",
     523              :             "add-label", "remove-label", "create-label", "delete-label",
     524              :             "create-folder", "delete-folder",
     525              :             "mark-junk", "mark-notjunk",
     526              :             "add-account", "remove-account", NULL
     527              :         };
     528            2 :         int blocked = 0;
     529           16 :         for (int i = 0; ro_blocked[i]; i++) {
     530           15 :             if (strcmp(cmd, ro_blocked[i]) == 0) {
     531            1 :                 fprintf(stderr, "Error: '%s' is not available in read-only mode (email-cli-ro).\n", cmd);
     532            1 :                 fprintf(stderr, "Use 'email-cli' for write operations.\n");
     533            1 :                 config_free(cfg);
     534            1 :                 logger_log(LOG_INFO, "--- email-cli-ro session finished ---");
     535            1 :                 logger_close();
     536            1 :                 return EXIT_FAILURE;
     537              :             }
     538              :         }
     539            1 :         if (!blocked) {
     540            1 :             fprintf(stderr, "Unknown command '%s'.\n", cmd);
     541            1 :             fprintf(stderr, "Run 'email-cli-ro help' for available commands.\n");
     542              :         }
     543              :     }
     544              : 
     545              :     /* 7. Cleanup */
     546           56 :     config_free(cfg);
     547           56 :     logger_log(LOG_INFO, "--- email-cli-ro session finished ---");
     548           56 :     logger_close();
     549              : 
     550           56 :     if (result >= 0)
     551           41 :         return EXIT_SUCCESS;
     552           15 :     fprintf(stderr, "\nFailed. Check logs in %s\n", log_file);
     553           15 :     return EXIT_FAILURE;
     554              : }
        

Generated by: LCOV version 2.0-1