LCOV - code coverage report
Current view: top level - libwrite/src - compose_service.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 86.6 % 82 71
Test Date: 2026-05-07 15:53:08 Functions: 100.0 % 4 4

            Line data    Source code
       1              : #include "compose_service.h"
       2              : #include "mime_util.h"
       3              : #include <stdio.h>
       4              : #include <stdlib.h>
       5              : #include <string.h>
       6              : #include <time.h>
       7              : #include <unistd.h>
       8              : 
       9              : /**
      10              :  * @file compose_service.c
      11              :  * @brief RFC 2822 message builder and reply metadata extractor.
      12              :  */
      13              : 
      14              : /* ── Internal helpers ────────────────────────────────────────────────── */
      15              : 
      16              : /** Convert LF-terminated body to CRLF. Returns heap string; caller frees. */
      17            6 : static char *lf_to_crlf(const char *body) {
      18            6 :     if (!body) return strdup("");
      19              :     /* Count LFs to pre-size the output */
      20            6 :     size_t lf_count = 0;
      21          132 :     for (const char *p = body; *p; p++)
      22          126 :         if (*p == '\n') lf_count++;
      23            6 :     size_t blen = strlen(body);
      24            6 :     char *out = malloc(blen + lf_count + 1);
      25            6 :     if (!out) return NULL;
      26            6 :     char *q = out;
      27          132 :     for (const char *p = body; *p; p++) {
      28          126 :         if (*p == '\n' && (p == body || *(p-1) != '\r'))
      29            2 :             *q++ = '\r';
      30          126 :         *q++ = *p;
      31              :     }
      32            6 :     *q = '\0';
      33            6 :     return out;
      34              : }
      35              : 
      36              : /** Trim leading/trailing whitespace in-place (returns pointer into s). */
      37            1 : static char *trim_ws(char *s) {
      38            1 :     while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') s++;
      39            1 :     char *end = s + strlen(s);
      40            1 :     while (end > s && (*(end-1) == ' ' || *(end-1) == '\t' ||
      41            1 :                        *(end-1) == '\r' || *(end-1) == '\n'))
      42            0 :         end--;
      43            1 :     *end = '\0';
      44            1 :     return s;
      45              : }
      46              : 
      47              : /* ── Public API ──────────────────────────────────────────────────────── */
      48              : 
      49            6 : int compose_build_message(const ComposeParams *p, char **out, size_t *outlen) {
      50            6 :     if (!p || !p->from || !p->to || !p->subject || !out || !outlen)
      51            0 :         return -1;
      52              : 
      53              :     /* Date header in RFC 2822 format */
      54            6 :     time_t now = time(NULL);
      55              :     struct tm tm_local;
      56            6 :     localtime_r(&now, &tm_local);
      57              :     char date_str[64];
      58            6 :     strftime(date_str, sizeof(date_str), "%a, %d %b %Y %H:%M:%S %z", &tm_local);
      59              : 
      60              :     /* Message-ID */
      61            6 :     char hostname[256] = "localhost";
      62            6 :     gethostname(hostname, sizeof(hostname));
      63              :     char msgid[512];
      64            6 :     snprintf(msgid, sizeof(msgid), "<%ld.%d@%s>", (long)now, (int)getpid(), hostname);
      65              : 
      66              :     /* In-Reply-To header (replies only) */
      67            6 :     char reply_hdr[512] = "";
      68            6 :     if (p->reply_to_msg_id && p->reply_to_msg_id[0])
      69            0 :         snprintf(reply_hdr, sizeof(reply_hdr),
      70              :                  "In-Reply-To: %s\r\nReferences: %s\r\n",
      71            0 :                  p->reply_to_msg_id, p->reply_to_msg_id);
      72              : 
      73              :     /* Cc/Bcc headers (optional) */
      74            6 :     char cc_hdr[600] = "";
      75            6 :     if (p->cc && p->cc[0])
      76            0 :         snprintf(cc_hdr, sizeof(cc_hdr), "Cc: %s\r\n", p->cc);
      77              : 
      78            6 :     char bcc_hdr[600] = "";
      79            6 :     if (p->bcc && p->bcc[0])
      80            0 :         snprintf(bcc_hdr, sizeof(bcc_hdr), "Bcc: %s\r\n", p->bcc);
      81              : 
      82              :     /* Convert body line endings to CRLF */
      83            6 :     char *body_crlf = lf_to_crlf(p->body ? p->body : "");
      84            6 :     if (!body_crlf) return -1;
      85              : 
      86              :     /* Assemble message */
      87            6 :     char *msg = NULL;
      88            6 :     int len = asprintf(&msg,
      89              :         "Date: %s\r\n"
      90              :         "From: %s\r\n"
      91              :         "To: %s\r\n"
      92              :         "%s"
      93              :         "%s"
      94              :         "Subject: %s\r\n"
      95              :         "Message-ID: %s\r\n"
      96              :         "%s"
      97              :         "MIME-Version: 1.0\r\n"
      98              :         "Content-Type: text/plain; charset=UTF-8\r\n"
      99              :         "Content-Transfer-Encoding: 8bit\r\n"
     100              :         "\r\n"
     101              :         "%s",
     102              :         date_str,
     103            6 :         p->from,
     104            6 :         p->to,
     105              :         cc_hdr,
     106              :         bcc_hdr,
     107            6 :         p->subject,
     108              :         msgid,
     109              :         reply_hdr,
     110              :         body_crlf);
     111            6 :     free(body_crlf);
     112              : 
     113            6 :     if (len < 0 || !msg) return -1;
     114            6 :     *out    = msg;
     115            6 :     *outlen = (size_t)len;
     116            6 :     return 0;
     117              : }
     118              : 
     119            1 : int compose_extract_reply_meta(const char *raw_msg,
     120              :                                 char **reply_to_out,
     121              :                                 char **subject_out,
     122              :                                 char **msg_id_out) {
     123            1 :     if (!raw_msg || !reply_to_out || !subject_out || !msg_id_out)
     124            0 :         return -1;
     125              : 
     126            1 :     *reply_to_out = NULL;
     127            1 :     *subject_out  = NULL;
     128            1 :     *msg_id_out   = NULL;
     129              : 
     130              :     /* Prefer Reply-To header; fall back to From */
     131            1 :     char *rt = mime_get_header(raw_msg, "Reply-To");
     132            1 :     if (!rt || !rt[0]) {
     133            1 :         free(rt);
     134            1 :         rt = mime_get_header(raw_msg, "From");
     135              :     }
     136            1 :     *reply_to_out = rt ? mime_decode_words(rt) : strdup("");
     137            1 :     free(rt);
     138              : 
     139              :     /* Subject: prefix with "Re: " (strip duplicates) */
     140            1 :     char *subj_raw = mime_get_header(raw_msg, "Subject");
     141            1 :     char *subj_dec = subj_raw ? mime_decode_words(subj_raw) : strdup("");
     142            1 :     free(subj_raw);
     143            1 :     char *s = subj_dec ? trim_ws(subj_dec) : NULL;
     144              :     /* Strip all leading "Re: " / "re: " prefixes */
     145            1 :     while (s && (strncasecmp(s, "re: ", 4) == 0 || strncasecmp(s, "re:", 3) == 0)) {
     146            0 :         if (strncasecmp(s, "re: ", 4) == 0) s += 4;
     147            0 :         else s += 3;
     148            0 :         while (*s == ' ') s++;
     149              :     }
     150            1 :     char *subj_out = NULL;
     151            1 :     if (asprintf(&subj_out, "Re: %s", s ? s : "") < 0)
     152            0 :         subj_out = strdup("Re: ");
     153            1 :     free(subj_dec);
     154            1 :     *subject_out = subj_out;
     155              : 
     156              :     /* Message-ID */
     157            1 :     char *msgid = mime_get_header(raw_msg, "Message-ID");
     158            1 :     *msg_id_out = msgid ? msgid : strdup("");
     159              : 
     160            1 :     return 0;
     161              : }
        

Generated by: LCOV version 2.0-1