LCOV - code coverage report
Current view: top level - libwrite/src - compose_service.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 49.4 % 79 39
Test Date: 2026-04-15 21:12:52 Functions: 50.0 % 4 2

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

Generated by: LCOV version 2.0-1