Drop SNAIL_ nonsense, use plain NAIL_..
[s-mailx.git] / head.c
blob0c8396eada42d665be37069fabcca0847fe2bc03
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "rcv.h"
41 #include "extern.h"
42 #include <time.h>
45 * Mail -- a mail program
47 * Routines for processing and detecting headlines.
50 static char *copyin(char *src, char **space);
51 static char *nextword(char *wp, char *wbuf);
52 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
53 char **colon);
54 static int msgidnextc(const char **cp, int *status);
55 static int charcount(char *str, int c);
58 * See if the passed line buffer is a mail header.
59 * Return true if yes. POSIX.2 leaves the content
60 * following 'From ' unspecified, so don't care about
61 * it.
63 /*ARGSUSED 2*/
64 int
65 is_head(char *linebuf, size_t linelen)
67 char *cp;
68 (void)linelen;
70 cp = linebuf;
71 if (*cp++ != 'F' || *cp++ != 'r' || *cp++ != 'o' || *cp++ != 'm' ||
72 *cp++ != ' ')
73 return (0);
74 return(1);
78 * Split a headline into its useful components.
79 * Copy the line into dynamic string space, then set
80 * pointers into the copied line in the passed headline
81 * structure. Actually, it scans.
83 void
84 parse(char *line, size_t linelen, struct headline *hl, char *pbuf)
86 char *cp;
87 char *sp;
88 char *word;
90 hl->l_from = NULL;
91 hl->l_tty = NULL;
92 hl->l_date = NULL;
93 cp = line;
94 sp = pbuf;
95 word = ac_alloc(linelen + 1);
97 * Skip over "From" first.
99 cp = nextword(cp, word);
100 cp = nextword(cp, word);
101 if (*word)
102 hl->l_from = copyin(word, &sp);
103 if (cp != NULL && cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
104 cp = nextword(cp, word);
105 hl->l_tty = copyin(word, &sp);
107 if (cp != NULL)
108 hl->l_date = copyin(cp, &sp);
109 else
110 hl->l_date = catgets(catd, CATSET, 213, "<Unknown date>");
111 ac_free(word);
115 * Copy the string on the left into the string on the right
116 * and bump the right (reference) string pointer by the length.
117 * Thus, dynamically allocate space in the right string, copying
118 * the left string into it.
120 static char *
121 copyin(char *src, char **space)
123 char *cp;
124 char *top;
126 top = cp = *space;
127 while ((*cp++ = *src++) != '\0')
129 *space = cp;
130 return (top);
133 #ifdef notdef
134 static int cmatch(char *, char *);
136 * Test to see if the passed string is a ctime(3) generated
137 * date string as documented in the manual. The template
138 * below is used as the criterion of correctness.
139 * Also, we check for a possible trailing time zone using
140 * the tmztype template.
144 * 'A' An upper case char
145 * 'a' A lower case char
146 * ' ' A space
147 * '0' A digit
148 * 'O' An optional digit or space
149 * ':' A colon
150 * '+' A sign
151 * 'N' A new line
153 static char *tmztype[] = {
154 "Aaa Aaa O0 00:00:00 0000",
155 "Aaa Aaa O0 00:00 0000",
156 "Aaa Aaa O0 00:00:00 AAA 0000",
157 "Aaa Aaa O0 00:00 AAA 0000",
159 * Sommer time, e.g. MET DST
161 "Aaa Aaa O0 00:00:00 AAA AAA 0000",
162 "Aaa Aaa O0 00:00 AAA AAA 0000",
164 * time zone offset, e.g.
165 * +0200 or +0200 MET or +0200 MET DST
167 "Aaa Aaa O0 00:00:00 +0000 0000",
168 "Aaa Aaa O0 00:00 +0000 0000",
169 "Aaa Aaa O0 00:00:00 +0000 AAA 0000",
170 "Aaa Aaa O0 00:00 +0000 AAA 0000",
171 "Aaa Aaa O0 00:00:00 +0000 AAA AAA 0000",
172 "Aaa Aaa O0 00:00 +0000 AAA AAA 0000",
174 * time zone offset without time zone specification (pine)
176 "Aaa Aaa O0 00:00:00 0000 +0000",
177 NULL,
180 static int
181 is_date(char *date)
183 int ret = 0, form = 0;
185 while (tmztype[form]) {
186 if ( (ret = cmatch(date, tmztype[form])) == 1 )
187 break;
188 form++;
191 return ret;
195 * Match the given string (cp) against the given template (tp).
196 * Return 1 if they match, 0 if they don't
198 static int
199 cmatch(char *cp, char *tp)
201 int c;
203 while (*cp && *tp)
204 switch (*tp++) {
205 case 'a':
206 if (c = *cp++, !lowerchar(c))
207 return 0;
208 break;
209 case 'A':
210 if (c = *cp++, !upperchar(c))
211 return 0;
212 break;
213 case ' ':
214 if (*cp++ != ' ')
215 return 0;
216 break;
217 case '0':
218 if (c = *cp++, !digitchar(c))
219 return 0;
220 break;
221 case 'O':
222 if (c = *cp, c != ' ' && !digitchar(c))
223 return 0;
224 cp++;
225 break;
226 case ':':
227 if (*cp++ != ':')
228 return 0;
229 break;
230 case '+':
231 if (*cp != '+' && *cp != '-')
232 return 0;
233 cp++;
234 break;
235 case 'N':
236 if (*cp++ != '\n')
237 return 0;
238 break;
240 if (*cp || *tp)
241 return 0;
242 return (1);
244 #endif /* notdef */
247 * Collect a liberal (space, tab delimited) word into the word buffer
248 * passed. Also, return a pointer to the next word following that,
249 * or NULL if none follow.
251 static char *
252 nextword(char *wp, char *wbuf)
254 int c;
256 if (wp == NULL) {
257 *wbuf = 0;
258 return (NULL);
260 while ((c = *wp++) != '\0' && !blankchar(c)) {
261 *wbuf++ = c;
262 if (c == '"') {
263 while ((c = *wp++) != '\0' && c != '"')
264 *wbuf++ = c;
265 if (c == '"')
266 *wbuf++ = c;
267 else
268 wp--;
271 *wbuf = '\0';
272 for (; blankchar(c); c = *wp++)
274 if (c == 0)
275 return (NULL);
276 return (wp - 1);
279 void
280 extract_header(FILE *fp, struct header *hp)
282 char *linebuf = NULL;
283 size_t linesize = 0;
284 int seenfields = 0;
285 char *colon, *cp, *value;
286 struct header nh;
287 struct header *hq = &nh;
288 int lc, c;
290 memset(hq, 0, sizeof *hq);
291 for (lc = 0; readline(fp, &linebuf, &linesize) > 0; lc++);
292 rewind(fp);
293 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
294 if ((value = thisfield(linebuf, "to")) != NULL) {
295 seenfields++;
296 hq->h_to = checkaddrs(cat(hq->h_to,
297 sextract(value, GTO|GFULL)));
298 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
299 seenfields++;
300 hq->h_cc = checkaddrs(cat(hq->h_cc,
301 sextract(value, GCC|GFULL)));
302 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
303 seenfields++;
304 hq->h_bcc = checkaddrs(cat(hq->h_bcc,
305 sextract(value, GBCC|GFULL)));
306 } else if ((value = thisfield(linebuf, "from")) != NULL) {
307 seenfields++;
308 hq->h_from = checkaddrs(cat(hq->h_from,
309 sextract(value, GEXTRA|GFULL)));
310 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
311 seenfields++;
312 hq->h_replyto = checkaddrs(cat(hq->h_replyto,
313 sextract(value, GEXTRA|GFULL)));
314 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
315 seenfields++;
316 hq->h_sender = checkaddrs(cat(hq->h_sender,
317 sextract(value, GEXTRA|GFULL)));
318 } else if ((value = thisfield(linebuf,
319 "organization")) != NULL) {
320 seenfields++;
321 for (cp = value; blankchar(*cp & 0377); cp++);
322 hq->h_organization = hq->h_organization ?
323 save2str(hq->h_organization, cp) :
324 savestr(cp);
325 } else if ((value = thisfield(linebuf, "subject")) != NULL ||
326 (value = thisfield(linebuf, "subj")) != NULL) {
327 seenfields++;
328 for (cp = value; blankchar(*cp & 0377); cp++);
329 hq->h_subject = hq->h_subject ?
330 save2str(hq->h_subject, cp) :
331 savestr(cp);
332 } else
333 fprintf(stderr, catgets(catd, CATSET, 266,
334 "Ignoring header field \"%s\"\n"),
335 linebuf);
338 * In case the blank line after the header has been edited out.
339 * Otherwise, fetch the header separator.
341 if (linebuf) {
342 if (linebuf[0] != '\0') {
343 for (cp = linebuf; *(++cp) != '\0'; );
344 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
345 } else {
346 if ((c = getc(fp)) != '\n' && c != EOF)
347 ungetc(c, fp);
350 if (seenfields) {
351 hp->h_to = hq->h_to;
352 hp->h_cc = hq->h_cc;
353 hp->h_bcc = hq->h_bcc;
354 hp->h_from = hq->h_from;
355 hp->h_replyto = hq->h_replyto;
356 hp->h_sender = hq->h_sender;
357 hp->h_organization = hq->h_organization;
358 hp->h_subject = hq->h_subject;
359 } else
360 fprintf(stderr, catgets(catd, CATSET, 267,
361 "Restoring deleted header lines\n"));
362 if (linebuf)
363 free(linebuf);
367 * Return the desired header line from the passed message
368 * pointer (or NULL if the desired header field is not available).
369 * If mult is zero, return the content of the first matching header
370 * field only, the content of all matching header fields else.
372 char *
373 hfield_mult(char *field, struct message *mp, int mult)
375 FILE *ibuf;
376 char *linebuf = NULL;
377 size_t linesize = 0;
378 int lc;
379 char *hfield;
380 char *colon, *oldhfield = NULL;
382 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
383 return NULL;
384 if ((lc = mp->m_lines - 1) < 0)
385 return NULL;
386 if ((mp->m_flag & MNOFROM) == 0) {
387 if (readline(ibuf, &linebuf, &linesize) < 0) {
388 if (linebuf)
389 free(linebuf);
390 return NULL;
393 while (lc > 0) {
394 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon))
395 < 0) {
396 if (linebuf)
397 free(linebuf);
398 return oldhfield;
400 if ((hfield = thisfield(linebuf, field)) != NULL) {
401 oldhfield = save2str(hfield, oldhfield);
402 if (mult == 0)
403 break;
406 if (linebuf)
407 free(linebuf);
408 return oldhfield;
412 * Return the next header field found in the given message.
413 * Return >= 0 if something found, < 0 elsewise.
414 * "colon" is set to point to the colon in the header.
415 * Must deal with \ continuations & other such fraud.
417 static int
418 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
420 char *line2 = NULL;
421 size_t line2size = 0;
422 char *cp, *cp2;
423 int c, isenc;
425 if (*linebuf == NULL)
426 *linebuf = srealloc(*linebuf, *linesize = 1);
427 **linebuf = '\0';
428 for (;;) {
429 if (--rem < 0)
430 return -1;
431 if ((c = readline(f, linebuf, linesize)) <= 0)
432 return -1;
433 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
434 if (cp > *linebuf)
435 while (blankchar(*cp & 0377))
436 cp++;
437 if (*cp != ':' || cp == *linebuf)
438 continue;
440 * I guess we got a headline.
441 * Handle wraparounding
443 *colon = cp;
444 cp = *linebuf + c;
445 for (;;) {
446 isenc = 0;
447 while (--cp >= *linebuf && blankchar(*cp & 0377));
448 cp++;
449 if (rem <= 0)
450 break;
451 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
452 isenc |= 1;
453 ungetc(c = getc(f), f);
454 if (!blankchar(c))
455 break;
456 if ((c = readline(f, &line2, &line2size)) < 0)
457 break;
458 rem--;
459 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
460 c -= cp2 - line2;
461 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
462 isenc |= 2;
463 if (cp + c >= *linebuf + *linesize - 2) {
464 size_t diff = cp - *linebuf;
465 size_t colondiff = *colon - *linebuf;
466 *linebuf = srealloc(*linebuf,
467 *linesize += c + 2);
468 cp = &(*linebuf)[diff];
469 *colon = &(*linebuf)[colondiff];
471 if (isenc != 3)
472 *cp++ = ' ';
473 memcpy(cp, cp2, c);
474 cp += c;
476 *cp = 0;
477 if (line2)
478 free(line2);
479 return rem;
481 /* NOTREACHED */
485 * Check whether the passed line is a header line of
486 * the desired breed. Return the field body, or 0.
488 char *
489 thisfield(const char *linebuf, const char *field)
491 while (lowerconv(*linebuf&0377) == lowerconv(*field&0377)) {
492 linebuf++;
493 field++;
495 if (*field != '\0')
496 return NULL;
497 while (blankchar(*linebuf&0377))
498 linebuf++;
499 if (*linebuf++ != ':')
500 return NULL;
501 while (blankchar(*linebuf&0377))
502 linebuf++;
503 return (char *)linebuf;
507 * Get sender's name from this message. If the message has
508 * a bunch of arpanet stuff in it, we may have to skin the name
509 * before returning it.
511 char *
512 nameof(struct message *mp, int reptype)
514 char *cp, *cp2;
516 cp = skin(name1(mp, reptype));
517 if (reptype != 0 || charcount(cp, '!') < 2)
518 return(cp);
519 cp2 = strrchr(cp, '!');
520 cp2--;
521 while (cp2 > cp && *cp2 != '!')
522 cp2--;
523 if (*cp2 == '!')
524 return(cp2 + 1);
525 return(cp);
529 * Start of a "comment".
530 * Ignore it.
532 char *
533 skip_comment(const char *cp)
535 int nesting = 1;
537 for (; nesting > 0 && *cp; cp++) {
538 switch (*cp) {
539 case '\\':
540 if (cp[1])
541 cp++;
542 break;
543 case '(':
544 nesting++;
545 break;
546 case ')':
547 nesting--;
548 break;
551 return (char *)cp;
555 * Return the start of a route-addr (address in angle brackets),
556 * if present.
558 char *
559 routeaddr(const char *name)
561 const char *np, *rp = NULL;
563 for (np = name; *np; np++) {
564 switch (*np) {
565 case '(':
566 np = skip_comment(&np[1]) - 1;
567 break;
568 case '"':
569 while (*np) {
570 if (*++np == '"')
571 break;
572 if (*np == '\\' && np[1])
573 np++;
575 break;
576 case '<':
577 rp = np;
578 break;
579 case '>':
580 return (char *)rp;
583 return NULL;
587 * Skin an arpa net address according to the RFC 822 interpretation
588 * of "host-phrase."
590 char *
591 skin(char *name)
593 int c;
594 char *cp, *cp2;
595 char *bufend;
596 int gotlt, lastsp;
597 char *nbuf;
599 if (name == NULL)
600 return(NULL);
601 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
602 && strchr(name, ' ') == NULL)
603 return(name);
604 gotlt = 0;
605 lastsp = 0;
606 nbuf = ac_alloc(strlen(name) + 1);
607 bufend = nbuf;
608 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
609 switch (c) {
610 case '(':
611 cp = skip_comment(cp);
612 lastsp = 0;
613 break;
615 case '"':
617 * Start of a "quoted-string".
618 * Copy it in its entirety.
620 *cp2++ = c;
621 while ((c = *cp) != '\0') {
622 cp++;
623 if (c == '"') {
624 *cp2++ = c;
625 break;
627 if (c != '\\')
628 *cp2++ = c;
629 else if ((c = *cp) != '\0') {
630 *cp2++ = c;
631 cp++;
634 lastsp = 0;
635 break;
637 case ' ':
638 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
639 cp += 3, *cp2++ = '@';
640 else
641 if (cp[0] == '@' && cp[1] == ' ')
642 cp += 2, *cp2++ = '@';
643 #if 0
645 * RFC 822 specifies spaces are STRIPPED when
646 * in an adress specifier.
648 else
649 lastsp = 1;
650 #endif
651 break;
653 case '<':
654 cp2 = bufend;
655 gotlt++;
656 lastsp = 0;
657 break;
659 case '>':
660 if (gotlt) {
661 gotlt = 0;
662 while ((c = *cp) != '\0' && c != ',') {
663 cp++;
664 if (c == '(')
665 cp = skip_comment(cp);
666 else if (c == '"')
667 while ((c = *cp) != '\0') {
668 cp++;
669 if (c == '"')
670 break;
671 if (c == '\\' && *cp)
672 cp++;
675 lastsp = 0;
676 break;
678 /* Fall into . . . */
680 default:
681 if (lastsp) {
682 lastsp = 0;
683 *cp2++ = ' ';
685 *cp2++ = c;
686 if (c == ',' && !gotlt) {
687 *cp2++ = ' ';
688 for (; *cp == ' '; cp++)
690 lastsp = 0;
691 bufend = cp2;
695 *cp2 = 0;
696 cp = savestr(nbuf);
697 ac_free(nbuf);
698 return cp;
702 * Fetch the real name from an internet mail address field.
704 char *
705 realname(char *name)
707 char *cstart = NULL, *cend = NULL, *cp, *cq;
708 char *rname, *rp;
709 struct str in, out;
710 int quoted, good, nogood;
712 if (name == NULL)
713 return NULL;
714 for (cp = name; *cp; cp++) {
715 switch (*cp) {
716 case '(':
717 if (cstart)
719 * More than one comment in address, doesn't
720 * make sense to display it without context.
721 * Return the entire field,
723 return mime_fromaddr(name);
724 cstart = cp++;
725 cp = skip_comment(cp);
726 cend = cp--;
727 if (cend <= cstart)
728 cend = cstart = NULL;
729 break;
730 case '"':
731 while (*cp) {
732 if (*++cp == '"')
733 break;
734 if (*cp == '\\' && cp[1])
735 cp++;
737 break;
738 case '<':
739 if (cp > name) {
740 cstart = name;
741 cend = cp;
743 break;
744 case ',':
746 * More than one address. Just use the first one.
748 goto brk;
751 brk: if (cstart == NULL) {
752 if (*name == '<')
754 * If name contains only a route-addr, the
755 * surrounding angle brackets don't serve any
756 * useful purpose when displaying, so they
757 * are removed.
759 return prstr(skin(name));
760 return mime_fromaddr(name);
762 rp = rname = ac_alloc(cend - cstart + 1);
764 * Strip quotes. Note that quotes that appear within a MIME-
765 * encoded word are not stripped. The idea is to strip only
766 * syntactical relevant things (but this is not necessarily
767 * the most sensible way in practice).
769 quoted = 0;
770 for (cp = cstart; cp < cend; cp++) {
771 if (*cp == '(' && !quoted) {
772 cq = skip_comment(++cp);
773 if (--cq > cend)
774 cq = cend;
775 while (cp < cq) {
776 if (*cp == '\\' && &cp[1] < cq)
777 cp++;
778 *rp++ = *cp++;
780 } else if (*cp == '\\' && &cp[1] < cend)
781 *rp++ = *++cp;
782 else if (*cp == '"') {
783 quoted = !quoted;
784 continue;
785 } else
786 *rp++ = *cp;
788 *rp = '\0';
789 in.s = rname;
790 in.l = rp - rname;
791 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
792 ac_free(rname);
793 rname = savestr(out.s);
794 free(out.s);
795 while (blankchar(*rname & 0377))
796 rname++;
797 for (rp = rname; *rp; rp++);
798 while (--rp >= rname && blankchar(*rp & 0377))
799 *rp = '\0';
800 if (rp == rname)
801 return mime_fromaddr(name);
803 * mime_fromhdr() has converted all nonprintable characters to
804 * question marks now. These and blanks are considered uninteresting;
805 * if the displayed part of the real name contains more than 25% of
806 * them, it is probably better to display the plain email address
807 * instead.
809 good = 0;
810 nogood = 0;
811 for (rp = rname; *rp && rp < &rname[20]; rp++)
812 if (*rp == '?' || blankchar(*rp & 0377))
813 nogood++;
814 else
815 good++;
816 if (good*3 < nogood)
817 return prstr(skin(name));
818 return rname;
822 * Fetch the sender's name from the passed message.
823 * Reptype can be
824 * 0 -- get sender's name for display purposes
825 * 1 -- get sender's name for reply
826 * 2 -- get sender's name for Reply
828 char *
829 name1(struct message *mp, int reptype)
831 char *namebuf;
832 size_t namesize;
833 char *linebuf = NULL;
834 size_t linesize = 0;
835 char *cp, *cp2;
836 FILE *ibuf;
837 int first = 1;
839 if ((cp = hfield("from", mp)) != NULL && *cp != '\0')
840 return cp;
841 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL &&
842 *cp != '\0')
843 return cp;
844 namebuf = smalloc(namesize = 1);
845 namebuf[0] = 0;
846 if (mp->m_flag & MNOFROM)
847 goto out;
848 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
849 goto out;
850 if (readline(ibuf, &linebuf, &linesize) < 0)
851 goto out;
852 newname:
853 if (namesize <= linesize)
854 namebuf = srealloc(namebuf, namesize = linesize + 1);
855 for (cp = linebuf; *cp && *cp != ' '; cp++)
857 for (; blankchar(*cp & 0377); cp++);
858 for (cp2 = &namebuf[strlen(namebuf)];
859 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
860 *cp2++ = *cp++;
861 *cp2 = '\0';
862 if (readline(ibuf, &linebuf, &linesize) < 0)
863 goto out;
864 if ((cp = strchr(linebuf, 'F')) == NULL)
865 goto out;
866 if (strncmp(cp, "From", 4) != 0)
867 goto out;
868 if (namesize <= linesize)
869 namebuf = srealloc(namebuf, namesize = linesize + 1);
870 while ((cp = strchr(cp, 'r')) != NULL) {
871 if (strncmp(cp, "remote", 6) == 0) {
872 if ((cp = strchr(cp, 'f')) == NULL)
873 break;
874 if (strncmp(cp, "from", 4) != 0)
875 break;
876 if ((cp = strchr(cp, ' ')) == NULL)
877 break;
878 cp++;
879 if (first) {
880 strncpy(namebuf, cp, namesize);
881 first = 0;
882 } else {
883 cp2=strrchr(namebuf, '!')+1;
884 strncpy(cp2, cp, (namebuf+namesize)-cp2);
886 namebuf[namesize-2]='\0';
887 strcat(namebuf, "!");
888 goto newname;
890 cp++;
892 out:
893 if (*namebuf != '\0' || ((cp = hfield("return-path", mp))) == NULL ||
894 *cp == '\0')
895 cp = savestr(namebuf);
896 if (linebuf)
897 free(linebuf);
898 free(namebuf);
899 return cp;
902 static int
903 msgidnextc(const char **cp, int *status)
905 int c;
907 for (;;) {
908 if (*status & 01) {
909 if (**cp == '"') {
910 *status &= ~01;
911 (*cp)++;
912 continue;
914 if (**cp == '\\') {
915 (*cp)++;
916 if (**cp == '\0')
917 goto eof;
919 goto dfl;
921 switch (**cp) {
922 case '(':
923 *cp = skip_comment(&(*cp)[1]);
924 continue;
925 case '>':
926 case '\0':
927 eof:
928 return '\0';
929 case '"':
930 (*cp)++;
931 *status |= 01;
932 continue;
933 case '@':
934 *status |= 02;
935 /*FALLTHRU*/
936 default:
937 dfl:
938 c = *(*cp)++ & 0377;
939 return *status & 02 ? lowerconv(c) : c;
944 int
945 msgidcmp(const char *s1, const char *s2)
947 int q1 = 0, q2 = 0;
948 int c1, c2;
950 do {
951 c1 = msgidnextc(&s1, &q1);
952 c2 = msgidnextc(&s2, &q2);
953 if (c1 != c2)
954 return c1 - c2;
955 } while (c1 && c2);
956 return c1 - c2;
960 * Count the occurances of c in str
962 static int
963 charcount(char *str, int c)
965 char *cp;
966 int i;
968 for (i = 0, cp = str; *cp; cp++)
969 if (*cp == c)
970 i++;
971 return(i);
975 * See if the given header field is supposed to be ignored.
978 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
980 char *realfld;
981 int ret;
983 if (ignore == NULL)
984 return 0;
985 if (ignore == allignore)
986 return 1;
988 * Lower-case the string, so that "Status" and "status"
989 * will hash to the same place.
991 realfld = ac_alloc(fieldlen + 1);
992 i_strcpy(realfld, field, fieldlen + 1);
993 if (ignore[1].i_count > 0)
994 ret = !member(realfld, ignore + 1);
995 else
996 ret = member(realfld, ignore);
997 ac_free(realfld);
998 return ret;
1001 int
1002 member(char *realfield, struct ignoretab *table)
1004 struct ignore *igp;
1006 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1007 if (*igp->i_field == *realfield &&
1008 equal(igp->i_field, realfield))
1009 return (1);
1010 return (0);
1014 * Fake Sender for From_ lines if missing, e. g. with POP3.
1016 char *
1017 fakefrom(struct message *mp)
1019 char *name;
1021 if (((name = skin(hfield("return-path", mp))) == NULL ||
1022 *name == '\0' ) &&
1023 ((name = skin(hfield("from", mp))) == NULL ||
1024 *name == '\0'))
1025 name = "-";
1026 return name;
1029 char *
1030 fakedate(time_t t)
1032 char *cp, *cq;
1034 cp = ctime(&t);
1035 for (cq = cp; *cq && *cq != '\n'; cq++);
1036 *cq = '\0';
1037 return savestr(cp);
1040 char *
1041 nexttoken(char *cp)
1043 for (;;) {
1044 if (*cp == '\0')
1045 return NULL;
1046 if (*cp == '(') {
1047 int nesting = 0;
1049 while (*cp != '\0') {
1050 switch (*cp++) {
1051 case '(':
1052 nesting++;
1053 break;
1054 case ')':
1055 nesting--;
1056 break;
1058 if (nesting <= 0)
1059 break;
1061 } else if (blankchar(*cp & 0377) || *cp == ',')
1062 cp++;
1063 else
1064 break;
1066 return cp;
1070 * From username Fri Jan 2 20:13:51 2004
1071 * | | | | |
1072 * 0 5 10 15 20
1074 time_t
1075 unixtime(char *from)
1077 char *fp, *xp;
1078 time_t t;
1079 int i, year, month, day, hour, minute, second;
1080 int tzdiff;
1081 struct tm *tmptr;
1083 for (fp = from; *fp && *fp != '\n'; fp++);
1084 fp -= 24;
1085 if (fp - from < 7)
1086 goto invalid;
1087 if (fp[3] != ' ')
1088 goto invalid;
1089 for (i = 0; month_names[i]; i++)
1090 if (strncmp(&fp[4], month_names[i], 3) == 0)
1091 break;
1092 if (month_names[i] == 0)
1093 goto invalid;
1094 month = i + 1;
1095 if (fp[7] != ' ')
1096 goto invalid;
1097 day = strtol(&fp[8], &xp, 10);
1098 if (*xp != ' ' || xp != &fp[10])
1099 goto invalid;
1100 hour = strtol(&fp[11], &xp, 10);
1101 if (*xp != ':' || xp != &fp[13])
1102 goto invalid;
1103 minute = strtol(&fp[14], &xp, 10);
1104 if (*xp != ':' || xp != &fp[16])
1105 goto invalid;
1106 second = strtol(&fp[17], &xp, 10);
1107 if (*xp != ' ' || xp != &fp[19])
1108 goto invalid;
1109 year = strtol(&fp[20], &xp, 10);
1110 if (xp != &fp[24])
1111 goto invalid;
1112 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1113 (time_t)-1)
1114 goto invalid;
1115 tzdiff = t - mktime(gmtime(&t));
1116 tmptr = localtime(&t);
1117 if (tmptr->tm_isdst > 0)
1118 tzdiff += 3600;
1119 t -= tzdiff;
1120 return t;
1121 invalid:
1122 time(&t);
1123 return t;
1126 time_t
1127 rfctime(char *date)
1129 char *cp = date, *x;
1130 time_t t;
1131 int i, year, month, day, hour, minute, second;
1133 if ((cp = nexttoken(cp)) == NULL)
1134 goto invalid;
1135 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1136 alphachar(cp[2] & 0377) && cp[3] == ',') {
1137 if ((cp = nexttoken(&cp[4])) == NULL)
1138 goto invalid;
1140 day = strtol(cp, &x, 10);
1141 if ((cp = nexttoken(x)) == NULL)
1142 goto invalid;
1143 for (i = 0; month_names[i]; i++) {
1144 if (strncmp(cp, month_names[i], 3) == 0)
1145 break;
1147 if (month_names[i] == NULL)
1148 goto invalid;
1149 month = i + 1;
1150 if ((cp = nexttoken(&cp[3])) == NULL)
1151 goto invalid;
1152 year = strtol(cp, &x, 10);
1153 if ((cp = nexttoken(x)) == NULL)
1154 goto invalid;
1155 hour = strtol(cp, &x, 10);
1156 if (*x != ':')
1157 goto invalid;
1158 cp = &x[1];
1159 minute = strtol(cp, &x, 10);
1160 if (*x == ':') {
1161 cp = &x[1];
1162 second = strtol(cp, &x, 10);
1163 } else
1164 second = 0;
1165 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1166 (time_t)-1)
1167 goto invalid;
1168 if ((cp = nexttoken(x)) != NULL) {
1169 int sign = -1;
1170 char buf[3];
1172 switch (*cp) {
1173 case '-':
1174 sign = 1;
1175 /*FALLTHRU*/
1176 case '+':
1177 cp++;
1179 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1180 digitchar(cp[2] & 0377) &&
1181 digitchar(cp[3] & 0377)) {
1182 buf[2] = '\0';
1183 buf[0] = cp[0];
1184 buf[1] = cp[1];
1185 t += strtol(buf, NULL, 10) * sign * 3600;
1186 buf[0] = cp[2];
1187 buf[1] = cp[3];
1188 t += strtol(buf, NULL, 10) * sign * 60;
1191 return t;
1192 invalid:
1193 return 0;
1196 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1198 time_t
1199 combinetime(int year, int month, int day, int hour, int minute, int second)
1201 time_t t;
1203 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1204 return -1;
1205 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1206 if (year < 70)
1207 year += 2000;
1208 else if (year < 1900)
1209 year += 1900;
1210 if (month > 1)
1211 t += 86400 * 31;
1212 if (month > 2)
1213 t += 86400 * (leapyear(year) ? 29 : 28);
1214 if (month > 3)
1215 t += 86400 * 31;
1216 if (month > 4)
1217 t += 86400 * 30;
1218 if (month > 5)
1219 t += 86400 * 31;
1220 if (month > 6)
1221 t += 86400 * 30;
1222 if (month > 7)
1223 t += 86400 * 31;
1224 if (month > 8)
1225 t += 86400 * 31;
1226 if (month > 9)
1227 t += 86400 * 30;
1228 if (month > 10)
1229 t += 86400 * 31;
1230 if (month > 11)
1231 t += 86400 * 30;
1232 year -= 1900;
1233 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1234 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1235 return t;
1238 void
1239 substdate(struct message *m)
1241 char *cp;
1242 time_t now;
1245 * Determine the date to print in faked 'From ' lines. This is
1246 * traditionally the date the message was written to the mail
1247 * file. Try to determine this using RFC message header fields,
1248 * or fall back to current time.
1250 time(&now);
1251 if ((cp = hfield_mult("received", m, 0)) != NULL) {
1252 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1254 cp++;
1255 while (alnumchar(*cp & 0377));
1257 if (cp && *++cp)
1258 m->m_time = rfctime(cp);
1260 if (m->m_time == 0 || m->m_time > now)
1261 if ((cp = hfield("date", m)) != NULL)
1262 m->m_time = rfctime(cp);
1263 if (m->m_time == 0 || m->m_time > now)
1264 m->m_time = now;
1268 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1270 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1271 fprintf(stderr, "A Sender: field is required with multiple "
1272 "addresses in From: field.\n");
1273 return 1;
1275 if (senderfield && senderfield->n_flink) {
1276 fprintf(stderr, "The Sender: field may contain "
1277 "only one address.\n");
1278 return 2;
1280 return 0;
1283 char *
1284 getsender(struct message *mp)
1286 char *cp;
1287 struct name *np;
1289 if ((cp = hfield("from", mp)) == NULL ||
1290 (np = sextract(cp, GEXTRA|GSKIN)) == NULL)
1291 return NULL;
1292 return np->n_flink != NULL ? skin(hfield("sender", mp)) : np->n_name;