Enable WANT_QUOTE_FOLD, *_ASSERT -> *_DEBUG, more cleanup
[s-mailx.git] / head.c
blob91c245d89c1ed0959b5025379a46669d084d88a2
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 #include "nail.h"
42 #ifdef HAVE_IDNA
43 # include <idna.h>
44 # include <stringprep.h>
45 #endif
47 struct cmatch_data {
48 size_t tlen; /* Length of .tdata */
49 char const *tdata; /* Template date - see _cmatch_data[] */
53 * Template characters for cmatch_data.tdata:
54 * 'A' An upper case char
55 * 'a' A lower case char
56 * ' ' A space
57 * '0' A digit
58 * 'O' An optional digit or space
59 * ':' A colon
60 * '+' Either a plus or a minus sign
62 static struct cmatch_data const _cmatch_data[] = {
63 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
64 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
65 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
66 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
68 * RFC 822-alike From_ lines do not conform to RFC 4155, but seem to
69 * be used in the wild by UW-imap
71 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
72 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
73 * zone strings; note that 1. is strictly speaking not correct as some
74 * letters are not used, and 2. is not because only "UT" is defined */
75 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
76 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
77 { 0, NULL }
79 #define _DATE_MINLEN 21
81 /* Skip over "word" as found in From_ line */
82 static char const * _from__skipword(char const *wp);
84 /* Match the date string against the date template (tp), return if match.
85 * See _cmatch_data[] for template character description */
86 static int _cmatch(size_t len, char const *date, char const *tp);
88 /* Check wether date is a valid 'From_' date.
89 * (Rather ctime(3) generated dates, according to RFC 4155) */
90 static int _is_date(char const *date);
92 /* Convert the domain part of a skinned address to IDNA.
93 * If an error occurs before Unicode information is available, revert the IDNA
94 * error to a normal CHAR one so that the error message doesn't talk Unicode */
95 #ifdef HAVE_IDNA
96 static struct addrguts * _idna_apply(struct addrguts *agp);
97 #endif
99 /* Classify and check a (possibly skinned) header body according to RFC
100 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
101 * also a file or a pipe command, so check that first, then.
102 * Otherwise perform content checking and isolate the domain part (for IDNA) */
103 static int _addrspec_check(int doskin, struct addrguts *agp);
105 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
106 char **colon);
107 static int msgidnextc(const char **cp, int *status);
108 static int charcount(char *str, int c);
110 static char const *
111 _from__skipword(char const *wp)
113 char c = 0;
115 if (wp != NULL) {
116 while ((c = *wp++) != '\0' && ! blankchar(c)) {
117 if (c == '"') {
118 while ((c = *wp++) != '\0' && c != '"')
120 if (c != '"')
121 --wp;
124 for (; blankchar(c); c = *wp++)
127 return (c == 0 ? NULL : wp - 1);
130 static int
131 _cmatch(size_t len, char const *date, char const *tp)
133 int ret = 0;
135 while (len--) {
136 char c = date[len];
137 switch (tp[len]) {
138 case 'a':
139 if (! lowerchar(c))
140 goto jleave;
141 break;
142 case 'A':
143 if (! upperchar(c))
144 goto jleave;
145 break;
146 case ' ':
147 if (c != ' ')
148 goto jleave;
149 break;
150 case '0':
151 if (! digitchar(c))
152 goto jleave;
153 break;
154 case 'O':
155 if (c != ' ' && ! digitchar(c))
156 goto jleave;
157 break;
158 case ':':
159 if (c != ':')
160 goto jleave;
161 break;
162 case '+':
163 if (c != '+' && c != '-')
164 goto jleave;
165 break;
168 ret = 1;
169 jleave:
170 return (ret);
173 static int
174 _is_date(char const *date)
176 struct cmatch_data const *cmdp;
177 size_t dl = strlen(date);
178 int ret = 0;
180 if (dl >= _DATE_MINLEN)
181 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
182 if (dl == cmdp->tlen &&
183 (ret = _cmatch(dl, date, cmdp->tdata)))
184 break;
185 return (ret);
188 #ifdef HAVE_IDNA
189 static struct addrguts *
190 _idna_apply(struct addrguts *agp)
192 char *idna_utf8, *idna_ascii, *cs;
193 size_t sz, i;
194 int strict = (value("idna-strict-checks") != NULL);
196 sz = agp->ag_slen - agp->ag_sdom_start;
197 assert(sz > 0);
198 idna_utf8 = ac_alloc(sz + 1);
199 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
200 idna_utf8[sz] = '\0';
202 /* GNU Libidn settles on top of iconv(3) without having any fallback,
203 * so let's just let it perform the charset conversion, if any should
204 * be necessary */
205 if (! utf8) {
206 char const *tcs = charset_get_lc();
207 idna_ascii = idna_utf8;
208 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
209 i = (idna_utf8 == NULL && errno == EINVAL);
210 ac_free(idna_ascii);
211 if (idna_utf8 == NULL) {
212 if (i)
213 fprintf(stderr, tr(179,
214 "Cannot convert from %s to %s\n"),
215 tcs, "UTF-8");
216 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA |
217 NAME_ADDRSPEC_ERR_CHAR;
218 goto jleave;
222 if (idna_to_ascii_8z(idna_utf8, &idna_ascii,
223 strict ? IDNA_USE_STD3_ASCII_RULES : 0)
224 != IDNA_SUCCESS) {
225 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA |
226 NAME_ADDRSPEC_ERR_CHAR;
227 goto jleave1;
230 /* Replace the domain part of .ag_skinned with IDNA version */
231 sz = strlen(idna_ascii);
232 i = agp->ag_sdom_start;
233 cs = salloc(agp->ag_slen - i + sz + 1);
234 memcpy(cs, agp->ag_skinned, i);
235 memcpy(cs + i, idna_ascii, sz);
236 i += sz;
237 cs[i] = '\0';
239 agp->ag_skinned = cs;
240 agp->ag_slen = i;
241 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
242 NAME_NAME_SALLOC|NAME_SKINNED|NAME_IDNA, 0);
244 (free)(idna_ascii);
245 jleave1:
246 if (utf8)
247 ac_free(idna_utf8);
248 else
249 (free)(idna_utf8);
250 jleave:
251 return (agp);
253 #endif
255 static int
256 _addrspec_check(int skinned, struct addrguts *agp)
258 char *addr, *p, in_quote, in_domain, hadat;
259 union {char c; unsigned char u;} c;
260 #ifdef HAVE_IDNA
261 uc_it use_idna = ! boption("idna-disable");
262 #endif
264 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
265 addr = agp->ag_skinned;
267 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
268 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY,
270 goto jleave;
273 /* If the field is not a recipient, it cannot be a file or a pipe */
274 if (! skinned)
275 goto jaddr_check;
278 * Excerpt from nail.1:
280 * Recipient address specifications
281 * The rules are: Any name which starts with a `|' character specifies
282 * a pipe, the command string following the `|' is executed and
283 * the message is sent to its standard input; any other name which
284 * contains a `@' character is treated as a mail address; any other
285 * name which starts with a `+' character specifies a folder name; any
286 * other name which contains a `/' character but no `!' or `%'
287 * character before also specifies a folder name; what remains is
288 * treated as a mail address.
290 if (*addr == '|') {
291 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
292 goto jleave;
294 if (memchr(addr, '@', agp->ag_slen) == NULL) {
295 if (*addr == '+')
296 goto jisfile;
297 for (p = addr; (c.c = *p); ++p) {
298 if (c.c == '!' || c.c == '%')
299 break;
300 if (c.c == '/') {
301 jisfile: agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
302 goto jleave;
307 jaddr_check:
308 in_quote = in_domain = hadat = 0;
310 for (p = addr; (c.c = *p++) != '\0';) {
311 if (c.c == '"') {
312 in_quote = ! in_quote;
313 } else if (c.u < 040 || c.u >= 0177) {
314 #ifdef HAVE_IDNA
315 if (in_domain && use_idna) {
316 if (use_idna == 1)
317 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
318 NAME_ADDRSPEC_ERR_IDNA, c.u);
319 use_idna = 2;
320 } else
321 #endif
322 break;
323 } else if (in_domain == 2) {
324 if ((c.c == ']' && *p != '\0') || c.c == '\\' ||
325 whitechar(c.c))
326 break;
327 } else if (in_quote && in_domain == 0) {
328 /*EMPTY*/;
329 } else if (c.c == '\\' && *p != '\0') {
330 ++p;
331 } else if (c.c == '@') {
332 if (hadat++) {
333 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
334 NAME_ADDRSPEC_ERR_ATSEQ, c.u);
335 goto jleave;
337 agp->ag_sdom_start = (size_t)(p - addr);
338 in_domain = (*p == '[') ? 2 : 1;
339 continue;
340 } else if (c.c == '(' || c.c == ')' ||
341 c.c == '<' || c.c == '>' ||
342 c.c == ',' || c.c == ';' || c.c == ':' ||
343 c.c == '\\' || c.c == '[' || c.c == ']')
344 break;
345 hadat = 0;
348 if (c.c != '\0') {
349 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR,
350 c.u);
351 goto jleave;
354 #ifdef HAVE_IDNA
355 if (use_idna == 2)
356 agp = _idna_apply(agp);
357 #endif
359 jleave:
360 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
363 char const *
364 myaddrs(struct header *hp)
366 struct name *np;
367 char *rv = NULL;
369 if (hp != NULL && (np = hp->h_from) != NULL) {
370 if ((rv = np->n_fullname) != NULL)
371 goto jleave;
372 if ((rv = np->n_name) != NULL)
373 goto jleave;
376 if ((rv = voption("from")) != NULL)
377 goto jleave;
379 /* When invoking *sendmail* directly, it's its task
380 * to generate an otherwise undeterminable From: address.
381 * However, if the user sets *hostname*, accept his desire */
382 if (voption("smtp") != NULL || voption("hostname") != NULL) {
383 char *hn = nodename(1);
384 size_t sz = strlen(myname) + strlen(hn) + 2;
385 rv = salloc(sz);
386 snprintf(rv, sz, "%s@%s", myname, hn);
388 jleave:
389 return rv;
392 char const *
393 myorigin(struct header *hp)
395 char const *ret = NULL, *ccp;
396 struct name *np;
398 if ((ccp = myaddrs(hp)) != NULL &&
399 (np = lextract(ccp, GEXTRA|GFULL)) != NULL)
400 ret = np->n_flink != NULL ? value("sender") : ccp;
401 return (ret);
405 is_head(char const *linebuf, size_t linelen) /* XXX verbose WARN */
407 char date[FROM_DATEBUF];
409 return ((linelen <= 5 || memcmp(linebuf, "From ", 5) != 0 ||
410 ! extract_date_from_from_(linebuf, linelen, date) ||
411 ! _is_date(date)) ? 0 : 1);
415 extract_date_from_from_(char const *line, size_t linelen,
416 char datebuf[FROM_DATEBUF])
418 int ret = 0;
419 char const *cp = line;
421 /* "From " */
422 cp = _from__skipword(cp);
423 if (cp == NULL)
424 goto jerr;
425 /* "addr-spec " */
426 cp = _from__skipword(cp);
427 if (cp == NULL)
428 goto jerr;
429 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
430 cp = _from__skipword(cp);
431 if (cp == NULL)
432 goto jerr;
435 linelen -= (size_t)(cp - line);
436 if (linelen < _DATE_MINLEN)
437 goto jerr;
438 if (cp[linelen - 1] == '\n') {
439 --linelen;
440 /* (Rather IMAP/POP3 only) */
441 if (cp[linelen - 1] == '\r')
442 --linelen;
443 if (linelen < _DATE_MINLEN)
444 goto jerr;
446 if (linelen >= FROM_DATEBUF)
447 goto jerr;
449 ret = 1;
450 jleave: memcpy(datebuf, cp, linelen);
451 datebuf[linelen] = '\0';
452 return (ret);
454 jerr: cp = tr(213, "<Unknown date>");
455 linelen = strlen(cp);
456 if (linelen >= FROM_DATEBUF)
457 linelen = FROM_DATEBUF;
458 goto jleave;
461 void
462 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
464 struct header nh, *hq = &nh;
465 char *linebuf = NULL, *colon;
466 size_t linesize = 0;
467 int seenfields = 0, lc, c;
468 char const *val, *cp;
470 memset(hq, 0, sizeof *hq);
471 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; lc++)
473 rewind(fp);
474 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
475 if ((val = thisfield(linebuf, "to")) != NULL) {
476 seenfields++;
477 hq->h_to = cat(hq->h_to, checkaddrs(
478 lextract(val, GTO|GFULL)));
479 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
480 seenfields++;
481 hq->h_cc = cat(hq->h_cc, checkaddrs(
482 lextract(val, GCC|GFULL)));
483 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
484 seenfields++;
485 hq->h_bcc = cat(hq->h_bcc, checkaddrs(
486 lextract(val, GBCC|GFULL)));
487 } else if ((val = thisfield(linebuf, "from")) != NULL) {
488 seenfields++;
489 hq->h_from = cat(hq->h_from, checkaddrs(
490 lextract(val, GEXTRA|GFULL)));
491 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
492 seenfields++;
493 hq->h_replyto = cat(hq->h_replyto, checkaddrs(
494 lextract(val, GEXTRA|GFULL)));
495 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
496 seenfields++;
497 hq->h_sender = cat(hq->h_sender, checkaddrs(
498 lextract(val, GEXTRA|GFULL)));
499 } else if ((val = thisfield(linebuf,
500 "organization")) != NULL) {
501 seenfields++;
502 for (cp = val; blankchar(*cp); cp++)
504 hq->h_organization = hq->h_organization ?
505 save2str(hq->h_organization, cp) :
506 savestr(cp);
507 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
508 (val = thisfield(linebuf, "subj")) != NULL) {
509 seenfields++;
510 for (cp = val; blankchar(*cp); cp++)
512 hq->h_subject = hq->h_subject ?
513 save2str(hq->h_subject, cp) :
514 savestr(cp);
515 } else
516 fprintf(stderr, tr(266,
517 "Ignoring header field \"%s\"\n"),
518 linebuf);
521 * In case the blank line after the header has been edited out.
522 * Otherwise, fetch the header separator.
524 if (linebuf) {
525 if (linebuf[0] != '\0') {
526 for (cp = linebuf; *(++cp) != '\0'; );
527 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
528 } else {
529 if ((c = getc(fp)) != '\n' && c != EOF)
530 ungetc(c, fp);
533 if (seenfields) {
534 hp->h_to = hq->h_to;
535 hp->h_cc = hq->h_cc;
536 hp->h_bcc = hq->h_bcc;
537 hp->h_from = hq->h_from;
538 hp->h_replyto = hq->h_replyto;
539 hp->h_sender = hq->h_sender;
540 hp->h_organization = hq->h_organization;
541 hp->h_subject = hq->h_subject;
542 } else
543 fprintf(stderr, tr(267, "Restoring deleted header lines\n"));
544 if (linebuf)
545 free(linebuf);
549 * Return the desired header line from the passed message
550 * pointer (or NULL if the desired header field is not available).
551 * If mult is zero, return the content of the first matching header
552 * field only, the content of all matching header fields else.
554 char *
555 hfield_mult(char const *field, struct message *mp, int mult)
557 FILE *ibuf;
558 int lc;
559 size_t linesize = 0;
560 char *linebuf = NULL, *colon, *oldhfield = NULL;
561 char const *hfield;
563 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
564 return NULL;
565 if ((lc = mp->m_lines - 1) < 0)
566 return NULL;
568 if ((mp->m_flag & MNOFROM) == 0 &&
569 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
570 goto jleave;
571 while (lc > 0) {
572 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
573 break;
574 if ((hfield = thisfield(linebuf, field)) != NULL) {
575 oldhfield = save2str(hfield, oldhfield);
576 if (mult == 0)
577 break;
581 jleave:
582 if (linebuf != NULL)
583 free(linebuf);
584 return (oldhfield);
588 * Return the next header field found in the given message.
589 * Return >= 0 if something found, < 0 elsewise.
590 * "colon" is set to point to the colon in the header.
591 * Must deal with \ continuations & other such fraud.
593 static int
594 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
596 char *line2 = NULL;
597 size_t line2size = 0;
598 char *cp, *cp2;
599 int c, isenc;
601 if (*linebuf == NULL)
602 *linebuf = srealloc(*linebuf, *linesize = 1);
603 **linebuf = '\0';
604 for (;;) {
605 if (--rem < 0)
606 return -1;
607 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0)
608 return -1;
609 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
610 if (cp > *linebuf)
611 while (blankchar(*cp & 0377))
612 cp++;
613 if (*cp != ':' || cp == *linebuf)
614 continue;
616 * I guess we got a headline.
617 * Handle wraparounding
619 *colon = cp;
620 cp = *linebuf + c;
621 for (;;) {
622 isenc = 0;
623 while (--cp >= *linebuf && blankchar(*cp & 0377));
624 cp++;
625 if (rem <= 0)
626 break;
627 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
628 isenc |= 1;
629 ungetc(c = getc(f), f);
630 if (!blankchar(c))
631 break;
632 c = readline_restart(f, &line2, &line2size, 0);
633 if (c < 0)
634 break;
635 rem--;
636 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
637 c -= cp2 - line2;
638 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
639 isenc |= 2;
640 if (cp + c >= *linebuf + *linesize - 2) {
641 size_t diff = cp - *linebuf;
642 size_t colondiff = *colon - *linebuf;
643 *linebuf = srealloc(*linebuf,
644 *linesize += c + 2);
645 cp = &(*linebuf)[diff];
646 *colon = &(*linebuf)[colondiff];
648 if (isenc != 3)
649 *cp++ = ' ';
650 memcpy(cp, cp2, c);
651 cp += c;
653 *cp = 0;
654 if (line2)
655 free(line2);
656 return rem;
658 /* NOTREACHED */
662 * Check whether the passed line is a header line of
663 * the desired breed. Return the field body, or 0.
665 char const *
666 thisfield(char const *linebuf, char const *field)
668 while (lowerconv(*linebuf) == lowerconv(*field)) {
669 ++linebuf;
670 ++field;
672 if (*field != '\0')
673 return NULL;
674 while (blankchar(*linebuf))
675 ++linebuf;
676 if (*linebuf++ != ':')
677 return NULL;
678 while (blankchar(*linebuf))
679 ++linebuf;
680 return linebuf;
684 * Get sender's name from this message. If the message has
685 * a bunch of arpanet stuff in it, we may have to skin the name
686 * before returning it.
688 char *
689 nameof(struct message *mp, int reptype)
691 char *cp, *cp2;
693 cp = skin(name1(mp, reptype));
694 if (reptype != 0 || charcount(cp, '!') < 2)
695 return(cp);
696 cp2 = strrchr(cp, '!');
697 cp2--;
698 while (cp2 > cp && *cp2 != '!')
699 cp2--;
700 if (*cp2 == '!')
701 return(cp2 + 1);
702 return(cp);
706 * Start of a "comment".
707 * Ignore it.
709 char const *
710 skip_comment(char const *cp)
712 int nesting = 1;
714 for (; nesting > 0 && *cp; cp++) {
715 switch (*cp) {
716 case '\\':
717 if (cp[1])
718 cp++;
719 break;
720 case '(':
721 nesting++;
722 break;
723 case ')':
724 nesting--;
725 break;
728 return (cp);
732 * Return the start of a route-addr (address in angle brackets),
733 * if present.
735 char const *
736 routeaddr(char const *name)
738 char const *np, *rp = NULL;
740 for (np = name; *np; np++) {
741 switch (*np) {
742 case '(':
743 np = skip_comment(&np[1]) - 1;
744 break;
745 case '"':
746 while (*np) {
747 if (*++np == '"')
748 break;
749 if (*np == '\\' && np[1])
750 np++;
752 break;
753 case '<':
754 rp = np;
755 break;
756 case '>':
757 return rp;
760 return NULL;
764 * Check if a name's address part contains invalid characters.
766 int
767 is_addr_invalid(struct name *np, int putmsg)
769 char cbuf[sizeof "'\\U12340'"], *name = np->n_name;
770 int f = np->n_flags, ok8bit = 1;
771 unsigned int c;
772 char const *fmt = "'\\x%02X'", *cs;
774 if ((f & NAME_ADDRSPEC_INVALID) == 0 || ! putmsg ||
775 (f & NAME_ADDRSPEC_ERR_EMPTY) != 0)
776 goto jleave;
778 if (f & NAME_ADDRSPEC_ERR_IDNA)
779 cs = tr(284, "Invalid domain name: \"%s\", character %s\n"),
780 fmt = "'\\U%04X'",
781 ok8bit = 0;
782 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
783 cs = tr(142, "\"%s\" contains invalid %s sequence\n");
784 else
785 cs = tr(143, "\"%s\" contains invalid character %s\n");
787 c = NAME_ADDRSPEC_ERR_GETWC(f);
788 if (ok8bit && c >= 040 && c <= 0177)
789 snprintf(cbuf, sizeof cbuf, "'%c'", c);
790 else
791 snprintf(cbuf, sizeof cbuf, fmt, c);
793 fprintf(stderr, cs, name, cbuf);
794 jleave:
795 return ((f & NAME_ADDRSPEC_INVALID) != 0);
798 char *
799 skin(char const *name)
801 struct addrguts ag;
802 char *ret = NULL;
804 if (name != NULL) {
805 (void)addrspec_with_guts(1, name, &ag);
806 ret = ag.ag_skinned;
807 if ((ag.ag_n_flags & NAME_NAME_SALLOC) == 0)
808 ret = savestrbuf(ret, ag.ag_slen);
810 return (ret);
813 /* TODO addrspec_with_guts: RFC 5322 */
815 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
817 char const *cp;
818 char *cp2, *bufend, *nbuf, c;
819 char gotlt, gotaddr, lastsp;
821 memset(agp, 0, sizeof *agp);
823 if ((agp->ag_input = name) == NULL || /* XXX ever? */
824 (agp->ag_ilen = strlen(name)) == 0) {
825 agp->ag_skinned = UNCONST(""); /* ok: NAME_SALLOC is not set */
826 agp->ag_slen = 0;
827 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
828 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY,
830 return (1);
833 if (! doskin || ! anyof(name, "(< ")) {
834 /*agp->ag_iaddr_start = 0;*/
835 agp->ag_iaddr_aend = agp->ag_ilen;
836 agp->ag_skinned = UNCONST(name); /* (NAME_SALLOC not set) */
837 agp->ag_slen = agp->ag_ilen;
838 agp->ag_n_flags = NAME_SKINNED;
839 return _addrspec_check(doskin, agp);
842 /* Something makes us think we have to perform the skin operation */
843 nbuf = ac_alloc(agp->ag_ilen + 1);
844 /*agp->ag_iaddr_start = 0;*/
845 cp2 = bufend = nbuf;
846 gotlt = gotaddr = lastsp = 0;
848 for (cp = name++; (c = *cp++) != '\0'; ) {
849 switch (c) {
850 case '(':
851 cp = skip_comment(cp);
852 lastsp = 0;
853 break;
854 case '"':
856 * Start of a "quoted-string".
857 * Copy it in its entirety.
858 * XXX RFC: quotes are "semantically invisible"
859 * XXX But it was explicitly added (Changelog.Heirloom,
860 * XXX [9.23] released 11/15/00, "Do not remove quotes
861 * XXX when skinning names"? No more info..
863 *cp2++ = c;
864 while ((c = *cp) != '\0') { /* TODO improve */
865 cp++;
866 if (c == '"') {
867 *cp2++ = c;
868 break;
870 if (c != '\\')
871 *cp2++ = c;
872 else if ((c = *cp) != '\0') {
873 *cp2++ = c;
874 cp++;
877 lastsp = 0;
878 break;
879 case ' ':
880 case '\t':
881 if (gotaddr == 1) {
882 gotaddr = 2;
883 agp->ag_iaddr_aend = (size_t)(cp - name);
885 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
886 cp += 3, *cp2++ = '@';
887 else if (cp[0] == '@' && blankchar(cp[1]))
888 cp += 2, *cp2++ = '@';
889 else
890 lastsp = 1;
891 break;
892 case '<':
893 agp->ag_iaddr_start = (size_t)(cp - (name - 1));
894 cp2 = bufend;
895 gotlt = gotaddr = 1;
896 lastsp = 0;
897 break;
898 case '>':
899 if (gotlt) {
900 /* (_addrspec_check() verifies these later!) */
901 agp->ag_iaddr_aend = (size_t)(cp - name);
902 gotlt = 0;
903 while ((c = *cp) != '\0' && c != ',') {
904 cp++;
905 if (c == '(')
906 cp = skip_comment(cp);
907 else if (c == '"')
908 while ((c = *cp) != '\0') {
909 cp++;
910 if (c == '"')
911 break;
912 if (c == '\\' && *cp)
913 cp++;
916 lastsp = 0;
917 break;
919 /* FALLTRHOUGH */
920 default:
921 if (lastsp) {
922 lastsp = 0;
923 if (gotaddr)
924 *cp2++ = ' ';
926 *cp2++ = c;
927 if (c == ',') {
928 if (! gotlt) {
929 *cp2++ = ' ';
930 for (; blankchar(*cp); ++cp)
932 lastsp = 0;
933 bufend = cp2;
935 } else if (! gotaddr) {
936 gotaddr = 1;
937 agp->ag_iaddr_start = (size_t)(cp - name);
941 agp->ag_slen = (size_t)(cp2 - nbuf);
942 if (agp->ag_iaddr_aend == 0)
943 agp->ag_iaddr_aend = agp->ag_ilen;
945 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
946 ac_free(nbuf);
947 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
948 return _addrspec_check(doskin, agp);
952 * Fetch the real name from an internet mail address field.
954 char *
955 realname(char const *name)
957 char const *cp, *cq, *cstart = NULL, *cend = NULL;
958 char *rname, *rp;
959 struct str in, out;
960 int quoted, good, nogood;
962 if (name == NULL)
963 return NULL;
964 for (cp = UNCONST(name); *cp; cp++) {
965 switch (*cp) {
966 case '(':
967 if (cstart)
969 * More than one comment in address, doesn't
970 * make sense to display it without context.
971 * Return the entire field,
973 return mime_fromaddr(name);
974 cstart = cp++;
975 cp = skip_comment(cp);
976 cend = cp--;
977 if (cend <= cstart)
978 cend = cstart = NULL;
979 break;
980 case '"':
981 while (*cp) {
982 if (*++cp == '"')
983 break;
984 if (*cp == '\\' && cp[1])
985 cp++;
987 break;
988 case '<':
989 if (cp > name) {
990 cstart = name;
991 cend = cp;
993 break;
994 case ',':
996 * More than one address. Just use the first one.
998 goto brk;
1001 brk: if (cstart == NULL) {
1002 if (*name == '<')
1004 * If name contains only a route-addr, the
1005 * surrounding angle brackets don't serve any
1006 * useful purpose when displaying, so they
1007 * are removed.
1009 return prstr(skin(name));
1010 return mime_fromaddr(name);
1012 rp = rname = ac_alloc(cend - cstart + 1);
1014 * Strip quotes. Note that quotes that appear within a MIME-
1015 * encoded word are not stripped. The idea is to strip only
1016 * syntactical relevant things (but this is not necessarily
1017 * the most sensible way in practice).
1019 quoted = 0;
1020 for (cp = cstart; cp < cend; cp++) {
1021 if (*cp == '(' && !quoted) {
1022 cq = skip_comment(++cp);
1023 if (--cq > cend)
1024 cq = cend;
1025 while (cp < cq) {
1026 if (*cp == '\\' && &cp[1] < cq)
1027 cp++;
1028 *rp++ = *cp++;
1030 } else if (*cp == '\\' && &cp[1] < cend)
1031 *rp++ = *++cp;
1032 else if (*cp == '"') {
1033 quoted = !quoted;
1034 continue;
1035 } else
1036 *rp++ = *cp;
1038 *rp = '\0';
1039 in.s = rname;
1040 in.l = rp - rname;
1041 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1042 ac_free(rname);
1043 rname = savestr(out.s);
1044 free(out.s);
1045 while (blankchar(*rname & 0377))
1046 rname++;
1047 for (rp = rname; *rp; rp++);
1048 while (--rp >= rname && blankchar(*rp & 0377))
1049 *rp = '\0';
1050 if (rp == rname)
1051 return mime_fromaddr(name);
1053 * mime_fromhdr() has converted all nonprintable characters to
1054 * question marks now. These and blanks are considered uninteresting;
1055 * if the displayed part of the real name contains more than 25% of
1056 * them, it is probably better to display the plain email address
1057 * instead.
1059 good = 0;
1060 nogood = 0;
1061 for (rp = rname; *rp && rp < &rname[20]; rp++)
1062 if (*rp == '?' || blankchar(*rp & 0377))
1063 nogood++;
1064 else
1065 good++;
1066 if (good*3 < nogood)
1067 return prstr(skin(name));
1068 return rname;
1072 * Fetch the sender's name from the passed message.
1073 * Reptype can be
1074 * 0 -- get sender's name for display purposes
1075 * 1 -- get sender's name for reply
1076 * 2 -- get sender's name for Reply
1078 char *
1079 name1(struct message *mp, int reptype)
1081 char *namebuf;
1082 size_t namesize;
1083 char *linebuf = NULL;
1084 size_t linesize = 0;
1085 char *cp, *cp2;
1086 FILE *ibuf;
1087 int f1st = 1;
1089 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1090 return cp;
1091 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL &&
1092 *cp != '\0')
1093 return cp;
1094 namebuf = smalloc(namesize = 1);
1095 namebuf[0] = 0;
1096 if (mp->m_flag & MNOFROM)
1097 goto out;
1098 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1099 goto out;
1100 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1101 goto out;
1102 newname:
1103 if (namesize <= linesize)
1104 namebuf = srealloc(namebuf, namesize = linesize + 1);
1105 for (cp = linebuf; *cp && *cp != ' '; cp++)
1107 for (; blankchar(*cp & 0377); cp++);
1108 for (cp2 = &namebuf[strlen(namebuf)];
1109 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
1110 *cp2++ = *cp++;
1111 *cp2 = '\0';
1112 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1113 goto out;
1114 if ((cp = strchr(linebuf, 'F')) == NULL)
1115 goto out;
1116 if (strncmp(cp, "From", 4) != 0)
1117 goto out;
1118 if (namesize <= linesize)
1119 namebuf = srealloc(namebuf, namesize = linesize + 1);
1120 while ((cp = strchr(cp, 'r')) != NULL) {
1121 if (strncmp(cp, "remote", 6) == 0) {
1122 if ((cp = strchr(cp, 'f')) == NULL)
1123 break;
1124 if (strncmp(cp, "from", 4) != 0)
1125 break;
1126 if ((cp = strchr(cp, ' ')) == NULL)
1127 break;
1128 cp++;
1129 if (f1st) {
1130 strncpy(namebuf, cp, namesize);
1131 f1st = 0;
1132 } else {
1133 cp2=strrchr(namebuf, '!')+1;
1134 strncpy(cp2, cp, (namebuf+namesize)-cp2);
1136 namebuf[namesize - 2] = '!';
1137 namebuf[namesize - 1] = '\0';
1138 goto newname;
1140 cp++;
1142 out:
1143 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1144 *cp == '\0')
1145 cp = savestr(namebuf);
1146 if (linebuf)
1147 free(linebuf);
1148 free(namebuf);
1149 return cp;
1152 static int
1153 msgidnextc(const char **cp, int *status)
1155 int c;
1157 for (;;) {
1158 if (*status & 01) {
1159 if (**cp == '"') {
1160 *status &= ~01;
1161 (*cp)++;
1162 continue;
1164 if (**cp == '\\') {
1165 (*cp)++;
1166 if (**cp == '\0')
1167 goto eof;
1169 goto dfl;
1171 switch (**cp) {
1172 case '(':
1173 *cp = skip_comment(&(*cp)[1]);
1174 continue;
1175 case '>':
1176 case '\0':
1177 eof:
1178 return '\0';
1179 case '"':
1180 (*cp)++;
1181 *status |= 01;
1182 continue;
1183 case '@':
1184 *status |= 02;
1185 /*FALLTHRU*/
1186 default:
1187 dfl:
1188 c = *(*cp)++ & 0377;
1189 return *status & 02 ? lowerconv(c) : c;
1194 int
1195 msgidcmp(const char *s1, const char *s2)
1197 int q1 = 0, q2 = 0;
1198 int c1, c2;
1200 do {
1201 c1 = msgidnextc(&s1, &q1);
1202 c2 = msgidnextc(&s2, &q2);
1203 if (c1 != c2)
1204 return c1 - c2;
1205 } while (c1 && c2);
1206 return c1 - c2;
1210 * Count the occurances of c in str
1212 static int
1213 charcount(char *str, int c)
1215 char *cp;
1216 int i;
1218 for (i = 0, cp = str; *cp; cp++)
1219 if (*cp == c)
1220 i++;
1221 return(i);
1225 * See if the given header field is supposed to be ignored.
1228 is_ign(char const *field, size_t fieldlen, struct ignoretab ignoret[2])
1230 char *realfld;
1231 int ret;
1233 if (ignoret == NULL)
1234 return 0;
1235 if (ignoret == allignore)
1236 return 1;
1238 * Lower-case the string, so that "Status" and "status"
1239 * will hash to the same place.
1241 realfld = ac_alloc(fieldlen + 1);
1242 i_strcpy(realfld, field, fieldlen + 1);
1243 if (ignoret[1].i_count > 0)
1244 ret = !member(realfld, ignoret + 1);
1245 else
1246 ret = member(realfld, ignoret);
1247 ac_free(realfld);
1248 return ret;
1251 int
1252 member(char const *realfield, struct ignoretab *table)
1254 struct ignore *igp;
1256 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1257 if (*igp->i_field == *realfield &&
1258 strcmp(igp->i_field, realfield) == 0)
1259 return (1);
1260 return (0);
1264 * Fake Sender for From_ lines if missing, e. g. with POP3.
1266 char const *
1267 fakefrom(struct message *mp)
1269 char const *name;
1271 if (((name = skin(hfield1("return-path", mp))) == NULL ||
1272 *name == '\0' ) &&
1273 ((name = skin(hfield1("from", mp))) == NULL ||
1274 *name == '\0'))
1276 * XXX MAILER-DAEMON is what an old MBOX manual page says.
1277 * RFC 4155 however requires a RFC 5322 (2822) conforming
1278 * "addr-spec", but we simply can't provide that
1280 name = "MAILER-DAEMON";
1281 return name;
1284 char const *
1285 fakedate(time_t t)
1287 char *cp, *cq;
1289 cp = ctime(&t);
1290 for (cq = cp; *cq && *cq != '\n'; ++cq)
1292 *cq = '\0';
1293 return savestr(cp);
1296 static char const *
1297 nexttoken(char const *cp)
1299 for (;;) {
1300 if (*cp == '\0')
1301 return NULL;
1302 if (*cp == '(') {
1303 int nesting = 0;
1305 while (*cp != '\0') {
1306 switch (*cp++) {
1307 case '(':
1308 nesting++;
1309 break;
1310 case ')':
1311 nesting--;
1312 break;
1314 if (nesting <= 0)
1315 break;
1317 } else if (blankchar(*cp) || *cp == ',')
1318 cp++;
1319 else
1320 break;
1322 return cp;
1326 * From username Fri Jan 2 20:13:51 2004
1327 * | | | | |
1328 * 0 5 10 15 20
1330 time_t
1331 unixtime(char const *fromline)
1333 char const *fp;
1334 char *xp;
1335 time_t t;
1336 int i, year, month, day, hour, minute, second;
1337 int tzdiff;
1338 struct tm *tmptr;
1340 for (fp = fromline; *fp && *fp != '\n'; fp++);
1341 fp -= 24;
1342 if (fp - fromline < 7)
1343 goto invalid;
1344 if (fp[3] != ' ')
1345 goto invalid;
1346 for (i = 0;;) {
1347 if (strncmp(&fp[4], month_names[i], 3) == 0)
1348 break;
1349 if (month_names[++i][0] == '\0')
1350 goto invalid;
1352 month = i + 1;
1353 if (fp[7] != ' ')
1354 goto invalid;
1355 day = strtol(&fp[8], &xp, 10);
1356 if (*xp != ' ' || xp != &fp[10])
1357 goto invalid;
1358 hour = strtol(&fp[11], &xp, 10);
1359 if (*xp != ':' || xp != &fp[13])
1360 goto invalid;
1361 minute = strtol(&fp[14], &xp, 10);
1362 if (*xp != ':' || xp != &fp[16])
1363 goto invalid;
1364 second = strtol(&fp[17], &xp, 10);
1365 if (*xp != ' ' || xp != &fp[19])
1366 goto invalid;
1367 year = strtol(&fp[20], &xp, 10);
1368 if (xp != &fp[24])
1369 goto invalid;
1370 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1371 (time_t)-1)
1372 goto invalid;
1373 tzdiff = t - mktime(gmtime(&t));
1374 tmptr = localtime(&t);
1375 if (tmptr->tm_isdst > 0)
1376 tzdiff += 3600;
1377 t -= tzdiff;
1378 return t;
1379 invalid:
1380 time(&t);
1381 return t;
1384 time_t
1385 rfctime(char const *date)
1387 char const *cp = date;
1388 char *x;
1389 time_t t;
1390 int i, year, month, day, hour, minute, second;
1392 if ((cp = nexttoken(cp)) == NULL)
1393 goto invalid;
1394 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1395 cp[3] == ',') {
1396 if ((cp = nexttoken(&cp[4])) == NULL)
1397 goto invalid;
1399 day = strtol(cp, &x, 10); /* XXX strtol */
1400 if ((cp = nexttoken(x)) == NULL)
1401 goto invalid;
1402 for (i = 0;;) {
1403 if (strncmp(cp, month_names[i], 3) == 0)
1404 break;
1405 if (month_names[++i][0] == '\0')
1406 goto invalid;
1408 month = i + 1;
1409 if ((cp = nexttoken(&cp[3])) == NULL)
1410 goto invalid;
1412 * RFC 5322, 4.3:
1413 * Where a two or three digit year occurs in a date, the year is to be
1414 * interpreted as follows: If a two digit year is encountered whose
1415 * value is between 00 and 49, the year is interpreted by adding 2000,
1416 * ending up with a value between 2000 and 2049. If a two digit year
1417 * is encountered with a value between 50 and 99, or any three digit
1418 * year is encountered, the year is interpreted by adding 1900.
1420 year = strtol(cp, &x, 10); /* XXX strtol */
1421 i = (int)(x - cp);
1422 if (i == 2 && year >= 0 && year <= 49)
1423 year += 2000;
1424 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1425 year += 1900;
1426 if ((cp = nexttoken(x)) == NULL)
1427 goto invalid;
1428 hour = strtol(cp, &x, 10); /* XXX strtol */
1429 if (*x != ':')
1430 goto invalid;
1431 cp = &x[1];
1432 minute = strtol(cp, &x, 10);
1433 if (*x == ':') {
1434 cp = &x[1];
1435 second = strtol(cp, &x, 10);
1436 } else
1437 second = 0;
1438 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1439 (time_t)-1)
1440 goto invalid;
1441 if ((cp = nexttoken(x)) != NULL) {
1442 int sign = -1;
1443 char buf[3];
1445 switch (*cp) {
1446 case '-':
1447 sign = 1;
1448 /*FALLTHRU*/
1449 case '+':
1450 cp++;
1452 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1453 digitchar(cp[3])) {
1454 buf[2] = '\0';
1455 buf[0] = cp[0];
1456 buf[1] = cp[1];
1457 t += strtol(buf, NULL, 10) * sign * 3600;/*XXX strtrol*/
1458 buf[0] = cp[2];
1459 buf[1] = cp[3];
1460 t += strtol(buf, NULL, 10) * sign * 60; /* XXX strtol*/
1462 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1463 * TODO once again, Christos Zoulas and NetBSD Mail have done
1464 * TODO a really good job already, but using strptime(3), which
1465 * TODO is not portable. Nonetheless, WE must improve, not
1466 * TODO at last because we simply ignore obsolete timezones!!
1467 * TODO See RFC 5322, 4.3! */
1469 return t;
1470 invalid:
1471 return 0;
1474 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1476 time_t
1477 combinetime(int year, int month, int day, int hour, int minute, int second)
1479 time_t t;
1481 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1482 return -1;
1483 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1484 if (month > 1)
1485 t += 86400 * 31;
1486 if (month > 2)
1487 t += 86400 * (is_leapyear(year) ? 29 : 28);
1488 if (month > 3)
1489 t += 86400 * 31;
1490 if (month > 4)
1491 t += 86400 * 30;
1492 if (month > 5)
1493 t += 86400 * 31;
1494 if (month > 6)
1495 t += 86400 * 30;
1496 if (month > 7)
1497 t += 86400 * 31;
1498 if (month > 8)
1499 t += 86400 * 31;
1500 if (month > 9)
1501 t += 86400 * 30;
1502 if (month > 10)
1503 t += 86400 * 31;
1504 if (month > 11)
1505 t += 86400 * 30;
1506 year -= 1900;
1507 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1508 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1509 return t;
1512 void
1513 substdate(struct message *m)
1515 char const *cp;
1518 * Determine the date to print in faked 'From ' lines. This is
1519 * traditionally the date the message was written to the mail
1520 * file. Try to determine this using RFC message header fields,
1521 * or fall back to current time.
1523 if ((cp = hfield1("received", m)) != NULL) {
1524 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1526 cp++;
1527 while (alnumchar(*cp));
1529 if (cp && *++cp)
1530 m->m_time = rfctime(cp);
1532 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1533 if ((cp = hfield1("date", m)) != NULL)
1534 m->m_time = rfctime(cp);
1536 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1537 m->m_time = time_current.tc_time;
1541 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1543 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1544 fprintf(stderr, "A Sender: field is required with multiple "
1545 "addresses in From: field.\n");
1546 return 1;
1548 if (senderfield && senderfield->n_flink) {
1549 fprintf(stderr, "The Sender: field may contain "
1550 "only one address.\n");
1551 return 2;
1553 return 0;
1556 char *
1557 getsender(struct message *mp)
1559 char *cp;
1560 struct name *np;
1562 if ((cp = hfield1("from", mp)) == NULL ||
1563 (np = lextract(cp, GEXTRA|GSKIN)) == NULL)
1564 return NULL;
1565 return np->n_flink != NULL ? skin(hfield1("sender", mp)) : np->n_name;
1569 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
1571 /* TODO grab_headers: again, check counts etc. against RFC;
1572 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1573 int errs;
1574 int volatile comma;
1576 errs = 0;
1577 comma = (value("bsdcompat") || value("bsdmsgs")) ? 0 : GCOMMA;
1579 if (gflags & GTO)
1580 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO|GFULL);
1582 if (subjfirst && (gflags & GSUBJECT))
1583 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1585 if (gflags & GCC)
1586 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC|GFULL);
1588 if (gflags & GBCC)
1589 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC|GFULL);
1591 if (gflags & GEXTRA) {
1592 if (hp->h_from == NULL)
1593 hp->h_from = lextract(myaddrs(hp), GEXTRA|GFULL);
1594 hp->h_from = grab_names("From: ", hp->h_from, comma,
1595 GEXTRA|GFULL);
1596 if (hp->h_replyto == NULL)
1597 hp->h_replyto = lextract(value("replyto"),
1598 GEXTRA|GFULL);
1599 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
1600 GEXTRA|GFULL);
1601 if (hp->h_sender == NULL)
1602 hp->h_sender = extract(value("sender"), GEXTRA|GFULL);
1603 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
1604 GEXTRA|GFULL);
1605 if (hp->h_organization == NULL)
1606 hp->h_organization = value("ORGANIZATION");
1607 hp->h_organization = readstr_input("Organization: ",
1608 hp->h_organization);
1611 if (! subjfirst && (gflags & GSUBJECT))
1612 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1614 return errs;