Split overlong line
[s-mailx.git] / head.c
blob42e344dbf73fb2dc8a9ca80b212d70e53b30481f
1 /*
2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 */
6 /*
7 * Copyright (c) 1980, 1993
8 * The Regents of the University of California. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
39 #ifndef lint
40 #ifdef DOSCCS
41 static char sccsid[] = "@(#)head.c 2.17 (gritter) 3/4/06";
42 #endif
43 #endif /* not lint */
45 #include "rcv.h"
46 #include "extern.h"
47 #include <time.h>
50 * Mail -- a mail program
52 * Routines for processing and detecting headlines.
55 static char *copyin(char *src, char **space);
56 static char *nextword(char *wp, char *wbuf);
57 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
58 char **colon);
59 static int msgidnextc(const char **cp, int *status);
60 static int charcount(char *str, int c);
63 * See if the passed line buffer is a mail header.
64 * Return true if yes. POSIX.2 leaves the content
65 * following 'From ' unspecified, so don't care about
66 * it.
68 /*ARGSUSED 2*/
69 int
70 is_head(char *linebuf, size_t linelen)
72 char *cp;
74 cp = linebuf;
75 if (*cp++ != 'F' || *cp++ != 'r' || *cp++ != 'o' || *cp++ != 'm' ||
76 *cp++ != ' ')
77 return (0);
78 return(1);
82 * Split a headline into its useful components.
83 * Copy the line into dynamic string space, then set
84 * pointers into the copied line in the passed headline
85 * structure. Actually, it scans.
87 void
88 parse(char *line, size_t linelen, struct headline *hl, char *pbuf)
90 char *cp;
91 char *sp;
92 char *word;
94 hl->l_from = NULL;
95 hl->l_tty = NULL;
96 hl->l_date = NULL;
97 cp = line;
98 sp = pbuf;
99 word = ac_alloc(linelen + 1);
101 * Skip over "From" first.
103 cp = nextword(cp, word);
104 cp = nextword(cp, word);
105 if (*word)
106 hl->l_from = copyin(word, &sp);
107 if (cp != NULL && cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
108 cp = nextword(cp, word);
109 hl->l_tty = copyin(word, &sp);
111 if (cp != NULL)
112 hl->l_date = copyin(cp, &sp);
113 else
114 hl->l_date = catgets(catd, CATSET, 213, "<Unknown date>");
115 ac_free(word);
119 * Copy the string on the left into the string on the right
120 * and bump the right (reference) string pointer by the length.
121 * Thus, dynamically allocate space in the right string, copying
122 * the left string into it.
124 static char *
125 copyin(char *src, char **space)
127 char *cp;
128 char *top;
130 top = cp = *space;
131 while ((*cp++ = *src++) != '\0')
133 *space = cp;
134 return (top);
137 #ifdef notdef
138 static int cmatch(char *, char *);
140 * Test to see if the passed string is a ctime(3) generated
141 * date string as documented in the manual. The template
142 * below is used as the criterion of correctness.
143 * Also, we check for a possible trailing time zone using
144 * the tmztype template.
148 * 'A' An upper case char
149 * 'a' A lower case char
150 * ' ' A space
151 * '0' A digit
152 * 'O' An optional digit or space
153 * ':' A colon
154 * '+' A sign
155 * 'N' A new line
157 static char *tmztype[] = {
158 "Aaa Aaa O0 00:00:00 0000",
159 "Aaa Aaa O0 00:00 0000",
160 "Aaa Aaa O0 00:00:00 AAA 0000",
161 "Aaa Aaa O0 00:00 AAA 0000",
163 * Sommer time, e.g. MET DST
165 "Aaa Aaa O0 00:00:00 AAA AAA 0000",
166 "Aaa Aaa O0 00:00 AAA AAA 0000",
168 * time zone offset, e.g.
169 * +0200 or +0200 MET or +0200 MET DST
171 "Aaa Aaa O0 00:00:00 +0000 0000",
172 "Aaa Aaa O0 00:00 +0000 0000",
173 "Aaa Aaa O0 00:00:00 +0000 AAA 0000",
174 "Aaa Aaa O0 00:00 +0000 AAA 0000",
175 "Aaa Aaa O0 00:00:00 +0000 AAA AAA 0000",
176 "Aaa Aaa O0 00:00 +0000 AAA AAA 0000",
178 * time zone offset without time zone specification (pine)
180 "Aaa Aaa O0 00:00:00 0000 +0000",
181 NULL,
184 static int
185 is_date(char *date)
187 int ret = 0, form = 0;
189 while (tmztype[form]) {
190 if ( (ret = cmatch(date, tmztype[form])) == 1 )
191 break;
192 form++;
195 return ret;
199 * Match the given string (cp) against the given template (tp).
200 * Return 1 if they match, 0 if they don't
202 static int
203 cmatch(char *cp, char *tp)
205 int c;
207 while (*cp && *tp)
208 switch (*tp++) {
209 case 'a':
210 if (c = *cp++, !lowerchar(c))
211 return 0;
212 break;
213 case 'A':
214 if (c = *cp++, !upperchar(c))
215 return 0;
216 break;
217 case ' ':
218 if (*cp++ != ' ')
219 return 0;
220 break;
221 case '0':
222 if (c = *cp++, !digitchar(c))
223 return 0;
224 break;
225 case 'O':
226 if (c = *cp, c != ' ' && !digitchar(c))
227 return 0;
228 cp++;
229 break;
230 case ':':
231 if (*cp++ != ':')
232 return 0;
233 break;
234 case '+':
235 if (*cp != '+' && *cp != '-')
236 return 0;
237 cp++;
238 break;
239 case 'N':
240 if (*cp++ != '\n')
241 return 0;
242 break;
244 if (*cp || *tp)
245 return 0;
246 return (1);
248 #endif /* notdef */
251 * Collect a liberal (space, tab delimited) word into the word buffer
252 * passed. Also, return a pointer to the next word following that,
253 * or NULL if none follow.
255 static char *
256 nextword(char *wp, char *wbuf)
258 int c;
260 if (wp == NULL) {
261 *wbuf = 0;
262 return (NULL);
264 while ((c = *wp++) != '\0' && !blankchar(c)) {
265 *wbuf++ = c;
266 if (c == '"') {
267 while ((c = *wp++) != '\0' && c != '"')
268 *wbuf++ = c;
269 if (c == '"')
270 *wbuf++ = c;
271 else
272 wp--;
275 *wbuf = '\0';
276 for (; blankchar(c); c = *wp++)
278 if (c == 0)
279 return (NULL);
280 return (wp - 1);
283 void
284 extract_header(FILE *fp, struct header *hp)
286 char *linebuf = NULL;
287 size_t linesize = 0;
288 int seenfields = 0;
289 char *colon, *cp, *value;
290 struct header nh;
291 struct header *hq = &nh;
292 int lc, c;
294 memset(hq, 0, sizeof *hq);
295 for (lc = 0; readline(fp, &linebuf, &linesize) > 0; lc++);
296 rewind(fp);
297 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
298 if ((value = thisfield(linebuf, "to")) != NULL) {
299 seenfields++;
300 hq->h_to = checkaddrs(cat(hq->h_to,
301 sextract(value, GTO|GFULL)));
302 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
303 seenfields++;
304 hq->h_cc = checkaddrs(cat(hq->h_cc,
305 sextract(value, GCC|GFULL)));
306 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
307 seenfields++;
308 hq->h_bcc = checkaddrs(cat(hq->h_bcc,
309 sextract(value, GBCC|GFULL)));
310 } else if ((value = thisfield(linebuf, "from")) != NULL) {
311 seenfields++;
312 hq->h_from = checkaddrs(cat(hq->h_from,
313 sextract(value, GEXTRA|GFULL)));
314 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
315 seenfields++;
316 hq->h_replyto = checkaddrs(cat(hq->h_replyto,
317 sextract(value, GEXTRA|GFULL)));
318 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
319 seenfields++;
320 hq->h_sender = checkaddrs(cat(hq->h_sender,
321 sextract(value, GEXTRA|GFULL)));
322 } else if ((value = thisfield(linebuf,
323 "organization")) != NULL) {
324 seenfields++;
325 for (cp = value; blankchar(*cp & 0377); cp++);
326 hq->h_organization = hq->h_organization ?
327 save2str(hq->h_organization, cp) :
328 savestr(cp);
329 } else if ((value = thisfield(linebuf, "subject")) != NULL ||
330 (value = thisfield(linebuf, "subj")) != NULL) {
331 seenfields++;
332 for (cp = value; blankchar(*cp & 0377); cp++);
333 hq->h_subject = hq->h_subject ?
334 save2str(hq->h_subject, cp) :
335 savestr(cp);
336 } else
337 fprintf(stderr, catgets(catd, CATSET, 266,
338 "Ignoring header field \"%s\"\n"),
339 linebuf);
342 * In case the blank line after the header has been edited out.
343 * Otherwise, fetch the header separator.
345 if (linebuf) {
346 if (linebuf[0] != '\0') {
347 for (cp = linebuf; *(++cp) != '\0'; );
348 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
349 } else {
350 if ((c = getc(fp)) != '\n' && c != EOF)
351 ungetc(c, fp);
354 if (seenfields) {
355 hp->h_to = hq->h_to;
356 hp->h_cc = hq->h_cc;
357 hp->h_bcc = hq->h_bcc;
358 hp->h_from = hq->h_from;
359 hp->h_replyto = hq->h_replyto;
360 hp->h_sender = hq->h_sender;
361 hp->h_organization = hq->h_organization;
362 hp->h_subject = hq->h_subject;
363 } else
364 fprintf(stderr, catgets(catd, CATSET, 267,
365 "Restoring deleted header lines\n"));
366 if (linebuf)
367 free(linebuf);
371 * Return the desired header line from the passed message
372 * pointer (or NULL if the desired header field is not available).
373 * If mult is zero, return the content of the first matching header
374 * field only, the content of all matching header fields else.
376 char *
377 hfield_mult(char *field, struct message *mp, int mult)
379 FILE *ibuf;
380 char *linebuf = NULL;
381 size_t linesize = 0;
382 int lc;
383 char *hfield;
384 char *colon, *oldhfield = NULL;
386 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
387 return NULL;
388 if ((lc = mp->m_lines - 1) < 0)
389 return NULL;
390 if ((mp->m_flag & MNOFROM) == 0) {
391 if (readline(ibuf, &linebuf, &linesize) < 0) {
392 if (linebuf)
393 free(linebuf);
394 return NULL;
397 while (lc > 0) {
398 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon))
399 < 0) {
400 if (linebuf)
401 free(linebuf);
402 return oldhfield;
404 if ((hfield = thisfield(linebuf, field)) != NULL) {
405 oldhfield = save2str(hfield, oldhfield);
406 if (mult == 0)
407 break;
410 if (linebuf)
411 free(linebuf);
412 return oldhfield;
416 * Return the next header field found in the given message.
417 * Return >= 0 if something found, < 0 elsewise.
418 * "colon" is set to point to the colon in the header.
419 * Must deal with \ continuations & other such fraud.
421 static int
422 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
424 char *line2 = NULL;
425 size_t line2size = 0;
426 char *cp, *cp2;
427 int c, isenc;
429 if (*linebuf == NULL)
430 *linebuf = srealloc(*linebuf, *linesize = 1);
431 **linebuf = '\0';
432 for (;;) {
433 if (--rem < 0)
434 return -1;
435 if ((c = readline(f, linebuf, linesize)) <= 0)
436 return -1;
437 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
438 if (cp > *linebuf)
439 while (blankchar(*cp & 0377))
440 cp++;
441 if (*cp != ':' || cp == *linebuf)
442 continue;
444 * I guess we got a headline.
445 * Handle wraparounding
447 *colon = cp;
448 cp = *linebuf + c;
449 for (;;) {
450 isenc = 0;
451 while (--cp >= *linebuf && blankchar(*cp & 0377));
452 cp++;
453 if (rem <= 0)
454 break;
455 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
456 isenc |= 1;
457 ungetc(c = getc(f), f);
458 if (!blankchar(c))
459 break;
460 if ((c = readline(f, &line2, &line2size)) < 0)
461 break;
462 rem--;
463 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
464 c -= cp2 - line2;
465 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
466 isenc |= 2;
467 if (cp + c >= *linebuf + *linesize - 2) {
468 size_t diff = cp - *linebuf;
469 size_t colondiff = *colon - *linebuf;
470 *linebuf = srealloc(*linebuf,
471 *linesize += c + 2);
472 cp = &(*linebuf)[diff];
473 *colon = &(*linebuf)[colondiff];
475 if (isenc != 3)
476 *cp++ = ' ';
477 memcpy(cp, cp2, c);
478 cp += c;
480 *cp = 0;
481 if (line2)
482 free(line2);
483 return rem;
485 /* NOTREACHED */
489 * Check whether the passed line is a header line of
490 * the desired breed. Return the field body, or 0.
492 char *
493 thisfield(const char *linebuf, const char *field)
495 while (lowerconv(*linebuf&0377) == lowerconv(*field&0377)) {
496 linebuf++;
497 field++;
499 if (*field != '\0')
500 return NULL;
501 while (blankchar(*linebuf&0377))
502 linebuf++;
503 if (*linebuf++ != ':')
504 return NULL;
505 while (blankchar(*linebuf&0377))
506 linebuf++;
507 return (char *)linebuf;
511 * Get sender's name from this message. If the message has
512 * a bunch of arpanet stuff in it, we may have to skin the name
513 * before returning it.
515 char *
516 nameof(struct message *mp, int reptype)
518 char *cp, *cp2;
520 cp = skin(name1(mp, reptype));
521 if (reptype != 0 || charcount(cp, '!') < 2)
522 return(cp);
523 cp2 = strrchr(cp, '!');
524 cp2--;
525 while (cp2 > cp && *cp2 != '!')
526 cp2--;
527 if (*cp2 == '!')
528 return(cp2 + 1);
529 return(cp);
533 * Start of a "comment".
534 * Ignore it.
536 char *
537 skip_comment(const char *cp)
539 int nesting = 1;
541 for (; nesting > 0 && *cp; cp++) {
542 switch (*cp) {
543 case '\\':
544 if (cp[1])
545 cp++;
546 break;
547 case '(':
548 nesting++;
549 break;
550 case ')':
551 nesting--;
552 break;
555 return (char *)cp;
559 * Return the start of a route-addr (address in angle brackets),
560 * if present.
562 char *
563 routeaddr(const char *name)
565 const char *np, *rp = NULL;
567 for (np = name; *np; np++) {
568 switch (*np) {
569 case '(':
570 np = skip_comment(&np[1]) - 1;
571 break;
572 case '"':
573 while (*np) {
574 if (*++np == '"')
575 break;
576 if (*np == '\\' && np[1])
577 np++;
579 break;
580 case '<':
581 rp = np;
582 break;
583 case '>':
584 return (char *)rp;
587 return NULL;
591 * Skin an arpa net address according to the RFC 822 interpretation
592 * of "host-phrase."
594 char *
595 skin(char *name)
597 int c;
598 char *cp, *cp2;
599 char *bufend;
600 int gotlt, lastsp;
601 char *nbuf;
603 if (name == NULL)
604 return(NULL);
605 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
606 && strchr(name, ' ') == NULL)
607 return(name);
608 gotlt = 0;
609 lastsp = 0;
610 nbuf = ac_alloc(strlen(name) + 1);
611 bufend = nbuf;
612 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
613 switch (c) {
614 case '(':
615 cp = skip_comment(cp);
616 lastsp = 0;
617 break;
619 case '"':
621 * Start of a "quoted-string".
622 * Copy it in its entirety.
624 *cp2++ = c;
625 while ((c = *cp) != '\0') {
626 cp++;
627 if (c == '"') {
628 *cp2++ = c;
629 break;
631 if (c != '\\')
632 *cp2++ = c;
633 else if ((c = *cp) != '\0') {
634 *cp2++ = c;
635 cp++;
638 lastsp = 0;
639 break;
641 case ' ':
642 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
643 cp += 3, *cp2++ = '@';
644 else
645 if (cp[0] == '@' && cp[1] == ' ')
646 cp += 2, *cp2++ = '@';
647 #if 0
649 * RFC 822 specifies spaces are STRIPPED when
650 * in an adress specifier.
652 else
653 lastsp = 1;
654 #endif
655 break;
657 case '<':
658 cp2 = bufend;
659 gotlt++;
660 lastsp = 0;
661 break;
663 case '>':
664 if (gotlt) {
665 gotlt = 0;
666 while ((c = *cp) != '\0' && c != ',') {
667 cp++;
668 if (c == '(')
669 cp = skip_comment(cp);
670 else if (c == '"')
671 while ((c = *cp) != '\0') {
672 cp++;
673 if (c == '"')
674 break;
675 if (c == '\\' && *cp)
676 cp++;
679 lastsp = 0;
680 break;
682 /* Fall into . . . */
684 default:
685 if (lastsp) {
686 lastsp = 0;
687 *cp2++ = ' ';
689 *cp2++ = c;
690 if (c == ',' && !gotlt) {
691 *cp2++ = ' ';
692 for (; *cp == ' '; cp++)
694 lastsp = 0;
695 bufend = cp2;
699 *cp2 = 0;
700 cp = savestr(nbuf);
701 ac_free(nbuf);
702 return cp;
706 * Fetch the real name from an internet mail address field.
708 char *
709 realname(char *name)
711 char *cstart = NULL, *cend = NULL, *cp, *cq;
712 char *rname, *rp;
713 struct str in, out;
714 int quoted, good, nogood;
716 if (name == NULL)
717 return NULL;
718 for (cp = name; *cp; cp++) {
719 switch (*cp) {
720 case '(':
721 if (cstart)
723 * More than one comment in address, doesn't
724 * make sense to display it without context.
725 * Return the entire field,
727 return mime_fromaddr(name);
728 cstart = cp++;
729 cp = skip_comment(cp);
730 cend = cp--;
731 if (cend <= cstart)
732 cend = cstart = NULL;
733 break;
734 case '"':
735 while (*cp) {
736 if (*++cp == '"')
737 break;
738 if (*cp == '\\' && cp[1])
739 cp++;
741 break;
742 case '<':
743 if (cp > name) {
744 cstart = name;
745 cend = cp;
747 break;
748 case ',':
750 * More than one address. Just use the first one.
752 goto brk;
755 brk: if (cstart == NULL) {
756 if (*name == '<')
758 * If name contains only a route-addr, the
759 * surrounding angle brackets don't serve any
760 * useful purpose when displaying, so they
761 * are removed.
763 return prstr(skin(name));
764 return mime_fromaddr(name);
766 rp = rname = ac_alloc(cend - cstart + 1);
768 * Strip quotes. Note that quotes that appear within a MIME-
769 * encoded word are not stripped. The idea is to strip only
770 * syntactical relevant things (but this is not necessarily
771 * the most sensible way in practice).
773 quoted = 0;
774 for (cp = cstart; cp < cend; cp++) {
775 if (*cp == '(' && !quoted) {
776 cq = skip_comment(++cp);
777 if (--cq > cend)
778 cq = cend;
779 while (cp < cq) {
780 if (*cp == '\\' && &cp[1] < cq)
781 cp++;
782 *rp++ = *cp++;
784 } else if (*cp == '\\' && &cp[1] < cend)
785 *rp++ = *++cp;
786 else if (*cp == '"') {
787 quoted = !quoted;
788 continue;
789 } else
790 *rp++ = *cp;
792 *rp = '\0';
793 in.s = rname;
794 in.l = rp - rname;
795 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
796 ac_free(rname);
797 rname = savestr(out.s);
798 free(out.s);
799 while (blankchar(*rname & 0377))
800 rname++;
801 for (rp = rname; *rp; rp++);
802 while (--rp >= rname && blankchar(*rp & 0377))
803 *rp = '\0';
804 if (rp == rname)
805 return mime_fromaddr(name);
807 * mime_fromhdr() has converted all nonprintable characters to
808 * question marks now. These and blanks are considered uninteresting;
809 * if the displayed part of the real name contains more than 25% of
810 * them, it is probably better to display the plain email address
811 * instead.
813 good = 0;
814 nogood = 0;
815 for (rp = rname; *rp && rp < &rname[20]; rp++)
816 if (*rp == '?' || blankchar(*rp & 0377))
817 nogood++;
818 else
819 good++;
820 if (good*3 < nogood)
821 return prstr(skin(name));
822 return rname;
826 * Fetch the sender's name from the passed message.
827 * Reptype can be
828 * 0 -- get sender's name for display purposes
829 * 1 -- get sender's name for reply
830 * 2 -- get sender's name for Reply
832 char *
833 name1(struct message *mp, int reptype)
835 char *namebuf;
836 size_t namesize;
837 char *linebuf = NULL;
838 size_t linesize = 0;
839 char *cp, *cp2;
840 FILE *ibuf;
841 int first = 1;
843 if ((cp = hfield("from", mp)) != NULL && *cp != '\0')
844 return cp;
845 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL &&
846 *cp != '\0')
847 return cp;
848 namebuf = smalloc(namesize = 1);
849 namebuf[0] = 0;
850 if (mp->m_flag & MNOFROM)
851 goto out;
852 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
853 goto out;
854 if (readline(ibuf, &linebuf, &linesize) < 0)
855 goto out;
856 newname:
857 if (namesize <= linesize)
858 namebuf = srealloc(namebuf, namesize = linesize + 1);
859 for (cp = linebuf; *cp && *cp != ' '; cp++)
861 for (; blankchar(*cp & 0377); cp++);
862 for (cp2 = &namebuf[strlen(namebuf)];
863 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
864 *cp2++ = *cp++;
865 *cp2 = '\0';
866 if (readline(ibuf, &linebuf, &linesize) < 0)
867 goto out;
868 if ((cp = strchr(linebuf, 'F')) == NULL)
869 goto out;
870 if (strncmp(cp, "From", 4) != 0)
871 goto out;
872 if (namesize <= linesize)
873 namebuf = srealloc(namebuf, namesize = linesize + 1);
874 while ((cp = strchr(cp, 'r')) != NULL) {
875 if (strncmp(cp, "remote", 6) == 0) {
876 if ((cp = strchr(cp, 'f')) == NULL)
877 break;
878 if (strncmp(cp, "from", 4) != 0)
879 break;
880 if ((cp = strchr(cp, ' ')) == NULL)
881 break;
882 cp++;
883 if (first) {
884 strncpy(namebuf, cp, namesize);
885 first = 0;
886 } else {
887 cp2=strrchr(namebuf, '!')+1;
888 strncpy(cp2, cp, (namebuf+namesize)-cp2);
890 namebuf[namesize-2]='\0';
891 strcat(namebuf, "!");
892 goto newname;
894 cp++;
896 out:
897 if (*namebuf != '\0' || ((cp = hfield("return-path", mp))) == NULL ||
898 *cp == '\0')
899 cp = savestr(namebuf);
900 if (linebuf)
901 free(linebuf);
902 free(namebuf);
903 return cp;
906 static int
907 msgidnextc(const char **cp, int *status)
909 int c;
911 for (;;) {
912 if (*status & 01) {
913 if (**cp == '"') {
914 *status &= ~01;
915 (*cp)++;
916 continue;
918 if (**cp == '\\') {
919 (*cp)++;
920 if (**cp == '\0')
921 goto eof;
923 goto dfl;
925 switch (**cp) {
926 case '(':
927 *cp = skip_comment(&(*cp)[1]);
928 continue;
929 case '>':
930 case '\0':
931 eof:
932 return '\0';
933 case '"':
934 (*cp)++;
935 *status |= 01;
936 continue;
937 case '@':
938 *status |= 02;
939 /*FALLTHRU*/
940 default:
941 dfl:
942 c = *(*cp)++ & 0377;
943 return *status & 02 ? lowerconv(c) : c;
948 int
949 msgidcmp(const char *s1, const char *s2)
951 int q1 = 0, q2 = 0;
952 int c1, c2;
954 do {
955 c1 = msgidnextc(&s1, &q1);
956 c2 = msgidnextc(&s2, &q2);
957 if (c1 != c2)
958 return c1 - c2;
959 } while (c1 && c2);
960 return c1 - c2;
964 * Count the occurances of c in str
966 static int
967 charcount(char *str, int c)
969 char *cp;
970 int i;
972 for (i = 0, cp = str; *cp; cp++)
973 if (*cp == c)
974 i++;
975 return(i);
979 * See if the given header field is supposed to be ignored.
982 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
984 char *realfld;
985 int ret;
987 if (ignore == NULL)
988 return 0;
989 if (ignore == allignore)
990 return 1;
992 * Lower-case the string, so that "Status" and "status"
993 * will hash to the same place.
995 realfld = ac_alloc(fieldlen + 1);
996 i_strcpy(realfld, field, fieldlen + 1);
997 if (ignore[1].i_count > 0)
998 ret = !member(realfld, ignore + 1);
999 else
1000 ret = member(realfld, ignore);
1001 ac_free(realfld);
1002 return ret;
1005 int
1006 member(char *realfield, struct ignoretab *table)
1008 struct ignore *igp;
1010 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1011 if (*igp->i_field == *realfield &&
1012 equal(igp->i_field, realfield))
1013 return (1);
1014 return (0);
1018 * Fake Sender for From_ lines if missing, e. g. with POP3.
1020 char *
1021 fakefrom(struct message *mp)
1023 char *name;
1025 if (((name = skin(hfield("return-path", mp))) == NULL ||
1026 *name == '\0' ) &&
1027 ((name = skin(hfield("from", mp))) == NULL ||
1028 *name == '\0'))
1029 name = "-";
1030 return name;
1033 char *
1034 fakedate(time_t t)
1036 char *cp, *cq;
1038 cp = ctime(&t);
1039 for (cq = cp; *cq && *cq != '\n'; cq++);
1040 *cq = '\0';
1041 return savestr(cp);
1044 char *
1045 nexttoken(char *cp)
1047 for (;;) {
1048 if (*cp == '\0')
1049 return NULL;
1050 if (*cp == '(') {
1051 int nesting = 0;
1053 while (*cp != '\0') {
1054 switch (*cp++) {
1055 case '(':
1056 nesting++;
1057 break;
1058 case ')':
1059 nesting--;
1060 break;
1062 if (nesting <= 0)
1063 break;
1065 } else if (blankchar(*cp & 0377) || *cp == ',')
1066 cp++;
1067 else
1068 break;
1070 return cp;
1074 * From username Fri Jan 2 20:13:51 2004
1075 * | | | | |
1076 * 0 5 10 15 20
1078 time_t
1079 unixtime(char *from)
1081 char *fp, *xp;
1082 time_t t;
1083 int i, year, month, day, hour, minute, second;
1084 int tzdiff;
1085 struct tm *tmptr;
1087 for (fp = from; *fp && *fp != '\n'; fp++);
1088 fp -= 24;
1089 if (fp - from < 7)
1090 goto invalid;
1091 if (fp[3] != ' ')
1092 goto invalid;
1093 for (i = 0; month_names[i]; i++)
1094 if (strncmp(&fp[4], month_names[i], 3) == 0)
1095 break;
1096 if (month_names[i] == 0)
1097 goto invalid;
1098 month = i + 1;
1099 if (fp[7] != ' ')
1100 goto invalid;
1101 day = strtol(&fp[8], &xp, 10);
1102 if (*xp != ' ' || xp != &fp[10])
1103 goto invalid;
1104 hour = strtol(&fp[11], &xp, 10);
1105 if (*xp != ':' || xp != &fp[13])
1106 goto invalid;
1107 minute = strtol(&fp[14], &xp, 10);
1108 if (*xp != ':' || xp != &fp[16])
1109 goto invalid;
1110 second = strtol(&fp[17], &xp, 10);
1111 if (*xp != ' ' || xp != &fp[19])
1112 goto invalid;
1113 year = strtol(&fp[20], &xp, 10);
1114 if (xp != &fp[24])
1115 goto invalid;
1116 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1117 (time_t)-1)
1118 goto invalid;
1119 tzdiff = t - mktime(gmtime(&t));
1120 tmptr = localtime(&t);
1121 if (tmptr->tm_isdst > 0)
1122 tzdiff += 3600;
1123 t -= tzdiff;
1124 return t;
1125 invalid:
1126 time(&t);
1127 return t;
1130 time_t
1131 rfctime(char *date)
1133 char *cp = date, *x;
1134 time_t t;
1135 int i, year, month, day, hour, minute, second;
1137 if ((cp = nexttoken(cp)) == NULL)
1138 goto invalid;
1139 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1140 alphachar(cp[2] & 0377) && cp[3] == ',') {
1141 if ((cp = nexttoken(&cp[4])) == NULL)
1142 goto invalid;
1144 day = strtol(cp, &x, 10);
1145 if ((cp = nexttoken(x)) == NULL)
1146 goto invalid;
1147 for (i = 0; month_names[i]; i++) {
1148 if (strncmp(cp, month_names[i], 3) == 0)
1149 break;
1151 if (month_names[i] == NULL)
1152 goto invalid;
1153 month = i + 1;
1154 if ((cp = nexttoken(&cp[3])) == NULL)
1155 goto invalid;
1156 year = strtol(cp, &x, 10);
1157 if ((cp = nexttoken(x)) == NULL)
1158 goto invalid;
1159 hour = strtol(cp, &x, 10);
1160 if (*x != ':')
1161 goto invalid;
1162 cp = &x[1];
1163 minute = strtol(cp, &x, 10);
1164 if (*x == ':') {
1165 cp = &x[1];
1166 second = strtol(cp, &x, 10);
1167 } else
1168 second = 0;
1169 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1170 (time_t)-1)
1171 goto invalid;
1172 if ((cp = nexttoken(x)) != NULL) {
1173 int sign = -1;
1174 char buf[3];
1176 switch (*cp) {
1177 case '-':
1178 sign = 1;
1179 /*FALLTHRU*/
1180 case '+':
1181 cp++;
1183 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1184 digitchar(cp[2] & 0377) &&
1185 digitchar(cp[3] & 0377)) {
1186 buf[2] = '\0';
1187 buf[0] = cp[0];
1188 buf[1] = cp[1];
1189 t += strtol(buf, NULL, 10) * sign * 3600;
1190 buf[0] = cp[2];
1191 buf[1] = cp[3];
1192 t += strtol(buf, NULL, 10) * sign * 60;
1195 return t;
1196 invalid:
1197 return 0;
1200 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1202 time_t
1203 combinetime(int year, int month, int day, int hour, int minute, int second)
1205 time_t t;
1207 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1208 return -1;
1209 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1210 if (year < 70)
1211 year += 2000;
1212 else if (year < 1900)
1213 year += 1900;
1214 if (month > 1)
1215 t += 86400 * 31;
1216 if (month > 2)
1217 t += 86400 * (leapyear(year) ? 29 : 28);
1218 if (month > 3)
1219 t += 86400 * 31;
1220 if (month > 4)
1221 t += 86400 * 30;
1222 if (month > 5)
1223 t += 86400 * 31;
1224 if (month > 6)
1225 t += 86400 * 30;
1226 if (month > 7)
1227 t += 86400 * 31;
1228 if (month > 8)
1229 t += 86400 * 31;
1230 if (month > 9)
1231 t += 86400 * 30;
1232 if (month > 10)
1233 t += 86400 * 31;
1234 if (month > 11)
1235 t += 86400 * 30;
1236 year -= 1900;
1237 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1238 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1239 return t;
1242 void
1243 substdate(struct message *m)
1245 char *cp;
1246 time_t now;
1249 * Determine the date to print in faked 'From ' lines. This is
1250 * traditionally the date the message was written to the mail
1251 * file. Try to determine this using RFC message header fields,
1252 * or fall back to current time.
1254 time(&now);
1255 if ((cp = hfield_mult("received", m, 0)) != NULL) {
1256 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1258 cp++;
1259 while (alnumchar(*cp & 0377));
1261 if (cp && *++cp)
1262 m->m_time = rfctime(cp);
1264 if (m->m_time == 0 || m->m_time > now)
1265 if ((cp = hfield("date", m)) != NULL)
1266 m->m_time = rfctime(cp);
1267 if (m->m_time == 0 || m->m_time > now)
1268 m->m_time = now;
1272 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1274 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1275 fprintf(stderr, "A Sender: field is required with multiple "
1276 "addresses in From: field.\n");
1277 return 1;
1279 if (senderfield && senderfield->n_flink) {
1280 fprintf(stderr, "The Sender: field may contain "
1281 "only one address.\n");
1282 return 2;
1284 return 0;
1287 char *
1288 getsender(struct message *mp)
1290 char *cp;
1291 struct name *np;
1293 if ((cp = hfield("from", mp)) == NULL ||
1294 (np = sextract(cp, GEXTRA|GSKIN)) == NULL)
1295 return NULL;
1296 return np->n_flink != NULL ? skin(hfield("sender", mp)) : np->n_name;