list.c: fix compiler warnings
[s-mailx.git] / head.c
blob07a07a174c7047ff9eab29c7e4eaae1e353961d9
1 /*
2 * Heirloom mailx - 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 #ifndef lint
41 #ifdef DOSCCS
42 static char sccsid[] = "@(#)head.c 2.17 (gritter) 3/4/06";
43 #endif
44 #endif /* not lint */
46 #include "rcv.h"
47 #include "extern.h"
48 #include <time.h>
51 * Mail -- a mail program
53 * Routines for processing and detecting headlines.
56 static char *copyin(char *src, char **space);
57 static char *nextword(char *wp, char *wbuf);
58 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
59 char **colon);
60 static int msgidnextc(const char **cp, int *status);
61 static int charcount(char *str, int c);
64 * See if the passed line buffer is a mail header.
65 * Return true if yes. POSIX.2 leaves the content
66 * following 'From ' unspecified, so don't care about
67 * it.
69 /*ARGSUSED 2*/
70 int
71 is_head(char *linebuf, size_t linelen)
73 char *cp;
74 (void)linelen;
76 cp = linebuf;
77 if (*cp++ != 'F' || *cp++ != 'r' || *cp++ != 'o' || *cp++ != 'm' ||
78 *cp++ != ' ')
79 return (0);
80 return(1);
84 * Split a headline into its useful components.
85 * Copy the line into dynamic string space, then set
86 * pointers into the copied line in the passed headline
87 * structure. Actually, it scans.
89 void
90 parse(char *line, size_t linelen, struct headline *hl, char *pbuf)
92 char *cp;
93 char *sp;
94 char *word;
96 hl->l_from = NULL;
97 hl->l_tty = NULL;
98 hl->l_date = NULL;
99 cp = line;
100 sp = pbuf;
101 word = ac_alloc(linelen + 1);
103 * Skip over "From" first.
105 cp = nextword(cp, word);
106 cp = nextword(cp, word);
107 if (*word)
108 hl->l_from = copyin(word, &sp);
109 if (cp != NULL && cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
110 cp = nextword(cp, word);
111 hl->l_tty = copyin(word, &sp);
113 if (cp != NULL)
114 hl->l_date = copyin(cp, &sp);
115 else
116 hl->l_date = catgets(catd, CATSET, 213, "<Unknown date>");
117 ac_free(word);
121 * Copy the string on the left into the string on the right
122 * and bump the right (reference) string pointer by the length.
123 * Thus, dynamically allocate space in the right string, copying
124 * the left string into it.
126 static char *
127 copyin(char *src, char **space)
129 char *cp;
130 char *top;
132 top = cp = *space;
133 while ((*cp++ = *src++) != '\0')
135 *space = cp;
136 return (top);
139 #ifdef notdef
140 static int cmatch(char *, char *);
142 * Test to see if the passed string is a ctime(3) generated
143 * date string as documented in the manual. The template
144 * below is used as the criterion of correctness.
145 * Also, we check for a possible trailing time zone using
146 * the tmztype template.
150 * 'A' An upper case char
151 * 'a' A lower case char
152 * ' ' A space
153 * '0' A digit
154 * 'O' An optional digit or space
155 * ':' A colon
156 * '+' A sign
157 * 'N' A new line
159 static char *tmztype[] = {
160 "Aaa Aaa O0 00:00:00 0000",
161 "Aaa Aaa O0 00:00 0000",
162 "Aaa Aaa O0 00:00:00 AAA 0000",
163 "Aaa Aaa O0 00:00 AAA 0000",
165 * Sommer time, e.g. MET DST
167 "Aaa Aaa O0 00:00:00 AAA AAA 0000",
168 "Aaa Aaa O0 00:00 AAA AAA 0000",
170 * time zone offset, e.g.
171 * +0200 or +0200 MET or +0200 MET DST
173 "Aaa Aaa O0 00:00:00 +0000 0000",
174 "Aaa Aaa O0 00:00 +0000 0000",
175 "Aaa Aaa O0 00:00:00 +0000 AAA 0000",
176 "Aaa Aaa O0 00:00 +0000 AAA 0000",
177 "Aaa Aaa O0 00:00:00 +0000 AAA AAA 0000",
178 "Aaa Aaa O0 00:00 +0000 AAA AAA 0000",
180 * time zone offset without time zone specification (pine)
182 "Aaa Aaa O0 00:00:00 0000 +0000",
183 NULL,
186 static int
187 is_date(char *date)
189 int ret = 0, form = 0;
191 while (tmztype[form]) {
192 if ( (ret = cmatch(date, tmztype[form])) == 1 )
193 break;
194 form++;
197 return ret;
201 * Match the given string (cp) against the given template (tp).
202 * Return 1 if they match, 0 if they don't
204 static int
205 cmatch(char *cp, char *tp)
207 int c;
209 while (*cp && *tp)
210 switch (*tp++) {
211 case 'a':
212 if (c = *cp++, !lowerchar(c))
213 return 0;
214 break;
215 case 'A':
216 if (c = *cp++, !upperchar(c))
217 return 0;
218 break;
219 case ' ':
220 if (*cp++ != ' ')
221 return 0;
222 break;
223 case '0':
224 if (c = *cp++, !digitchar(c))
225 return 0;
226 break;
227 case 'O':
228 if (c = *cp, c != ' ' && !digitchar(c))
229 return 0;
230 cp++;
231 break;
232 case ':':
233 if (*cp++ != ':')
234 return 0;
235 break;
236 case '+':
237 if (*cp != '+' && *cp != '-')
238 return 0;
239 cp++;
240 break;
241 case 'N':
242 if (*cp++ != '\n')
243 return 0;
244 break;
246 if (*cp || *tp)
247 return 0;
248 return (1);
250 #endif /* notdef */
253 * Collect a liberal (space, tab delimited) word into the word buffer
254 * passed. Also, return a pointer to the next word following that,
255 * or NULL if none follow.
257 static char *
258 nextword(char *wp, char *wbuf)
260 int c;
262 if (wp == NULL) {
263 *wbuf = 0;
264 return (NULL);
266 while ((c = *wp++) != '\0' && !blankchar(c)) {
267 *wbuf++ = c;
268 if (c == '"') {
269 while ((c = *wp++) != '\0' && c != '"')
270 *wbuf++ = c;
271 if (c == '"')
272 *wbuf++ = c;
273 else
274 wp--;
277 *wbuf = '\0';
278 for (; blankchar(c); c = *wp++)
280 if (c == 0)
281 return (NULL);
282 return (wp - 1);
285 void
286 extract_header(FILE *fp, struct header *hp)
288 char *linebuf = NULL;
289 size_t linesize = 0;
290 int seenfields = 0;
291 char *colon, *cp, *value;
292 struct header nh;
293 struct header *hq = &nh;
294 int lc, c;
296 memset(hq, 0, sizeof *hq);
297 for (lc = 0; readline(fp, &linebuf, &linesize) > 0; lc++);
298 rewind(fp);
299 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
300 if ((value = thisfield(linebuf, "to")) != NULL) {
301 seenfields++;
302 hq->h_to = checkaddrs(cat(hq->h_to,
303 sextract(value, GTO|GFULL)));
304 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
305 seenfields++;
306 hq->h_cc = checkaddrs(cat(hq->h_cc,
307 sextract(value, GCC|GFULL)));
308 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
309 seenfields++;
310 hq->h_bcc = checkaddrs(cat(hq->h_bcc,
311 sextract(value, GBCC|GFULL)));
312 } else if ((value = thisfield(linebuf, "from")) != NULL) {
313 seenfields++;
314 hq->h_from = checkaddrs(cat(hq->h_from,
315 sextract(value, GEXTRA|GFULL)));
316 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
317 seenfields++;
318 hq->h_replyto = checkaddrs(cat(hq->h_replyto,
319 sextract(value, GEXTRA|GFULL)));
320 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
321 seenfields++;
322 hq->h_sender = checkaddrs(cat(hq->h_sender,
323 sextract(value, GEXTRA|GFULL)));
324 } else if ((value = thisfield(linebuf,
325 "organization")) != NULL) {
326 seenfields++;
327 for (cp = value; blankchar(*cp & 0377); cp++);
328 hq->h_organization = hq->h_organization ?
329 save2str(hq->h_organization, cp) :
330 savestr(cp);
331 } else if ((value = thisfield(linebuf, "subject")) != NULL ||
332 (value = thisfield(linebuf, "subj")) != NULL) {
333 seenfields++;
334 for (cp = value; blankchar(*cp & 0377); cp++);
335 hq->h_subject = hq->h_subject ?
336 save2str(hq->h_subject, cp) :
337 savestr(cp);
338 } else
339 fprintf(stderr, catgets(catd, CATSET, 266,
340 "Ignoring header field \"%s\"\n"),
341 linebuf);
344 * In case the blank line after the header has been edited out.
345 * Otherwise, fetch the header separator.
347 if (linebuf) {
348 if (linebuf[0] != '\0') {
349 for (cp = linebuf; *(++cp) != '\0'; );
350 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
351 } else {
352 if ((c = getc(fp)) != '\n' && c != EOF)
353 ungetc(c, fp);
356 if (seenfields) {
357 hp->h_to = hq->h_to;
358 hp->h_cc = hq->h_cc;
359 hp->h_bcc = hq->h_bcc;
360 hp->h_from = hq->h_from;
361 hp->h_replyto = hq->h_replyto;
362 hp->h_sender = hq->h_sender;
363 hp->h_organization = hq->h_organization;
364 hp->h_subject = hq->h_subject;
365 } else
366 fprintf(stderr, catgets(catd, CATSET, 267,
367 "Restoring deleted header lines\n"));
368 if (linebuf)
369 free(linebuf);
373 * Return the desired header line from the passed message
374 * pointer (or NULL if the desired header field is not available).
375 * If mult is zero, return the content of the first matching header
376 * field only, the content of all matching header fields else.
378 char *
379 hfield_mult(char *field, struct message *mp, int mult)
381 FILE *ibuf;
382 char *linebuf = NULL;
383 size_t linesize = 0;
384 int lc;
385 char *hfield;
386 char *colon, *oldhfield = NULL;
388 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
389 return NULL;
390 if ((lc = mp->m_lines - 1) < 0)
391 return NULL;
392 if ((mp->m_flag & MNOFROM) == 0) {
393 if (readline(ibuf, &linebuf, &linesize) < 0) {
394 if (linebuf)
395 free(linebuf);
396 return NULL;
399 while (lc > 0) {
400 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon))
401 < 0) {
402 if (linebuf)
403 free(linebuf);
404 return oldhfield;
406 if ((hfield = thisfield(linebuf, field)) != NULL) {
407 oldhfield = save2str(hfield, oldhfield);
408 if (mult == 0)
409 break;
412 if (linebuf)
413 free(linebuf);
414 return oldhfield;
418 * Return the next header field found in the given message.
419 * Return >= 0 if something found, < 0 elsewise.
420 * "colon" is set to point to the colon in the header.
421 * Must deal with \ continuations & other such fraud.
423 static int
424 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
426 char *line2 = NULL;
427 size_t line2size = 0;
428 char *cp, *cp2;
429 int c, isenc;
431 if (*linebuf == NULL)
432 *linebuf = srealloc(*linebuf, *linesize = 1);
433 **linebuf = '\0';
434 for (;;) {
435 if (--rem < 0)
436 return -1;
437 if ((c = readline(f, linebuf, linesize)) <= 0)
438 return -1;
439 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
440 if (cp > *linebuf)
441 while (blankchar(*cp & 0377))
442 cp++;
443 if (*cp != ':' || cp == *linebuf)
444 continue;
446 * I guess we got a headline.
447 * Handle wraparounding
449 *colon = cp;
450 cp = *linebuf + c;
451 for (;;) {
452 isenc = 0;
453 while (--cp >= *linebuf && blankchar(*cp & 0377));
454 cp++;
455 if (rem <= 0)
456 break;
457 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
458 isenc |= 1;
459 ungetc(c = getc(f), f);
460 if (!blankchar(c))
461 break;
462 if ((c = readline(f, &line2, &line2size)) < 0)
463 break;
464 rem--;
465 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
466 c -= cp2 - line2;
467 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
468 isenc |= 2;
469 if (cp + c >= *linebuf + *linesize - 2) {
470 size_t diff = cp - *linebuf;
471 size_t colondiff = *colon - *linebuf;
472 *linebuf = srealloc(*linebuf,
473 *linesize += c + 2);
474 cp = &(*linebuf)[diff];
475 *colon = &(*linebuf)[colondiff];
477 if (isenc != 3)
478 *cp++ = ' ';
479 memcpy(cp, cp2, c);
480 cp += c;
482 *cp = 0;
483 if (line2)
484 free(line2);
485 return rem;
487 /* NOTREACHED */
491 * Check whether the passed line is a header line of
492 * the desired breed. Return the field body, or 0.
494 char *
495 thisfield(const char *linebuf, const char *field)
497 while (lowerconv(*linebuf&0377) == lowerconv(*field&0377)) {
498 linebuf++;
499 field++;
501 if (*field != '\0')
502 return NULL;
503 while (blankchar(*linebuf&0377))
504 linebuf++;
505 if (*linebuf++ != ':')
506 return NULL;
507 while (blankchar(*linebuf&0377))
508 linebuf++;
509 return (char *)linebuf;
513 * Get sender's name from this message. If the message has
514 * a bunch of arpanet stuff in it, we may have to skin the name
515 * before returning it.
517 char *
518 nameof(struct message *mp, int reptype)
520 char *cp, *cp2;
522 cp = skin(name1(mp, reptype));
523 if (reptype != 0 || charcount(cp, '!') < 2)
524 return(cp);
525 cp2 = strrchr(cp, '!');
526 cp2--;
527 while (cp2 > cp && *cp2 != '!')
528 cp2--;
529 if (*cp2 == '!')
530 return(cp2 + 1);
531 return(cp);
535 * Start of a "comment".
536 * Ignore it.
538 char *
539 skip_comment(const char *cp)
541 int nesting = 1;
543 for (; nesting > 0 && *cp; cp++) {
544 switch (*cp) {
545 case '\\':
546 if (cp[1])
547 cp++;
548 break;
549 case '(':
550 nesting++;
551 break;
552 case ')':
553 nesting--;
554 break;
557 return (char *)cp;
561 * Return the start of a route-addr (address in angle brackets),
562 * if present.
564 char *
565 routeaddr(const char *name)
567 const char *np, *rp = NULL;
569 for (np = name; *np; np++) {
570 switch (*np) {
571 case '(':
572 np = skip_comment(&np[1]) - 1;
573 break;
574 case '"':
575 while (*np) {
576 if (*++np == '"')
577 break;
578 if (*np == '\\' && np[1])
579 np++;
581 break;
582 case '<':
583 rp = np;
584 break;
585 case '>':
586 return (char *)rp;
589 return NULL;
593 * Skin an arpa net address according to the RFC 822 interpretation
594 * of "host-phrase."
596 char *
597 skin(char *name)
599 int c;
600 char *cp, *cp2;
601 char *bufend;
602 int gotlt, lastsp;
603 char *nbuf;
605 if (name == NULL)
606 return(NULL);
607 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
608 && strchr(name, ' ') == NULL)
609 return(name);
610 gotlt = 0;
611 lastsp = 0;
612 nbuf = ac_alloc(strlen(name) + 1);
613 bufend = nbuf;
614 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
615 switch (c) {
616 case '(':
617 cp = skip_comment(cp);
618 lastsp = 0;
619 break;
621 case '"':
623 * Start of a "quoted-string".
624 * Copy it in its entirety.
626 *cp2++ = c;
627 while ((c = *cp) != '\0') {
628 cp++;
629 if (c == '"') {
630 *cp2++ = c;
631 break;
633 if (c != '\\')
634 *cp2++ = c;
635 else if ((c = *cp) != '\0') {
636 *cp2++ = c;
637 cp++;
640 lastsp = 0;
641 break;
643 case ' ':
644 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
645 cp += 3, *cp2++ = '@';
646 else
647 if (cp[0] == '@' && cp[1] == ' ')
648 cp += 2, *cp2++ = '@';
649 #if 0
651 * RFC 822 specifies spaces are STRIPPED when
652 * in an adress specifier.
654 else
655 lastsp = 1;
656 #endif
657 break;
659 case '<':
660 cp2 = bufend;
661 gotlt++;
662 lastsp = 0;
663 break;
665 case '>':
666 if (gotlt) {
667 gotlt = 0;
668 while ((c = *cp) != '\0' && c != ',') {
669 cp++;
670 if (c == '(')
671 cp = skip_comment(cp);
672 else if (c == '"')
673 while ((c = *cp) != '\0') {
674 cp++;
675 if (c == '"')
676 break;
677 if (c == '\\' && *cp)
678 cp++;
681 lastsp = 0;
682 break;
684 /* Fall into . . . */
686 default:
687 if (lastsp) {
688 lastsp = 0;
689 *cp2++ = ' ';
691 *cp2++ = c;
692 if (c == ',' && !gotlt) {
693 *cp2++ = ' ';
694 for (; *cp == ' '; cp++)
696 lastsp = 0;
697 bufend = cp2;
701 *cp2 = 0;
702 cp = savestr(nbuf);
703 ac_free(nbuf);
704 return cp;
708 * Fetch the real name from an internet mail address field.
710 char *
711 realname(char *name)
713 char *cstart = NULL, *cend = NULL, *cp, *cq;
714 char *rname, *rp;
715 struct str in, out;
716 int quoted, good, nogood;
718 if (name == NULL)
719 return NULL;
720 for (cp = name; *cp; cp++) {
721 switch (*cp) {
722 case '(':
723 if (cstart)
725 * More than one comment in address, doesn't
726 * make sense to display it without context.
727 * Return the entire field,
729 return mime_fromaddr(name);
730 cstart = cp++;
731 cp = skip_comment(cp);
732 cend = cp--;
733 if (cend <= cstart)
734 cend = cstart = NULL;
735 break;
736 case '"':
737 while (*cp) {
738 if (*++cp == '"')
739 break;
740 if (*cp == '\\' && cp[1])
741 cp++;
743 break;
744 case '<':
745 if (cp > name) {
746 cstart = name;
747 cend = cp;
749 break;
750 case ',':
752 * More than one address. Just use the first one.
754 goto brk;
757 brk: if (cstart == NULL) {
758 if (*name == '<')
760 * If name contains only a route-addr, the
761 * surrounding angle brackets don't serve any
762 * useful purpose when displaying, so they
763 * are removed.
765 return prstr(skin(name));
766 return mime_fromaddr(name);
768 rp = rname = ac_alloc(cend - cstart + 1);
770 * Strip quotes. Note that quotes that appear within a MIME-
771 * encoded word are not stripped. The idea is to strip only
772 * syntactical relevant things (but this is not necessarily
773 * the most sensible way in practice).
775 quoted = 0;
776 for (cp = cstart; cp < cend; cp++) {
777 if (*cp == '(' && !quoted) {
778 cq = skip_comment(++cp);
779 if (--cq > cend)
780 cq = cend;
781 while (cp < cq) {
782 if (*cp == '\\' && &cp[1] < cq)
783 cp++;
784 *rp++ = *cp++;
786 } else if (*cp == '\\' && &cp[1] < cend)
787 *rp++ = *++cp;
788 else if (*cp == '"') {
789 quoted = !quoted;
790 continue;
791 } else
792 *rp++ = *cp;
794 *rp = '\0';
795 in.s = rname;
796 in.l = rp - rname;
797 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
798 ac_free(rname);
799 rname = savestr(out.s);
800 free(out.s);
801 while (blankchar(*rname & 0377))
802 rname++;
803 for (rp = rname; *rp; rp++);
804 while (--rp >= rname && blankchar(*rp & 0377))
805 *rp = '\0';
806 if (rp == rname)
807 return mime_fromaddr(name);
809 * mime_fromhdr() has converted all nonprintable characters to
810 * question marks now. These and blanks are considered uninteresting;
811 * if the displayed part of the real name contains more than 25% of
812 * them, it is probably better to display the plain email address
813 * instead.
815 good = 0;
816 nogood = 0;
817 for (rp = rname; *rp && rp < &rname[20]; rp++)
818 if (*rp == '?' || blankchar(*rp & 0377))
819 nogood++;
820 else
821 good++;
822 if (good*3 < nogood)
823 return prstr(skin(name));
824 return rname;
828 * Fetch the sender's name from the passed message.
829 * Reptype can be
830 * 0 -- get sender's name for display purposes
831 * 1 -- get sender's name for reply
832 * 2 -- get sender's name for Reply
834 char *
835 name1(struct message *mp, int reptype)
837 char *namebuf;
838 size_t namesize;
839 char *linebuf = NULL;
840 size_t linesize = 0;
841 char *cp, *cp2;
842 FILE *ibuf;
843 int first = 1;
845 if ((cp = hfield("from", mp)) != NULL && *cp != '\0')
846 return cp;
847 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL &&
848 *cp != '\0')
849 return cp;
850 namebuf = smalloc(namesize = 1);
851 namebuf[0] = 0;
852 if (mp->m_flag & MNOFROM)
853 goto out;
854 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
855 goto out;
856 if (readline(ibuf, &linebuf, &linesize) < 0)
857 goto out;
858 newname:
859 if (namesize <= linesize)
860 namebuf = srealloc(namebuf, namesize = linesize + 1);
861 for (cp = linebuf; *cp && *cp != ' '; cp++)
863 for (; blankchar(*cp & 0377); cp++);
864 for (cp2 = &namebuf[strlen(namebuf)];
865 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
866 *cp2++ = *cp++;
867 *cp2 = '\0';
868 if (readline(ibuf, &linebuf, &linesize) < 0)
869 goto out;
870 if ((cp = strchr(linebuf, 'F')) == NULL)
871 goto out;
872 if (strncmp(cp, "From", 4) != 0)
873 goto out;
874 if (namesize <= linesize)
875 namebuf = srealloc(namebuf, namesize = linesize + 1);
876 while ((cp = strchr(cp, 'r')) != NULL) {
877 if (strncmp(cp, "remote", 6) == 0) {
878 if ((cp = strchr(cp, 'f')) == NULL)
879 break;
880 if (strncmp(cp, "from", 4) != 0)
881 break;
882 if ((cp = strchr(cp, ' ')) == NULL)
883 break;
884 cp++;
885 if (first) {
886 strncpy(namebuf, cp, namesize);
887 first = 0;
888 } else {
889 cp2=strrchr(namebuf, '!')+1;
890 strncpy(cp2, cp, (namebuf+namesize)-cp2);
892 namebuf[namesize-2]='\0';
893 strcat(namebuf, "!");
894 goto newname;
896 cp++;
898 out:
899 if (*namebuf != '\0' || ((cp = hfield("return-path", mp))) == NULL ||
900 *cp == '\0')
901 cp = savestr(namebuf);
902 if (linebuf)
903 free(linebuf);
904 free(namebuf);
905 return cp;
908 static int
909 msgidnextc(const char **cp, int *status)
911 int c;
913 for (;;) {
914 if (*status & 01) {
915 if (**cp == '"') {
916 *status &= ~01;
917 (*cp)++;
918 continue;
920 if (**cp == '\\') {
921 (*cp)++;
922 if (**cp == '\0')
923 goto eof;
925 goto dfl;
927 switch (**cp) {
928 case '(':
929 *cp = skip_comment(&(*cp)[1]);
930 continue;
931 case '>':
932 case '\0':
933 eof:
934 return '\0';
935 case '"':
936 (*cp)++;
937 *status |= 01;
938 continue;
939 case '@':
940 *status |= 02;
941 /*FALLTHRU*/
942 default:
943 dfl:
944 c = *(*cp)++ & 0377;
945 return *status & 02 ? lowerconv(c) : c;
950 int
951 msgidcmp(const char *s1, const char *s2)
953 int q1 = 0, q2 = 0;
954 int c1, c2;
956 do {
957 c1 = msgidnextc(&s1, &q1);
958 c2 = msgidnextc(&s2, &q2);
959 if (c1 != c2)
960 return c1 - c2;
961 } while (c1 && c2);
962 return c1 - c2;
966 * Count the occurances of c in str
968 static int
969 charcount(char *str, int c)
971 char *cp;
972 int i;
974 for (i = 0, cp = str; *cp; cp++)
975 if (*cp == c)
976 i++;
977 return(i);
981 * See if the given header field is supposed to be ignored.
984 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
986 char *realfld;
987 int ret;
989 if (ignore == NULL)
990 return 0;
991 if (ignore == allignore)
992 return 1;
994 * Lower-case the string, so that "Status" and "status"
995 * will hash to the same place.
997 realfld = ac_alloc(fieldlen + 1);
998 i_strcpy(realfld, field, fieldlen + 1);
999 if (ignore[1].i_count > 0)
1000 ret = !member(realfld, ignore + 1);
1001 else
1002 ret = member(realfld, ignore);
1003 ac_free(realfld);
1004 return ret;
1007 int
1008 member(char *realfield, struct ignoretab *table)
1010 struct ignore *igp;
1012 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1013 if (*igp->i_field == *realfield &&
1014 equal(igp->i_field, realfield))
1015 return (1);
1016 return (0);
1020 * Fake Sender for From_ lines if missing, e. g. with POP3.
1022 char *
1023 fakefrom(struct message *mp)
1025 char *name;
1027 if (((name = skin(hfield("return-path", mp))) == NULL ||
1028 *name == '\0' ) &&
1029 ((name = skin(hfield("from", mp))) == NULL ||
1030 *name == '\0'))
1031 name = "-";
1032 return name;
1035 char *
1036 fakedate(time_t t)
1038 char *cp, *cq;
1040 cp = ctime(&t);
1041 for (cq = cp; *cq && *cq != '\n'; cq++);
1042 *cq = '\0';
1043 return savestr(cp);
1046 char *
1047 nexttoken(char *cp)
1049 for (;;) {
1050 if (*cp == '\0')
1051 return NULL;
1052 if (*cp == '(') {
1053 int nesting = 0;
1055 while (*cp != '\0') {
1056 switch (*cp++) {
1057 case '(':
1058 nesting++;
1059 break;
1060 case ')':
1061 nesting--;
1062 break;
1064 if (nesting <= 0)
1065 break;
1067 } else if (blankchar(*cp & 0377) || *cp == ',')
1068 cp++;
1069 else
1070 break;
1072 return cp;
1076 * From username Fri Jan 2 20:13:51 2004
1077 * | | | | |
1078 * 0 5 10 15 20
1080 time_t
1081 unixtime(char *from)
1083 char *fp, *xp;
1084 time_t t;
1085 int i, year, month, day, hour, minute, second;
1086 int tzdiff;
1087 struct tm *tmptr;
1089 for (fp = from; *fp && *fp != '\n'; fp++);
1090 fp -= 24;
1091 if (fp - from < 7)
1092 goto invalid;
1093 if (fp[3] != ' ')
1094 goto invalid;
1095 for (i = 0; month_names[i]; i++)
1096 if (strncmp(&fp[4], month_names[i], 3) == 0)
1097 break;
1098 if (month_names[i] == 0)
1099 goto invalid;
1100 month = i + 1;
1101 if (fp[7] != ' ')
1102 goto invalid;
1103 day = strtol(&fp[8], &xp, 10);
1104 if (*xp != ' ' || xp != &fp[10])
1105 goto invalid;
1106 hour = strtol(&fp[11], &xp, 10);
1107 if (*xp != ':' || xp != &fp[13])
1108 goto invalid;
1109 minute = strtol(&fp[14], &xp, 10);
1110 if (*xp != ':' || xp != &fp[16])
1111 goto invalid;
1112 second = strtol(&fp[17], &xp, 10);
1113 if (*xp != ' ' || xp != &fp[19])
1114 goto invalid;
1115 year = strtol(&fp[20], &xp, 10);
1116 if (xp != &fp[24])
1117 goto invalid;
1118 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1119 (time_t)-1)
1120 goto invalid;
1121 tzdiff = t - mktime(gmtime(&t));
1122 tmptr = localtime(&t);
1123 if (tmptr->tm_isdst > 0)
1124 tzdiff += 3600;
1125 t -= tzdiff;
1126 return t;
1127 invalid:
1128 time(&t);
1129 return t;
1132 time_t
1133 rfctime(char *date)
1135 char *cp = date, *x;
1136 time_t t;
1137 int i, year, month, day, hour, minute, second;
1139 if ((cp = nexttoken(cp)) == NULL)
1140 goto invalid;
1141 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1142 alphachar(cp[2] & 0377) && cp[3] == ',') {
1143 if ((cp = nexttoken(&cp[4])) == NULL)
1144 goto invalid;
1146 day = strtol(cp, &x, 10);
1147 if ((cp = nexttoken(x)) == NULL)
1148 goto invalid;
1149 for (i = 0; month_names[i]; i++) {
1150 if (strncmp(cp, month_names[i], 3) == 0)
1151 break;
1153 if (month_names[i] == NULL)
1154 goto invalid;
1155 month = i + 1;
1156 if ((cp = nexttoken(&cp[3])) == NULL)
1157 goto invalid;
1158 year = strtol(cp, &x, 10);
1159 if ((cp = nexttoken(x)) == NULL)
1160 goto invalid;
1161 hour = strtol(cp, &x, 10);
1162 if (*x != ':')
1163 goto invalid;
1164 cp = &x[1];
1165 minute = strtol(cp, &x, 10);
1166 if (*x == ':') {
1167 cp = &x[1];
1168 second = strtol(cp, &x, 10);
1169 } else
1170 second = 0;
1171 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1172 (time_t)-1)
1173 goto invalid;
1174 if ((cp = nexttoken(x)) != NULL) {
1175 int sign = -1;
1176 char buf[3];
1178 switch (*cp) {
1179 case '-':
1180 sign = 1;
1181 /*FALLTHRU*/
1182 case '+':
1183 cp++;
1185 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1186 digitchar(cp[2] & 0377) &&
1187 digitchar(cp[3] & 0377)) {
1188 buf[2] = '\0';
1189 buf[0] = cp[0];
1190 buf[1] = cp[1];
1191 t += strtol(buf, NULL, 10) * sign * 3600;
1192 buf[0] = cp[2];
1193 buf[1] = cp[3];
1194 t += strtol(buf, NULL, 10) * sign * 60;
1197 return t;
1198 invalid:
1199 return 0;
1202 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1204 time_t
1205 combinetime(int year, int month, int day, int hour, int minute, int second)
1207 time_t t;
1209 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1210 return -1;
1211 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1212 if (year < 70)
1213 year += 2000;
1214 else if (year < 1900)
1215 year += 1900;
1216 if (month > 1)
1217 t += 86400 * 31;
1218 if (month > 2)
1219 t += 86400 * (leapyear(year) ? 29 : 28);
1220 if (month > 3)
1221 t += 86400 * 31;
1222 if (month > 4)
1223 t += 86400 * 30;
1224 if (month > 5)
1225 t += 86400 * 31;
1226 if (month > 6)
1227 t += 86400 * 30;
1228 if (month > 7)
1229 t += 86400 * 31;
1230 if (month > 8)
1231 t += 86400 * 31;
1232 if (month > 9)
1233 t += 86400 * 30;
1234 if (month > 10)
1235 t += 86400 * 31;
1236 if (month > 11)
1237 t += 86400 * 30;
1238 year -= 1900;
1239 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1240 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1241 return t;
1244 void
1245 substdate(struct message *m)
1247 char *cp;
1248 time_t now;
1251 * Determine the date to print in faked 'From ' lines. This is
1252 * traditionally the date the message was written to the mail
1253 * file. Try to determine this using RFC message header fields,
1254 * or fall back to current time.
1256 time(&now);
1257 if ((cp = hfield_mult("received", m, 0)) != NULL) {
1258 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1260 cp++;
1261 while (alnumchar(*cp & 0377));
1263 if (cp && *++cp)
1264 m->m_time = rfctime(cp);
1266 if (m->m_time == 0 || m->m_time > now)
1267 if ((cp = hfield("date", m)) != NULL)
1268 m->m_time = rfctime(cp);
1269 if (m->m_time == 0 || m->m_time > now)
1270 m->m_time = now;
1274 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1276 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1277 fprintf(stderr, "A Sender: field is required with multiple "
1278 "addresses in From: field.\n");
1279 return 1;
1281 if (senderfield && senderfield->n_flink) {
1282 fprintf(stderr, "The Sender: field may contain "
1283 "only one address.\n");
1284 return 2;
1286 return 0;
1289 char *
1290 getsender(struct message *mp)
1292 char *cp;
1293 struct name *np;
1295 if ((cp = hfield("from", mp)) == NULL ||
1296 (np = sextract(cp, GEXTRA|GSKIN)) == NULL)
1297 return NULL;
1298 return np->n_flink != NULL ? skin(hfield("sender", mp)) : np->n_name;