Tweak (outof(): FIX and recode.., 2012-10-04)..
[s-mailx.git] / head.c
blob499f6a9c95030b84fb2a83b929bc9f1a1f3638a0
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 = cat(hq->h_to, checkaddrs(
297 lextract(value, GTO|GFULL)));
298 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
299 seenfields++;
300 hq->h_cc = cat(hq->h_cc, checkaddrs(
301 lextract(value, GCC|GFULL)));
302 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
303 seenfields++;
304 hq->h_bcc = cat(hq->h_bcc, checkaddrs(
305 lextract(value, GBCC|GFULL)));
306 } else if ((value = thisfield(linebuf, "from")) != NULL) {
307 seenfields++;
308 hq->h_from = cat(hq->h_from, checkaddrs(
309 sextract(value, GEXTRA|GFULL)));
310 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
311 seenfields++;
312 hq->h_replyto = cat(hq->h_replyto, checkaddrs(
313 sextract(value, GEXTRA|GFULL)));
314 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
315 seenfields++;
316 hq->h_sender = cat(hq->h_sender, checkaddrs(
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 * Returned the skinned n_name, use the cached value if available.
588 * Note well that it may *not* create a duplicate.
590 char *
591 skinned_name(struct name *np)
593 return ((np->n_flags & NAME_SKINNED) ? np->n_name : skin(np->n_name));
597 * Skin an arpa net address according to the RFC 822 interpretation
598 * of "host-phrase."
600 char *
601 skin(char *name)
603 int c;
604 char *cp, *cp2;
605 char *bufend;
606 int gotlt, lastsp;
607 char *nbuf;
609 if (name == NULL)
610 return(NULL);
611 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
612 && strchr(name, ' ') == NULL)
613 return(name);
614 gotlt = 0;
615 lastsp = 0;
616 nbuf = ac_alloc(strlen(name) + 1);
617 bufend = nbuf;
618 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; ) {
619 switch (c) {
620 case '(':
621 cp = skip_comment(cp);
622 lastsp = 0;
623 break;
625 case '"':
627 * Start of a "quoted-string".
628 * Copy it in its entirety.
630 *cp2++ = c;
631 while ((c = *cp) != '\0') {
632 cp++;
633 if (c == '"') {
634 *cp2++ = c;
635 break;
637 if (c != '\\')
638 *cp2++ = c;
639 else if ((c = *cp) != '\0') {
640 *cp2++ = c;
641 cp++;
644 lastsp = 0;
645 break;
647 case ' ':
648 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
649 cp += 3, *cp2++ = '@';
650 else
651 if (cp[0] == '@' && cp[1] == ' ')
652 cp += 2, *cp2++ = '@';
653 #if 0
655 * RFC 822 specifies spaces are STRIPPED when
656 * in an adress specifier.
658 else
659 lastsp = 1;
660 #endif
661 break;
663 case '<':
664 cp2 = bufend;
665 gotlt++;
666 lastsp = 0;
667 break;
669 case '>':
670 if (gotlt) {
671 gotlt = 0;
672 while ((c = *cp) != '\0' && c != ',') {
673 cp++;
674 if (c == '(')
675 cp = skip_comment(cp);
676 else if (c == '"')
677 while ((c = *cp) != '\0') {
678 cp++;
679 if (c == '"')
680 break;
681 if (c == '\\' && *cp)
682 cp++;
685 lastsp = 0;
686 break;
688 /* Fall into . . . */
690 default:
691 if (lastsp) {
692 lastsp = 0;
693 *cp2++ = ' ';
695 *cp2++ = c;
696 if (c == ',' && !gotlt) {
697 *cp2++ = ' ';
698 for (; *cp == ' '; cp++)
700 lastsp = 0;
701 bufend = cp2;
705 *cp2 = 0;
706 cp = savestr(nbuf);
707 ac_free(nbuf);
708 return cp;
712 * Fetch the real name from an internet mail address field.
714 char *
715 realname(char *name)
717 char *cstart = NULL, *cend = NULL, *cp, *cq;
718 char *rname, *rp;
719 struct str in, out;
720 int quoted, good, nogood;
722 if (name == NULL)
723 return NULL;
724 for (cp = name; *cp; cp++) {
725 switch (*cp) {
726 case '(':
727 if (cstart)
729 * More than one comment in address, doesn't
730 * make sense to display it without context.
731 * Return the entire field,
733 return mime_fromaddr(name);
734 cstart = cp++;
735 cp = skip_comment(cp);
736 cend = cp--;
737 if (cend <= cstart)
738 cend = cstart = NULL;
739 break;
740 case '"':
741 while (*cp) {
742 if (*++cp == '"')
743 break;
744 if (*cp == '\\' && cp[1])
745 cp++;
747 break;
748 case '<':
749 if (cp > name) {
750 cstart = name;
751 cend = cp;
753 break;
754 case ',':
756 * More than one address. Just use the first one.
758 goto brk;
761 brk: if (cstart == NULL) {
762 if (*name == '<')
764 * If name contains only a route-addr, the
765 * surrounding angle brackets don't serve any
766 * useful purpose when displaying, so they
767 * are removed.
769 return prstr(skin(name));
770 return mime_fromaddr(name);
772 rp = rname = ac_alloc(cend - cstart + 1);
774 * Strip quotes. Note that quotes that appear within a MIME-
775 * encoded word are not stripped. The idea is to strip only
776 * syntactical relevant things (but this is not necessarily
777 * the most sensible way in practice).
779 quoted = 0;
780 for (cp = cstart; cp < cend; cp++) {
781 if (*cp == '(' && !quoted) {
782 cq = skip_comment(++cp);
783 if (--cq > cend)
784 cq = cend;
785 while (cp < cq) {
786 if (*cp == '\\' && &cp[1] < cq)
787 cp++;
788 *rp++ = *cp++;
790 } else if (*cp == '\\' && &cp[1] < cend)
791 *rp++ = *++cp;
792 else if (*cp == '"') {
793 quoted = !quoted;
794 continue;
795 } else
796 *rp++ = *cp;
798 *rp = '\0';
799 in.s = rname;
800 in.l = rp - rname;
801 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
802 ac_free(rname);
803 rname = savestr(out.s);
804 free(out.s);
805 while (blankchar(*rname & 0377))
806 rname++;
807 for (rp = rname; *rp; rp++);
808 while (--rp >= rname && blankchar(*rp & 0377))
809 *rp = '\0';
810 if (rp == rname)
811 return mime_fromaddr(name);
813 * mime_fromhdr() has converted all nonprintable characters to
814 * question marks now. These and blanks are considered uninteresting;
815 * if the displayed part of the real name contains more than 25% of
816 * them, it is probably better to display the plain email address
817 * instead.
819 good = 0;
820 nogood = 0;
821 for (rp = rname; *rp && rp < &rname[20]; rp++)
822 if (*rp == '?' || blankchar(*rp & 0377))
823 nogood++;
824 else
825 good++;
826 if (good*3 < nogood)
827 return prstr(skin(name));
828 return rname;
832 * Fetch the sender's name from the passed message.
833 * Reptype can be
834 * 0 -- get sender's name for display purposes
835 * 1 -- get sender's name for reply
836 * 2 -- get sender's name for Reply
838 char *
839 name1(struct message *mp, int reptype)
841 char *namebuf;
842 size_t namesize;
843 char *linebuf = NULL;
844 size_t linesize = 0;
845 char *cp, *cp2;
846 FILE *ibuf;
847 int first = 1;
849 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
850 return cp;
851 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL &&
852 *cp != '\0')
853 return cp;
854 namebuf = smalloc(namesize = 1);
855 namebuf[0] = 0;
856 if (mp->m_flag & MNOFROM)
857 goto out;
858 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
859 goto out;
860 if (readline(ibuf, &linebuf, &linesize) < 0)
861 goto out;
862 newname:
863 if (namesize <= linesize)
864 namebuf = srealloc(namebuf, namesize = linesize + 1);
865 for (cp = linebuf; *cp && *cp != ' '; cp++)
867 for (; blankchar(*cp & 0377); cp++);
868 for (cp2 = &namebuf[strlen(namebuf)];
869 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
870 *cp2++ = *cp++;
871 *cp2 = '\0';
872 if (readline(ibuf, &linebuf, &linesize) < 0)
873 goto out;
874 if ((cp = strchr(linebuf, 'F')) == NULL)
875 goto out;
876 if (strncmp(cp, "From", 4) != 0)
877 goto out;
878 if (namesize <= linesize)
879 namebuf = srealloc(namebuf, namesize = linesize + 1);
880 while ((cp = strchr(cp, 'r')) != NULL) {
881 if (strncmp(cp, "remote", 6) == 0) {
882 if ((cp = strchr(cp, 'f')) == NULL)
883 break;
884 if (strncmp(cp, "from", 4) != 0)
885 break;
886 if ((cp = strchr(cp, ' ')) == NULL)
887 break;
888 cp++;
889 if (first) {
890 strncpy(namebuf, cp, namesize);
891 first = 0;
892 } else {
893 cp2=strrchr(namebuf, '!')+1;
894 strncpy(cp2, cp, (namebuf+namesize)-cp2);
896 namebuf[namesize-2]='\0';
897 strcat(namebuf, "!");
898 goto newname;
900 cp++;
902 out:
903 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
904 *cp == '\0')
905 cp = savestr(namebuf);
906 if (linebuf)
907 free(linebuf);
908 free(namebuf);
909 return cp;
912 static int
913 msgidnextc(const char **cp, int *status)
915 int c;
917 for (;;) {
918 if (*status & 01) {
919 if (**cp == '"') {
920 *status &= ~01;
921 (*cp)++;
922 continue;
924 if (**cp == '\\') {
925 (*cp)++;
926 if (**cp == '\0')
927 goto eof;
929 goto dfl;
931 switch (**cp) {
932 case '(':
933 *cp = skip_comment(&(*cp)[1]);
934 continue;
935 case '>':
936 case '\0':
937 eof:
938 return '\0';
939 case '"':
940 (*cp)++;
941 *status |= 01;
942 continue;
943 case '@':
944 *status |= 02;
945 /*FALLTHRU*/
946 default:
947 dfl:
948 c = *(*cp)++ & 0377;
949 return *status & 02 ? lowerconv(c) : c;
954 int
955 msgidcmp(const char *s1, const char *s2)
957 int q1 = 0, q2 = 0;
958 int c1, c2;
960 do {
961 c1 = msgidnextc(&s1, &q1);
962 c2 = msgidnextc(&s2, &q2);
963 if (c1 != c2)
964 return c1 - c2;
965 } while (c1 && c2);
966 return c1 - c2;
970 * Count the occurances of c in str
972 static int
973 charcount(char *str, int c)
975 char *cp;
976 int i;
978 for (i = 0, cp = str; *cp; cp++)
979 if (*cp == c)
980 i++;
981 return(i);
985 * See if the given header field is supposed to be ignored.
988 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
990 char *realfld;
991 int ret;
993 if (ignore == NULL)
994 return 0;
995 if (ignore == allignore)
996 return 1;
998 * Lower-case the string, so that "Status" and "status"
999 * will hash to the same place.
1001 realfld = ac_alloc(fieldlen + 1);
1002 i_strcpy(realfld, field, fieldlen + 1);
1003 if (ignore[1].i_count > 0)
1004 ret = !member(realfld, ignore + 1);
1005 else
1006 ret = member(realfld, ignore);
1007 ac_free(realfld);
1008 return ret;
1011 int
1012 member(char *realfield, struct ignoretab *table)
1014 struct ignore *igp;
1016 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1017 if (*igp->i_field == *realfield &&
1018 strcmp(igp->i_field, realfield) == 0)
1019 return (1);
1020 return (0);
1024 * Fake Sender for From_ lines if missing, e. g. with POP3.
1026 char *
1027 fakefrom(struct message *mp)
1029 char *name;
1031 if (((name = skin(hfield1("return-path", mp))) == NULL ||
1032 *name == '\0' ) &&
1033 ((name = skin(hfield1("from", mp))) == NULL ||
1034 *name == '\0'))
1035 name = "-";
1036 return name;
1039 char *
1040 fakedate(time_t t)
1042 char *cp, *cq;
1044 cp = ctime(&t);
1045 for (cq = cp; *cq && *cq != '\n'; cq++);
1046 *cq = '\0';
1047 return savestr(cp);
1050 char *
1051 nexttoken(char *cp)
1053 for (;;) {
1054 if (*cp == '\0')
1055 return NULL;
1056 if (*cp == '(') {
1057 int nesting = 0;
1059 while (*cp != '\0') {
1060 switch (*cp++) {
1061 case '(':
1062 nesting++;
1063 break;
1064 case ')':
1065 nesting--;
1066 break;
1068 if (nesting <= 0)
1069 break;
1071 } else if (blankchar(*cp & 0377) || *cp == ',')
1072 cp++;
1073 else
1074 break;
1076 return cp;
1080 * From username Fri Jan 2 20:13:51 2004
1081 * | | | | |
1082 * 0 5 10 15 20
1084 time_t
1085 unixtime(char *from)
1087 char *fp, *xp;
1088 time_t t;
1089 int i, year, month, day, hour, minute, second;
1090 int tzdiff;
1091 struct tm *tmptr;
1093 for (fp = from; *fp && *fp != '\n'; fp++);
1094 fp -= 24;
1095 if (fp - from < 7)
1096 goto invalid;
1097 if (fp[3] != ' ')
1098 goto invalid;
1099 for (i = 0; month_names[i]; i++)
1100 if (strncmp(&fp[4], month_names[i], 3) == 0)
1101 break;
1102 if (month_names[i] == 0)
1103 goto invalid;
1104 month = i + 1;
1105 if (fp[7] != ' ')
1106 goto invalid;
1107 day = strtol(&fp[8], &xp, 10);
1108 if (*xp != ' ' || xp != &fp[10])
1109 goto invalid;
1110 hour = strtol(&fp[11], &xp, 10);
1111 if (*xp != ':' || xp != &fp[13])
1112 goto invalid;
1113 minute = strtol(&fp[14], &xp, 10);
1114 if (*xp != ':' || xp != &fp[16])
1115 goto invalid;
1116 second = strtol(&fp[17], &xp, 10);
1117 if (*xp != ' ' || xp != &fp[19])
1118 goto invalid;
1119 year = strtol(&fp[20], &xp, 10);
1120 if (xp != &fp[24])
1121 goto invalid;
1122 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1123 (time_t)-1)
1124 goto invalid;
1125 tzdiff = t - mktime(gmtime(&t));
1126 tmptr = localtime(&t);
1127 if (tmptr->tm_isdst > 0)
1128 tzdiff += 3600;
1129 t -= tzdiff;
1130 return t;
1131 invalid:
1132 time(&t);
1133 return t;
1136 time_t
1137 rfctime(char *date)
1139 char *cp = date, *x;
1140 time_t t;
1141 int i, year, month, day, hour, minute, second;
1143 if ((cp = nexttoken(cp)) == NULL)
1144 goto invalid;
1145 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1146 alphachar(cp[2] & 0377) && cp[3] == ',') {
1147 if ((cp = nexttoken(&cp[4])) == NULL)
1148 goto invalid;
1150 day = strtol(cp, &x, 10);
1151 if ((cp = nexttoken(x)) == NULL)
1152 goto invalid;
1153 for (i = 0; month_names[i]; i++) {
1154 if (strncmp(cp, month_names[i], 3) == 0)
1155 break;
1157 if (month_names[i] == NULL)
1158 goto invalid;
1159 month = i + 1;
1160 if ((cp = nexttoken(&cp[3])) == NULL)
1161 goto invalid;
1162 year = strtol(cp, &x, 10);
1163 if ((cp = nexttoken(x)) == NULL)
1164 goto invalid;
1165 hour = strtol(cp, &x, 10);
1166 if (*x != ':')
1167 goto invalid;
1168 cp = &x[1];
1169 minute = strtol(cp, &x, 10);
1170 if (*x == ':') {
1171 cp = &x[1];
1172 second = strtol(cp, &x, 10);
1173 } else
1174 second = 0;
1175 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1176 (time_t)-1)
1177 goto invalid;
1178 if ((cp = nexttoken(x)) != NULL) {
1179 int sign = -1;
1180 char buf[3];
1182 switch (*cp) {
1183 case '-':
1184 sign = 1;
1185 /*FALLTHRU*/
1186 case '+':
1187 cp++;
1189 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1190 digitchar(cp[2] & 0377) &&
1191 digitchar(cp[3] & 0377)) {
1192 buf[2] = '\0';
1193 buf[0] = cp[0];
1194 buf[1] = cp[1];
1195 t += strtol(buf, NULL, 10) * sign * 3600;
1196 buf[0] = cp[2];
1197 buf[1] = cp[3];
1198 t += strtol(buf, NULL, 10) * sign * 60;
1201 return t;
1202 invalid:
1203 return 0;
1206 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1208 time_t
1209 combinetime(int year, int month, int day, int hour, int minute, int second)
1211 time_t t;
1213 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1214 return -1;
1215 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1216 if (year < 70)
1217 year += 2000;
1218 else if (year < 1900)
1219 year += 1900;
1220 if (month > 1)
1221 t += 86400 * 31;
1222 if (month > 2)
1223 t += 86400 * (leapyear(year) ? 29 : 28);
1224 if (month > 3)
1225 t += 86400 * 31;
1226 if (month > 4)
1227 t += 86400 * 30;
1228 if (month > 5)
1229 t += 86400 * 31;
1230 if (month > 6)
1231 t += 86400 * 30;
1232 if (month > 7)
1233 t += 86400 * 31;
1234 if (month > 8)
1235 t += 86400 * 31;
1236 if (month > 9)
1237 t += 86400 * 30;
1238 if (month > 10)
1239 t += 86400 * 31;
1240 if (month > 11)
1241 t += 86400 * 30;
1242 year -= 1900;
1243 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1244 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1245 return t;
1248 void
1249 substdate(struct message *m)
1251 char *cp;
1252 time_t now;
1255 * Determine the date to print in faked 'From ' lines. This is
1256 * traditionally the date the message was written to the mail
1257 * file. Try to determine this using RFC message header fields,
1258 * or fall back to current time.
1260 time(&now);
1261 if ((cp = hfield1("received", m)) != NULL) {
1262 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1264 cp++;
1265 while (alnumchar(*cp & 0377));
1267 if (cp && *++cp)
1268 m->m_time = rfctime(cp);
1270 if (m->m_time == 0 || m->m_time > now)
1271 if ((cp = hfield1("date", m)) != NULL)
1272 m->m_time = rfctime(cp);
1273 if (m->m_time == 0 || m->m_time > now)
1274 m->m_time = now;
1278 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1280 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1281 fprintf(stderr, "A Sender: field is required with multiple "
1282 "addresses in From: field.\n");
1283 return 1;
1285 if (senderfield && senderfield->n_flink) {
1286 fprintf(stderr, "The Sender: field may contain "
1287 "only one address.\n");
1288 return 2;
1290 return 0;
1293 char *
1294 getsender(struct message *mp)
1296 char *cp;
1297 struct name *np;
1299 if ((cp = hfield1("from", mp)) == NULL ||
1300 (np = sextract(cp, GEXTRA|GSKIN)) == NULL)
1301 return NULL;
1302 return np->n_flink != NULL ? skin(hfield1("sender", mp)) : np->n_name;