NetBSD works after snprintf(3) argument change
[s-mailx.git] / head.c
blob0e19809b880411ee40f6018b4c26e2cc7f5c8ef7
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, "" }
1275 bool_t any = FAL0;
1276 char *orig_s = s, *re_st = NULL, *re_st_x;
1277 size_t re_l;
1278 NYD_ENTER;
1280 if ((re_st_x = ok_vlook(reply_strings)) != NULL &&
1281 (re_l = strlen(re_st_x)) > 0) {
1282 re_st = ac_alloc(++re_l * 2);
1283 memcpy(re_st, re_st_x, re_l);
1286 jouter:
1287 while (*s != '\0') {
1288 while (spacechar(*s))
1289 ++s;
1291 /* TODO While it is maybe ok not to MIME decode these (for purpose), we
1292 * TODO should skip =?..?= at the beginning? */
1293 for (pp = ignored; pp->len > 0; ++pp)
1294 if (is_asccaseprefix(pp->dat, s)) {
1295 s += pp->len;
1296 any = TRU1;
1297 goto jouter;
1300 if (re_st != NULL) {
1301 char *cp;
1303 memcpy(re_st_x = re_st + re_l, re_st, re_l);
1304 while ((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
1305 if (is_asccaseprefix(cp, s)) {
1306 s += strlen(cp);
1307 any = TRU1;
1308 goto jouter;
1311 break;
1314 if (re_st != NULL)
1315 ac_free(re_st);
1316 NYD_LEAVE;
1317 return any ? s : orig_s;
1320 FL int
1321 msgidcmp(char const *s1, char const *s2)
1323 int q1 = 0, q2 = 0, c1, c2;
1324 NYD_ENTER;
1326 do {
1327 c1 = msgidnextc(&s1, &q1);
1328 c2 = msgidnextc(&s2, &q2);
1329 if (c1 != c2)
1330 break;
1331 } while (c1 && c2);
1332 NYD_LEAVE;
1333 return c1 - c2;
1336 FL int
1337 is_ign(char const *field, size_t fieldlen, struct ignoretab ignoret[2])
1339 char *realfld;
1340 int rv;
1341 NYD_ENTER;
1343 rv = 0;
1344 if (ignoret == NULL)
1345 goto jleave;
1346 rv = 1;
1347 if (ignoret == allignore)
1348 goto jleave;
1350 /* Lowercase it so that "Status" and "status" will hash to the same place */
1351 realfld = ac_alloc(fieldlen +1);
1352 i_strcpy(realfld, field, fieldlen +1);
1353 if (ignoret[1].i_count > 0)
1354 rv = !member(realfld, ignoret + 1);
1355 else
1356 rv = member(realfld, ignoret);
1357 ac_free(realfld);
1358 jleave:
1359 NYD_LEAVE;
1360 return rv;
1363 FL int
1364 member(char const *realfield, struct ignoretab *table)
1366 struct ignore *igp;
1367 int rv = 0;
1368 NYD_ENTER;
1370 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1371 if (*igp->i_field == *realfield && !strcmp(igp->i_field, realfield)) {
1372 rv = 1;
1373 break;
1375 NYD_LEAVE;
1376 return rv;
1379 FL char const *
1380 fakefrom(struct message *mp)
1382 char const *name;
1383 NYD_ENTER;
1385 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1386 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1387 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1388 * RFC 4155 however requires a RFC 5322 (2822) conforming
1389 * "addr-spec", but we simply can't provide that */
1390 name = "MAILER-DAEMON";
1391 NYD_LEAVE;
1392 return name;
1395 FL char const *
1396 fakedate(time_t t)
1398 char *cp, *cq;
1399 NYD_ENTER;
1401 cp = ctime(&t);
1402 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1404 *cq = '\0';
1405 cp = savestr(cp);
1406 NYD_LEAVE;
1407 return cp;
1410 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
1411 FL time_t
1412 unixtime(char const *fromline)
1414 char const *fp;
1415 char *xp;
1416 time_t t;
1417 int i, year, month, day, hour, minute, second, tzdiff;
1418 struct tm *tmptr;
1419 NYD2_ENTER;
1421 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1423 fp -= 24;
1424 if (PTR2SIZE(fp - fromline) < 7)
1425 goto jinvalid;
1426 if (fp[3] != ' ')
1427 goto jinvalid;
1428 for (i = 0;;) {
1429 if (!strncmp(fp + 4, month_names[i], 3))
1430 break;
1431 if (month_names[++i][0] == '\0')
1432 goto jinvalid;
1434 month = i + 1;
1435 if (fp[7] != ' ')
1436 goto jinvalid;
1437 day = strtol(fp + 8, &xp, 10);
1438 if (*xp != ' ' || xp != fp + 10)
1439 goto jinvalid;
1440 hour = strtol(fp + 11, &xp, 10);
1441 if (*xp != ':' || xp != fp + 13)
1442 goto jinvalid;
1443 minute = strtol(fp + 14, &xp, 10);
1444 if (*xp != ':' || xp != fp + 16)
1445 goto jinvalid;
1446 second = strtol(fp + 17, &xp, 10);
1447 if (*xp != ' ' || xp != fp + 19)
1448 goto jinvalid;
1449 year = strtol(fp + 20, &xp, 10);
1450 if (xp != fp + 24)
1451 goto jinvalid;
1452 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1453 goto jinvalid;
1454 tzdiff = t - mktime(gmtime(&t));
1455 tmptr = localtime(&t);
1456 if (tmptr->tm_isdst > 0)
1457 tzdiff += 3600;
1458 t -= tzdiff;
1459 jleave:
1460 NYD2_LEAVE;
1461 return t;
1462 jinvalid:
1463 time(&t);
1464 goto jleave;
1466 #endif /* HAVE_IMAP_SEARCH || defined HAVE_IMAP */
1468 FL time_t
1469 rfctime(char const *date)
1471 char const *cp = date;
1472 char *x;
1473 time_t t;
1474 int i, year, month, day, hour, minute, second;
1475 NYD2_ENTER;
1477 if ((cp = nexttoken(cp)) == NULL)
1478 goto jinvalid;
1479 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1480 cp[3] == ',') {
1481 if ((cp = nexttoken(&cp[4])) == NULL)
1482 goto jinvalid;
1484 day = strtol(cp, &x, 10); /* XXX strtol */
1485 if ((cp = nexttoken(x)) == NULL)
1486 goto jinvalid;
1487 for (i = 0;;) {
1488 if (!strncmp(cp, month_names[i], 3))
1489 break;
1490 if (month_names[++i][0] == '\0')
1491 goto jinvalid;
1493 month = i + 1;
1494 if ((cp = nexttoken(&cp[3])) == NULL)
1495 goto jinvalid;
1496 /* RFC 5322, 4.3:
1497 * Where a two or three digit year occurs in a date, the year is to be
1498 * interpreted as follows: If a two digit year is encountered whose
1499 * value is between 00 and 49, the year is interpreted by adding 2000,
1500 * ending up with a value between 2000 and 2049. If a two digit year
1501 * is encountered with a value between 50 and 99, or any three digit
1502 * year is encountered, the year is interpreted by adding 1900 */
1503 year = strtol(cp, &x, 10); /* XXX strtol */
1504 i = (int)PTR2SIZE(x - cp);
1505 if (i == 2 && year >= 0 && year <= 49)
1506 year += 2000;
1507 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1508 year += 1900;
1509 if ((cp = nexttoken(x)) == NULL)
1510 goto jinvalid;
1511 hour = strtol(cp, &x, 10); /* XXX strtol */
1512 if (*x != ':')
1513 goto jinvalid;
1514 cp = &x[1];
1515 minute = strtol(cp, &x, 10);
1516 if (*x == ':') {
1517 cp = x + 1;
1518 second = strtol(cp, &x, 10);
1519 } else
1520 second = 0;
1521 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1522 goto jinvalid;
1523 if ((cp = nexttoken(x)) != NULL) {
1524 int sign = -1;
1525 char buf[3];
1527 switch (*cp) {
1528 case '-':
1529 sign = 1;
1530 /*FALLTHRU*/
1531 case '+':
1532 ++cp;
1533 break;
1535 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1536 digitchar(cp[3])) {
1537 buf[2] = '\0';
1538 buf[0] = cp[0];
1539 buf[1] = cp[1];
1540 t += strtol(buf, NULL, 10) * sign * 3600;/*XXX strtrol*/
1541 buf[0] = cp[2];
1542 buf[1] = cp[3];
1543 t += strtol(buf, NULL, 10) * sign * 60; /* XXX strtol*/
1545 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1546 * TODO once again, Christos Zoulas and NetBSD Mail have done
1547 * TODO a really good job already, but using strptime(3), which
1548 * TODO is not portable. Nonetheless, WE must improve, not
1549 * TODO at last because we simply ignore obsolete timezones!!
1550 * TODO See RFC 5322, 4.3! */
1552 jleave:
1553 NYD2_LEAVE;
1554 return t;
1555 jinvalid:
1556 t = 0;
1557 goto jleave;
1560 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1562 FL time_t
1563 combinetime(int year, int month, int day, int hour, int minute, int second)
1565 time_t t;
1566 NYD2_ENTER;
1568 if (second < 0 || minute < 0 || hour < 0 || day < 1) {
1569 t = (time_t)-1;
1570 goto jleave;
1573 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1574 if (month > 1)
1575 t += 86400 * 31;
1576 if (month > 2)
1577 t += 86400 * (is_leapyear(year) ? 29 : 28);
1578 if (month > 3)
1579 t += 86400 * 31;
1580 if (month > 4)
1581 t += 86400 * 30;
1582 if (month > 5)
1583 t += 86400 * 31;
1584 if (month > 6)
1585 t += 86400 * 30;
1586 if (month > 7)
1587 t += 86400 * 31;
1588 if (month > 8)
1589 t += 86400 * 31;
1590 if (month > 9)
1591 t += 86400 * 30;
1592 if (month > 10)
1593 t += 86400 * 31;
1594 if (month > 11)
1595 t += 86400 * 30;
1596 year -= 1900;
1597 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1598 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1599 jleave:
1600 NYD2_LEAVE;
1601 return t;
1604 FL void
1605 substdate(struct message *m)
1607 char const *cp;
1608 NYD_ENTER;
1610 /* Determine the date to print in faked 'From ' lines. This is traditionally
1611 * the date the message was written to the mail file. Try to determine this
1612 * using RFC message header fields, or fall back to current time */
1613 if ((cp = hfield1("received", m)) != NULL) {
1614 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1616 ++cp;
1617 while (alnumchar(*cp));
1619 if (cp && *++cp)
1620 m->m_time = rfctime(cp);
1622 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1623 if ((cp = hfield1("date", m)) != NULL)
1624 m->m_time = rfctime(cp);
1626 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1627 m->m_time = time_current.tc_time;
1628 NYD_LEAVE;
1631 FL struct name const *
1632 check_from_and_sender(struct name const *fromfield,
1633 struct name const *senderfield)
1635 struct name const *rv = NULL;
1636 NYD_ENTER;
1638 if (senderfield != NULL) {
1639 if (senderfield->n_flink != NULL) {
1640 fprintf(stderr, _(
1641 "The Sender: field may contain only one address.\n"));
1642 goto jleave;
1644 rv = senderfield;
1647 if (fromfield != NULL) {
1648 if (fromfield->n_flink != NULL && senderfield == NULL) {
1649 fprintf(stderr, _("A Sender: field is required with multiple "
1650 "addresses in From: field.\n"));
1651 goto jleave;
1653 if (rv == NULL)
1654 rv = fromfield;
1657 if (rv == NULL)
1658 rv = (struct name*)0x1;
1659 jleave:
1660 NYD_LEAVE;
1661 return rv;
1664 #ifdef HAVE_OPENSSL
1665 FL char *
1666 getsender(struct message *mp)
1668 char *cp;
1669 struct name *np;
1670 NYD_ENTER;
1672 if ((cp = hfield1("from", mp)) == NULL ||
1673 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
1674 cp = NULL;
1675 else
1676 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
1677 NYD_LEAVE;
1678 return cp;
1680 #endif
1682 FL int
1683 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
1685 /* TODO grab_headers: again, check counts etc. against RFC;
1686 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1687 int errs;
1688 int volatile comma;
1689 NYD_ENTER;
1691 errs = 0;
1692 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
1694 if (gflags & GTO)
1695 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO | GFULL);
1696 if (subjfirst && (gflags & GSUBJECT))
1697 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1698 if (gflags & GCC)
1699 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC | GFULL);
1700 if (gflags & GBCC)
1701 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
1703 if (gflags & GEXTRA) {
1704 if (hp->h_from == NULL)
1705 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL);
1706 hp->h_from = grab_names("From: ", hp->h_from, comma, GEXTRA | GFULL);
1707 if (hp->h_replyto == NULL)
1708 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
1709 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
1710 GEXTRA | GFULL);
1711 if (hp->h_sender == NULL)
1712 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
1713 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
1714 GEXTRA | GFULL);
1715 if (hp->h_organization == NULL)
1716 hp->h_organization = ok_vlook(ORGANIZATION);
1717 hp->h_organization = readstr_input("Organization: ", hp->h_organization);
1720 if (!subjfirst && (gflags & GSUBJECT))
1721 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1723 NYD_LEAVE;
1724 return errs;
1727 FL bool_t
1728 header_match(struct message *mp, struct search_expr const *sep)
1730 struct str in, out;
1731 FILE *ibuf;
1732 int lc;
1733 size_t linesize = 0; /* TODO line pool */
1734 char *linebuf = NULL, *colon;
1735 bool_t rv = FAL0;
1736 NYD_ENTER;
1738 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1739 goto jleave;
1740 if ((lc = mp->m_lines - 1) < 0)
1741 goto jleave;
1743 if ((mp->m_flag & MNOFROM) == 0 &&
1744 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1745 goto jleave;
1746 while (lc > 0) {
1747 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
1748 break;
1749 if (blankchar(*++colon))
1750 ++colon;
1751 in.l = strlen(in.s = colon);
1752 mime_fromhdr(&in, &out, TD_ICONV);
1753 #ifdef HAVE_REGEX
1754 if (sep->ss_sexpr == NULL)
1755 rv = (regexec(&sep->ss_reexpr, out.s, 0,NULL, 0) != REG_NOMATCH);
1756 else
1757 #endif
1758 rv = substr(out.s, sep->ss_sexpr);
1759 free(out.s);
1760 if (rv)
1761 break;
1764 jleave:
1765 if (linebuf != NULL)
1766 free(linebuf);
1767 NYD_LEAVE;
1768 return rv;
1771 /* s-it-mode */