2
* Get mail using the pop3 protocol.
3
* Format the mail in ascii, or in html.
5
* Copyright (c) Karl Dahlke, 2008
6
* This file is part of the edbrowse project, released under GPL.
11
#define MHLINE 200 /* length of a mail header line */
15
struct MHINFO *next, *prev;
16
struct listHead components;
18
char subject[MHLINE + 4];
23
char boundary[MHLINE];
25
char *tolist, *cclist;
27
char mid[MHLINE]; /* message id */
28
char ref[MHLINE]; /* references */
29
char cfn[MHLINE]; /* content file name */
30
uchar ct, ce; /* content type, content encoding */
35
bool ne; /* non english */
38
static int nattach; /* number of attachments */
39
static int nimages; /* number of attached images */
40
static char *firstAttach; /* name of first file */
41
static bool mailIsHtml, mailIsSpam, mailIsBlack;
42
static char *fm; /* formatted mail string */
44
static struct MHINFO *lastMailInfo;
45
static char *lastMailText;
46
#define MAXIPBLACK 3000
47
static IP32bit ipblacklist[MAXIPBLACK];
48
static IP32bit ipblackmask[MAXIPBLACK];
49
static bool ipblackcomp[MAXIPBLACK];
60
if(!fileIntoMemory(ipbFile, &buf, &buflen))
69
while(isspaceByte(*s))
75
char *q = strpbrk(s, "/!");
77
dotstop = *q, *q++ = 0;
91
if(bits > 0 && bits < 32) {
92
static const IP32bit masklist[] = {
93
0xffffffff, 0xfeffffff, 0xfcffffff, 0xf8ffffff,
94
0xf0ffffff, 0xe0ffffff, 0xc0ffffff, 0x80ffffff,
95
0x00ffffff, 0x00feffff, 0x00fcffff, 0x00f8ffff,
96
0x00f0ffff, 0x00e0ffff, 0x00c0ffff, 0x0080ffff,
97
0x0000ffff, 0x0000feff, 0x0000fcff, 0x0000f8ff,
98
0x0000f0ff, 0x0000e0ff, 0x0000c0ff, 0x000080ff,
99
0x000000ff, 0x000000fe, 0x000000fc, 0x000000f8,
100
0x000000f0, 0x000000e0, 0x000000c0, 0x00000080,
103
ipmask = masklist[32 - bits];
108
if(nipblack == MAXIPBLACK)
109
i_printfExit(MSG_ManyIP, MAXIPBLACK);
110
ipblacklist[nipblack] = ip;
111
ipblackmask[nipblack] = ipmask;
112
ipblackcomp[nipblack] = (dotstop == '!');
119
debugPrint(3, "%d ip addresses in blacklist", nipblack);
120
} /* loadBlacklist */
123
onBlacklist1(IP32bit tip)
125
IP32bit blip; /* black ip */
128
for(j = 0; j < nipblack; ++j) {
129
bool comp = ipblackcomp[j];
130
blip = ipblacklist[j];
131
mask = ipblackmask[j];
132
if((tip ^ blip) & mask)
136
debugPrint(3, "blocked by rule %d", j + 1);
145
IP32bit *ipp = cw->iplist;
146
IP32bit tip; /* test ip */
149
while((tip = *ipp++) != NULL_IP)
150
if(onBlacklist1(tip))
156
freeMailInfo(struct MHINFO *w)
159
while(!listIsEmpty(&w->components)) {
160
v = w->components.next;
170
getFileName(const char *defname, bool isnew)
172
static char buf[ABSPATH];
176
i_printf(MSG_FileName);
178
printf("[%s] ", defname);
179
if(!fgets(buf, sizeof (buf), stdin))
181
for(p = buf; isspaceByte(*p); ++p) ;
183
while(l && isspaceByte(p[l - 1]))
189
/* make a copy just to be safe */
190
strcpy(buf, defname);
194
if(isnew && fileTypeByName(p, false)) {
195
i_printf(MSG_FileExists, p);
203
static bool ignoreImages;
206
writeAttachment(struct MHINFO *w)
209
if((ismc | ignoreImages) && w->atimage)
210
return; /* image ignored */
211
if(w->error64 == BAD64)
212
i_printf(MSG_Abbreviated);
213
if(w->start == w->end) {
214
i_printf(MSG_AttEmpty);
216
printf(" %s", w->cfn);
221
atname = getFileName((w->cfn[0] ? w->cfn : 0), true);
222
/* X is like x, but deletes all future images */
223
if(stringEqual(atname, "X")) {
228
if(!ismc && stringEqual(atname, "e")) {
229
int cx, svcx = context;
230
for(cx = 1; cx < MAXSESSION; ++cx)
231
if(!sessionList[cx].lw)
233
if(cx == MAXSESSION) {
234
i_printf(MSG_AttNoBuffer);
237
i_printf(MSG_SessionX, cx);
238
if(!addTextToBuffer((pst) w->start, w->end - w->start, 0))
239
i_printf(MSG_AttNoCopy, cx);
241
cw->fileName = cloneString(w->cfn);
242
cxSwitch(svcx, false); /* back to where we were */
244
} else if(!stringEqual(atname, "x")) {
245
int fh = open(atname, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
247
i_printf(MSG_AttNoSave, atname);
251
int nb = w->end - w->start;
252
if(write(fh, w->start, nb) < nb) {
253
i_printf(MSG_AttNoWrite, atname);
260
} /* writeAttachment */
263
writeAttachments(struct MHINFO *w)
269
foreach(v, w->components)
272
} /* writeAttachments */
275
serverPutGet(const char *line, bool secure)
277
if(!serverPutLine(line, secure))
279
if(!serverGetLine(secure))
284
fetchMail(int account)
286
const struct MACCOUNT *a = accounts + account - 1;
287
const char *login = a->login;
288
const char *pass = a->password;
289
const char *host = a->inurl;
294
i_printfExit(MSG_FetchNotBackgnd);
298
i_printfExit(MSG_NoMailDir);
300
i_printfExit(MSG_NoDirChange, mailDir);
302
if(!loadAddressBook())
305
if(!mailConnect(host, a->inport, a->inssl))
307
if(!serverGetLine(a->inssl))
309
if(memcmp(serverLine, "+OK ", 4))
310
i_printfExit(MSG_BadPopIntro, serverLine);
311
sprintf(serverLine, "user %s%s", login, eol);
312
serverPutGet(serverLine, a->inssl);
313
if(pass) { /* I think this is always required */
314
sprintf(serverLine, "pass %s%s", pass, eol);
315
serverPutGet(serverLine, a->inssl);
317
if(memcmp(serverLine, "+OK", 3))
318
i_printfExit(MSG_PopNotComplete, serverLine);
320
/* How many mail messages? */
321
serverPutGet("stat\r\n", a->inssl);
322
if(memcmp(serverLine, "+OK ", 4))
323
i_printfExit(MSG_NoStatusMailBox, serverLine);
324
nmsgs = atoi(serverLine + 4);
327
serverClose(a->inssl);
331
if(!(zapMail | passMail)) {
332
/* We look up a lot of hosts; make a tcp connect */
334
/* the conect will drop when the program exits */
337
i_printf(MSG_MessagesX, nmsgs);
338
if(zapMail && nmsgs > 300)
341
for(m = 1; m <= nmsgs; ++m) {
342
const char *redirect = 0; /* send mail elsewhere */
344
const char *atname; /* name of attachment */
345
bool delflag = false; /* delete this mail */
347
char *exact = initString(&exact_l); /* the exact message */
349
char *exactf = 0; /* utf8 formatted */
354
else { /* read the next message */
357
/* We need to clear out the editor, from the last message,
358
* then read in this one, in its entirety.
359
* This is probably a waste, since the user might delete a megabyte message
360
* after seeing only the first paragraph, or even the subject,
361
* but as for programming, it's easier to read the whole thing in right now. */
363
freeMailInfo(lastMailInfo);
365
nzFree(lastMailText);
367
if(sessionList[1].lw)
372
/* Now grab the entire message */
373
sprintf(serverLine, "retr %d%s", m, eol);
374
if(!serverPutLine(serverLine, a->inssl))
380
nr = ssl_read(retrbuf, sizeof (retrbuf));
382
nr = tcp_read(mssock, retrbuf, sizeof (retrbuf));
384
i_printfExit(MSG_ErrorReadMess, errno);
386
/* add null, to make it easy to print the error message, if necessary */
387
if(nr < sizeof (retrbuf))
389
if(memcmp(retrbuf, "+OK", 3))
390
i_printfExit(MSG_ErrorFetchMess, m, retrbuf);
392
while(retrbuf[j] != '\n')
396
memcpy(retrbuf, retrbuf + j, nr);
400
stringAndBytes(&exact, &exact_l, retrbuf, nr);
401
/* . by itself on a line ends the transmission */
408
if(j >= 0 && exact[j] == '\r')
416
if(exact[j - 1] == '\n')
421
/* get rid of the dos returns */
422
for(j = k = 0; j < exact_l; ++j) {
423
if(exact[j] == '\r' && j < exact_l - 1 && exact[j + 1] == '\n')
425
exact[k++] = exact[j];
430
iuReformat(exact, exact_l, &exactf, &exactf_l);
433
if(!addTextToBuffer((pst) exactf, exactf_l, 0))
436
if(!addTextToBuffer((pst) exact, exact_l, 0))
441
browseCurrentBuffer();
443
mailIsBlack = onBlacklist();
444
redirect = mailRedirect(lastMailInfo->to,
445
lastMailInfo->from, lastMailInfo->reply,
446
lastMailInfo->subject);
451
++redirect, key = 'u';
452
if(stringEqual(redirect, "x"))
455
printf("> %s\n", redirect);
456
} else if((mailIsSpam | mailIsBlack) && spamCan) {
460
if(lastMailInfo->from[0]) {
462
printf("%s", lastMailInfo->from);
463
} else if(lastMailInfo->reply[0]) {
465
printf("%s", lastMailInfo->reply);
468
} else if(!nattach && /* drop empty mail message */
470
(lastMailInfo->subject != 0) -
471
(lastMailInfo->from != 0) -
472
(lastMailInfo->reply != 0) <= 1) {
480
/* display the next page of mail and get a command from the keyboard */
483
if(!delflag) { /* show next page */
485
if(displine <= cw->dol) {
486
for(j = 0; j < 20 && displine <= cw->dol;
488
char *showline = (char *)fetchLine(displine, 1);
489
k = pstLength((pst) showline);
491
printf("%s\n", showline);
497
/* get key command */
500
/* interactive prompt depends on whether there is more text or not */
501
printf("%c ", displine > cw->dol ? '?' : '*');
503
key = getLetter("qx? nwkudijJ");
510
serverClose(a->inssl);
521
i_puts(MSG_IPDelete);
522
if(!cw->iplist || cw->iplist[0] == -1) {
526
i_puts(ipbFile ? MSG_None :
530
for(k = 0; (addr = cw->iplist[k]) != NULL_IP;
532
puts(tcp_ip_dots(addr));
533
if(nipblack == MAXIPBLACK)
535
ipblacklist[nipblack] = addr;
536
ipblackmask[nipblack] = 0xffffffff;
537
ipblackcomp[nipblack] = false;
546
if(!junkSubject(lastMailInfo->subject, key))
551
if(displine > cw->dol)
552
i_puts(MSG_EndMessage);
555
i_puts(MSG_MailHelp);
568
/* At this point we're saving the mail somewhere. */
574
atname = getFileName(0, false);
575
if(!stringEqual(atname, "x")) {
576
char exists = fileTypeByName(atname, false);
577
int fsize; /* file size */
579
open(atname, O_WRONLY | O_TEXT | O_CREAT | O_APPEND,
582
i_printf(MSG_NoCreate, atname);
587
"======================================================================\n",
589
if(key == 'u' || unformatMail) {
590
if(write(fh, exact, exact_l) < exact_l) {
592
i_printf(MSG_NoWrite, atname);
600
for(j = 1; j <= cw->dol; ++j) {
601
char *showline = (char *)fetchLine(j, 1);
602
int len = pstLength((pst) showline);
603
if(write(fh, showline, len) < len)
607
} /* loop over lines */
610
writeAttachments(lastMailInfo);
611
} /* unformat or format */
612
if(atname != spamCan) {
613
i_printf(MSG_MailSaved, fsize);
615
i_printf(MSG_Appended);
618
} /* saving to a real file */
622
} /* paging through the message */
623
} /* interactive or zap */
627
/* Remember, it isn't really gone until you quit the session.
628
* So if you didn't mean to delete, type x to exit abruptly,
629
* then fetch your mail again. */
630
sprintf(serverLine, "dele %d%s", m, eol);
631
if(!serverPutLine(serverLine, a->inssl))
633
if(!serverGetLine(a->inssl))
634
i_printfExit(MSG_MailTimeOver);
635
if(memcmp(serverLine, "+OK", 3))
636
i_printfExit(MSG_UnableDelMail, serverLine);
641
} /* loop over mail messages */
644
printf("%d\n", nmsgs);
645
serverClose(a->inssl);
649
/* Here are the common keywords for mail header lines.
650
* These are in alphabetical order, so you can stick more in as you find them.
651
* The more words we have, the more accurate the test. */
652
static const char *const mhwords[] = {
657
"content-transfer-encoding:",
665
"last-attempt-date:",
686
"x-mailman-version:",
687
"x-mdaemon-deliver-to:",
690
"x-ms-tnef-correlator:",
691
"x-msmail-priority:",
695
"X-Spam-Checker-Version:",
698
"X-SPAM-Msg-Sniffer-Result:",
705
/* Before we render a mail message, let's make sure it looks like email.
706
* This is similar to htmlTest() in html.c. */
712
/* This is a very simple test - hopefully not too simple.
713
* The first 20 non-indented lines have to look like mail header lines,
714
* with at least half the keywords recognized. */
715
for(i = 1, j = k = 0; i <= cw->dol && j < 20; ++i) {
717
char *p = (char *)fetchLine(i, -1);
719
if(first == '\n' || first == '\r' && p[1] == '\n')
721
if(first == ' ' || first == '\t')
723
++j; /* nonindented line */
724
for(q = p; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ;
729
/* X-Whatever is a mail header word */
730
if(q - p >= 8 && p[1] == '-' && toupper(p[0]) == 'X') {
733
for(n = 0; mhwords[n]; ++n)
734
if(memEqualCI(mhwords[n], p, q - p))
739
if(k >= 4 && k * 2 >= j)
741
} /* loop over lines */
752
return c - ('a' - 26);
754
return c - ('0' - 52);
759
return 64; /* error */
763
unpack64(struct MHINFO *w)
765
uchar val, leftover, mod;
768
/* Since this is a copy, and the unpacked version is always
769
* smaller, just unpack it inline. */
772
for(q = r = w->start; q < w->end; ++q) {
779
runningError(MSG_AttAfterChars);
780
w->error64 = EXTRA64;
789
runningError(MSG_AttBad64);
795
} else if(mod == 1) {
796
*r++ = (leftover | (val >> 4));
798
} else if(mod == 2) {
799
*r++ = (leftover | (val >> 2));
802
*r++ = (leftover | val);
811
unpackQP(struct MHINFO *w)
815
for(q = r = w->start; q < w->end; ++q) {
825
if(isxdigit(c) && isxdigit(d)) {
840
/* Look for the name of the attachment and boundary */
842
ctExtras(struct MHINFO *w, const char *s, const char *t)
845
const char *q, *al, *ar;
847
if(w->ct < CT_MULTI) {
849
for(q = s + 1; q < t; ++q) {
850
if(isalnumByte(q[-1]))
852
/* could be name= or filename= */
853
if(memEqualCI(q, "file", 4))
855
if(!memEqualCI(q, "name=", 5))
862
for(al = q; q < t; ++q) {
867
if(strchr(",; \t", *q))
871
if(ar - al >= MHLINE)
872
ar = al + MHLINE - 1;
873
strncpy(w->cfn, al, ar - al);
878
if(w->ct >= CT_MULTI) {
880
for(q = s + 1; q < t; ++q) {
881
if(isalnumByte(q[-1]))
883
if(!memEqualCI(q, "boundary=", 9))
890
for(al = q; q < t; ++q) {
895
if(strchr(",; \t", *q))
899
w->boundlen = ar - al;
900
strncpy(w->boundary, al, ar - al);
907
isoDecode(char *vl, char **vrp)
910
char *start, *end; /* section being decoded */
911
char *s, *t, c, d, code;
913
uchar val, leftover, mod;
917
start = strstr(start, "=?");
918
if(!start || start >= vr)
921
if(!memEqualCI(start, "iso-", 4) &&
922
!memEqualCI(start, "utf-", 4) &&
923
!memEqualCI(start, "gb", 2) && !memEqualCI(start, "windows-", 8))
925
s = strchr(start, '?');
926
if(!s || s > vr - 5 || s[2] != '?')
929
code = toupper(code);
930
if(code != 'Q' && code != 'B')
933
end = strstr(s, "?=");
934
if(!end || end > vr - 2)
945
if(isxdigit(c) && isxdigit(d)) {
960
for(; s < end; ++s) {
968
val = 0; /* ignore errors here */
971
} else if(mod == 1) {
972
*t++ = (leftover | (val >> 4));
974
} else if(mod == 2) {
975
*t++ = (leftover | (val >> 2));
978
*t++ = (leftover | val);
994
for(s = vl; s < vr; ++s) {
996
if(c == 0 || c == '\t')
1003
/* mail header reformat, to/from utf8 */
1005
mhReformat(char *line)
1008
int tlen = strlen(line);
1009
iuReformat(line, tlen, &tbuf, &tlen);
1013
tbuf[MHLINE - 1] = 0;
1019
extractLessGreater(char *s)
1022
vl = strchr(s, '<');
1023
vr = strchr(s, '>');
1024
if(vl && vr && vl < vr) {
1028
} /* extractLessGreater */
1030
/* Now that we know it's mail, see what information we can
1031
* glean from the headers.
1032
* Returns a pointer to an allocated MHINFO structure.
1033
* This routine is recursive. */
1034
static struct MHINFO *
1035
headerGlean(char *start, char *end)
1038
char *vl, *vr; /* value left and value right */
1044
w = allocZeroMem(sizeof (struct MHINFO));
1045
initList(&w->components);
1048
w->andOthers = false;
1049
w->tolist = initString(&w->tolen);
1050
w->cclist = initString(&w->cclen);
1051
w->start = start, w->end = end;
1053
for(s = start; s < end; s = t + 1) {
1056
t = strchr(s, '\n');
1058
t = end - 1; /* should never happen */
1060
break; /* empty line */
1062
if(first == ' ' || first == '\t') {
1066
stringAndBytes(&w->tolist, &w->tolen, s, t - s);
1068
stringAndBytes(&w->cclist, &w->cclen, s, t - s);
1072
/* find the lead word */
1073
for(q = s; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ;
1075
continue; /* should never happen */
1077
continue; /* should never happen */
1078
for(vl = q; *vl == ' ' || *vl == '\t'; ++vl) ;
1079
for(vr = t; vr > vl && (vr[-1] == ' ' || vr[-1] == '\t'); --vr) ;
1081
continue; /* empty */
1084
if(vr - vl > MHLINE - 1)
1085
vr = vl + MHLINE - 1;
1087
/* This is sort of a switch statement on the word */
1088
if(memEqualCI(s, "subject:", q - s)) {
1092
/* get rid of forward/reply prefixes */
1093
for(q = vl; q < vr; ++q) {
1094
static const char *const prefix[] = {
1095
"re", "sv", "fwd", 0
1097
if(!isalphaByte(*q))
1099
if(q > vl && isalnumByte(q[-1]))
1101
for(j = 0; prefix[j]; ++j)
1102
if(memEqualCI(q, prefix[j], strlen(prefix[j])))
1106
j = strlen(prefix[j]);
1107
if(!strchr(":-,;", q[j]))
1110
while(q + j < vr && q[j] == ' ')
1112
memcpy(q, q + j, vr - q - j);
1114
--q; /* try again */
1117
strncpy(w->subject, vl, vr - vl);
1118
/* If the subject is really long, spreads onto the next line,
1119
* I'll just use ... */
1120
if(t < end - 1 && (t[1] == ' ' || t[1] == '\t'))
1121
strcat(w->subject, "...");
1122
mhReformat(w->subject);
1126
if(memEqualCI(s, "reply-to:", q - s)) {
1129
strncpy(w->reply, vl, vr - vl);
1133
if(memEqualCI(s, "message-id:", q - s)) {
1136
strncpy(w->mid, vl, vr - vl);
1140
if(memEqualCI(s, "references:", q - s)) {
1143
strncpy(w->ref, vl, vr - vl);
1147
if(memEqualCI(s, "from:", q - s)) {
1152
strncpy(w->from, vl, vr - vl);
1153
mhReformat(w->from);
1157
if(memEqualCI(s, "date:", q - s) || memEqualCI(s, "sent:", q - s)) {
1161
/* don't need the weekday, seconds, or timezone */
1163
isalphaByte(vl[0]) && isalphaByte(vl[1]) && isalphaByte(vl[2]) &&
1164
vl[3] == ',' && vl[4] == ' ')
1166
strncpy(w->date, vl, vr - vl);
1167
q = strrchr(w->date, ':');
1173
if(memEqualCI(s, "to:", q - s)) {
1176
stringAndChar(&w->tolist, &w->tolen, ',');
1177
stringAndBytes(&w->tolist, &w->tolen, q, vr - q);
1180
strncpy(w->to, vl, vr - vl);
1181
/* Only retain the first recipient */
1183
for(q = w->to; *q; ++q) {
1184
if(*q == ',' && !quote) {
1185
w->andOthers = true;
1191
else if(quote == *q)
1206
*q = 0; /* cut it off at the comma */
1210
if(memEqualCI(s, "cc:", q - s)) {
1213
stringAndChar(&w->cclist, &w->cclen, ',');
1214
stringAndBytes(&w->cclist, &w->cclen, q, vr - q);
1215
w->andOthers = true;
1219
if(memEqualCI(s, "content-type:", q - s)) {
1221
if(memEqualCI(vl, "text", 4))
1223
if(memEqualCI(vl, "text/html", 9))
1225
if(memEqualCI(vl, "text/plain", 10))
1227
if(memEqualCI(vl, "application", 11))
1229
if(memEqualCI(vl, "multipart", 9))
1231
if(memEqualCI(vl, "multipart/alternative", 21))
1238
if(memEqualCI(s, "content-transfer-encoding:", q - s)) {
1240
if(memEqualCI(vl, "quoted-printable", 16))
1242
if(memEqualCI(vl, "7bit", 4))
1244
if(memEqualCI(vl, "8bit", 4))
1246
if(memEqualCI(vl, "base64", 6))
1252
} /* loop over lines */
1254
/* make sure there's room for a final nl */
1255
stringAndChar(&w->tolist, &w->tolen, ' ');
1256
stringAndChar(&w->cclist, &w->cclen, ' ');
1257
extractEmailAddresses(w->tolist);
1258
extractEmailAddresses(w->cclist);
1260
w->start = start = s + 1;
1262
/* Fix up reply and from lines.
1263
* From should be the name, reply the address. */
1265
strcpy(w->from, w->reply);
1267
strcpy(w->reply, w->from);
1268
if(w->from[0] == '"') {
1269
strcpy(w->from, w->from + 1);
1270
q = strchr(w->from, '"');
1274
vl = strchr(w->from, '<');
1275
vr = strchr(w->from, '>');
1276
if(vl && vr && vl < vr) {
1277
while(vl > w->from && vl[-1] == ' ')
1281
extractLessGreater(w->reply);
1282
/* get rid of (name) comment */
1283
vl = strchr(w->reply, '(');
1284
vr = strchr(w->reply, ')');
1285
if(vl && vr && vl < vr) {
1286
while(vl > w->reply && vl[-1] == ' ')
1290
/* no @ means it's not an email address */
1291
if(!strchr(w->reply, '@'))
1293
if(stringEqual(w->reply, w->from))
1295
extractLessGreater(w->to);
1296
extractLessGreater(w->mid);
1297
extractLessGreater(w->ref);
1299
cutDuplicateEmails(w->tolist, w->cclist, w->reply);
1300
if(debugLevel >= 5) {
1301
puts("mail header analyzed");
1302
printf("subject: %s\n", w->subject);
1303
printf("from: %s\n", w->from);
1304
printf("date: %s\n", w->date);
1305
printf("reply: %s\n", w->reply);
1306
printf("tolist: %s\n", w->tolist);
1307
printf("cclist: %s\n", w->cclist);
1308
printf("reference: %s\n", w->ref);
1309
printf("message: %s\n", w->mid);
1310
printf("boundary: %d|%s\n", w->boundlen, w->boundary);
1311
printf("filename: %s\n", w->cfn);
1312
printf("content %d/%d\n", w->ct, w->ce);
1319
if(w->ce == CE_64 && w->ct == CT_OTHER || w->ct == CT_APPLIC || w->cfn[0]) {
1323
if(*q) { /* name present */
1324
if(stringEqual(q, "winmail.dat")) {
1327
} else if((q = strrchr(q, '.'))) {
1328
static const char *const imagelist[] = {
1329
"gif", "jpg", "tif", "bmp", "asc", "png", 0
1331
/* the asc isn't an image, it's a signature card. */
1332
/* Similarly for the winmail.dat */
1333
if(stringInListCI(imagelist, q + 1) >= 0) {
1338
if(!w->atimage && nattach == nimages + 1)
1339
firstAttach = w->cfn;
1344
/* loop over the mime components */
1345
if(w->ct == CT_MULTI || w->ct == CT_ALT) {
1346
char *lastbound = 0;
1347
bool endmode = false;
1348
struct MHINFO *child;
1349
/* We really need the -1 here, because sometimes the boundary will
1350
* be the very first thing in the message body. */
1352
while(!endmode && (t = strstr(s, "\n--")) && t < end) {
1353
if(memcmp(t + 3, w->boundary, w->boundlen)) {
1357
q = t + 3 + w->boundlen;
1359
endmode = true, ++q;
1362
debugPrint(5, "boundary found at offset %d", t - w->start);
1364
child = headerGlean(lastbound, t);
1365
addToListBack(&w->components, child);
1369
w->start = w->end = 0;
1374
/* Scan through, we might have a mail message included inline */
1375
vl = 0; /* first mail header keyword line */
1376
for(s = start; s < end; s = t + 1) {
1378
t = strchr(s, '\n');
1380
t = end - 1; /* should never happen */
1381
if(t == s) { /* empty line */
1384
/* Do we have enough for a mail header? */
1385
if(k >= 4 && k * 2 >= j) {
1386
struct MHINFO *child = headerGlean(vl, end);
1387
addToListBack(&w->components, child);
1390
} /* found mail message inside */
1393
if(first == ' ' || first == '\t')
1394
continue; /* indented */
1395
for(q = s; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ;
1396
if(q == s || *q != ':') {
1400
/* looks like header: stuff */
1406
for(n = 0; mhwords[n]; ++n)
1407
if(memEqualCI(mhwords[n], s, q - s))
1411
} /* loop over lines */
1413
/* Header could be at the very end */
1414
if(vl && k >= 4 && k * 2 >= j) {
1415
struct MHINFO *child = headerGlean(vl, end);
1416
addToListBack(&w->components, child);
1421
/* Any additional processing of the text, from start to end, can go here. */
1422
/* Remove leading blank lines or lines with useless words */
1423
for(s = start; s < end; s = t + 1) {
1424
t = strchr(s, '\n');
1426
t = end - 1; /* should never happen */
1428
if(vr - vl >= 4 && memEqualCI(vr - 4, "<br>", 4))
1431
if(isalnumByte(*vl))
1436
if(isalnumByte(vr[-1]))
1441
continue; /* empty */
1442
if(memEqualCI(vl, "forwarded message", vr - vl))
1444
if(memEqualCI(vl, "original message", vr - vl))
1446
break; /* something real */
1448
w->start = start = s;
1456
headerShow(struct MHINFO *w, bool top)
1458
static char buf[(MHLINE + 30) * 4];
1459
static char lastsubject[MHLINE];
1464
if(!(w->subject[0] | w->from[0] | w->reply[0]))
1468
strcpy(buf, "Message");
1471
strcat(buf, " from ");
1472
strcat(buf, w->from);
1475
if(stringEqual(w->subject, lastsubject)) {
1476
strcat(buf, " with the same subject");
1478
strcat(buf, " with subject: ");
1479
strcat(buf, w->subject);
1482
strcat(buf, " with no subject");
1483
if(mailIsHtml) { /* trash & < > */
1484
for(s = buf; *s; ++s) {
1485
/* This is quick and stupid */
1494
/* need a dot at the end? */
1495
s = buf + strlen(buf);
1496
if(isalnumByte(s[-1]))
1498
strcpy(s, mailIsHtml ? "\n<br>" : "\n");
1500
strcat(buf, "Sent ");
1501
strcat(buf, w->date);
1505
strcat(buf, "From ");
1507
strcat(buf, " from ");
1508
strcat(buf, w->reply);
1510
if(w->date[0] | w->reply[0]) { /* second line */
1515
/* This is at the top of the file */
1517
sprintf(buf, "Subject: %s\n", w->subject);
1520
if(nattach && ismc) {
1522
if(lines & mailIsHtml)
1523
strcat(buf, "<br>");
1526
sprintf(atbuf, "%d images\n", nimages);
1528
strcpy(atbuf, "1 image");
1530
if(nattach > nimages)
1533
if(nattach == nimages + 1) {
1534
strcat(buf, "1 attachment");
1535
if(firstAttach && firstAttach[0]) {
1537
strcat(buf, firstAttach);
1540
if(nattach > nimages + 1) {
1541
sprintf(atbuf, "%d attachments\n", nattach - nimages);
1547
if(w->to[0] && !ismc) {
1548
if(lines & mailIsHtml)
1549
strcat(buf, "<br>");
1554
strcat(buf, " and others");
1558
if(lines & mailIsHtml)
1559
strcat(buf, "<br>");
1561
strcat(buf, "From ");
1562
strcat(buf, w->from);
1565
if(w->date[0] && !ismc) {
1566
if(lines & mailIsHtml)
1567
strcat(buf, "<br>");
1569
strcat(buf, "Mail sent ");
1570
strcat(buf, w->date);
1574
if(lines & mailIsHtml)
1575
strcat(buf, "<br>");
1577
strcat(buf, "Reply to ");
1578
strcat(buf, w->reply);
1584
strcat(buf, mailIsHtml ? "<P>\n" : "\n");
1585
strcpy(lastsubject, w->subject);
1589
/* Depth first block of text determines the type */
1591
mailTextType(struct MHINFO *w)
1594
int texttype = CT_OTHER, rc;
1599
/* jump right into the hard part, multi/alt */
1600
if(w->ct >= CT_MULTI) {
1601
foreach(v, w->components) {
1602
rc = mailTextType(v);
1607
if(w->ct == CT_MULTI)
1615
/* If there is no text, return nothing */
1616
if(w->start == w->end)
1618
/* I don't know if this is right, but I override the type,
1619
* and make it html, if we start out with <html> */
1620
if(memEqualCI(w->start, "<html>", 6))
1622
return w->ct == CT_HTML ? CT_HTML : CT_TEXT;
1623
} /* mailTextType */
1626
formatMail(struct MHINFO *w, bool top)
1634
debugPrint(5, "format headers for content %d subject %s", ct, w->subject);
1635
stringAndString(&fm, &fm_l, headerShow(w, top));
1638
char *start = w->start;
1641
/* If mail is not in html, reformat it */
1645
if(breakLine(start, end - start, &newlen)) {
1646
start = replaceLine;
1647
end = start + newlen;
1650
if(mailIsHtml && ct != CT_HTML)
1651
stringAndString(&fm, &fm_l, "<pre>");
1652
stringAndBytes(&fm, &fm_l, start, end - start);
1653
if(mailIsHtml && ct != CT_HTML)
1654
stringAndString(&fm, &fm_l, "</pre>\n");
1658
/* There could be a mail message inline */
1659
foreach(v, w->components) {
1661
stringAndString(&fm, &fm_l, mailIsHtml ? "<P>\n" : "\n");
1662
formatMail(v, false);
1668
if(ct == CT_MULTI) {
1669
foreach(v, w->components)
1670
formatMail(v, false);
1674
/* alternate presentations, pick the best one */
1676
foreach(v, w->components) {
1677
int subtype = mailTextType(v);
1679
if(subtype != CT_OTHER)
1681
if(mailIsHtml && subtype == CT_HTML ||
1682
!mailIsHtml && subtype == CT_TEXT)
1689
foreach(v, w->components) {
1693
formatMail(v, false);
1698
/* Browse the email file. */
1700
emailParse(char *buf)
1702
struct MHINFO *w, *v;
1703
nattach = nimages = 0;
1705
mailIsHtml = mailIsSpam = ignoreImages = false;
1706
fm = initString(&fm_l);
1707
w = headerGlean(buf, buf + strlen(buf));
1708
mailIsHtml = (mailTextType(w) == CT_HTML);
1711
else if(w->ct == CT_ALT) {
1712
foreach(v, w->components)
1717
stringAndString(&fm, &fm_l, "<html>\n");
1718
formatMail(w, true);
1719
/* Remember, we always need a nonzero buffer */
1720
if(!fm_l || fm[fm_l - 1] != '\n')
1721
stringAndChar(&fm, &fm_l, '\n');
1723
writeAttachments(w);
1725
allocMem(strlen(w->ref) + strlen(w->mid) + strlen(w->tolist) +
1726
strlen(w->cclist) + strlen(w->reply) + 6);
1727
sprintf(cw->mailInfo, "%s>%s>%s>%s>%s>",
1728
w->reply, w->tolist, w->cclist, w->ref, w->mid);
1731
debugPrint(5, "mailInfo: %s", cw->mailInfo);
1740
/*********************************************************************
1742
This looks at the first 5 lines, which could contain
1748
in no particular order.
1749
Move replyt to the top and get rid of the others.
1750
Then, if you have browsed a mail file,
1751
grab the message id and reference it.
1752
Also, if mailing to all, stick in the other recipients.
1753
*********************************************************************/
1756
setupReply(bool all)
1766
setError(MSG_ReDir);
1776
setError(MSG_ReEmpty);
1781
setError(MSG_ReBinary);
1786
strcpy(linetype, " xxxxxx");
1787
for(j = 1; j <= 6; ++j) {
1791
char *p = (char *)fetchLine(j, -1);
1793
if(memEqualCI(p, "subject:", 8)) {
1799
if(memEqualCI(p, "to ", 3)) {
1804
if(memEqualCI(p, "from ", 5)) {
1809
if(memEqualCI(p, "mail sent ", 10)) {
1814
if(memEqualCI(p, "reply to ", 9)) {
1820
/* This one has to be last. */
1821
while(isdigitByte(*p))
1823
if(memEqualCI(p, " attachment", 11) || memEqualCI(p, " image", 6)) {
1831
if(!subln || !repln) {
1832
setError(MSG_ReSubjectReply);
1836
/* delete the lines we don't need */
1837
for(j = 6; j >= 1; --j) {
1838
if(!strchr("wfta", linetype[j]))
1841
strcpy(linetype + j, linetype + j + 1);
1844
/* move reply to 1, if it isn't already there */
1845
if(linetype[1] != 'r') {
1846
char *map = cw->map;
1848
char *q1 = map + LNWIDTH;
1849
char *q2 = map + LNWIDTH * 2;
1850
memcpy(swap, q1, LNWIDTH);
1851
memcpy(q1, q2, LNWIDTH);
1852
memcpy(q2, swap, LNWIDTH);
1856
cw->browseMode = false;
1858
setError(MSG_ReNoInfo);
1861
return true; /* that's all we can do */
1864
/* Build the header lines and put them in the buffer */
1865
out = initString(&j);
1866
/* step through the to list */
1867
s = strchr(cw->mailInfo, '>') + 1;
1871
stringAndString(&out, &j, "to: ");
1872
stringAndBytes(&out, &j, s, t - s);
1873
stringAndChar(&out, &j, '\n');
1878
/* step through the cc list */
1883
stringAndString(&out, &j, "cc: ");
1884
stringAndBytes(&out, &j, s, t - s);
1885
stringAndChar(&out, &j, '\n');
1895
stringAndString(&out, &j, "references: <");
1897
stringAndBytes(&out, &j, s, t - s);
1898
stringAndString(&out, &j, "> <");
1900
stringAndString(&out, &j, t + 1);
1901
stringAndChar(&out, &j, '\n');
1906
rc = addTextToBuffer((unsigned char *)out, j, 1);
1908
cw->browseMode = false;