LCOV - code coverage report
Current view: top level - libemail/src/infrastructure - setup_wizard.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 24.1 % 158 38
Test Date: 2026-04-15 21:12:52 Functions: 50.0 % 6 3

            Line data    Source code
       1              : #include "setup_wizard.h"
       2              : #include "platform/terminal.h"
       3              : #include <stdio.h>
       4              : #include <stdlib.h>
       5              : #include <string.h>
       6              : #include <unistd.h>
       7              : 
       8           20 : static char* get_input(const char *prompt, int hide, FILE *stream) {
       9           20 :     int is_tty = isatty(fileno(stream));
      10              : 
      11           20 :     if (hide && stream == stdin) {
      12              :         /* Delegate password reading (with echo suppression) to the platform layer.
      13              :          * terminal_read_password() reads from stdin and handles TTY detection. */
      14            1 :         char buf[512];
      15            1 :         int n = terminal_read_password(prompt, buf, sizeof(buf));
      16            1 :         if (n < 0) return NULL;
      17            1 :         return strdup(buf);
      18              :     }
      19              : 
      20           19 :     if (stream == stdin && is_tty) {
      21            0 :         printf("%s: ", prompt);
      22            0 :         fflush(stdout);
      23              :     }
      24              : 
      25           19 :     char *line = NULL;
      26           19 :     size_t len = 0;
      27           19 :     if (getline(&line, &len, stream) == -1) {
      28            5 :         free(line);
      29            5 :         return NULL;
      30              :     }
      31              : 
      32              :     /* Remove newline */
      33           14 :     line[strcspn(line, "\r\n")] = 0;
      34           14 :     return line;
      35              : }
      36              : 
      37              : /**
      38              :  * @brief Internal wizard implementation that can take any input stream.
      39              :  */
      40            5 : Config* setup_wizard_run_internal(FILE *stream) {
      41            5 :     int is_tty = isatty(fileno(stream));
      42            5 :     if (stream == stdin && is_tty) {
      43            0 :         printf("\n--- email-cli Configuration Wizard ---\n");
      44            0 :         printf("Please enter your email server details.\n\n");
      45              :     }
      46              : 
      47            5 :     Config *cfg = calloc(1, sizeof(Config));
      48            5 :     if (!cfg) return NULL;
      49              : 
      50            5 :     cfg->host = get_input("IMAP Host — include protocol (e.g., imaps://imap.example.com)", 0, stream);
      51            5 :     if (!cfg->host) { config_free(cfg); return NULL; }
      52              : 
      53            5 :     cfg->user = get_input("Email Username", 0, stream);
      54            5 :     if (!cfg->user) { config_free(cfg); return NULL; }
      55              : 
      56            4 :     cfg->pass = get_input("Email Password", 1, stream);
      57            4 :     if (!cfg->pass) { config_free(cfg); return NULL; }
      58              : 
      59            3 :     cfg->folder = get_input("Default Folder [INBOX]", 0, stream);
      60            3 :     if (!cfg->folder || strlen(cfg->folder) == 0) {
      61            1 :         if (cfg->folder) free(cfg->folder);
      62            1 :         cfg->folder = strdup("INBOX");
      63              :     }
      64              : 
      65              :     /* SMTP configuration (optional) */
      66            3 :     if (stream == stdin && is_tty)
      67            0 :         printf("\n--- SMTP (outgoing mail) — press Enter to skip ---\n");
      68              : 
      69            3 :     char *smtp_host = get_input("SMTP Host (e.g. smtp://smtp.example.com) [Enter=skip]", 0, stream);
      70            3 :     if (smtp_host && smtp_host[0] != '\0') {
      71            0 :         cfg->smtp_host = smtp_host;
      72              : 
      73            0 :         char *port_str = get_input("SMTP Port [587]", 0, stream);
      74            0 :         if (port_str && port_str[0] != '\0')
      75            0 :             cfg->smtp_port = atoi(port_str);
      76              :         else
      77            0 :             cfg->smtp_port = 587;
      78            0 :         free(port_str);
      79              : 
      80            0 :         char *su = get_input("SMTP Username [Enter=same as IMAP]", 0, stream);
      81            0 :         if (su && su[0] != '\0')
      82            0 :             cfg->smtp_user = su;
      83              :         else
      84            0 :             free(su);
      85              : 
      86            0 :         char *sp = get_input("SMTP Password [Enter=same as IMAP]", 1, stream);
      87            0 :         if (sp && sp[0] != '\0')
      88            0 :             cfg->smtp_pass = sp;
      89              :         else
      90            0 :             free(sp);
      91              :     } else {
      92            3 :         free(smtp_host);
      93              :     }
      94              : 
      95            3 :     if (stream == stdin && is_tty)
      96            0 :         printf("\nConfiguration collected. Checking connection...\n");
      97              : 
      98            3 :     return cfg;
      99              : }
     100              : 
     101            1 : Config* setup_wizard_run(void) {
     102            1 :     return setup_wizard_run_internal(stdin);
     103              : }
     104              : 
     105              : /* ── SMTP sub-wizard ─────────────────────────────────────────────────── */
     106              : 
     107              : /**
     108              :  * Derive a plausible default SMTP URL from the IMAP URL in cfg->host.
     109              :  * Writes into buf (size bytes).  buf[0] == 0 if derivation is not possible.
     110              :  */
     111            0 : static void derive_smtp_url(const Config *cfg, char *buf, size_t size) {
     112            0 :     buf[0] = '\0';
     113            0 :     if (!cfg->host) return;
     114            0 :     if (strncmp(cfg->host, "imaps://", 8) == 0)
     115            0 :         snprintf(buf, size, "smtps://%s", cfg->host + 8);
     116              :     /* imap:// is no longer accepted; no derivation for it */
     117              : }
     118              : 
     119            0 : int setup_wizard_smtp(Config *cfg) {
     120            0 :     printf("\n--- SMTP (outgoing mail) configuration ---\n");
     121            0 :     printf("Press Enter on any field to keep the shown default.\n\n");
     122              : 
     123              :     /* ── SMTP Host ────────────────────────────────────────────────── */
     124            0 :     char derived[512];
     125            0 :     derive_smtp_url(cfg, derived, sizeof(derived));
     126              : 
     127            0 :     char host_prompt[1024];
     128            0 :     if (cfg->smtp_host)
     129            0 :         snprintf(host_prompt, sizeof(host_prompt),
     130              :                  "SMTP Host [current: %s]", cfg->smtp_host);
     131            0 :     else if (derived[0])
     132            0 :         snprintf(host_prompt, sizeof(host_prompt),
     133              :                  "SMTP Host [Enter = %s]", derived);
     134              :     else
     135            0 :         snprintf(host_prompt, sizeof(host_prompt),
     136              :                  "SMTP Host (e.g. smtps://smtp.example.com)");
     137              : 
     138            0 :     char *host = get_input(host_prompt, 0, stdin);
     139            0 :     if (!host) return -1;   /* EOF / Ctrl-D → abort */
     140            0 :     if (host[0]) {
     141            0 :         free(cfg->smtp_host);
     142            0 :         cfg->smtp_host = host;
     143              :     } else {
     144            0 :         free(host);
     145            0 :         if (!cfg->smtp_host && derived[0])
     146            0 :             cfg->smtp_host = strdup(derived);
     147              :         /* else keep whatever was there */
     148              :     }
     149              : 
     150              :     /* ── SMTP Port ────────────────────────────────────────────────── */
     151            0 :     int cur_port = cfg->smtp_port ? cfg->smtp_port : 587;
     152            0 :     char port_prompt[64];
     153            0 :     snprintf(port_prompt, sizeof(port_prompt), "SMTP Port [%d]", cur_port);
     154            0 :     char *port_str = get_input(port_prompt, 0, stdin);
     155            0 :     if (port_str && port_str[0])
     156            0 :         cfg->smtp_port = atoi(port_str);
     157              :     else
     158            0 :         cfg->smtp_port = cur_port;
     159            0 :     free(port_str);
     160              : 
     161              :     /* ── SMTP Username ────────────────────────────────────────────── */
     162            0 :     char user_prompt[768];
     163            0 :     if (cfg->smtp_user)
     164            0 :         snprintf(user_prompt, sizeof(user_prompt),
     165              :                  "SMTP Username [current: %s]", cfg->smtp_user);
     166              :     else
     167            0 :         snprintf(user_prompt, sizeof(user_prompt),
     168              :                  "SMTP Username [Enter = same as IMAP (%s)]",
     169            0 :                  cfg->user ? cfg->user : "");
     170            0 :     char *su = get_input(user_prompt, 0, stdin);
     171            0 :     if (su && su[0]) {
     172            0 :         free(cfg->smtp_user);
     173            0 :         cfg->smtp_user = su;
     174              :     } else {
     175            0 :         free(su);
     176              :         /* NULL means "use IMAP username" — keep as-is */
     177              :     }
     178              : 
     179              :     /* ── SMTP Password ────────────────────────────────────────────── */
     180            0 :     char pass_prompt[64];
     181            0 :     snprintf(pass_prompt, sizeof(pass_prompt), "%s",
     182            0 :              cfg->smtp_pass ? "SMTP Password [Enter = keep current]"
     183              :                             : "SMTP Password [Enter = same as IMAP]");
     184            0 :     char *sp = get_input(pass_prompt, 1, stdin);
     185            0 :     if (sp && sp[0]) {
     186            0 :         free(cfg->smtp_pass);
     187            0 :         cfg->smtp_pass = sp;
     188              :     } else {
     189            0 :         free(sp);
     190              :         /* NULL means "use IMAP password" — keep as-is */
     191              :     }
     192              : 
     193            0 :     printf("\nSMTP configuration updated.\n");
     194            0 :     return 0;
     195              : }
     196              : 
     197              : /* ── IMAP sub-wizard ─────────────────────────────────────────────────── */
     198              : 
     199            0 : int setup_wizard_imap(Config *cfg) {
     200            0 :     printf("\n--- IMAP (incoming mail) configuration ---\n");
     201            0 :     printf("Press Enter on any field to keep the shown default.\n\n");
     202              : 
     203              :     /* ── IMAP Host ────────────────────────────────────────────────── */
     204            0 :     for (;;) {
     205            0 :         char host_prompt[1024];
     206            0 :         if (cfg->host)
     207            0 :             snprintf(host_prompt, sizeof(host_prompt),
     208              :                      "IMAP Host [current: %s]", cfg->host);
     209              :         else
     210            0 :             snprintf(host_prompt, sizeof(host_prompt),
     211              :                      "IMAP Host (e.g. imaps://imap.example.com)");
     212              : 
     213            0 :         char *host = get_input(host_prompt, 0, stdin);
     214            0 :         if (!host) return -1;   /* EOF / Ctrl-D → abort */
     215            0 :         if (host[0] == '\0') {
     216            0 :             free(host);
     217            0 :             break;  /* keep current */
     218              :         }
     219            0 :         if (strncmp(host, "imaps://", 8) != 0) {
     220            0 :             fprintf(stderr,
     221              :                     "Error: IMAP host must start with 'imaps://' (got '%s').\n",
     222              :                     host);
     223            0 :             free(host);
     224            0 :             continue;  /* re-prompt */
     225              :         }
     226            0 :         free(cfg->host);
     227            0 :         cfg->host = host;
     228            0 :         break;
     229              :     }
     230              : 
     231              :     /* ── IMAP Username ────────────────────────────────────────────── */
     232            0 :     char user_prompt[768];
     233            0 :     if (cfg->user)
     234            0 :         snprintf(user_prompt, sizeof(user_prompt),
     235              :                  "IMAP Username [current: %s]", cfg->user);
     236              :     else
     237            0 :         snprintf(user_prompt, sizeof(user_prompt), "IMAP Username");
     238              : 
     239            0 :     char *user = get_input(user_prompt, 0, stdin);
     240            0 :     if (user && user[0]) {
     241            0 :         free(cfg->user);
     242            0 :         cfg->user = user;
     243              :     } else {
     244            0 :         free(user);
     245              :     }
     246              : 
     247              :     /* ── IMAP Password ────────────────────────────────────────────── */
     248            0 :     const char *pass_prompt = cfg->pass
     249              :         ? "IMAP Password [Enter=keep current]"
     250            0 :         : "IMAP Password";
     251            0 :     char *pass = get_input(pass_prompt, 1, stdin);
     252            0 :     if (pass && pass[0]) {
     253            0 :         free(cfg->pass);
     254            0 :         cfg->pass = pass;
     255              :     } else {
     256            0 :         free(pass);
     257              :     }
     258              : 
     259              :     /* ── Default Folder ───────────────────────────────────────────── */
     260            0 :     char folder_prompt[768];
     261            0 :     const char *cur_folder = cfg->folder ? cfg->folder : "INBOX";
     262            0 :     snprintf(folder_prompt, sizeof(folder_prompt),
     263              :              "Default Folder [current: %s]", cur_folder);
     264              : 
     265            0 :     char *folder = get_input(folder_prompt, 0, stdin);
     266            0 :     if (folder && folder[0]) {
     267            0 :         free(cfg->folder);
     268            0 :         cfg->folder = folder;
     269              :     } else {
     270            0 :         free(folder);
     271            0 :         if (!cfg->folder)
     272            0 :             cfg->folder = strdup("INBOX");
     273              :     }
     274              : 
     275            0 :     printf("\nIMAP configuration updated.\n");
     276            0 :     return 0;
     277              : }
        

Generated by: LCOV version 2.0-1