mime.c:mime_write_tohdr(): complete rewrite (Peter Hofmann)..
[s-mailx.git] / head.c
blob727432a62bbd68de8c34cc872908ddbf2d878c0d
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 - 2014 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 <idn-free.h>
47 # include <stringprep.h>
48 #endif
50 struct cmatch_data {
51 size_t tlen; /* Length of .tdata */
52 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 */
63 static struct cmatch_data const _cmatch_data[] = {
64 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
65 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
66 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
67 { 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 be used
69 * in the wild (by UW-imap) */
70 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
71 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
72 * zone strings; note that 1. is strictly speaking not correct as some
73 * letters are not used, and 2. is not because only "UT" is defined */
74 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
75 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
76 { 0, NULL }
78 #define _DATE_MINLEN 21
80 /* Skip over "word" as found in From_ line */
81 static char const * _from__skipword(char const *wp);
83 /* Match the date string against the date template (tp), return if match.
84 * See _cmatch_data[] for template character description */
85 static int _cmatch(size_t len, char const *date,
86 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 /* Return the next header field found in the given message.
106 * Return >= 0 if something found, < 0 elsewise.
107 * "colon" is set to point to the colon in the header.
108 * Must deal with \ continuations & other such fraud */
109 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
110 int rem, char **colon);
112 static int msgidnextc(char const **cp, int *status);
114 /* Count the occurances of c in str */
115 static int charcount(char *str, int c);
117 static char const * nexttoken(char const *cp);
119 static char const *
120 _from__skipword(char const *wp)
122 char c = 0;
123 NYD2_ENTER;
125 if (wp != NULL) {
126 while ((c = *wp++) != '\0' && !blankchar(c)) {
127 if (c == '"') {
128 while ((c = *wp++) != '\0' && c != '"')
130 if (c != '"')
131 --wp;
134 for (; blankchar(c); c = *wp++)
137 NYD2_LEAVE;
138 return (c == 0 ? NULL : wp - 1);
141 static int
142 _cmatch(size_t len, char const *date, char const *tp)
144 int ret = 0;
145 NYD2_ENTER;
147 while (len--) {
148 char c = date[len];
149 switch (tp[len]) {
150 case 'a':
151 if (!lowerchar(c))
152 goto jleave;
153 break;
154 case 'A':
155 if (!upperchar(c))
156 goto jleave;
157 break;
158 case ' ':
159 if (c != ' ')
160 goto jleave;
161 break;
162 case '0':
163 if (!digitchar(c))
164 goto jleave;
165 break;
166 case 'O':
167 if (c != ' ' && !digitchar(c))
168 goto jleave;
169 break;
170 case ':':
171 if (c != ':')
172 goto jleave;
173 break;
174 case '+':
175 if (c != '+' && c != '-')
176 goto jleave;
177 break;
180 ret = 1;
181 jleave:
182 NYD2_LEAVE;
183 return ret;
186 static int
187 _is_date(char const *date)
189 struct cmatch_data const *cmdp;
190 size_t dl;
191 int rv = 0;
192 NYD2_ENTER;
194 if ((dl = strlen(date)) >= _DATE_MINLEN)
195 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
196 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
197 break;
198 NYD2_LEAVE;
199 return rv;
202 #ifdef HAVE_IDNA
203 static struct addrguts *
204 _idna_apply(struct addrguts *agp)
206 char *idna_utf8, *idna_ascii, *cs;
207 size_t sz, i;
208 NYD_ENTER;
210 sz = agp->ag_slen - agp->ag_sdom_start;
211 assert(sz > 0);
212 idna_utf8 = ac_alloc(sz +1);
213 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
214 idna_utf8[sz] = '\0';
216 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
217 * let it perform the charset conversion, if any should be necessary */
218 if (!(options & OPT_UNICODE)) {
219 char const *tcs = charset_get_lc();
220 idna_ascii = idna_utf8;
221 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
222 i = (idna_utf8 == NULL && errno == EINVAL);
223 ac_free(idna_ascii);
225 if (idna_utf8 == NULL) {
226 if (i)
227 fprintf(stderr, _("Cannot convert from %s to %s\n"),
228 tcs, "UTF-8");
229 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
230 goto jleave;
234 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
235 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
236 goto jleave1;
239 /* Replace the domain part of .ag_skinned with IDNA version */
240 sz = strlen(idna_ascii);
241 i = agp->ag_sdom_start;
242 cs = salloc(agp->ag_slen - i + sz +1);
243 memcpy(cs, agp->ag_skinned, i);
244 memcpy(cs + i, idna_ascii, sz);
245 i += sz;
246 cs[i] = '\0';
248 agp->ag_skinned = cs;
249 agp->ag_slen = i;
250 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
251 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
253 idn_free(idna_ascii);
254 jleave1:
255 if (options & OPT_UNICODE)
256 ac_free(idna_utf8);
257 else
258 idn_free(idna_utf8);
259 jleave:
260 NYD_LEAVE;
261 return agp;
263 #endif
265 static int
266 _addrspec_check(int skinned, struct addrguts *agp)
268 char *addr, *p;
269 bool_t in_quote;
270 ui8_t in_domain, hadat;
271 union {char c; unsigned char u;} c;
272 #ifdef HAVE_IDNA
273 ui8_t use_idna;
274 #endif
275 NYD_ENTER;
277 #ifdef HAVE_IDNA
278 use_idna = ok_blook(idna_disable) ? 0 : 1;
279 #endif
280 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
281 addr = agp->ag_skinned;
283 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
284 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
285 goto jleave;
288 /* If the field is not a recipient, it cannot be a file or a pipe */
289 if (!skinned)
290 goto jaddr_check;
292 /* Excerpt from nail.1:
293 * Recipient address specifications
294 * The rules are: Any name which starts with a `|' character specifies
295 * a pipe, the command string following the `|' is executed and
296 * the message is sent to its standard input; any other name which
297 * contains a `@' character is treated as a mail address; any other
298 * name which starts with a `+' character specifies a folder name; any
299 * other name which contains a `/' character but no `!' or `%'
300 * character before also specifies a folder name; what remains is
301 * treated as a mail address */
302 if (*addr == '|') {
303 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
304 goto jleave;
306 if (memchr(addr, '@', agp->ag_slen) == NULL) {
307 if (*addr == '+')
308 goto jisfile;
309 for (p = addr; (c.c = *p); ++p) {
310 if (c.c == '!' || c.c == '%')
311 break;
312 if (c.c == '/') {
313 jisfile:
314 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
315 goto jleave;
320 jaddr_check:
321 in_quote = FAL0;
322 in_domain = hadat = 0;
324 for (p = addr; (c.c = *p++) != '\0';) {
325 if (c.c == '"') {
326 in_quote = !in_quote;
327 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
328 #ifdef HAVE_IDNA
329 if (in_domain && use_idna > 0) {
330 if (use_idna == 1)
331 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
332 c.u);
333 use_idna = 2;
334 } else
335 #endif
336 break;
337 } else if (in_domain == 2) {
338 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
339 break;
340 } else if (in_quote && in_domain == 0) {
341 /*EMPTY*/;
342 } else if (c.c == '\\' && *p != '\0') {
343 ++p;
344 } else if (c.c == '@') {
345 if (hadat++ > 0) {
346 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
347 c.u);
348 goto jleave;
350 agp->ag_sdom_start = PTR2SIZE(p - addr);
351 in_domain = (*p == '[') ? 2 : 1;
352 continue;
353 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
354 c.c == ',' || c.c == ';' || c.c == ':' || c.c == '\\' ||
355 c.c == '[' || c.c == ']')
356 break;
357 hadat = 0;
360 if (c.c != '\0') {
361 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
362 goto jleave;
364 #ifdef HAVE_IDNA
365 if (use_idna == 2)
366 agp = _idna_apply(agp);
367 #endif
368 jleave:
369 NYD_LEAVE;
370 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
373 static int
374 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
376 char *line2 = NULL, *cp, *cp2;
377 size_t line2size = 0;
378 int c, isenc;
379 NYD_ENTER;
381 if (*linebuf == NULL)
382 *linebuf = srealloc(*linebuf, *linesize = 1);
383 **linebuf = '\0';
384 for (;;) {
385 if (--rem < 0) {
386 rem = -1;
387 break;
389 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
390 rem = -1;
391 break;
393 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
395 if (cp > *linebuf)
396 while (blankchar(*cp))
397 ++cp;
398 if (*cp != ':' || cp == *linebuf)
399 continue;
401 /* I guess we got a headline. Handle wraparound */
402 *colon = cp;
403 cp = *linebuf + c;
404 for (;;) {
405 isenc = 0;
406 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
408 cp++;
409 if (rem <= 0)
410 break;
411 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
412 isenc |= 1;
413 ungetc(c = getc(f), f);
414 if (!blankchar(c))
415 break;
416 c = readline_restart(f, &line2, &line2size, 0);
417 if (c < 0)
418 break;
419 --rem;
420 for (cp2 = line2; blankchar(*cp2); ++cp2)
422 c -= (int)PTR2SIZE(cp2 - line2);
423 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
424 isenc |= 2;
425 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
426 size_t diff = PTR2SIZE(cp - *linebuf),
427 colondiff = PTR2SIZE(*colon - *linebuf);
428 *linebuf = srealloc(*linebuf, *linesize += c + 2);
429 cp = &(*linebuf)[diff];
430 *colon = &(*linebuf)[colondiff];
432 if (isenc != 3)
433 *cp++ = ' ';
434 memcpy(cp, cp2, c);
435 cp += c;
437 *cp = '\0';
439 if (line2 != NULL)
440 free(line2);
441 break;
443 NYD_LEAVE;
444 return rem;
447 static int
448 msgidnextc(char const **cp, int *status)
450 int c;
451 NYD_ENTER;
453 assert(cp != NULL);
454 assert(*cp != NULL);
455 assert(status != NULL);
457 for (;;) {
458 if (*status & 01) {
459 if (**cp == '"') {
460 *status &= ~01;
461 (*cp)++;
462 continue;
464 if (**cp == '\\') {
465 (*cp)++;
466 if (**cp == '\0')
467 goto jeof;
469 goto jdfl;
471 switch (**cp) {
472 case '(':
473 *cp = skip_comment(&(*cp)[1]);
474 continue;
475 case '>':
476 case '\0':
477 jeof:
478 c = '\0';
479 goto jleave;
480 case '"':
481 (*cp)++;
482 *status |= 01;
483 continue;
484 case '@':
485 *status |= 02;
486 /*FALLTHRU*/
487 default:
488 jdfl:
489 c = *(*cp)++ & 0377;
490 c = (*status & 02) ? lowerconv(c) : c;
491 goto jleave;
494 jleave:
495 NYD_LEAVE;
496 return c;
499 static int
500 charcount(char *str, int c)
502 char *cp;
503 int i;
504 NYD2_ENTER;
506 for (i = 0, cp = str; *cp; ++cp)
507 if (*cp == c)
508 ++i;
509 NYD2_LEAVE;
510 return i;
513 static char const *
514 nexttoken(char const *cp)
516 NYD2_ENTER;
517 for (;;) {
518 if (*cp == '\0') {
519 cp = NULL;
520 break;
523 if (*cp == '(') {
524 size_t nesting = 1;
526 do switch (*++cp) {
527 case '(':
528 ++nesting;
529 break;
530 case ')':
531 --nesting;
532 break;
533 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
534 } else if (blankchar(*cp) || *cp == ',')
535 ++cp;
536 else
537 break;
539 NYD2_LEAVE;
540 return cp;
543 FL char const *
544 myaddrs(struct header *hp)
546 struct name *np;
547 char *rv;
548 NYD_ENTER;
550 if (hp != NULL && (np = hp->h_from) != NULL) {
551 if ((rv = np->n_fullname) != NULL)
552 goto jleave;
553 if ((rv = np->n_name) != NULL)
554 goto jleave;
557 if ((rv = ok_vlook(from)) != NULL)
558 goto jleave;
560 /* When invoking *sendmail* directly, it's its task to generate an otherwise
561 * undeterminable From: address. However, if the user sets *hostname*,
562 * accept his desire */
563 if (ok_vlook(smtp) != NULL || ok_vlook(hostname) != NULL) {
564 char *hn = nodename(1);
565 size_t sz = strlen(myname) + strlen(hn) + 1 +1;
566 rv = salloc(sz);
567 sstpcpy(sstpcpy(sstpcpy(rv, myname), "@"), hn);
569 jleave:
570 NYD_LEAVE;
571 return rv;
574 FL char const *
575 myorigin(struct header *hp)
577 char const *rv = NULL, *ccp;
578 struct name *np;
579 NYD_ENTER;
581 if ((ccp = myaddrs(hp)) != NULL &&
582 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
583 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
584 NYD_LEAVE;
585 return rv;
588 FL int
589 is_head(char const *linebuf, size_t linelen) /* XXX verbose WARN */
591 char date[FROM_DATEBUF];
592 int rv;
593 NYD_ENTER;
595 rv = ((linelen <= 5 || strncmp(linebuf, "From ", 5) != 0 ||
596 !extract_date_from_from_(linebuf, linelen, date) ||
597 !_is_date(date)) ? 0 : 1);
598 NYD_LEAVE;
599 return rv;
602 FL int
603 extract_date_from_from_(char const *line, size_t linelen,
604 char datebuf[FROM_DATEBUF])
606 int rv = 0;
607 char const *cp = line;
608 NYD_ENTER;
610 /* "From " */
611 cp = _from__skipword(cp);
612 if (cp == NULL)
613 goto jerr;
614 /* "addr-spec " */
615 cp = _from__skipword(cp);
616 if (cp == NULL)
617 goto jerr;
618 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
619 cp = _from__skipword(cp);
620 if (cp == NULL)
621 goto jerr;
624 linelen -= PTR2SIZE(cp - line);
625 if (linelen < _DATE_MINLEN)
626 goto jerr;
627 if (cp[linelen - 1] == '\n') {
628 --linelen;
629 /* (Rather IMAP/POP3 only) */
630 if (cp[linelen - 1] == '\r')
631 --linelen;
632 if (linelen < _DATE_MINLEN)
633 goto jerr;
635 if (linelen >= FROM_DATEBUF)
636 goto jerr;
638 rv = 1;
639 jleave:
640 memcpy(datebuf, cp, linelen);
641 datebuf[linelen] = '\0';
642 NYD_LEAVE;
643 return rv;
644 jerr:
645 cp = _("<Unknown date>");
646 linelen = strlen(cp);
647 if (linelen >= FROM_DATEBUF)
648 linelen = FROM_DATEBUF;
649 goto jleave;
652 FL void
653 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
655 struct header nh, *hq = &nh;
656 char *linebuf = NULL /* TODO line pool */, *colon;
657 size_t linesize = 0, seenfields = 0;
658 int lc, c;
659 char const *val, *cp;
660 NYD_ENTER;
662 memset(hq, 0, sizeof *hq);
663 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
666 /* TODO yippieia, cat(check(lextract)) :-) */
667 rewind(fp);
668 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
669 if ((val = thisfield(linebuf, "to")) != NULL) {
670 ++seenfields;
671 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL)));
672 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
673 ++seenfields;
674 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL)));
675 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
676 ++seenfields;
677 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL)));
678 } else if ((val = thisfield(linebuf, "from")) != NULL) {
679 ++seenfields;
680 hq->h_from = cat(hq->h_from,
681 checkaddrs(lextract(val, GEXTRA | GFULL)));
682 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
683 ++seenfields;
684 hq->h_replyto = cat(hq->h_replyto,
685 checkaddrs(lextract(val, GEXTRA | GFULL)));
686 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
687 ++seenfields;
688 hq->h_sender = cat(hq->h_sender,
689 checkaddrs(lextract(val, GEXTRA | GFULL)));
690 } else if ((val = thisfield(linebuf, "organization")) != NULL) {
691 ++seenfields;
692 for (cp = val; blankchar(*cp); ++cp)
694 hq->h_organization = (hq->h_organization != NULL)
695 ? save2str(hq->h_organization, cp) : savestr(cp);
696 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
697 (val = thisfield(linebuf, "subj")) != NULL) {
698 ++seenfields;
699 for (cp = val; blankchar(*cp); ++cp)
701 hq->h_subject = (hq->h_subject != NULL)
702 ? save2str(hq->h_subject, cp) : savestr(cp);
703 } else
704 fprintf(stderr, _("Ignoring header field \"%s\"\n"), linebuf);
707 /* In case the blank line after the header has been edited out. Otherwise,
708 * fetch the header separator */
709 if (linebuf != NULL) {
710 if (linebuf[0] != '\0') {
711 for (cp = linebuf; *(++cp) != '\0';)
713 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
714 } else {
715 if ((c = getc(fp)) != '\n' && c != EOF)
716 ungetc(c, fp);
720 if (seenfields > 0) {
721 hp->h_to = hq->h_to;
722 hp->h_cc = hq->h_cc;
723 hp->h_bcc = hq->h_bcc;
724 hp->h_from = hq->h_from;
725 hp->h_replyto = hq->h_replyto;
726 hp->h_sender = hq->h_sender;
727 hp->h_organization = hq->h_organization;
728 hp->h_subject = hq->h_subject;
729 } else
730 fprintf(stderr, _("Restoring deleted header lines\n"));
732 if (linebuf != NULL)
733 free(linebuf);
734 NYD_LEAVE;
737 FL char *
738 hfield_mult(char const *field, struct message *mp, int mult)
740 FILE *ibuf;
741 int lc;
742 size_t linesize = 0; /* TODO line pool */
743 char *linebuf = NULL, *colon, *oldhfield = NULL;
744 char const *hfield;
745 NYD_ENTER;
747 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
748 goto jleave;
749 if ((lc = mp->m_lines - 1) < 0)
750 goto jleave;
752 if ((mp->m_flag & MNOFROM) == 0 &&
753 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
754 goto jleave;
755 while (lc > 0) {
756 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
757 break;
758 if ((hfield = thisfield(linebuf, field)) != NULL) {
759 oldhfield = save2str(hfield, oldhfield);
760 if (mult == 0)
761 break;
765 jleave:
766 if (linebuf != NULL)
767 free(linebuf);
768 NYD_LEAVE;
769 return oldhfield;
772 FL char const *
773 thisfield(char const *linebuf, char const *field)
775 char const *rv = NULL;
776 NYD_ENTER;
778 while (lowerconv(*linebuf) == lowerconv(*field)) {
779 ++linebuf;
780 ++field;
782 if (*field != '\0')
783 goto jleave;
785 while (blankchar(*linebuf))
786 ++linebuf;
787 if (*linebuf++ != ':')
788 goto jleave;
790 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
791 ++linebuf;
792 rv = linebuf;
793 jleave:
794 NYD_LEAVE;
795 return rv;
798 FL char *
799 nameof(struct message *mp, int reptype)
801 char *cp, *cp2;
802 NYD_ENTER;
804 cp = skin(name1(mp, reptype));
805 if (reptype != 0 || charcount(cp, '!') < 2)
806 goto jleave;
807 cp2 = strrchr(cp, '!');
808 --cp2;
809 while (cp2 > cp && *cp2 != '!')
810 --cp2;
811 if (*cp2 == '!')
812 cp = cp2 + 1;
813 jleave:
814 NYD_LEAVE;
815 return cp;
818 FL char const *
819 skip_comment(char const *cp)
821 size_t nesting;
822 NYD_ENTER;
824 for (nesting = 1; nesting > 0 && *cp; ++cp) {
825 switch (*cp) {
826 case '\\':
827 if (cp[1])
828 ++cp;
829 break;
830 case '(':
831 ++nesting;
832 break;
833 case ')':
834 --nesting;
835 break;
838 NYD_LEAVE;
839 return cp;
842 FL char const *
843 routeaddr(char const *name)
845 char const *np, *rp = NULL;
846 NYD_ENTER;
848 for (np = name; *np; np++) {
849 switch (*np) {
850 case '(':
851 np = skip_comment(np + 1) - 1;
852 break;
853 case '"':
854 while (*np) {
855 if (*++np == '"')
856 break;
857 if (*np == '\\' && np[1])
858 np++;
860 break;
861 case '<':
862 rp = np;
863 break;
864 case '>':
865 goto jleave;
868 rp = NULL;
869 jleave:
870 NYD_LEAVE;
871 return rp;
874 FL int
875 is_addr_invalid(struct name *np, int putmsg)
877 char cbuf[sizeof "'\\U12340'"], *name;
878 int f, ok8bit;
879 ui32_t c;
880 char const *fmt, *cs;
881 NYD_ENTER;
883 name = np->n_name;
884 f = np->n_flags;
885 ok8bit = 1;
886 fmt = "'\\x%02X'";
888 if (!(f & NAME_ADDRSPEC_INVALID) || !putmsg || (f & NAME_ADDRSPEC_ERR_EMPTY))
889 goto jleave;
891 if (f & NAME_ADDRSPEC_ERR_IDNA)
892 cs = _("Invalid domain name: \"%s\", character %s\n"),
893 fmt = "'\\U%04X'",
894 ok8bit = 0;
895 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
896 cs = _("\"%s\" contains invalid %s sequence\n");
897 else
898 cs = _("\"%s\" contains invalid character %s\n");
900 c = NAME_ADDRSPEC_ERR_GETWC(f);
901 if (ok8bit && c >= 040 && c <= 0177)
902 snprintf(cbuf, sizeof cbuf, "'%c'", c);
903 else
904 snprintf(cbuf, sizeof cbuf, fmt, c);
906 fprintf(stderr, cs, name, cbuf);
907 jleave:
908 NYD_LEAVE;
909 return ((f & NAME_ADDRSPEC_INVALID) != 0);
912 FL char *
913 skin(char const *name)
915 struct addrguts ag;
916 char *ret = NULL;
917 NYD_ENTER;
919 if (name != NULL) {
920 addrspec_with_guts(1, name, &ag);
921 ret = ag.ag_skinned;
922 if (!(ag.ag_n_flags & NAME_NAME_SALLOC))
923 ret = savestrbuf(ret, ag.ag_slen);
925 NYD_LEAVE;
926 return ret;
929 /* TODO addrspec_with_guts: RFC 5322 */
930 FL int
931 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
933 char const *cp;
934 char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp;
935 int rv = 1;
936 NYD_ENTER;
938 memset(agp, 0, sizeof *agp);
940 if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) {
941 agp->ag_skinned = UNCONST(""); /* ok: NAME_SALLOC is not set */
942 agp->ag_slen = 0;
943 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
944 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
945 goto jleave;
948 if (!doskin || !anyof(name, "(< ")) {
949 /*agp->ag_iaddr_start = 0;*/
950 agp->ag_iaddr_aend = agp->ag_ilen;
951 agp->ag_skinned = UNCONST(name); /* (NAME_SALLOC not set) */
952 agp->ag_slen = agp->ag_ilen;
953 agp->ag_n_flags = NAME_SKINNED;
954 goto jcheck;
957 /* Something makes us think we have to perform the skin operation */
958 nbuf = ac_alloc(agp->ag_ilen + 1);
959 /*agp->ag_iaddr_start = 0;*/
960 cp2 = bufend = nbuf;
961 gotlt = gotaddr = lastsp = 0;
963 for (cp = name++; (c = *cp++) != '\0'; ) {
964 switch (c) {
965 case '(':
966 cp = skip_comment(cp);
967 lastsp = 0;
968 break;
969 case '"':
970 /* Start of a "quoted-string".
971 * Copy it in its entirety */
972 /* XXX RFC: quotes are "semantically invisible"
973 * XXX But it was explicitly added (Changelog.Heirloom,
974 * XXX [9.23] released 11/15/00, "Do not remove quotes
975 * XXX when skinning names"? No more info.. */
976 *cp2++ = c;
977 while ((c = *cp) != '\0') { /* TODO improve */
978 cp++;
979 if (c == '"') {
980 *cp2++ = c;
981 break;
983 if (c != '\\')
984 *cp2++ = c;
985 else if ((c = *cp) != '\0') {
986 *cp2++ = c;
987 cp++;
990 lastsp = 0;
991 break;
992 case ' ':
993 case '\t':
994 if (gotaddr == 1) {
995 gotaddr = 2;
996 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
998 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
999 cp += 3, *cp2++ = '@';
1000 else if (cp[0] == '@' && blankchar(cp[1]))
1001 cp += 2, *cp2++ = '@';
1002 else
1003 lastsp = 1;
1004 break;
1005 case '<':
1006 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1007 cp2 = bufend;
1008 gotlt = gotaddr = 1;
1009 lastsp = 0;
1010 break;
1011 case '>':
1012 if (gotlt) {
1013 /* (_addrspec_check() verifies these later!) */
1014 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1015 gotlt = 0;
1016 while ((c = *cp) != '\0' && c != ',') {
1017 cp++;
1018 if (c == '(')
1019 cp = skip_comment(cp);
1020 else if (c == '"')
1021 while ((c = *cp) != '\0') {
1022 cp++;
1023 if (c == '"')
1024 break;
1025 if (c == '\\' && *cp != '\0')
1026 ++cp;
1029 lastsp = 0;
1030 break;
1032 /* FALLTRHOUGH */
1033 default:
1034 if (lastsp) {
1035 lastsp = 0;
1036 if (gotaddr)
1037 *cp2++ = ' ';
1039 *cp2++ = c;
1040 if (c == ',') {
1041 if (!gotlt) {
1042 *cp2++ = ' ';
1043 for (; blankchar(*cp); ++cp)
1045 lastsp = 0;
1046 bufend = cp2;
1048 } else if (!gotaddr) {
1049 gotaddr = 1;
1050 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1054 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1055 if (agp->ag_iaddr_aend == 0)
1056 agp->ag_iaddr_aend = agp->ag_ilen;
1058 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1059 ac_free(nbuf);
1060 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1061 jcheck:
1062 rv = _addrspec_check(doskin, agp);
1063 jleave:
1064 NYD_LEAVE;
1065 return rv;
1068 FL char *
1069 realname(char const *name)
1071 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1072 char *rname, *rp;
1073 struct str in, out;
1074 int quoted, good, nogood;
1075 NYD_ENTER;
1077 if ((cp = UNCONST(name)) == NULL)
1078 goto jleave;
1079 for (; *cp != '\0'; ++cp) {
1080 switch (*cp) {
1081 case '(':
1082 if (cstart != NULL) {
1083 /* More than one comment in address, doesn't make sense to display
1084 * it without context. Return the entire field */
1085 cp = mime_fromaddr(name);
1086 goto jleave;
1088 cstart = cp++;
1089 cp = skip_comment(cp);
1090 cend = cp--;
1091 if (cend <= cstart)
1092 cend = cstart = NULL;
1093 break;
1094 case '"':
1095 while (*cp) {
1096 if (*++cp == '"')
1097 break;
1098 if (*cp == '\\' && cp[1])
1099 ++cp;
1101 break;
1102 case '<':
1103 if (cp > name) {
1104 cstart = name;
1105 cend = cp;
1107 break;
1108 case ',':
1109 /* More than one address. Just use the first one */
1110 goto jbrk;
1114 jbrk:
1115 if (cstart == NULL) {
1116 if (*name == '<') {
1117 /* If name contains only a route-addr, the surrounding angle brackets
1118 * don't serve any useful purpose when displaying, so remove */
1119 cp = prstr(skin(name));
1120 } else
1121 cp = mime_fromaddr(name);
1122 goto jleave;
1125 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1126 * not stripped. The idea is to strip only syntactical relevant things (but
1127 * this is not necessarily the most sensible way in practice) */
1128 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1129 quoted = 0;
1130 for (cp = cstart; cp < cend; ++cp) {
1131 if (*cp == '(' && !quoted) {
1132 cq = skip_comment(++cp);
1133 if (PTRCMP(--cq, >, cend))
1134 cq = cend;
1135 while (cp < cq) {
1136 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1137 ++cp;
1138 *rp++ = *cp++;
1140 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1141 *rp++ = *++cp;
1142 else if (*cp == '"') {
1143 quoted = !quoted;
1144 continue;
1145 } else
1146 *rp++ = *cp;
1148 *rp = '\0';
1149 in.s = rname;
1150 in.l = rp - rname;
1151 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1152 ac_free(rname);
1153 rname = savestr(out.s);
1154 free(out.s);
1156 while (blankchar(*rname))
1157 ++rname;
1158 for (rp = rname; *rp != '\0'; ++rp)
1160 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1161 *rp = '\0';
1162 if (rp == rname) {
1163 cp = mime_fromaddr(name);
1164 goto jleave;
1167 /* mime_fromhdr() has converted all nonprintable characters to question
1168 * marks now. These and blanks are considered uninteresting; if the
1169 * displayed part of the real name contains more than 25% of them, it is
1170 * probably better to display the plain email address instead */
1171 good = 0;
1172 nogood = 0;
1173 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1174 if (*rp == '?' || blankchar(*rp))
1175 ++nogood;
1176 else
1177 ++good;
1178 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1179 jleave:
1180 NYD_LEAVE;
1181 return UNCONST(cp);
1184 FL char *
1185 name1(struct message *mp, int reptype)
1187 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1188 size_t namesize, linesize = 0;
1189 FILE *ibuf;
1190 int f1st = 1;
1191 NYD_ENTER;
1193 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1194 goto jleave;
1195 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1196 goto jleave;
1198 namebuf = smalloc(namesize = 1);
1199 namebuf[0] = 0;
1200 if (mp->m_flag & MNOFROM)
1201 goto jout;
1202 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1203 goto jout;
1204 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1205 goto jout;
1207 jnewname:
1208 if (namesize <= linesize)
1209 namebuf = srealloc(namebuf, namesize = linesize +1);
1210 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1212 for (; blankchar(*cp); ++cp)
1214 for (cp2 = namebuf + strlen(namebuf);
1215 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1216 *cp2++ = *cp++;
1217 *cp2 = '\0';
1219 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1220 goto jout;
1221 if ((cp = strchr(linebuf, 'F')) == NULL)
1222 goto jout;
1223 if (strncmp(cp, "From", 4)) /* XXX is_head? */
1224 goto jout;
1225 if (namesize <= linesize)
1226 namebuf = srealloc(namebuf, namesize = linesize + 1);
1228 while ((cp = strchr(cp, 'r')) != NULL) {
1229 if (!strncmp(cp, "remote", 6)) {
1230 if ((cp = strchr(cp, 'f')) == NULL)
1231 break;
1232 if (strncmp(cp, "from", 4) != 0)
1233 break;
1234 if ((cp = strchr(cp, ' ')) == NULL)
1235 break;
1236 cp++;
1237 if (f1st) {
1238 strncpy(namebuf, cp, namesize);
1239 f1st = 0;
1240 } else {
1241 cp2 = strrchr(namebuf, '!') + 1;
1242 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1244 namebuf[namesize - 2] = '!';
1245 namebuf[namesize - 1] = '\0';
1246 goto jnewname;
1248 cp++;
1250 jout:
1251 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1252 *cp == '\0')
1253 cp = savestr(namebuf);
1255 if (linebuf != NULL)
1256 free(linebuf);
1257 free(namebuf);
1258 jleave:
1259 NYD_LEAVE;
1260 return cp;
1263 FL char *
1264 subject_re_trim(char *s) /* XXX add bool_t mime_decode argument?! */
1266 struct {
1267 ui8_t len;
1268 char dat[7];
1269 } const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
1270 { 3, "re:" },
1271 { 3, "aw:" }, { 5, "antw:" }, /* de */
1272 { 0, "" }
1274 char *re_st, *re_st_x;
1275 size_t re_l;
1276 NYD_ENTER;
1278 if ((re_st = re_st_x = ok_vlook(reply_strings)) != NULL &&
1279 (re_l = strlen(re_st_x)) > 0) {
1280 re_st = ac_alloc(++re_l * 2);
1281 memcpy(re_st, re_st_x, re_l);
1284 jouter:
1285 while (*s != '\0') {
1286 while (spacechar(*s))
1287 ++s;
1289 /* TODO While it is maybe ok not to MIME decode these (for purpose), we
1290 * TODO should skip =?..?= at the beginning? */
1291 for (pp = ignored; pp->len > 0; ++pp)
1292 if (is_asccaseprefix(pp->dat, s)) {
1293 s += pp->len;
1294 goto jouter;
1297 if (re_st != NULL) {
1298 char *cp;
1300 memcpy(re_st_x = re_st + re_l, re_st, re_l);
1301 while ((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
1302 if (is_asccaseprefix(cp, s)) {
1303 s += strlen(cp);
1304 goto jouter;
1307 break;
1310 if (re_st != NULL)
1311 ac_free(re_st);
1312 NYD_LEAVE;
1313 return s;
1316 FL int
1317 msgidcmp(char const *s1, char const *s2)
1319 int q1 = 0, q2 = 0, c1, c2;
1320 NYD_ENTER;
1322 do {
1323 c1 = msgidnextc(&s1, &q1);
1324 c2 = msgidnextc(&s2, &q2);
1325 if (c1 != c2)
1326 break;
1327 } while (c1 && c2);
1328 NYD_LEAVE;
1329 return c1 - c2;
1332 FL int
1333 is_ign(char const *field, size_t fieldlen, struct ignoretab ignoret[2])
1335 char *realfld;
1336 int rv;
1337 NYD_ENTER;
1339 rv = 0;
1340 if (ignoret == NULL)
1341 goto jleave;
1342 rv = 1;
1343 if (ignoret == allignore)
1344 goto jleave;
1346 /* Lowercase it so that "Status" and "status" will hash to the same place */
1347 realfld = ac_alloc(fieldlen +1);
1348 i_strcpy(realfld, field, fieldlen +1);
1349 if (ignoret[1].i_count > 0)
1350 rv = !member(realfld, ignoret + 1);
1351 else
1352 rv = member(realfld, ignoret);
1353 ac_free(realfld);
1354 jleave:
1355 NYD_LEAVE;
1356 return rv;
1359 FL int
1360 member(char const *realfield, struct ignoretab *table)
1362 struct ignore *igp;
1363 int rv = 0;
1364 NYD_ENTER;
1366 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1367 if (*igp->i_field == *realfield && !strcmp(igp->i_field, realfield)) {
1368 rv = 1;
1369 break;
1371 NYD_LEAVE;
1372 return rv;
1375 FL char const *
1376 fakefrom(struct message *mp)
1378 char const *name;
1379 NYD_ENTER;
1381 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1382 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1383 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1384 * RFC 4155 however requires a RFC 5322 (2822) conforming
1385 * "addr-spec", but we simply can't provide that */
1386 name = "MAILER-DAEMON";
1387 NYD_LEAVE;
1388 return name;
1391 FL char const *
1392 fakedate(time_t t)
1394 char *cp, *cq;
1395 NYD_ENTER;
1397 cp = ctime(&t);
1398 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1400 *cq = '\0';
1401 cp = savestr(cp);
1402 NYD_LEAVE;
1403 return cp;
1406 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
1407 FL time_t
1408 unixtime(char const *fromline)
1410 char const *fp;
1411 char *xp;
1412 time_t t;
1413 int i, year, month, day, hour, minute, second, tzdiff;
1414 struct tm *tmptr;
1415 NYD2_ENTER;
1417 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1419 fp -= 24;
1420 if (PTR2SIZE(fp - fromline) < 7)
1421 goto jinvalid;
1422 if (fp[3] != ' ')
1423 goto jinvalid;
1424 for (i = 0;;) {
1425 if (!strncmp(fp + 4, month_names[i], 3))
1426 break;
1427 if (month_names[++i][0] == '\0')
1428 goto jinvalid;
1430 month = i + 1;
1431 if (fp[7] != ' ')
1432 goto jinvalid;
1433 day = strtol(fp + 8, &xp, 10);
1434 if (*xp != ' ' || xp != fp + 10)
1435 goto jinvalid;
1436 hour = strtol(fp + 11, &xp, 10);
1437 if (*xp != ':' || xp != fp + 13)
1438 goto jinvalid;
1439 minute = strtol(fp + 14, &xp, 10);
1440 if (*xp != ':' || xp != fp + 16)
1441 goto jinvalid;
1442 second = strtol(fp + 17, &xp, 10);
1443 if (*xp != ' ' || xp != fp + 19)
1444 goto jinvalid;
1445 year = strtol(fp + 20, &xp, 10);
1446 if (xp != fp + 24)
1447 goto jinvalid;
1448 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1449 goto jinvalid;
1450 tzdiff = t - mktime(gmtime(&t));
1451 tmptr = localtime(&t);
1452 if (tmptr->tm_isdst > 0)
1453 tzdiff += 3600;
1454 t -= tzdiff;
1455 jleave:
1456 NYD2_LEAVE;
1457 return t;
1458 jinvalid:
1459 time(&t);
1460 goto jleave;
1462 #endif /* HAVE_IMAP_SEARCH || defined HAVE_IMAP */
1464 FL time_t
1465 rfctime(char const *date)
1467 char const *cp = date;
1468 char *x;
1469 time_t t;
1470 int i, year, month, day, hour, minute, second;
1471 NYD2_ENTER;
1473 if ((cp = nexttoken(cp)) == NULL)
1474 goto jinvalid;
1475 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1476 cp[3] == ',') {
1477 if ((cp = nexttoken(&cp[4])) == NULL)
1478 goto jinvalid;
1480 day = strtol(cp, &x, 10); /* XXX strtol */
1481 if ((cp = nexttoken(x)) == NULL)
1482 goto jinvalid;
1483 for (i = 0;;) {
1484 if (!strncmp(cp, month_names[i], 3))
1485 break;
1486 if (month_names[++i][0] == '\0')
1487 goto jinvalid;
1489 month = i + 1;
1490 if ((cp = nexttoken(&cp[3])) == NULL)
1491 goto jinvalid;
1492 /* RFC 5322, 4.3:
1493 * Where a two or three digit year occurs in a date, the year is to be
1494 * interpreted as follows: If a two digit year is encountered whose
1495 * value is between 00 and 49, the year is interpreted by adding 2000,
1496 * ending up with a value between 2000 and 2049. If a two digit year
1497 * is encountered with a value between 50 and 99, or any three digit
1498 * year is encountered, the year is interpreted by adding 1900 */
1499 year = strtol(cp, &x, 10); /* XXX strtol */
1500 i = (int)PTR2SIZE(x - cp);
1501 if (i == 2 && year >= 0 && year <= 49)
1502 year += 2000;
1503 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1504 year += 1900;
1505 if ((cp = nexttoken(x)) == NULL)
1506 goto jinvalid;
1507 hour = strtol(cp, &x, 10); /* XXX strtol */
1508 if (*x != ':')
1509 goto jinvalid;
1510 cp = &x[1];
1511 minute = strtol(cp, &x, 10);
1512 if (*x == ':') {
1513 cp = x + 1;
1514 second = strtol(cp, &x, 10);
1515 } else
1516 second = 0;
1517 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1518 goto jinvalid;
1519 if ((cp = nexttoken(x)) != NULL) {
1520 int sign = -1;
1521 char buf[3];
1523 switch (*cp) {
1524 case '-':
1525 sign = 1;
1526 /*FALLTHRU*/
1527 case '+':
1528 ++cp;
1529 break;
1531 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1532 digitchar(cp[3])) {
1533 buf[2] = '\0';
1534 buf[0] = cp[0];
1535 buf[1] = cp[1];
1536 t += strtol(buf, NULL, 10) * sign * 3600;/*XXX strtrol*/
1537 buf[0] = cp[2];
1538 buf[1] = cp[3];
1539 t += strtol(buf, NULL, 10) * sign * 60; /* XXX strtol*/
1541 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1542 * TODO once again, Christos Zoulas and NetBSD Mail have done
1543 * TODO a really good job already, but using strptime(3), which
1544 * TODO is not portable. Nonetheless, WE must improve, not
1545 * TODO at last because we simply ignore obsolete timezones!!
1546 * TODO See RFC 5322, 4.3! */
1548 jleave:
1549 NYD2_LEAVE;
1550 return t;
1551 jinvalid:
1552 t = 0;
1553 goto jleave;
1556 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1558 FL time_t
1559 combinetime(int year, int month, int day, int hour, int minute, int second)
1561 time_t t;
1562 NYD2_ENTER;
1564 if (second < 0 || minute < 0 || hour < 0 || day < 1) {
1565 t = (time_t)-1;
1566 goto jleave;
1569 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1570 if (month > 1)
1571 t += 86400 * 31;
1572 if (month > 2)
1573 t += 86400 * (is_leapyear(year) ? 29 : 28);
1574 if (month > 3)
1575 t += 86400 * 31;
1576 if (month > 4)
1577 t += 86400 * 30;
1578 if (month > 5)
1579 t += 86400 * 31;
1580 if (month > 6)
1581 t += 86400 * 30;
1582 if (month > 7)
1583 t += 86400 * 31;
1584 if (month > 8)
1585 t += 86400 * 31;
1586 if (month > 9)
1587 t += 86400 * 30;
1588 if (month > 10)
1589 t += 86400 * 31;
1590 if (month > 11)
1591 t += 86400 * 30;
1592 year -= 1900;
1593 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1594 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1595 jleave:
1596 NYD2_LEAVE;
1597 return t;
1600 FL void
1601 substdate(struct message *m)
1603 char const *cp;
1604 NYD_ENTER;
1606 /* Determine the date to print in faked 'From ' lines. This is traditionally
1607 * the date the message was written to the mail file. Try to determine this
1608 * using RFC message header fields, or fall back to current time */
1609 if ((cp = hfield1("received", m)) != NULL) {
1610 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1612 ++cp;
1613 while (alnumchar(*cp));
1615 if (cp && *++cp)
1616 m->m_time = rfctime(cp);
1618 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1619 if ((cp = hfield1("date", m)) != NULL)
1620 m->m_time = rfctime(cp);
1622 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1623 m->m_time = time_current.tc_time;
1624 NYD_LEAVE;
1627 FL struct name const *
1628 check_from_and_sender(struct name const *fromfield,
1629 struct name const *senderfield)
1631 struct name const *rv = NULL;
1632 NYD_ENTER;
1634 if (senderfield != NULL) {
1635 if (senderfield->n_flink != NULL) {
1636 fprintf(stderr, _(
1637 "The Sender: field may contain only one address.\n"));
1638 goto jleave;
1640 rv = senderfield;
1643 if (fromfield != NULL) {
1644 if (fromfield->n_flink != NULL && senderfield == NULL) {
1645 fprintf(stderr, _("A Sender: field is required with multiple "
1646 "addresses in From: field.\n"));
1647 goto jleave;
1649 if (rv == NULL)
1650 rv = fromfield;
1653 if (rv == NULL)
1654 rv = (struct name*)0x1;
1655 jleave:
1656 NYD_LEAVE;
1657 return rv;
1660 #ifdef HAVE_OPENSSL
1661 FL char *
1662 getsender(struct message *mp)
1664 char *cp;
1665 struct name *np;
1666 NYD_ENTER;
1668 if ((cp = hfield1("from", mp)) == NULL ||
1669 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
1670 cp = NULL;
1671 else
1672 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
1673 NYD_LEAVE;
1674 return cp;
1676 #endif
1678 FL int
1679 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
1681 /* TODO grab_headers: again, check counts etc. against RFC;
1682 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1683 int errs;
1684 int volatile comma;
1685 NYD_ENTER;
1687 errs = 0;
1688 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
1690 if (gflags & GTO)
1691 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO | GFULL);
1692 if (subjfirst && (gflags & GSUBJECT))
1693 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1694 if (gflags & GCC)
1695 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC | GFULL);
1696 if (gflags & GBCC)
1697 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
1699 if (gflags & GEXTRA) {
1700 if (hp->h_from == NULL)
1701 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL);
1702 hp->h_from = grab_names("From: ", hp->h_from, comma, GEXTRA | GFULL);
1703 if (hp->h_replyto == NULL)
1704 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
1705 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
1706 GEXTRA | GFULL);
1707 if (hp->h_sender == NULL)
1708 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
1709 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
1710 GEXTRA | GFULL);
1711 if (hp->h_organization == NULL)
1712 hp->h_organization = ok_vlook(ORGANIZATION);
1713 hp->h_organization = readstr_input("Organization: ", hp->h_organization);
1716 if (!subjfirst && (gflags & GSUBJECT))
1717 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1719 NYD_LEAVE;
1720 return errs;
1723 FL bool_t
1724 header_match(struct message *mp, struct search_expr const *sep)
1726 struct str in, out;
1727 FILE *ibuf;
1728 int lc;
1729 size_t linesize = 0; /* TODO line pool */
1730 char *linebuf = NULL, *colon;
1731 bool_t rv = FAL0;
1732 NYD_ENTER;
1734 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1735 goto jleave;
1736 if ((lc = mp->m_lines - 1) < 0)
1737 goto jleave;
1739 if ((mp->m_flag & MNOFROM) == 0 &&
1740 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1741 goto jleave;
1742 while (lc > 0) {
1743 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
1744 break;
1745 if (blankchar(*++colon))
1746 ++colon;
1747 in.l = strlen(in.s = colon);
1748 mime_fromhdr(&in, &out, TD_ICONV);
1749 #ifdef HAVE_REGEX
1750 if (sep->ss_sexpr == NULL)
1751 rv = (regexec(&sep->ss_reexpr, out.s, 0,NULL, 0) != REG_NOMATCH);
1752 else
1753 #endif
1754 rv = substr(out.s, sep->ss_sexpr);
1755 free(out.s);
1756 if (rv)
1757 break;
1760 jleave:
1761 if (linebuf != NULL)
1762 free(linebuf);
1763 NYD_LEAVE;
1764 return rv;
1767 /* s-it-mode */