README: omit -i option in examples
[neatmail.git] / pg.c
blob8168874b442ceecb4e94e26131dcd2c45a0b8e94
1 #include <ctype.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <time.h>
9 #include "mail.h"
11 #define USERAGENT "neatmail (git://repo.or.cz/neatmail.git)"
12 #define MBOUNDARY "neatmail-boundary"
13 #define PARTS_N 16
14 #define HDRS_N 128
16 static char *parts[PARTS_N];
17 static int parts_n;
19 static void msg_new(char **msg, long *msglen);
20 static int msg_reply(char *msg, long msglen, char **mod, long *modlen);
21 static int msg_forward(char *msg, long msglen, char **mod, long *modlen);
23 static int msg_filter(char *msg, long msglen, char **mod, long *modlen, char *hdrs[])
25 struct sbuf *sb = sbuf_make();
26 char *s = msg;
27 char *e = msg + msglen;
28 int i;
29 for (i = 0; hdrs[i]; i++) {
30 char *val = msg_get(msg, msglen, hdrs[i]);
31 if (val)
32 sbuf_mem(sb, val, hdrlen(val, msg + msglen - val));
34 while (s + 1 < e && (s[0] != '\n' || s[1] != '\n'))
35 s++;
36 s++;
37 sbuf_mem(sb, s, e - s);
38 *modlen = sbuf_len(sb);
39 *mod = sbuf_done(sb);
40 return 0;
43 /* obtain a message from an mbox by its message id */
44 static int mbox_mid(char **path, char *mid)
46 struct mbox *mbox = mbox_open(path);
47 int ret = -1;
48 int i;
49 if (!mbox)
50 return -1;
51 for (i = 0; i < mbox_len(mbox); i++) {
52 char *id_hdr;
53 char *msg = NULL;
54 long msglen;
55 if (mbox_get(mbox, i, &msg, &msglen))
56 continue;
57 id_hdr = msg_get(msg, msglen, "Message-ID:");
58 if (id_hdr) {
59 int len = hdrlen(id_hdr, (msg + msglen) - id_hdr);
60 char *beg = memchr(id_hdr, '<', len);
61 char *end = beg ? memchr(id_hdr, '>', len) : NULL;
62 if (!beg || !end || beg > end)
63 continue;
64 beg++;
65 if (strlen(mid) == end - beg &&
66 !memcmp(mid, beg, end - beg))
67 ret = i;
70 mbox_free(mbox);
71 return ret;
74 static char *usage =
75 "usage: neatmail pg [options] [msg@path]\n\n"
76 "options:\n"
77 " -b path \tmbox path\n"
78 " -i msg \tmsg number or message id (=msg_id)\n"
79 " -h hdr \tspecify headers to include\n"
80 " -m \tdecode mime message\n"
81 " -r \tgenerate a reply\n"
82 " -f \tgenerate a forward\n"
83 " -n \tgenerate a new message\n"
84 " -a file \tadd an attachment\n"
85 " -s \tfollow neatmail-source: header\n";
87 int pg(char *argv[])
89 char *msg, *mod;
90 char *hdrs[HDRS_N] = {NULL};
91 char *path[16] = {NULL};
92 char *msgnum = NULL;
93 long msglen, modlen;
94 int demime = 0;
95 int reply = 0;
96 int forward = 0;
97 int source = 0;
98 int newmsg = 0;
99 int hdrs_n = 0;
100 int path_n = 0;
101 int addr;
102 int i;
103 for (i = 0; argv[i] && argv[i][0] == '-'; i++) {
104 if (argv[i][1] == 'm')
105 demime = 1;
106 if (argv[i][1] == 'r')
107 reply = 1;
108 if (argv[i][1] == 'n')
109 newmsg = 1;
110 if (argv[i][1] == 'f')
111 forward = 1;
112 if (argv[i][1] == 's')
113 source = 1;
114 if (argv[i][1] == 'b') {
115 path[path_n++] = argv[i][2] ? argv[i] + 2 : argv[++i];
116 continue;
118 if (argv[i][1] == 'i') {
119 msgnum = argv[i][2] ? argv[i] + 2 : argv[++i];
120 continue;
122 if (argv[i][1] == 'h') {
123 char *hdr = argv[i][2] ? argv[i] + 2 : argv[++i];
124 if (hdrs_n + 1 < HDRS_N)
125 hdrs[hdrs_n++] = hdr;
126 continue;
128 if (argv[i][1] == 'a') {
129 if (parts_n < PARTS_N)
130 parts[parts_n++] = argv[i][2] ? argv[i] + 2 : argv[++i];
131 continue;
134 if (newmsg) {
135 msg_new(&msg, &msglen);
136 xwrite(1, msg, msglen);
137 free(msg);
138 return 0;
140 if (!path[0] && argv[i])
141 path[0] = argv[i++];
142 if (!msgnum && argv[i])
143 msgnum = argv[i++];
144 if (msgnum && msgnum[0] != '=' && strchr(msgnum, '@') != NULL) {
145 path[0] = strchr(msgnum, '@') + 1;
146 path[0][-1] = '\0';
148 if (!path[0] || !msgnum) {
149 printf("%s", usage);
150 return 1;
152 if (msgnum[0] == '=')
153 addr = mbox_mid(path, msgnum + 1);
154 else
155 addr = atoi(msgnum);
156 if (addr >= 0 && !mbox_ith(path[0], addr, &msg, &msglen)) {
157 char *hdr;
158 char box[512];
159 long beg, end;
160 int num;
161 if (source && (hdr = msg_get(msg, msglen, "Neatmail-Source:")) != NULL) {
162 while (*hdr && *hdr != ' ')
163 hdr++;
164 free(msg);
165 if (sscanf(hdr, "%d@%s %ld %ld", &num, box, &beg, &end) != 4)
166 return 1;
167 if (mbox_off(box, beg, end, &msg, &msglen) != 0)
168 return 1;
170 if (demime && !msg_demime(msg, msglen, &mod, &modlen)) {
171 free(msg);
172 msg = mod;
173 msglen = modlen;
175 if (reply && !msg_reply(msg, msglen, &mod, &modlen)) {
176 free(msg);
177 msg = mod;
178 msglen = modlen;
180 if (hdrs_n && !msg_filter(msg, msglen, &mod, &modlen, hdrs)) {
181 free(msg);
182 msg = mod;
183 msglen = modlen;
185 if (forward && !msg_forward(msg, msglen, &mod, &modlen)) {
186 free(msg);
187 msg = mod;
188 msglen = modlen;
190 xwrite(1, msg, msglen);
191 free(msg);
193 return addr < 0;
196 static void put_from_(struct sbuf *sb)
198 char buf[128];
199 time_t t;
200 time(&t);
201 strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y", localtime(&t));
202 sbuf_printf(sb, "From %s %s\n",
203 getenv("LOGNAME") ? getenv("LOGNAME") : "me", buf);
206 static void put_date(struct sbuf *sb)
208 char buf[128];
209 time_t t;
210 time(&t);
211 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", localtime(&t));
212 sbuf_printf(sb, "Date: %s\n", buf);
215 static void put_id(struct sbuf *sb)
217 char buf[128];
218 char host[32] = "neatmail.host";
219 time_t t;
220 time(&t);
221 strftime(buf, sizeof(buf), "%Y%d%m%H%M%S", localtime(&t));
222 sbuf_printf(sb, "Message-ID: <%s@%s>\n", buf, host);
225 static void put_agent(struct sbuf *sb)
227 sbuf_printf(sb, "User-Agent: " USERAGENT "\n");
230 static char *fileread(char *path, int *len)
232 int fd = open(path, O_RDONLY);
233 char buf[4096];
234 struct sbuf *sb;
235 if (fd < 0)
236 return NULL;
237 sb = sbuf_make();
238 while (1) {
239 int ret = read(fd, buf, sizeof(buf));
240 if (ret == -1 && (errno == EAGAIN || errno == EINTR))
241 continue;
242 if (ret <= 0)
243 break;
244 sbuf_mem(sb, buf, ret);
246 close(fd);
247 *len = sbuf_len(sb);
248 return sbuf_done(sb);
251 static char *filename(char *path)
253 char *sl = strrchr(path, '/');
254 return sl ? sl + 1 : path;
257 static void put_body(struct sbuf *sb, char *body)
259 sbuf_printf(sb, "MIME-Version: 1.0\n");
260 if (!parts_n) {
261 sbuf_printf(sb, "Content-Type: text/plain; charset=utf-8\n");
262 sbuf_printf(sb, "Content-Transfer-Encoding: 8bit\n");
263 sbuf_printf(sb, "\n");
264 sbuf_str(sb, body);
265 } else {
266 int i;
267 sbuf_printf(sb, "Content-Type: multipart/mixed; boundary=%s\n", MBOUNDARY);
268 sbuf_printf(sb, "\nMulti-part MIME message.\n");
269 sbuf_printf(sb, "--%s\n", MBOUNDARY);
270 sbuf_printf(sb, "Content-Type: text/plain; charset=utf-8\n");
271 sbuf_printf(sb, "Content-Transfer-Encoding: 8bit\n");
272 sbuf_printf(sb, "\n");
273 sbuf_str(sb, body);
274 for (i = 0; i < parts_n; i++) {
275 char *cont;
276 int cont_len = 0;
277 sbuf_printf(sb, "--%s\n", MBOUNDARY);
278 sbuf_printf(sb, "Content-Type: application/octet-stream\n");
279 sbuf_printf(sb, "Content-Disposition: attachment; filename=\"%s\"\n",
280 filename(parts[i]));
281 sbuf_printf(sb, "Content-Transfer-Encoding: base64\n");
282 sbuf_printf(sb, "\n");
283 cont = fileread(parts[i], &cont_len);
284 if (cont) {
285 char *b64 = base64(cont, cont_len);
286 sbuf_mem(sb, b64, strlen(b64));
287 free(b64);
289 free(cont);
291 sbuf_printf(sb, "--%s--\n", MBOUNDARY);
295 static void msg_new(char **msg, long *msglen)
297 struct sbuf *sb = sbuf_make();
298 put_from_(sb);
299 sbuf_printf(sb, "From: \n");
300 sbuf_printf(sb, "To: \n");
301 sbuf_printf(sb, "Subject: \n");
302 put_id(sb);
303 put_date(sb);
304 put_agent(sb);
305 put_body(sb, "MAIL BODY...\n");
306 sbuf_chr(sb, '\n');
307 *msglen = sbuf_len(sb);
308 *msg = sbuf_done(sb);
311 static char *hdr_val(char *hdr)
313 hdr = strchr(hdr, ':') + 1;
314 while (isspace(*hdr))
315 hdr++;
316 return hdr;
319 static int hdr_len(char *hdr)
321 int l = hdrlen(hdr, 1024);
322 while (l > 0 && strchr(" \r\n", (unsigned char) hdr[l - 1]))
323 l--;
324 return l;
327 static void put_subjreply(struct sbuf *sb, char *subj)
329 subj = hdr_val(subj);
330 sbuf_str(sb, "Subject: ");
331 if (tolower(subj[0]) != 'r' || tolower(subj[1]) != 'e')
332 sbuf_str(sb, "Re: ");
333 sbuf_mem(sb, subj, hdr_len(subj));
334 sbuf_str(sb, "\n");
337 static void put_subjfwd(struct sbuf *sb, char *subj)
339 subj = hdr_val(subj);
340 sbuf_str(sb, "Subject: ");
341 sbuf_str(sb, "Fwd: ");
342 sbuf_mem(sb, subj, hdr_len(subj));
343 sbuf_str(sb, "\n");
346 static void put_replyto(struct sbuf *sb, char *id, char *ref)
348 id = hdr_val(id);
349 sbuf_str(sb, "In-Reply-To: ");
350 sbuf_mem(sb, id, hdr_len(id));
351 sbuf_str(sb, "\n");
352 sbuf_str(sb, "References: ");
353 if (ref) {
354 ref = hdr_val(ref);
355 sbuf_mem(sb, ref, hdr_len(ref));
356 sbuf_str(sb, "\n\t");
358 sbuf_mem(sb, id, hdr_len(id));
359 sbuf_str(sb, "\n");
362 static void put_reply(struct sbuf *sb, char *from, char *to, char *cc, char *rply)
364 if (from || rply) {
365 char *hdr = rply ? rply : from;
366 hdr = hdr_val(hdr);
367 sbuf_str(sb, "To: ");
368 sbuf_mem(sb, hdr, hdr_len(hdr));
369 sbuf_str(sb, "\n");
371 if (to || cc || (rply && from)) {
372 int n = 0;
373 sbuf_str(sb, "Cc: ");
374 if (to) {
375 to = hdr_val(to);
376 if (n++)
377 sbuf_str(sb, ",\n\t");
378 sbuf_mem(sb, to, hdr_len(to));
380 if (rply && from) {
381 from = hdr_val(from);
382 if (n++)
383 sbuf_str(sb, ",\n\t");
384 sbuf_mem(sb, from, hdr_len(from));
386 if (cc) {
387 cc = hdr_val(cc);
388 if (n++)
389 sbuf_str(sb, ",\n\t");
390 sbuf_mem(sb, cc, hdr_len(cc));
392 sbuf_str(sb, "\n");
396 static char *quote_body(char *msg, long msglen)
398 struct sbuf *sb = sbuf_make();
399 char *from = msg_get(msg, msglen, "From:");
400 char *s = msg;
401 char *e = msg + msglen;
402 while (s + 1 < e && (s[0] != '\n' || s[1] != '\n'))
403 s++;
404 s += 2;
405 sbuf_chr(sb, '\n');
406 if (from) {
407 from = hdr_val(from);
408 sbuf_mem(sb, from, hdr_len(from));
409 sbuf_str(sb, " wrote:\n");
411 while (s < e) {
412 char *r = memchr(s, '\n', e - s);
413 if (!r)
414 break;
415 sbuf_str(sb, "> ");
416 sbuf_mem(sb, s, r - s + 1);
417 s = r + 1;
419 return sbuf_done(sb);
422 static int msg_reply(char *msg, long msglen, char **mod, long *modlen)
424 struct sbuf *sb = sbuf_make();
425 char *id_hdr = msg_get(msg, msglen, "Message-ID:");
426 char *ref_hdr = msg_get(msg, msglen, "References:");
427 char *from_hdr = msg_get(msg, msglen, "From:");
428 char *subj_hdr = msg_get(msg, msglen, "Subject:");
429 char *to_hdr = msg_get(msg, msglen, "To:");
430 char *cc_hdr = msg_get(msg, msglen, "CC:");
431 char *rply_hdr = msg_get(msg, msglen, "Reply-To:");
432 char *body;
433 put_from_(sb);
434 put_date(sb);
435 sbuf_printf(sb, "From: \n");
436 put_reply(sb, from_hdr, to_hdr, cc_hdr, rply_hdr);
437 if (subj_hdr)
438 put_subjreply(sb, subj_hdr);
439 put_id(sb);
440 if (id_hdr)
441 put_replyto(sb, id_hdr, ref_hdr);
442 put_agent(sb);
443 body = quote_body(msg, msglen);
444 put_body(sb, body);
445 free(body);
446 *modlen = sbuf_len(sb);
447 *mod = sbuf_done(sb);
448 return 0;
451 static int msg_forward(char *msg, long msglen, char **mod, long *modlen)
453 struct sbuf *sb = sbuf_make();
454 struct sbuf *sb_body = sbuf_make();
455 char *subj_hdr = msg_get(msg, msglen, "Subject:");
456 put_from_(sb);
457 put_date(sb);
458 sbuf_printf(sb, "From: \n");
459 sbuf_printf(sb, "To: \n");
460 put_subjfwd(sb, subj_hdr);
461 put_id(sb);
462 put_agent(sb);
463 sbuf_str(sb_body, "\n-------- Original Message --------\n");
464 sbuf_mem(sb_body, msg, msglen);
465 put_body(sb, sbuf_buf(sb_body));
466 sbuf_free(sb_body);
467 *modlen = sbuf_len(sb);
468 *mod = sbuf_done(sb);
469 return 0;