Obsolete *autoinc* (keep only *newmail*)
[s-mailx.git] / head.c
blobe58bcf50e322055e6facb4e38b6c386278853673
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 NYD_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 NYD_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 NYD_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 NYD_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 NYD_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 NYD_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 (!utf8) {
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, tr(179, "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 (utf8)
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 for (;;) {
454 if (*status & 01) {
455 if (**cp == '"') {
456 *status &= ~01;
457 (*cp)++;
458 continue;
460 if (**cp == '\\') {
461 (*cp)++;
462 if (**cp == '\0')
463 goto jeof;
465 goto jdfl;
467 switch (**cp) {
468 case '(':
469 *cp = skip_comment(&(*cp)[1]);
470 continue;
471 case '>':
472 case '\0':
473 jeof:
474 c = '\0';
475 goto jleave;
476 case '"':
477 (*cp)++;
478 *status |= 01;
479 continue;
480 case '@':
481 *status |= 02;
482 /*FALLTHRU*/
483 default:
484 jdfl:
485 c = *(*cp)++ & 0377;
486 c = (*status & 02) ? lowerconv(c) : c;
487 goto jleave;
490 jleave:
491 NYD_LEAVE;
492 return c;
495 static int
496 charcount(char *str, int c)
498 char *cp;
499 int i;
500 NYD_ENTER;
502 for (i = 0, cp = str; *cp; ++cp)
503 if (*cp == c)
504 ++i;
505 NYD_LEAVE;
506 return i;
509 static char const *
510 nexttoken(char const *cp)
512 NYD_ENTER;
513 for (;;) {
514 if (*cp == '\0') {
515 cp = NULL;
516 break;
519 if (*cp == '(') {
520 size_t nesting = 1;
522 do switch (*++cp) {
523 case '(':
524 ++nesting;
525 break;
526 case ')':
527 --nesting;
528 break;
529 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
530 } else if (blankchar(*cp) || *cp == ',')
531 ++cp;
532 else
533 break;
535 NYD_LEAVE;
536 return cp;
539 FL char const *
540 myaddrs(struct header *hp)
542 struct name *np;
543 char *rv;
544 NYD_ENTER;
546 if (hp != NULL && (np = hp->h_from) != NULL) {
547 if ((rv = np->n_fullname) != NULL)
548 goto jleave;
549 if ((rv = np->n_name) != NULL)
550 goto jleave;
553 if ((rv = ok_vlook(from)) != NULL)
554 goto jleave;
556 /* When invoking *sendmail* directly, it's its task to generate an otherwise
557 * undeterminable From: address. However, if the user sets *hostname*,
558 * accept his desire */
559 if (ok_vlook(smtp) != NULL || ok_vlook(hostname) != NULL) {
560 char *hn = nodename(1);
561 size_t sz = strlen(myname) + strlen(hn) + 1 +1;
562 rv = salloc(sz);
563 sstpcpy(sstpcpy(sstpcpy(rv, myname), "@"), hn);
565 jleave:
566 NYD_LEAVE;
567 return rv;
570 FL char const *
571 myorigin(struct header *hp)
573 char const *rv = NULL, *ccp;
574 struct name *np;
575 NYD_ENTER;
577 if ((ccp = myaddrs(hp)) != NULL &&
578 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
579 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
580 NYD_LEAVE;
581 return rv;
584 FL int
585 is_head(char const *linebuf, size_t linelen) /* XXX verbose WARN */
587 char date[FROM_DATEBUF];
588 int rv;
589 NYD_ENTER;
591 rv = ((linelen <= 5 || strncmp(linebuf, "From ", 5) != 0 ||
592 !extract_date_from_from_(linebuf, linelen, date) ||
593 !_is_date(date)) ? 0 : 1);
594 NYD_LEAVE;
595 return rv;
598 FL int
599 extract_date_from_from_(char const *line, size_t linelen,
600 char datebuf[FROM_DATEBUF])
602 int rv = 0;
603 char const *cp = line;
604 NYD_ENTER;
606 /* "From " */
607 cp = _from__skipword(cp);
608 if (cp == NULL)
609 goto jerr;
610 /* "addr-spec " */
611 cp = _from__skipword(cp);
612 if (cp == NULL)
613 goto jerr;
614 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
615 cp = _from__skipword(cp);
616 if (cp == NULL)
617 goto jerr;
620 linelen -= PTR2SIZE(cp - line);
621 if (linelen < _DATE_MINLEN)
622 goto jerr;
623 if (cp[linelen - 1] == '\n') {
624 --linelen;
625 /* (Rather IMAP/POP3 only) */
626 if (cp[linelen - 1] == '\r')
627 --linelen;
628 if (linelen < _DATE_MINLEN)
629 goto jerr;
631 if (linelen >= FROM_DATEBUF)
632 goto jerr;
634 rv = 1;
635 jleave:
636 memcpy(datebuf, cp, linelen);
637 datebuf[linelen] = '\0';
638 NYD_LEAVE;
639 return rv;
640 jerr:
641 cp = tr(213, "<Unknown date>");
642 linelen = strlen(cp);
643 if (linelen >= FROM_DATEBUF)
644 linelen = FROM_DATEBUF;
645 goto jleave;
648 FL void
649 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
651 struct header nh, *hq = &nh;
652 char *linebuf = NULL /* TODO line pool */, *colon;
653 size_t linesize = 0, seenfields = 0;
654 int lc, c;
655 char const *val, *cp;
656 NYD_ENTER;
658 memset(hq, 0, sizeof *hq);
659 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
662 /* TODO yippieia, cat(check(lextract)) :-) */
663 rewind(fp);
664 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
665 if ((val = thisfield(linebuf, "to")) != NULL) {
666 ++seenfields;
667 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL)));
668 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
669 ++seenfields;
670 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL)));
671 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
672 ++seenfields;
673 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL)));
674 } else if ((val = thisfield(linebuf, "from")) != NULL) {
675 ++seenfields;
676 hq->h_from = cat(hq->h_from,
677 checkaddrs(lextract(val, GEXTRA | GFULL)));
678 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
679 ++seenfields;
680 hq->h_replyto = cat(hq->h_replyto,
681 checkaddrs(lextract(val, GEXTRA | GFULL)));
682 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
683 ++seenfields;
684 hq->h_sender = cat(hq->h_sender,
685 checkaddrs(lextract(val, GEXTRA | GFULL)));
686 } else if ((val = thisfield(linebuf, "organization")) != NULL) {
687 ++seenfields;
688 for (cp = val; blankchar(*cp); ++cp)
690 hq->h_organization = (hq->h_organization != NULL)
691 ? save2str(hq->h_organization, cp) : savestr(cp);
692 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
693 (val = thisfield(linebuf, "subj")) != NULL) {
694 ++seenfields;
695 for (cp = val; blankchar(*cp); ++cp)
697 hq->h_subject = (hq->h_subject != NULL)
698 ? save2str(hq->h_subject, cp) : savestr(cp);
699 } else
700 fprintf(stderr, tr(266, "Ignoring header field \"%s\"\n"), linebuf);
703 /* In case the blank line after the header has been edited out. Otherwise,
704 * fetch the header separator */
705 if (linebuf != NULL) {
706 if (linebuf[0] != '\0') {
707 for (cp = linebuf; *(++cp) != '\0';)
709 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
710 } else {
711 if ((c = getc(fp)) != '\n' && c != EOF)
712 ungetc(c, fp);
716 if (seenfields > 0) {
717 hp->h_to = hq->h_to;
718 hp->h_cc = hq->h_cc;
719 hp->h_bcc = hq->h_bcc;
720 hp->h_from = hq->h_from;
721 hp->h_replyto = hq->h_replyto;
722 hp->h_sender = hq->h_sender;
723 hp->h_organization = hq->h_organization;
724 hp->h_subject = hq->h_subject;
725 } else
726 fprintf(stderr, tr(267, "Restoring deleted header lines\n"));
728 if (linebuf != NULL)
729 free(linebuf);
730 NYD_LEAVE;
733 FL char *
734 hfield_mult(char const *field, struct message *mp, int mult)
736 FILE *ibuf;
737 int lc;
738 size_t linesize = 0; /* TODO line pool */
739 char *linebuf = NULL, *colon, *oldhfield = NULL;
740 char const *hfield;
741 NYD_ENTER;
743 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
744 goto jleave;
745 if ((lc = mp->m_lines - 1) < 0)
746 goto jleave;
748 if ((mp->m_flag & MNOFROM) == 0 &&
749 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
750 goto jleave;
751 while (lc > 0) {
752 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
753 break;
754 if ((hfield = thisfield(linebuf, field)) != NULL) {
755 oldhfield = save2str(hfield, oldhfield);
756 if (mult == 0)
757 break;
761 jleave:
762 if (linebuf != NULL)
763 free(linebuf);
764 NYD_LEAVE;
765 return oldhfield;
768 FL char const *
769 thisfield(char const *linebuf, char const *field)
771 char const *rv = NULL;
772 NYD_ENTER;
774 while (lowerconv(*linebuf) == lowerconv(*field)) {
775 ++linebuf;
776 ++field;
778 if (*field != '\0')
779 goto jleave;
781 while (blankchar(*linebuf))
782 ++linebuf;
783 if (*linebuf++ != ':')
784 goto jleave;
786 while (blankchar(*linebuf))
787 ++linebuf;
788 rv = linebuf;
789 jleave:
790 NYD_LEAVE;
791 return rv;
794 FL char *
795 nameof(struct message *mp, int reptype)
797 char *cp, *cp2;
798 NYD_ENTER;
800 cp = skin(name1(mp, reptype));
801 if (reptype != 0 || charcount(cp, '!') < 2)
802 goto jleave;
803 cp2 = strrchr(cp, '!');
804 --cp2;
805 while (cp2 > cp && *cp2 != '!')
806 --cp2;
807 if (*cp2 == '!')
808 cp = cp2 + 1;
809 jleave:
810 NYD_LEAVE;
811 return cp;
814 FL char const *
815 skip_comment(char const *cp)
817 size_t nesting;
818 NYD_ENTER;
820 for (nesting = 1; nesting > 0 && *cp; ++cp) {
821 switch (*cp) {
822 case '\\':
823 if (cp[1])
824 ++cp;
825 break;
826 case '(':
827 ++nesting;
828 break;
829 case ')':
830 --nesting;
831 break;
834 NYD_LEAVE;
835 return cp;
838 FL char const *
839 routeaddr(char const *name)
841 char const *np, *rp = NULL;
842 NYD_ENTER;
844 for (np = name; *np; np++) {
845 switch (*np) {
846 case '(':
847 np = skip_comment(np + 1) - 1;
848 break;
849 case '"':
850 while (*np) {
851 if (*++np == '"')
852 break;
853 if (*np == '\\' && np[1])
854 np++;
856 break;
857 case '<':
858 rp = np;
859 break;
860 case '>':
861 goto jleave;
864 rp = NULL;
865 jleave:
866 NYD_LEAVE;
867 return rp;
870 FL int
871 is_addr_invalid(struct name *np, int putmsg)
873 char cbuf[sizeof "'\\U12340'"], *name;
874 int f, ok8bit;
875 ui32_t c;
876 char const *fmt, *cs;
877 NYD_ENTER;
879 name = np->n_name;
880 f = np->n_flags;
881 ok8bit = 1;
882 fmt = "'\\x%02X'";
884 if (!(f & NAME_ADDRSPEC_INVALID) || !putmsg || (f & NAME_ADDRSPEC_ERR_EMPTY))
885 goto jleave;
887 if (f & NAME_ADDRSPEC_ERR_IDNA)
888 cs = tr(284, "Invalid domain name: \"%s\", character %s\n"),
889 fmt = "'\\U%04X'",
890 ok8bit = 0;
891 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
892 cs = tr(142, "\"%s\" contains invalid %s sequence\n");
893 else
894 cs = tr(143, "\"%s\" contains invalid character %s\n");
896 c = NAME_ADDRSPEC_ERR_GETWC(f);
897 if (ok8bit && c >= 040 && c <= 0177)
898 snprintf(cbuf, sizeof cbuf, "'%c'", c);
899 else
900 snprintf(cbuf, sizeof cbuf, fmt, c);
902 fprintf(stderr, cs, name, cbuf);
903 jleave:
904 NYD_LEAVE;
905 return ((f & NAME_ADDRSPEC_INVALID) != 0);
908 FL char *
909 skin(char const *name)
911 struct addrguts ag;
912 char *ret = NULL;
913 NYD_ENTER;
915 if (name != NULL) {
916 addrspec_with_guts(1, name, &ag);
917 ret = ag.ag_skinned;
918 if (!(ag.ag_n_flags & NAME_NAME_SALLOC))
919 ret = savestrbuf(ret, ag.ag_slen);
921 NYD_LEAVE;
922 return ret;
925 /* TODO addrspec_with_guts: RFC 5322 */
926 FL int
927 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
929 char const *cp;
930 char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp;
931 int rv = 1;
932 NYD_ENTER;
934 memset(agp, 0, sizeof *agp);
936 if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) {
937 agp->ag_skinned = UNCONST(""); /* ok: NAME_SALLOC is not set */
938 agp->ag_slen = 0;
939 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
940 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
941 goto jleave;
944 if (!doskin || !anyof(name, "(< ")) {
945 /*agp->ag_iaddr_start = 0;*/
946 agp->ag_iaddr_aend = agp->ag_ilen;
947 agp->ag_skinned = UNCONST(name); /* (NAME_SALLOC not set) */
948 agp->ag_slen = agp->ag_ilen;
949 agp->ag_n_flags = NAME_SKINNED;
950 goto jcheck;
953 /* Something makes us think we have to perform the skin operation */
954 nbuf = ac_alloc(agp->ag_ilen + 1);
955 /*agp->ag_iaddr_start = 0;*/
956 cp2 = bufend = nbuf;
957 gotlt = gotaddr = lastsp = 0;
959 for (cp = name++; (c = *cp++) != '\0'; ) {
960 switch (c) {
961 case '(':
962 cp = skip_comment(cp);
963 lastsp = 0;
964 break;
965 case '"':
966 /* Start of a "quoted-string".
967 * Copy it in its entirety */
968 /* XXX RFC: quotes are "semantically invisible"
969 * XXX But it was explicitly added (Changelog.Heirloom,
970 * XXX [9.23] released 11/15/00, "Do not remove quotes
971 * XXX when skinning names"? No more info.. */
972 *cp2++ = c;
973 while ((c = *cp) != '\0') { /* TODO improve */
974 cp++;
975 if (c == '"') {
976 *cp2++ = c;
977 break;
979 if (c != '\\')
980 *cp2++ = c;
981 else if ((c = *cp) != '\0') {
982 *cp2++ = c;
983 cp++;
986 lastsp = 0;
987 break;
988 case ' ':
989 case '\t':
990 if (gotaddr == 1) {
991 gotaddr = 2;
992 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
994 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
995 cp += 3, *cp2++ = '@';
996 else if (cp[0] == '@' && blankchar(cp[1]))
997 cp += 2, *cp2++ = '@';
998 else
999 lastsp = 1;
1000 break;
1001 case '<':
1002 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1003 cp2 = bufend;
1004 gotlt = gotaddr = 1;
1005 lastsp = 0;
1006 break;
1007 case '>':
1008 if (gotlt) {
1009 /* (_addrspec_check() verifies these later!) */
1010 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1011 gotlt = 0;
1012 while ((c = *cp) != '\0' && c != ',') {
1013 cp++;
1014 if (c == '(')
1015 cp = skip_comment(cp);
1016 else if (c == '"')
1017 while ((c = *cp) != '\0') {
1018 cp++;
1019 if (c == '"')
1020 break;
1021 if (c == '\\' && *cp != '\0')
1022 ++cp;
1025 lastsp = 0;
1026 break;
1028 /* FALLTRHOUGH */
1029 default:
1030 if (lastsp) {
1031 lastsp = 0;
1032 if (gotaddr)
1033 *cp2++ = ' ';
1035 *cp2++ = c;
1036 if (c == ',') {
1037 if (!gotlt) {
1038 *cp2++ = ' ';
1039 for (; blankchar(*cp); ++cp)
1041 lastsp = 0;
1042 bufend = cp2;
1044 } else if (!gotaddr) {
1045 gotaddr = 1;
1046 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1050 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1051 if (agp->ag_iaddr_aend == 0)
1052 agp->ag_iaddr_aend = agp->ag_ilen;
1054 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1055 ac_free(nbuf);
1056 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1057 jcheck:
1058 rv = _addrspec_check(doskin, agp);
1059 jleave:
1060 NYD_LEAVE;
1061 return rv;
1064 FL char *
1065 realname(char const *name)
1067 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1068 char *rname, *rp;
1069 struct str in, out;
1070 int quoted, good, nogood;
1071 NYD_ENTER;
1073 if ((cp = UNCONST(name)) == NULL)
1074 goto jleave;
1075 for (; *cp != '\0'; ++cp) {
1076 switch (*cp) {
1077 case '(':
1078 if (cstart != NULL) {
1079 /* More than one comment in address, doesn't make sense to display
1080 * it without context. Return the entire field */
1081 cp = mime_fromaddr(name);
1082 goto jleave;
1084 cstart = cp++;
1085 cp = skip_comment(cp);
1086 cend = cp--;
1087 if (cend <= cstart)
1088 cend = cstart = NULL;
1089 break;
1090 case '"':
1091 while (*cp) {
1092 if (*++cp == '"')
1093 break;
1094 if (*cp == '\\' && cp[1])
1095 ++cp;
1097 break;
1098 case '<':
1099 if (cp > name) {
1100 cstart = name;
1101 cend = cp;
1103 break;
1104 case ',':
1105 /* More than one address. Just use the first one */
1106 goto jbrk;
1110 jbrk:
1111 if (cstart == NULL) {
1112 if (*name == '<') {
1113 /* If name contains only a route-addr, the surrounding angle brackets
1114 * don't serve any useful purpose when displaying, so remove */
1115 cp = prstr(skin(name));
1116 } else
1117 cp = mime_fromaddr(name);
1118 goto jleave;
1121 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1122 * not stripped. The idea is to strip only syntactical relevant things (but
1123 * this is not necessarily the most sensible way in practice) */
1124 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1125 quoted = 0;
1126 for (cp = cstart; cp < cend; ++cp) {
1127 if (*cp == '(' && !quoted) {
1128 cq = skip_comment(++cp);
1129 if (PTRCMP(--cq, >, cend))
1130 cq = cend;
1131 while (cp < cq) {
1132 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1133 ++cp;
1134 *rp++ = *cp++;
1136 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1137 *rp++ = *++cp;
1138 else if (*cp == '"') {
1139 quoted = !quoted;
1140 continue;
1141 } else
1142 *rp++ = *cp;
1144 *rp = '\0';
1145 in.s = rname;
1146 in.l = rp - rname;
1147 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1148 ac_free(rname);
1149 rname = savestr(out.s);
1150 free(out.s);
1152 while (blankchar(*rname))
1153 ++rname;
1154 for (rp = rname; *rp != '\0'; ++rp)
1156 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1157 *rp = '\0';
1158 if (rp == rname) {
1159 cp = mime_fromaddr(name);
1160 goto jleave;
1163 /* mime_fromhdr() has converted all nonprintable characters to question
1164 * marks now. These and blanks are considered uninteresting; if the
1165 * displayed part of the real name contains more than 25% of them, it is
1166 * probably better to display the plain email address instead */
1167 good = 0;
1168 nogood = 0;
1169 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1170 if (*rp == '?' || blankchar(*rp))
1171 ++nogood;
1172 else
1173 ++good;
1174 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1175 jleave:
1176 NYD_LEAVE;
1177 return UNCONST(cp);
1180 FL char *
1181 name1(struct message *mp, int reptype)
1183 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1184 size_t namesize, linesize = 0;
1185 FILE *ibuf;
1186 int f1st = 1;
1187 NYD_ENTER;
1189 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1190 goto jleave;
1191 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1192 goto jleave;
1194 namebuf = smalloc(namesize = 1);
1195 namebuf[0] = 0;
1196 if (mp->m_flag & MNOFROM)
1197 goto jout;
1198 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1199 goto jout;
1200 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1201 goto jout;
1203 jnewname:
1204 if (namesize <= linesize)
1205 namebuf = srealloc(namebuf, namesize = linesize +1);
1206 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1208 for (; blankchar(*cp); ++cp)
1210 for (cp2 = namebuf + strlen(namebuf);
1211 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1212 *cp2++ = *cp++;
1213 *cp2 = '\0';
1215 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1216 goto jout;
1217 if ((cp = strchr(linebuf, 'F')) == NULL)
1218 goto jout;
1219 if (strncmp(cp, "From", 4)) /* XXX is_head? */
1220 goto jout;
1221 if (namesize <= linesize)
1222 namebuf = srealloc(namebuf, namesize = linesize + 1);
1224 while ((cp = strchr(cp, 'r')) != NULL) {
1225 if (!strncmp(cp, "remote", 6)) {
1226 if ((cp = strchr(cp, 'f')) == NULL)
1227 break;
1228 if (strncmp(cp, "from", 4) != 0)
1229 break;
1230 if ((cp = strchr(cp, ' ')) == NULL)
1231 break;
1232 cp++;
1233 if (f1st) {
1234 strncpy(namebuf, cp, namesize);
1235 f1st = 0;
1236 } else {
1237 cp2 = strrchr(namebuf, '!') + 1;
1238 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1240 namebuf[namesize - 2] = '!';
1241 namebuf[namesize - 1] = '\0';
1242 goto jnewname;
1244 cp++;
1246 jout:
1247 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1248 *cp == '\0')
1249 cp = savestr(namebuf);
1251 if (linebuf != NULL)
1252 free(linebuf);
1253 free(namebuf);
1254 jleave:
1255 NYD_LEAVE;
1256 return cp;
1259 FL int
1260 msgidcmp(char const *s1, char const *s2)
1262 int q1 = 0, q2 = 0, c1, c2;
1263 NYD_ENTER;
1265 do {
1266 c1 = msgidnextc(&s1, &q1);
1267 c2 = msgidnextc(&s2, &q2);
1268 if (c1 != c2)
1269 break;
1270 } while (c1 && c2);
1271 NYD_LEAVE;
1272 return c1 - c2;
1275 FL int
1276 is_ign(char const *field, size_t fieldlen, struct ignoretab ignoret[2])
1278 char *realfld;
1279 int rv;
1280 NYD_ENTER;
1282 rv = 0;
1283 if (ignoret == NULL)
1284 goto jleave;
1285 rv = 1;
1286 if (ignoret == allignore)
1287 goto jleave;
1289 /* Lowercase it so that "Status" and "status" will hash to the same place */
1290 realfld = ac_alloc(fieldlen +1);
1291 i_strcpy(realfld, field, fieldlen +1);
1292 if (ignoret[1].i_count > 0)
1293 rv = !member(realfld, ignoret + 1);
1294 else
1295 rv = member(realfld, ignoret);
1296 ac_free(realfld);
1297 jleave:
1298 NYD_LEAVE;
1299 return rv;
1302 FL int
1303 member(char const *realfield, struct ignoretab *table)
1305 struct ignore *igp;
1306 int rv = 0;
1307 NYD_ENTER;
1309 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1310 if (*igp->i_field == *realfield && !strcmp(igp->i_field, realfield)) {
1311 rv = 1;
1312 break;
1314 NYD_LEAVE;
1315 return rv;
1318 FL char const *
1319 fakefrom(struct message *mp)
1321 char const *name;
1322 NYD_ENTER;
1324 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1325 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1326 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1327 * RFC 4155 however requires a RFC 5322 (2822) conforming
1328 * "addr-spec", but we simply can't provide that */
1329 name = "MAILER-DAEMON";
1330 NYD_LEAVE;
1331 return name;
1334 FL char const *
1335 fakedate(time_t t)
1337 char *cp, *cq;
1338 NYD_ENTER;
1340 cp = ctime(&t);
1341 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1343 *cq = '\0';
1344 cp = savestr(cp);
1345 NYD_LEAVE;
1346 return cp;
1349 FL time_t
1350 unixtime(char const *fromline)
1352 char const *fp;
1353 char *xp;
1354 time_t t;
1355 int i, year, month, day, hour, minute, second, tzdiff;
1356 struct tm *tmptr;
1357 NYD_ENTER;
1359 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1361 fp -= 24;
1362 if (PTR2SIZE(fp - fromline) < 7)
1363 goto jinvalid;
1364 if (fp[3] != ' ')
1365 goto jinvalid;
1366 for (i = 0;;) {
1367 if (!strncmp(fp + 4, month_names[i], 3))
1368 break;
1369 if (month_names[++i][0] == '\0')
1370 goto jinvalid;
1372 month = i + 1;
1373 if (fp[7] != ' ')
1374 goto jinvalid;
1375 day = strtol(fp + 8, &xp, 10);
1376 if (*xp != ' ' || xp != fp + 10)
1377 goto jinvalid;
1378 hour = strtol(fp + 11, &xp, 10);
1379 if (*xp != ':' || xp != fp + 13)
1380 goto jinvalid;
1381 minute = strtol(fp + 14, &xp, 10);
1382 if (*xp != ':' || xp != fp + 16)
1383 goto jinvalid;
1384 second = strtol(fp + 17, &xp, 10);
1385 if (*xp != ' ' || xp != fp + 19)
1386 goto jinvalid;
1387 year = strtol(fp + 20, &xp, 10);
1388 if (xp != fp + 24)
1389 goto jinvalid;
1390 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1391 goto jinvalid;
1392 tzdiff = t - mktime(gmtime(&t));
1393 tmptr = localtime(&t);
1394 if (tmptr->tm_isdst > 0)
1395 tzdiff += 3600;
1396 t -= tzdiff;
1397 jleave:
1398 NYD_LEAVE;
1399 return t;
1400 jinvalid:
1401 time(&t);
1402 goto jleave;
1405 FL time_t
1406 rfctime(char const *date)
1408 char const *cp = date;
1409 char *x;
1410 time_t t;
1411 int i, year, month, day, hour, minute, second;
1412 NYD_ENTER;
1414 if ((cp = nexttoken(cp)) == NULL)
1415 goto jinvalid;
1416 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1417 cp[3] == ',') {
1418 if ((cp = nexttoken(&cp[4])) == NULL)
1419 goto jinvalid;
1421 day = strtol(cp, &x, 10); /* XXX strtol */
1422 if ((cp = nexttoken(x)) == NULL)
1423 goto jinvalid;
1424 for (i = 0;;) {
1425 if (!strncmp(cp, month_names[i], 3))
1426 break;
1427 if (month_names[++i][0] == '\0')
1428 goto jinvalid;
1430 month = i + 1;
1431 if ((cp = nexttoken(&cp[3])) == NULL)
1432 goto jinvalid;
1433 /* RFC 5322, 4.3:
1434 * Where a two or three digit year occurs in a date, the year is to be
1435 * interpreted as follows: If a two digit year is encountered whose
1436 * value is between 00 and 49, the year is interpreted by adding 2000,
1437 * ending up with a value between 2000 and 2049. If a two digit year
1438 * is encountered with a value between 50 and 99, or any three digit
1439 * year is encountered, the year is interpreted by adding 1900 */
1440 year = strtol(cp, &x, 10); /* XXX strtol */
1441 i = (int)PTR2SIZE(x - cp);
1442 if (i == 2 && year >= 0 && year <= 49)
1443 year += 2000;
1444 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1445 year += 1900;
1446 if ((cp = nexttoken(x)) == NULL)
1447 goto jinvalid;
1448 hour = strtol(cp, &x, 10); /* XXX strtol */
1449 if (*x != ':')
1450 goto jinvalid;
1451 cp = &x[1];
1452 minute = strtol(cp, &x, 10);
1453 if (*x == ':') {
1454 cp = x + 1;
1455 second = strtol(cp, &x, 10);
1456 } else
1457 second = 0;
1458 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1459 goto jinvalid;
1460 if ((cp = nexttoken(x)) != NULL) {
1461 int sign = -1;
1462 char buf[3];
1464 switch (*cp) {
1465 case '-':
1466 sign = 1;
1467 /*FALLTHRU*/
1468 case '+':
1469 ++cp;
1470 break;
1472 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1473 digitchar(cp[3])) {
1474 buf[2] = '\0';
1475 buf[0] = cp[0];
1476 buf[1] = cp[1];
1477 t += strtol(buf, NULL, 10) * sign * 3600;/*XXX strtrol*/
1478 buf[0] = cp[2];
1479 buf[1] = cp[3];
1480 t += strtol(buf, NULL, 10) * sign * 60; /* XXX strtol*/
1482 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1483 * TODO once again, Christos Zoulas and NetBSD Mail have done
1484 * TODO a really good job already, but using strptime(3), which
1485 * TODO is not portable. Nonetheless, WE must improve, not
1486 * TODO at last because we simply ignore obsolete timezones!!
1487 * TODO See RFC 5322, 4.3! */
1489 jleave:
1490 NYD_LEAVE;
1491 return t;
1492 jinvalid:
1493 t = 0;
1494 goto jleave;
1497 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1499 FL time_t
1500 combinetime(int year, int month, int day, int hour, int minute, int second)
1502 time_t t;
1503 NYD_ENTER;
1505 if (second < 0 || minute < 0 || hour < 0 || day < 1) {
1506 t = (time_t)-1;
1507 goto jleave;
1510 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1511 if (month > 1)
1512 t += 86400 * 31;
1513 if (month > 2)
1514 t += 86400 * (is_leapyear(year) ? 29 : 28);
1515 if (month > 3)
1516 t += 86400 * 31;
1517 if (month > 4)
1518 t += 86400 * 30;
1519 if (month > 5)
1520 t += 86400 * 31;
1521 if (month > 6)
1522 t += 86400 * 30;
1523 if (month > 7)
1524 t += 86400 * 31;
1525 if (month > 8)
1526 t += 86400 * 31;
1527 if (month > 9)
1528 t += 86400 * 30;
1529 if (month > 10)
1530 t += 86400 * 31;
1531 if (month > 11)
1532 t += 86400 * 30;
1533 year -= 1900;
1534 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1535 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1536 jleave:
1537 NYD_LEAVE;
1538 return t;
1541 FL void
1542 substdate(struct message *m)
1544 char const *cp;
1545 NYD_ENTER;
1547 /* Determine the date to print in faked 'From ' lines. This is traditionally
1548 * the date the message was written to the mail file. Try to determine this
1549 * using RFC message header fields, or fall back to current time */
1550 if ((cp = hfield1("received", m)) != NULL) {
1551 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1553 ++cp;
1554 while (alnumchar(*cp));
1556 if (cp && *++cp)
1557 m->m_time = rfctime(cp);
1559 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1560 if ((cp = hfield1("date", m)) != NULL)
1561 m->m_time = rfctime(cp);
1563 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1564 m->m_time = time_current.tc_time;
1565 NYD_LEAVE;
1568 FL int
1569 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1571 int rv;
1572 NYD_ENTER;
1574 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1575 fprintf(stderr, tr(529, "A Sender: field is required with multiple "
1576 "addresses in From: field.\n"));
1577 rv = 1;
1578 } else if (senderfield && senderfield->n_flink) {
1579 fprintf(stderr, tr(530,
1580 "The Sender: field may contain only one address.\n"));
1581 rv = 2;
1582 } else
1583 rv = 0;
1584 NYD_LEAVE;
1585 return rv;
1588 FL char *
1589 getsender(struct message *mp)
1591 char *cp;
1592 struct name *np;
1593 NYD_ENTER;
1595 if ((cp = hfield1("from", mp)) == NULL ||
1596 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
1597 cp = NULL;
1598 else
1599 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
1600 NYD_LEAVE;
1601 return cp;
1604 FL int
1605 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
1607 /* TODO grab_headers: again, check counts etc. against RFC;
1608 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1609 int errs;
1610 int volatile comma;
1611 NYD_ENTER;
1613 errs = 0;
1614 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
1616 if (gflags & GTO)
1617 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO | GFULL);
1618 if (subjfirst && (gflags & GSUBJECT))
1619 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1620 if (gflags & GCC)
1621 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC | GFULL);
1622 if (gflags & GBCC)
1623 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
1625 if (gflags & GEXTRA) {
1626 if (hp->h_from == NULL)
1627 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL);
1628 hp->h_from = grab_names("From: ", hp->h_from, comma, GEXTRA | GFULL);
1629 if (hp->h_replyto == NULL)
1630 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
1631 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
1632 GEXTRA | GFULL);
1633 if (hp->h_sender == NULL)
1634 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
1635 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
1636 GEXTRA | GFULL);
1637 if (hp->h_organization == NULL)
1638 hp->h_organization = ok_vlook(ORGANIZATION);
1639 hp->h_organization = readstr_input("Organization: ", hp->h_organization);
1642 if (!subjfirst && (gflags & GSUBJECT))
1643 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1645 NYD_LEAVE;
1646 return errs;
1649 /* vim:set fenc=utf-8:s-it-mode */