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 : }
|