NEWS: v14.5
[s-mailx.git] / head.c
blob291e7bfd9fd5f4599e6b8a31eda359d7fadf9ee1
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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 HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #ifdef HAVE_IDNA
45 # include <idna.h>
46 # include <stringprep.h>
47 #endif
49 struct cmatch_data {
50 size_t tlen; /* Length of .tdata */
51 char const *tdata; /* Template date - see _cmatch_data[] */
55 * Template characters for cmatch_data.tdata:
56 * 'A' An upper case char
57 * 'a' A lower case char
58 * ' ' A space
59 * '0' A digit
60 * 'O' An optional digit or space
61 * ':' A colon
62 * '+' Either a plus or a minus sign
64 static struct cmatch_data const _cmatch_data[] = {
65 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
66 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
67 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
68 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
70 * RFC 822-alike From_ lines do not conform to RFC 4155, but seem to
71 * be used in the wild by UW-imap
73 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
74 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
75 * zone strings; note that 1. is strictly speaking not correct as some
76 * letters are not used, and 2. is not because only "UT" is defined */
77 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
78 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
79 { 0, NULL }
81 #define _DATE_MINLEN 21
83 /* Skip over "word" as found in From_ line */
84 static char const * _from__skipword(char const *wp);
86 /* Match the date string against the date template (tp), return if match.
87 * See _cmatch_data[] for template character description */
88 static int _cmatch(size_t len, char const *date, char const *tp);
90 /* Check wether date is a valid 'From_' date.
91 * (Rather ctime(3) generated dates, according to RFC 4155) */
92 static int _is_date(char const *date);
94 /* Convert the domain part of a skinned address to IDNA.
95 * If an error occurs before Unicode information is available, revert the IDNA
96 * error to a normal CHAR one so that the error message doesn't talk Unicode */
97 #ifdef HAVE_IDNA
98 static struct addrguts * _idna_apply(struct addrguts *agp);
99 #endif
101 /* Classify and check a (possibly skinned) header body according to RFC
102 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
103 * also a file or a pipe command, so check that first, then.
104 * Otherwise perform content checking and isolate the domain part (for IDNA) */
105 static int _addrspec_check(int doskin, struct addrguts *agp);
107 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
108 char **colon);
109 static int msgidnextc(const char **cp, int *status);
110 static int charcount(char *str, int c);
112 static char const *
113 _from__skipword(char const *wp)
115 char c = 0;
117 if (wp != NULL) {
118 while ((c = *wp++) != '\0' && ! blankchar(c)) {
119 if (c == '"') {
120 while ((c = *wp++) != '\0' && c != '"')
122 if (c != '"')
123 --wp;
126 for (; blankchar(c); c = *wp++)
129 return (c == 0 ? NULL : wp - 1);
132 static int
133 _cmatch(size_t len, char const *date, char const *tp)
135 int ret = 0;
137 while (len--) {
138 char c = date[len];
139 switch (tp[len]) {
140 case 'a':
141 if (! lowerchar(c))
142 goto jleave;
143 break;
144 case 'A':
145 if (! upperchar(c))
146 goto jleave;
147 break;
148 case ' ':
149 if (c != ' ')
150 goto jleave;
151 break;
152 case '0':
153 if (! digitchar(c))
154 goto jleave;
155 break;
156 case 'O':
157 if (c != ' ' && ! digitchar(c))
158 goto jleave;
159 break;
160 case ':':
161 if (c != ':')
162 goto jleave;
163 break;
164 case '+':
165 if (c != '+' && c != '-')
166 goto jleave;
167 break;
170 ret = 1;
171 jleave:
172 return (ret);
175 static int
176 _is_date(char const *date)
178 struct cmatch_data const *cmdp;
179 size_t dl = strlen(date);
180 int ret = 0;
182 if (dl >= _DATE_MINLEN)
183 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
184 if (dl == cmdp->tlen &&
185 (ret = _cmatch(dl, date, cmdp->tdata)))
186 break;
187 return (ret);
190 #ifdef HAVE_IDNA
191 static struct addrguts *
192 _idna_apply(struct addrguts *agp)
194 char *idna_utf8, *idna_ascii, *cs;
195 size_t sz, i;
196 int strict = (value("idna-strict-checks") != NULL);
198 sz = agp->ag_slen - agp->ag_sdom_start;
199 assert(sz > 0);
200 idna_utf8 = ac_alloc(sz + 1);
201 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
202 idna_utf8[sz] = '\0';
204 /* GNU Libidn settles on top of iconv(3) without having any fallback,
205 * so let's just let it perform the charset conversion, if any should
206 * be necessary */
207 if (! utf8) {
208 char const *tcs = charset_get_lc();
209 idna_ascii = idna_utf8;
210 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
211 i = (idna_utf8 == NULL && errno == EINVAL);
212 ac_free(idna_ascii);
213 if (idna_utf8 == NULL) {
214 if (i)
215 fprintf(stderr, tr(179,
216 "Cannot convert from %s to %s\n"),
217 tcs, "UTF-8");
218 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA |
219 NAME_ADDRSPEC_ERR_CHAR;
220 goto jleave;
224 if (idna_to_ascii_8z(idna_utf8, &idna_ascii,
225 strict ? IDNA_USE_STD3_ASCII_RULES : 0)
226 != IDNA_SUCCESS) {
227 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA |
228 NAME_ADDRSPEC_ERR_CHAR;
229 goto jleave1;
232 /* Replace the domain part of .ag_skinned with IDNA version */
233 sz = strlen(idna_ascii);
234 i = agp->ag_sdom_start;
235 cs = salloc(agp->ag_slen - i + sz + 1);
236 memcpy(cs, agp->ag_skinned, i);
237 memcpy(cs + i, idna_ascii, sz);
238 i += sz;
239 cs[i] = '\0';
241 agp->ag_skinned = cs;
242 agp->ag_slen = i;
243 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
244 NAME_NAME_SALLOC|NAME_SKINNED|NAME_IDNA, 0);
246 (free)(idna_ascii);
247 jleave1:
248 if (utf8)
249 ac_free(idna_utf8);
250 else
251 (free)(idna_utf8);
252 jleave:
253 return (agp);
255 #endif
257 static int
258 _addrspec_check(int skinned, struct addrguts *agp)
260 char *addr, *p, in_quote, in_domain, hadat;
261 union {char c; unsigned char u;} c;
262 #ifdef HAVE_IDNA
263 uc_it use_idna = ! boption("idna-disable");
264 #endif
266 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
267 addr = agp->ag_skinned;
269 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
270 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY,
272 goto jleave;
275 /* If the field is not a recipient, it cannot be a file or a pipe */
276 if (! skinned)
277 goto jaddr_check;
280 * Excerpt from nail.1:
282 * Recipient address specifications
283 * The rules are: Any name which starts with a `|' character specifies
284 * a pipe, the command string following the `|' is executed and
285 * the message is sent to its standard input; any other name which
286 * contains a `@' character is treated as a mail address; any other
287 * name which starts with a `+' character specifies a folder name; any
288 * other name which contains a `/' character but no `!' or `%'
289 * character before also specifies a folder name; what remains is
290 * treated as a mail address.
292 if (*addr == '|') {
293 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
294 goto jleave;
296 if (memchr(addr, '@', agp->ag_slen) == NULL) {
297 if (*addr == '+')
298 goto jisfile;
299 for (p = addr; (c.c = *p); ++p) {
300 if (c.c == '!' || c.c == '%')
301 break;
302 if (c.c == '/') {
303 jisfile: agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
304 goto jleave;
309 jaddr_check:
310 in_quote = in_domain = hadat = 0;
312 for (p = addr; (c.c = *p++) != '\0';) {
313 if (c.c == '"') {
314 in_quote = ! in_quote;
315 } else if (c.u < 040 || c.u >= 0177) {
316 #ifdef HAVE_IDNA
317 if (in_domain && use_idna) {
318 if (use_idna == 1)
319 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
320 NAME_ADDRSPEC_ERR_IDNA, c.u);
321 use_idna = 2;
322 } else
323 #endif
324 break;
325 } else if (in_domain == 2) {
326 if ((c.c == ']' && *p != '\0') || c.c == '\\' ||
327 whitechar(c.c))
328 break;
329 } else if (in_quote && in_domain == 0) {
330 /*EMPTY*/;
331 } else if (c.c == '\\' && *p != '\0') {
332 ++p;
333 } else if (c.c == '@') {
334 if (hadat++) {
335 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
336 NAME_ADDRSPEC_ERR_ATSEQ, c.u);
337 goto jleave;
339 agp->ag_sdom_start = (size_t)(p - addr);
340 in_domain = (*p == '[') ? 2 : 1;
341 continue;
342 } else if (c.c == '(' || c.c == ')' ||
343 c.c == '<' || c.c == '>' ||
344 c.c == ',' || c.c == ';' || c.c == ':' ||
345 c.c == '\\' || c.c == '[' || c.c == ']')
346 break;
347 hadat = 0;
350 if (c.c != '\0') {
351 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR,
352 c.u);
353 goto jleave;
356 #ifdef HAVE_IDNA
357 if (use_idna == 2)
358 agp = _idna_apply(agp);
359 #endif
361 jleave:
362 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
365 FL char const *
366 myaddrs(struct header *hp)
368 struct name *np;
369 char *rv = NULL;
371 if (hp != NULL && (np = hp->h_from) != NULL) {
372 if ((rv = np->n_fullname) != NULL)
373 goto jleave;
374 if ((rv = np->n_name) != NULL)
375 goto jleave;
378 if ((rv = voption("from")) != NULL)
379 goto jleave;
381 /* When invoking *sendmail* directly, it's its task
382 * to generate an otherwise undeterminable From: address.
383 * However, if the user sets *hostname*, accept his desire */
384 if (voption("smtp") != NULL || voption("hostname") != NULL) {
385 char *hn = nodename(1);
386 size_t sz = strlen(myname) + strlen(hn) + 2;
387 rv = salloc(sz);
388 snprintf(rv, sz, "%s@%s", myname, hn);
390 jleave:
391 return rv;
394 FL char const *
395 myorigin(struct header *hp)
397 char const *ret = NULL, *ccp;
398 struct name *np;
400 if ((ccp = myaddrs(hp)) != NULL &&
401 (np = lextract(ccp, GEXTRA|GFULL)) != NULL)
402 ret = np->n_flink != NULL ? value("sender") : ccp;
403 return (ret);
406 FL int
407 is_head(char const *linebuf, size_t linelen) /* XXX verbose WARN */
409 char date[FROM_DATEBUF];
411 return ((linelen <= 5 || memcmp(linebuf, "From ", 5) != 0 ||
412 ! extract_date_from_from_(linebuf, linelen, date) ||
413 ! _is_date(date)) ? 0 : 1);
416 FL int
417 extract_date_from_from_(char const *line, size_t linelen,
418 char datebuf[FROM_DATEBUF])
420 int ret = 0;
421 char const *cp = line;
423 /* "From " */
424 cp = _from__skipword(cp);
425 if (cp == NULL)
426 goto jerr;
427 /* "addr-spec " */
428 cp = _from__skipword(cp);
429 if (cp == NULL)
430 goto jerr;
431 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
432 cp = _from__skipword(cp);
433 if (cp == NULL)
434 goto jerr;
437 linelen -= (size_t)(cp - line);
438 if (linelen < _DATE_MINLEN)
439 goto jerr;
440 if (cp[linelen - 1] == '\n') {
441 --linelen;
442 /* (Rather IMAP/POP3 only) */
443 if (cp[linelen - 1] == '\r')
444 --linelen;
445 if (linelen < _DATE_MINLEN)
446 goto jerr;
448 if (linelen >= FROM_DATEBUF)
449 goto jerr;
451 ret = 1;
452 jleave: memcpy(datebuf, cp, linelen);
453 datebuf[linelen] = '\0';
454 return (ret);
456 jerr: cp = tr(213, "<Unknown date>");
457 linelen = strlen(cp);
458 if (linelen >= FROM_DATEBUF)
459 linelen = FROM_DATEBUF;
460 goto jleave;
463 FL void
464 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
466 struct header nh, *hq = &nh;
467 char *linebuf = NULL, *colon;
468 size_t linesize = 0;
469 int seenfields = 0, lc, c;
470 char const *val, *cp;
472 memset(hq, 0, sizeof *hq);
473 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; lc++)
475 rewind(fp);
476 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
477 if ((val = thisfield(linebuf, "to")) != NULL) {
478 seenfields++;
479 hq->h_to = cat(hq->h_to, checkaddrs(
480 lextract(val, GTO|GFULL)));
481 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
482 seenfields++;
483 hq->h_cc = cat(hq->h_cc, checkaddrs(
484 lextract(val, GCC|GFULL)));
485 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
486 seenfields++;
487 hq->h_bcc = cat(hq->h_bcc, checkaddrs(
488 lextract(val, GBCC|GFULL)));
489 } else if ((val = thisfield(linebuf, "from")) != NULL) {
490 seenfields++;
491 hq->h_from = cat(hq->h_from, checkaddrs(
492 lextract(val, GEXTRA|GFULL)));
493 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
494 seenfields++;
495 hq->h_replyto = cat(hq->h_replyto, checkaddrs(
496 lextract(val, GEXTRA|GFULL)));
497 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
498 seenfields++;
499 hq->h_sender = cat(hq->h_sender, checkaddrs(
500 lextract(val, GEXTRA|GFULL)));
501 } else if ((val = thisfield(linebuf,
502 "organization")) != NULL) {
503 seenfields++;
504 for (cp = val; blankchar(*cp); cp++)
506 hq->h_organization = hq->h_organization ?
507 save2str(hq->h_organization, cp) :
508 savestr(cp);
509 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
510 (val = thisfield(linebuf, "subj")) != NULL) {
511 seenfields++;
512 for (cp = val; blankchar(*cp); cp++)
514 hq->h_subject = hq->h_subject ?
515 save2str(hq->h_subject, cp) :
516 savestr(cp);
517 } else
518 fprintf(stderr, tr(266,
519 "Ignoring header field \"%s\"\n"),
520 linebuf);
523 * In case the blank line after the header has been edited out.
524 * Otherwise, fetch the header separator.
526 if (linebuf) {
527 if (linebuf[0] != '\0') {
528 for (cp = linebuf; *(++cp) != '\0'; );
529 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
530 } else {
531 if ((c = getc(fp)) != '\n' && c != EOF)
532 ungetc(c, fp);
535 if (seenfields) {
536 hp->h_to = hq->h_to;
537 hp->h_cc = hq->h_cc;
538 hp->h_bcc = hq->h_bcc;
539 hp->h_from = hq->h_from;
540 hp->h_replyto = hq->h_replyto;
541 hp->h_sender = hq->h_sender;
542 hp->h_organization = hq->h_organization;
543 hp->h_subject = hq->h_subject;
544 } else
545 fprintf(stderr, tr(267, "Restoring deleted header lines\n"));
546 if (linebuf)
547 free(linebuf);
551 * Return the desired header line from the passed message
552 * pointer (or NULL if the desired header field is not available).
553 * If mult is zero, return the content of the first matching header
554 * field only, the content of all matching header fields else.
556 FL char *
557 hfield_mult(char const *field, struct message *mp, int mult)
559 FILE *ibuf;
560 int lc;
561 size_t linesize = 0;
562 char *linebuf = NULL, *colon, *oldhfield = NULL;
563 char const *hfield;
565 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
566 return NULL;
567 if ((lc = mp->m_lines - 1) < 0)
568 return NULL;
570 if ((mp->m_flag & MNOFROM) == 0 &&
571 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
572 goto jleave;
573 while (lc > 0) {
574 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
575 break;
576 if ((hfield = thisfield(linebuf, field)) != NULL) {
577 oldhfield = save2str(hfield, oldhfield);
578 if (mult == 0)
579 break;
583 jleave:
584 if (linebuf != NULL)
585 free(linebuf);
586 return (oldhfield);
590 * Return the next header field found in the given message.
591 * Return >= 0 if something found, < 0 elsewise.
592 * "colon" is set to point to the colon in the header.
593 * Must deal with \ continuations & other such fraud.
595 static int
596 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
598 char *line2 = NULL;
599 size_t line2size = 0;
600 char *cp, *cp2;
601 int c, isenc;
603 if (*linebuf == NULL)
604 *linebuf = srealloc(*linebuf, *linesize = 1);
605 **linebuf = '\0';
606 for (;;) {
607 if (--rem < 0)
608 return -1;
609 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0)
610 return -1;
611 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
612 if (cp > *linebuf)
613 while (blankchar(*cp & 0377))
614 cp++;
615 if (*cp != ':' || cp == *linebuf)
616 continue;
618 * I guess we got a headline.
619 * Handle wraparounding
621 *colon = cp;
622 cp = *linebuf + c;
623 for (;;) {
624 isenc = 0;
625 while (--cp >= *linebuf && blankchar(*cp & 0377));
626 cp++;
627 if (rem <= 0)
628 break;
629 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
630 isenc |= 1;
631 ungetc(c = getc(f), f);
632 if (!blankchar(c))
633 break;
634 c = readline_restart(f, &line2, &line2size, 0);
635 if (c < 0)
636 break;
637 rem--;
638 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
639 c -= cp2 - line2;
640 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
641 isenc |= 2;
642 if (cp + c >= *linebuf + *linesize - 2) {
643 size_t diff = cp - *linebuf;
644 size_t colondiff = *colon - *linebuf;
645 *linebuf = srealloc(*linebuf,
646 *linesize += c + 2);
647 cp = &(*linebuf)[diff];
648 *colon = &(*linebuf)[colondiff];
650 if (isenc != 3)
651 *cp++ = ' ';
652 memcpy(cp, cp2, c);
653 cp += c;
655 *cp = 0;
656 if (line2)
657 free(line2);
658 return rem;
660 /* NOTREACHED */
664 * Check whether the passed line is a header line of
665 * the desired breed. Return the field body, or 0.
667 FL char const *
668 thisfield(char const *linebuf, char const *field)
670 while (lowerconv(*linebuf) == lowerconv(*field)) {
671 ++linebuf;
672 ++field;
674 if (*field != '\0')
675 return NULL;
676 while (blankchar(*linebuf))
677 ++linebuf;
678 if (*linebuf++ != ':')
679 return NULL;
680 while (blankchar(*linebuf))
681 ++linebuf;
682 return linebuf;
686 * Get sender's name from this message. If the message has
687 * a bunch of arpanet stuff in it, we may have to skin the name
688 * before returning it.
690 FL char *
691 nameof(struct message *mp, int reptype)
693 char *cp, *cp2;
695 cp = skin(name1(mp, reptype));
696 if (reptype != 0 || charcount(cp, '!') < 2)
697 return(cp);
698 cp2 = strrchr(cp, '!');
699 cp2--;
700 while (cp2 > cp && *cp2 != '!')
701 cp2--;
702 if (*cp2 == '!')
703 return(cp2 + 1);
704 return(cp);
708 * Start of a "comment".
709 * Ignore it.
711 FL char const *
712 skip_comment(char const *cp)
714 int nesting = 1;
716 for (; nesting > 0 && *cp; cp++) {
717 switch (*cp) {
718 case '\\':
719 if (cp[1])
720 cp++;
721 break;
722 case '(':
723 nesting++;
724 break;
725 case ')':
726 nesting--;
727 break;
730 return (cp);
734 * Return the start of a route-addr (address in angle brackets),
735 * if present.
737 FL char const *
738 routeaddr(char const *name)
740 char const *np, *rp = NULL;
742 for (np = name; *np; np++) {
743 switch (*np) {
744 case '(':
745 np = skip_comment(&np[1]) - 1;
746 break;
747 case '"':
748 while (*np) {
749 if (*++np == '"')
750 break;
751 if (*np == '\\' && np[1])
752 np++;
754 break;
755 case '<':
756 rp = np;
757 break;
758 case '>':
759 return rp;
762 return NULL;
766 * Check if a name's address part contains invalid characters.
768 FL int
769 is_addr_invalid(struct name *np, int putmsg)
771 char cbuf[sizeof "'\\U12340'"], *name = np->n_name;
772 int f = np->n_flags, ok8bit = 1;
773 unsigned int c;
774 char const *fmt = "'\\x%02X'", *cs;
776 if ((f & NAME_ADDRSPEC_INVALID) == 0 || ! putmsg ||
777 (f & NAME_ADDRSPEC_ERR_EMPTY) != 0)
778 goto jleave;
780 if (f & NAME_ADDRSPEC_ERR_IDNA)
781 cs = tr(284, "Invalid domain name: \"%s\", character %s\n"),
782 fmt = "'\\U%04X'",
783 ok8bit = 0;
784 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
785 cs = tr(142, "\"%s\" contains invalid %s sequence\n");
786 else
787 cs = tr(143, "\"%s\" contains invalid character %s\n");
789 c = NAME_ADDRSPEC_ERR_GETWC(f);
790 if (ok8bit && c >= 040 && c <= 0177)
791 snprintf(cbuf, sizeof cbuf, "'%c'", c);
792 else
793 snprintf(cbuf, sizeof cbuf, fmt, c);
795 fprintf(stderr, cs, name, cbuf);
796 jleave:
797 return ((f & NAME_ADDRSPEC_INVALID) != 0);
800 FL char *
801 skin(char const *name)
803 struct addrguts ag;
804 char *ret = NULL;
806 if (name != NULL) {
807 (void)addrspec_with_guts(1, name, &ag);
808 ret = ag.ag_skinned;
809 if ((ag.ag_n_flags & NAME_NAME_SALLOC) == 0)
810 ret = savestrbuf(ret, ag.ag_slen);
812 return (ret);
815 /* TODO addrspec_with_guts: RFC 5322 */
816 FL int
817 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
819 char const *cp;
820 char *cp2, *bufend, *nbuf, c;
821 char gotlt, gotaddr, lastsp;
823 memset(agp, 0, sizeof *agp);
825 if ((agp->ag_input = name) == NULL || /* XXX ever? */
826 (agp->ag_ilen = strlen(name)) == 0) {
827 agp->ag_skinned = UNCONST(""); /* ok: NAME_SALLOC is not set */
828 agp->ag_slen = 0;
829 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
830 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY,
832 return (1);
835 if (! doskin || ! anyof(name, "(< ")) {
836 /*agp->ag_iaddr_start = 0;*/
837 agp->ag_iaddr_aend = agp->ag_ilen;
838 agp->ag_skinned = UNCONST(name); /* (NAME_SALLOC not set) */
839 agp->ag_slen = agp->ag_ilen;
840 agp->ag_n_flags = NAME_SKINNED;
841 return _addrspec_check(doskin, agp);
844 /* Something makes us think we have to perform the skin operation */
845 nbuf = ac_alloc(agp->ag_ilen + 1);
846 /*agp->ag_iaddr_start = 0;*/
847 cp2 = bufend = nbuf;
848 gotlt = gotaddr = lastsp = 0;
850 for (cp = name++; (c = *cp++) != '\0'; ) {
851 switch (c) {
852 case '(':
853 cp = skip_comment(cp);
854 lastsp = 0;
855 break;
856 case '"':
858 * Start of a "quoted-string".
859 * Copy it in its entirety.
860 * XXX RFC: quotes are "semantically invisible"
861 * XXX But it was explicitly added (Changelog.Heirloom,
862 * XXX [9.23] released 11/15/00, "Do not remove quotes
863 * XXX when skinning names"? No more info..
865 *cp2++ = c;
866 while ((c = *cp) != '\0') { /* TODO improve */
867 cp++;
868 if (c == '"') {
869 *cp2++ = c;
870 break;
872 if (c != '\\')
873 *cp2++ = c;
874 else if ((c = *cp) != '\0') {
875 *cp2++ = c;
876 cp++;
879 lastsp = 0;
880 break;
881 case ' ':
882 case '\t':
883 if (gotaddr == 1) {
884 gotaddr = 2;
885 agp->ag_iaddr_aend = (size_t)(cp - name);
887 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
888 cp += 3, *cp2++ = '@';
889 else if (cp[0] == '@' && blankchar(cp[1]))
890 cp += 2, *cp2++ = '@';
891 else
892 lastsp = 1;
893 break;
894 case '<':
895 agp->ag_iaddr_start = (size_t)(cp - (name - 1));
896 cp2 = bufend;
897 gotlt = gotaddr = 1;
898 lastsp = 0;
899 break;
900 case '>':
901 if (gotlt) {
902 /* (_addrspec_check() verifies these later!) */
903 agp->ag_iaddr_aend = (size_t)(cp - name);
904 gotlt = 0;
905 while ((c = *cp) != '\0' && c != ',') {
906 cp++;
907 if (c == '(')
908 cp = skip_comment(cp);
909 else if (c == '"')
910 while ((c = *cp) != '\0') {
911 cp++;
912 if (c == '"')
913 break;
914 if (c == '\\' && *cp)
915 cp++;
918 lastsp = 0;
919 break;
921 /* FALLTRHOUGH */
922 default:
923 if (lastsp) {
924 lastsp = 0;
925 if (gotaddr)
926 *cp2++ = ' ';
928 *cp2++ = c;
929 if (c == ',') {
930 if (! gotlt) {
931 *cp2++ = ' ';
932 for (; blankchar(*cp); ++cp)
934 lastsp = 0;
935 bufend = cp2;
937 } else if (! gotaddr) {
938 gotaddr = 1;
939 agp->ag_iaddr_start = (size_t)(cp - name);
943 agp->ag_slen = (size_t)(cp2 - nbuf);
944 if (agp->ag_iaddr_aend == 0)
945 agp->ag_iaddr_aend = agp->ag_ilen;
947 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
948 ac_free(nbuf);
949 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
950 return _addrspec_check(doskin, agp);
954 * Fetch the real name from an internet mail address field.
956 FL char *
957 realname(char const *name)
959 char const *cp, *cq, *cstart = NULL, *cend = NULL;
960 char *rname, *rp;
961 struct str in, out;
962 int quoted, good, nogood;
964 if (name == NULL)
965 return NULL;
966 for (cp = UNCONST(name); *cp; cp++) {
967 switch (*cp) {
968 case '(':
969 if (cstart)
971 * More than one comment in address, doesn't
972 * make sense to display it without context.
973 * Return the entire field,
975 return mime_fromaddr(name);
976 cstart = cp++;
977 cp = skip_comment(cp);
978 cend = cp--;
979 if (cend <= cstart)
980 cend = cstart = NULL;
981 break;
982 case '"':
983 while (*cp) {
984 if (*++cp == '"')
985 break;
986 if (*cp == '\\' && cp[1])
987 cp++;
989 break;
990 case '<':
991 if (cp > name) {
992 cstart = name;
993 cend = cp;
995 break;
996 case ',':
998 * More than one address. Just use the first one.
1000 goto brk;
1003 brk: if (cstart == NULL) {
1004 if (*name == '<')
1006 * If name contains only a route-addr, the
1007 * surrounding angle brackets don't serve any
1008 * useful purpose when displaying, so they
1009 * are removed.
1011 return prstr(skin(name));
1012 return mime_fromaddr(name);
1014 rp = rname = ac_alloc(cend - cstart + 1);
1016 * Strip quotes. Note that quotes that appear within a MIME-
1017 * encoded word are not stripped. The idea is to strip only
1018 * syntactical relevant things (but this is not necessarily
1019 * the most sensible way in practice).
1021 quoted = 0;
1022 for (cp = cstart; cp < cend; cp++) {
1023 if (*cp == '(' && !quoted) {
1024 cq = skip_comment(++cp);
1025 if (--cq > cend)
1026 cq = cend;
1027 while (cp < cq) {
1028 if (*cp == '\\' && &cp[1] < cq)
1029 cp++;
1030 *rp++ = *cp++;
1032 } else if (*cp == '\\' && &cp[1] < cend)
1033 *rp++ = *++cp;
1034 else if (*cp == '"') {
1035 quoted = !quoted;
1036 continue;
1037 } else
1038 *rp++ = *cp;
1040 *rp = '\0';
1041 in.s = rname;
1042 in.l = rp - rname;
1043 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1044 ac_free(rname);
1045 rname = savestr(out.s);
1046 free(out.s);
1047 while (blankchar(*rname & 0377))
1048 rname++;
1049 for (rp = rname; *rp; rp++);
1050 while (--rp >= rname && blankchar(*rp & 0377))
1051 *rp = '\0';
1052 if (rp == rname)
1053 return mime_fromaddr(name);
1055 * mime_fromhdr() has converted all nonprintable characters to
1056 * question marks now. These and blanks are considered uninteresting;
1057 * if the displayed part of the real name contains more than 25% of
1058 * them, it is probably better to display the plain email address
1059 * instead.
1061 good = 0;
1062 nogood = 0;
1063 for (rp = rname; *rp && rp < &rname[20]; rp++)
1064 if (*rp == '?' || blankchar(*rp & 0377))
1065 nogood++;
1066 else
1067 good++;
1068 if (good*3 < nogood)
1069 return prstr(skin(name));
1070 return rname;
1074 * Fetch the sender's name from the passed message.
1075 * Reptype can be
1076 * 0 -- get sender's name for display purposes
1077 * 1 -- get sender's name for reply
1078 * 2 -- get sender's name for Reply
1080 FL char *
1081 name1(struct message *mp, int reptype)
1083 char *namebuf;
1084 size_t namesize;
1085 char *linebuf = NULL;
1086 size_t linesize = 0;
1087 char *cp, *cp2;
1088 FILE *ibuf;
1089 int f1st = 1;
1091 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1092 return cp;
1093 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL &&
1094 *cp != '\0')
1095 return cp;
1096 namebuf = smalloc(namesize = 1);
1097 namebuf[0] = 0;
1098 if (mp->m_flag & MNOFROM)
1099 goto out;
1100 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1101 goto out;
1102 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1103 goto out;
1104 newname:
1105 if (namesize <= linesize)
1106 namebuf = srealloc(namebuf, namesize = linesize + 1);
1107 for (cp = linebuf; *cp && *cp != ' '; cp++)
1109 for (; blankchar(*cp & 0377); cp++);
1110 for (cp2 = &namebuf[strlen(namebuf)];
1111 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
1112 *cp2++ = *cp++;
1113 *cp2 = '\0';
1114 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1115 goto out;
1116 if ((cp = strchr(linebuf, 'F')) == NULL)
1117 goto out;
1118 if (strncmp(cp, "From", 4) != 0)
1119 goto out;
1120 if (namesize <= linesize)
1121 namebuf = srealloc(namebuf, namesize = linesize + 1);
1122 while ((cp = strchr(cp, 'r')) != NULL) {
1123 if (strncmp(cp, "remote", 6) == 0) {
1124 if ((cp = strchr(cp, 'f')) == NULL)
1125 break;
1126 if (strncmp(cp, "from", 4) != 0)
1127 break;
1128 if ((cp = strchr(cp, ' ')) == NULL)
1129 break;
1130 cp++;
1131 if (f1st) {
1132 strncpy(namebuf, cp, namesize);
1133 f1st = 0;
1134 } else {
1135 cp2=strrchr(namebuf, '!')+1;
1136 strncpy(cp2, cp, (namebuf+namesize)-cp2);
1138 namebuf[namesize - 2] = '!';
1139 namebuf[namesize - 1] = '\0';
1140 goto newname;
1142 cp++;
1144 out:
1145 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1146 *cp == '\0')
1147 cp = savestr(namebuf);
1148 if (linebuf)
1149 free(linebuf);
1150 free(namebuf);
1151 return cp;
1154 static int
1155 msgidnextc(const char **cp, int *status)
1157 int c;
1159 for (;;) {
1160 if (*status & 01) {
1161 if (**cp == '"') {
1162 *status &= ~01;
1163 (*cp)++;
1164 continue;
1166 if (**cp == '\\') {
1167 (*cp)++;
1168 if (**cp == '\0')
1169 goto eof;
1171 goto dfl;
1173 switch (**cp) {
1174 case '(':
1175 *cp = skip_comment(&(*cp)[1]);
1176 continue;
1177 case '>':
1178 case '\0':
1179 eof:
1180 return '\0';
1181 case '"':
1182 (*cp)++;
1183 *status |= 01;
1184 continue;
1185 case '@':
1186 *status |= 02;
1187 /*FALLTHRU*/
1188 default:
1189 dfl:
1190 c = *(*cp)++ & 0377;
1191 return *status & 02 ? lowerconv(c) : c;
1196 FL int
1197 msgidcmp(const char *s1, const char *s2)
1199 int q1 = 0, q2 = 0;
1200 int c1, c2;
1202 do {
1203 c1 = msgidnextc(&s1, &q1);
1204 c2 = msgidnextc(&s2, &q2);
1205 if (c1 != c2)
1206 return c1 - c2;
1207 } while (c1 && c2);
1208 return c1 - c2;
1212 * Count the occurances of c in str
1214 static int
1215 charcount(char *str, int c)
1217 char *cp;
1218 int i;
1220 for (i = 0, cp = str; *cp; cp++)
1221 if (*cp == c)
1222 i++;
1223 return(i);
1227 * See if the given header field is supposed to be ignored.
1229 FL int
1230 is_ign(char const *field, size_t fieldlen, struct ignoretab ignoret[2])
1232 char *realfld;
1233 int ret;
1235 if (ignoret == NULL)
1236 return 0;
1237 if (ignoret == allignore)
1238 return 1;
1240 * Lower-case the string, so that "Status" and "status"
1241 * will hash to the same place.
1243 realfld = ac_alloc(fieldlen + 1);
1244 i_strcpy(realfld, field, fieldlen + 1);
1245 if (ignoret[1].i_count > 0)
1246 ret = !member(realfld, ignoret + 1);
1247 else
1248 ret = member(realfld, ignoret);
1249 ac_free(realfld);
1250 return ret;
1253 FL int
1254 member(char const *realfield, struct ignoretab *table)
1256 struct ignore *igp;
1258 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1259 if (*igp->i_field == *realfield &&
1260 strcmp(igp->i_field, realfield) == 0)
1261 return (1);
1262 return (0);
1266 * Fake Sender for From_ lines if missing, e. g. with POP3.
1268 FL char const *
1269 fakefrom(struct message *mp)
1271 char const *name;
1273 if (((name = skin(hfield1("return-path", mp))) == NULL ||
1274 *name == '\0' ) &&
1275 ((name = skin(hfield1("from", mp))) == NULL ||
1276 *name == '\0'))
1278 * XXX MAILER-DAEMON is what an old MBOX manual page says.
1279 * RFC 4155 however requires a RFC 5322 (2822) conforming
1280 * "addr-spec", but we simply can't provide that
1282 name = "MAILER-DAEMON";
1283 return name;
1286 FL char const *
1287 fakedate(time_t t)
1289 char *cp, *cq;
1291 cp = ctime(&t);
1292 for (cq = cp; *cq && *cq != '\n'; ++cq)
1294 *cq = '\0';
1295 return savestr(cp);
1298 static char const *
1299 nexttoken(char const *cp)
1301 for (;;) {
1302 if (*cp == '\0')
1303 return NULL;
1304 if (*cp == '(') {
1305 int nesting = 0;
1307 while (*cp != '\0') {
1308 switch (*cp++) {
1309 case '(':
1310 nesting++;
1311 break;
1312 case ')':
1313 nesting--;
1314 break;
1316 if (nesting <= 0)
1317 break;
1319 } else if (blankchar(*cp) || *cp == ',')
1320 cp++;
1321 else
1322 break;
1324 return cp;
1328 * From username Fri Jan 2 20:13:51 2004
1329 * | | | | |
1330 * 0 5 10 15 20
1332 FL time_t
1333 unixtime(char const *fromline)
1335 char const *fp;
1336 char *xp;
1337 time_t t;
1338 int i, year, month, day, hour, minute, second;
1339 int tzdiff;
1340 struct tm *tmptr;
1342 for (fp = fromline; *fp && *fp != '\n'; fp++);
1343 fp -= 24;
1344 if (fp - fromline < 7)
1345 goto invalid;
1346 if (fp[3] != ' ')
1347 goto invalid;
1348 for (i = 0;;) {
1349 if (strncmp(&fp[4], month_names[i], 3) == 0)
1350 break;
1351 if (month_names[++i][0] == '\0')
1352 goto invalid;
1354 month = i + 1;
1355 if (fp[7] != ' ')
1356 goto invalid;
1357 day = strtol(&fp[8], &xp, 10);
1358 if (*xp != ' ' || xp != &fp[10])
1359 goto invalid;
1360 hour = strtol(&fp[11], &xp, 10);
1361 if (*xp != ':' || xp != &fp[13])
1362 goto invalid;
1363 minute = strtol(&fp[14], &xp, 10);
1364 if (*xp != ':' || xp != &fp[16])
1365 goto invalid;
1366 second = strtol(&fp[17], &xp, 10);
1367 if (*xp != ' ' || xp != &fp[19])
1368 goto invalid;
1369 year = strtol(&fp[20], &xp, 10);
1370 if (xp != &fp[24])
1371 goto invalid;
1372 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1373 (time_t)-1)
1374 goto invalid;
1375 tzdiff = t - mktime(gmtime(&t));
1376 tmptr = localtime(&t);
1377 if (tmptr->tm_isdst > 0)
1378 tzdiff += 3600;
1379 t -= tzdiff;
1380 return t;
1381 invalid:
1382 time(&t);
1383 return t;
1386 FL time_t
1387 rfctime(char const *date)
1389 char const *cp = date;
1390 char *x;
1391 time_t t;
1392 int i, year, month, day, hour, minute, second;
1394 if ((cp = nexttoken(cp)) == NULL)
1395 goto invalid;
1396 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1397 cp[3] == ',') {
1398 if ((cp = nexttoken(&cp[4])) == NULL)
1399 goto invalid;
1401 day = strtol(cp, &x, 10); /* XXX strtol */
1402 if ((cp = nexttoken(x)) == NULL)
1403 goto invalid;
1404 for (i = 0;;) {
1405 if (strncmp(cp, month_names[i], 3) == 0)
1406 break;
1407 if (month_names[++i][0] == '\0')
1408 goto invalid;
1410 month = i + 1;
1411 if ((cp = nexttoken(&cp[3])) == NULL)
1412 goto invalid;
1414 * RFC 5322, 4.3:
1415 * Where a two or three digit year occurs in a date, the year is to be
1416 * interpreted as follows: If a two digit year is encountered whose
1417 * value is between 00 and 49, the year is interpreted by adding 2000,
1418 * ending up with a value between 2000 and 2049. If a two digit year
1419 * is encountered with a value between 50 and 99, or any three digit
1420 * year is encountered, the year is interpreted by adding 1900.
1422 year = strtol(cp, &x, 10); /* XXX strtol */
1423 i = (int)(x - cp);
1424 if (i == 2 && year >= 0 && year <= 49)
1425 year += 2000;
1426 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1427 year += 1900;
1428 if ((cp = nexttoken(x)) == NULL)
1429 goto invalid;
1430 hour = strtol(cp, &x, 10); /* XXX strtol */
1431 if (*x != ':')
1432 goto invalid;
1433 cp = &x[1];
1434 minute = strtol(cp, &x, 10);
1435 if (*x == ':') {
1436 cp = &x[1];
1437 second = strtol(cp, &x, 10);
1438 } else
1439 second = 0;
1440 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1441 (time_t)-1)
1442 goto invalid;
1443 if ((cp = nexttoken(x)) != NULL) {
1444 int sign = -1;
1445 char buf[3];
1447 switch (*cp) {
1448 case '-':
1449 sign = 1;
1450 /*FALLTHRU*/
1451 case '+':
1452 cp++;
1454 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1455 digitchar(cp[3])) {
1456 buf[2] = '\0';
1457 buf[0] = cp[0];
1458 buf[1] = cp[1];
1459 t += strtol(buf, NULL, 10) * sign * 3600;/*XXX strtrol*/
1460 buf[0] = cp[2];
1461 buf[1] = cp[3];
1462 t += strtol(buf, NULL, 10) * sign * 60; /* XXX strtol*/
1464 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1465 * TODO once again, Christos Zoulas and NetBSD Mail have done
1466 * TODO a really good job already, but using strptime(3), which
1467 * TODO is not portable. Nonetheless, WE must improve, not
1468 * TODO at last because we simply ignore obsolete timezones!!
1469 * TODO See RFC 5322, 4.3! */
1471 return t;
1472 invalid:
1473 return 0;
1476 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1478 FL time_t
1479 combinetime(int year, int month, int day, int hour, int minute, int second)
1481 time_t t;
1483 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1484 return -1;
1485 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1486 if (month > 1)
1487 t += 86400 * 31;
1488 if (month > 2)
1489 t += 86400 * (is_leapyear(year) ? 29 : 28);
1490 if (month > 3)
1491 t += 86400 * 31;
1492 if (month > 4)
1493 t += 86400 * 30;
1494 if (month > 5)
1495 t += 86400 * 31;
1496 if (month > 6)
1497 t += 86400 * 30;
1498 if (month > 7)
1499 t += 86400 * 31;
1500 if (month > 8)
1501 t += 86400 * 31;
1502 if (month > 9)
1503 t += 86400 * 30;
1504 if (month > 10)
1505 t += 86400 * 31;
1506 if (month > 11)
1507 t += 86400 * 30;
1508 year -= 1900;
1509 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1510 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1511 return t;
1514 FL void
1515 substdate(struct message *m)
1517 char const *cp;
1520 * Determine the date to print in faked 'From ' lines. This is
1521 * traditionally the date the message was written to the mail
1522 * file. Try to determine this using RFC message header fields,
1523 * or fall back to current time.
1525 if ((cp = hfield1("received", m)) != NULL) {
1526 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1528 cp++;
1529 while (alnumchar(*cp));
1531 if (cp && *++cp)
1532 m->m_time = rfctime(cp);
1534 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1535 if ((cp = hfield1("date", m)) != NULL)
1536 m->m_time = rfctime(cp);
1538 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1539 m->m_time = time_current.tc_time;
1542 FL int
1543 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1545 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1546 fprintf(stderr, "A Sender: field is required with multiple "
1547 "addresses in From: field.\n");
1548 return 1;
1550 if (senderfield && senderfield->n_flink) {
1551 fprintf(stderr, "The Sender: field may contain "
1552 "only one address.\n");
1553 return 2;
1555 return 0;
1558 FL char *
1559 getsender(struct message *mp)
1561 char *cp;
1562 struct name *np;
1564 if ((cp = hfield1("from", mp)) == NULL ||
1565 (np = lextract(cp, GEXTRA|GSKIN)) == NULL)
1566 return NULL;
1567 return np->n_flink != NULL ? skin(hfield1("sender", mp)) : np->n_name;
1570 FL int
1571 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
1573 /* TODO grab_headers: again, check counts etc. against RFC;
1574 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1575 int errs;
1576 int volatile comma;
1578 errs = 0;
1579 comma = (value("bsdcompat") || value("bsdmsgs")) ? 0 : GCOMMA;
1581 if (gflags & GTO)
1582 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO|GFULL);
1584 if (subjfirst && (gflags & GSUBJECT))
1585 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1587 if (gflags & GCC)
1588 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC|GFULL);
1590 if (gflags & GBCC)
1591 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC|GFULL);
1593 if (gflags & GEXTRA) {
1594 if (hp->h_from == NULL)
1595 hp->h_from = lextract(myaddrs(hp), GEXTRA|GFULL);
1596 hp->h_from = grab_names("From: ", hp->h_from, comma,
1597 GEXTRA|GFULL);
1598 if (hp->h_replyto == NULL)
1599 hp->h_replyto = lextract(value("replyto"),
1600 GEXTRA|GFULL);
1601 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
1602 GEXTRA|GFULL);
1603 if (hp->h_sender == NULL)
1604 hp->h_sender = extract(value("sender"), GEXTRA|GFULL);
1605 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
1606 GEXTRA|GFULL);
1607 if (hp->h_organization == NULL)
1608 hp->h_organization = value("ORGANIZATION");
1609 hp->h_organization = readstr_input("Organization: ",
1610 hp->h_organization);
1613 if (! subjfirst && (gflags & GSUBJECT))
1614 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1616 return errs;