NYD: quit.c
[s-mailx.git] / head.c
blob334fff99c80ee3606c9ac2fadb6904d08f19122c
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 <stringprep.h>
47 #endif
49 struct cmatch_data {
50 size_t tlen; /* Length of .tdata */
51 char const *tdata; /* Template date - see _cmatch_data[] */
54 /* Template characters for cmatch_data.tdata:
55 * 'A' An upper case char
56 * 'a' A lower case char
57 * ' ' A space
58 * '0' A digit
59 * 'O' An optional digit or space
60 * ':' A colon
61 * '+' Either a plus or a minus sign */
62 static struct cmatch_data const _cmatch_data[] = {
63 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
64 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
65 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
66 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
67 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
68 * in the wild (by UW-imap) */
69 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
70 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
71 * zone strings; note that 1. is strictly speaking not correct as some
72 * letters are not used, and 2. is not because only "UT" is defined */
73 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
74 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
75 { 0, NULL }
77 #define _DATE_MINLEN 21
79 /* Skip over "word" as found in From_ line */
80 static char const * _from__skipword(char const *wp);
82 /* Match the date string against the date template (tp), return if match.
83 * See _cmatch_data[] for template character description */
84 static int _cmatch(size_t len, char const *date,
85 char const *tp);
87 /* Check wether date is a valid 'From_' date.
88 * (Rather ctime(3) generated dates, according to RFC 4155) */
89 static int _is_date(char const *date);
91 /* Convert the domain part of a skinned address to IDNA.
92 * If an error occurs before Unicode information is available, revert the IDNA
93 * error to a normal CHAR one so that the error message doesn't talk Unicode */
94 #ifdef HAVE_IDNA
95 static struct addrguts * _idna_apply(struct addrguts *agp);
96 #endif
98 /* Classify and check a (possibly skinned) header body according to RFC
99 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
100 * also a file or a pipe command, so check that first, then.
101 * Otherwise perform content checking and isolate the domain part (for IDNA) */
102 static int _addrspec_check(int doskin, struct addrguts *agp);
104 /* Return the next header field found in the given message.
105 * Return >= 0 if something found, < 0 elsewise.
106 * "colon" is set to point to the colon in the header.
107 * Must deal with \ continuations & other such fraud */
108 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
109 int rem, char **colon);
111 static int msgidnextc(char const **cp, int *status);
113 /* Count the occurances of c in str */
114 static int charcount(char *str, int c);
116 static char const * nexttoken(char const *cp);
118 static char const *
119 _from__skipword(char const *wp)
121 char c = 0;
122 NYD_ENTER;
124 if (wp != NULL) {
125 while ((c = *wp++) != '\0' && !blankchar(c)) {
126 if (c == '"') {
127 while ((c = *wp++) != '\0' && c != '"')
129 if (c != '"')
130 --wp;
133 for (; blankchar(c); c = *wp++)
136 NYD_LEAVE;
137 return (c == 0 ? NULL : wp - 1);
140 static int
141 _cmatch(size_t len, char const *date, char const *tp)
143 int ret = 0;
144 NYD_ENTER;
146 while (len--) {
147 char c = date[len];
148 switch (tp[len]) {
149 case 'a':
150 if (!lowerchar(c))
151 goto jleave;
152 break;
153 case 'A':
154 if (!upperchar(c))
155 goto jleave;
156 break;
157 case ' ':
158 if (c != ' ')
159 goto jleave;
160 break;
161 case '0':
162 if (!digitchar(c))
163 goto jleave;
164 break;
165 case 'O':
166 if (c != ' ' && !digitchar(c))
167 goto jleave;
168 break;
169 case ':':
170 if (c != ':')
171 goto jleave;
172 break;
173 case '+':
174 if (c != '+' && c != '-')
175 goto jleave;
176 break;
179 ret = 1;
180 jleave:
181 NYD_LEAVE;
182 return ret;
185 static int
186 _is_date(char const *date)
188 struct cmatch_data const *cmdp;
189 size_t dl;
190 int rv = 0;
191 NYD_ENTER;
193 if ((dl = strlen(date)) >= _DATE_MINLEN)
194 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
195 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
196 break;
197 NYD_LEAVE;
198 return rv;
201 #ifdef HAVE_IDNA
202 static struct addrguts *
203 _idna_apply(struct addrguts *agp)
205 char *idna_utf8, *idna_ascii, *cs;
206 size_t sz, i;
207 NYD_ENTER;
209 sz = agp->ag_slen - agp->ag_sdom_start;
210 assert(sz > 0);
211 idna_utf8 = ac_alloc(sz + 1);
212 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
213 idna_utf8[sz] = '\0';
215 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
216 * let it perform the charset conversion, if any should be necessary */
217 if (!utf8) {
218 char const *tcs = charset_get_lc();
219 idna_ascii = idna_utf8;
220 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
221 i = (idna_utf8 == NULL && errno == EINVAL);
222 ac_free(idna_ascii);
224 if (idna_utf8 == NULL) {
225 if (i)
226 fprintf(stderr, tr(179, "Cannot convert from %s to %s\n"),
227 tcs, "UTF-8");
228 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
229 goto jleave;
233 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
234 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
235 goto jleave1;
238 /* Replace the domain part of .ag_skinned with IDNA version */
239 sz = strlen(idna_ascii);
240 i = agp->ag_sdom_start;
241 cs = salloc(agp->ag_slen - i + sz +1);
242 memcpy(cs, agp->ag_skinned, i);
243 memcpy(cs + i, idna_ascii, sz);
244 i += sz;
245 cs[i] = '\0';
247 agp->ag_skinned = cs;
248 agp->ag_slen = i;
249 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
250 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
252 (free)(idna_ascii);
253 jleave1:
254 if (utf8)
255 ac_free(idna_utf8);
256 else
257 (free)(idna_utf8);
258 jleave:
259 NYD_LEAVE;
260 return agp;
262 #endif
264 static int
265 _addrspec_check(int skinned, struct addrguts *agp)
267 char *addr, *p;
268 bool_t in_quote;
269 ui8_t in_domain, hadat;
270 union {char c; unsigned char u;} c;
271 #ifdef HAVE_IDNA
272 ui8_t use_idna;
273 #endif
274 NYD_ENTER;
276 #ifdef HAVE_IDNA
277 use_idna = ok_blook(idna_disable) ? 0 : 1;
278 #endif
279 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
280 addr = agp->ag_skinned;
282 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
283 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
284 goto jleave;
287 /* If the field is not a recipient, it cannot be a file or a pipe */
288 if (!skinned)
289 goto jaddr_check;
291 /* Excerpt from nail.1:
292 * Recipient address specifications
293 * The rules are: Any name which starts with a `|' character specifies
294 * a pipe, the command string following the `|' is executed and
295 * the message is sent to its standard input; any other name which
296 * contains a `@' character is treated as a mail address; any other
297 * name which starts with a `+' character specifies a folder name; any
298 * other name which contains a `/' character but no `!' or `%'
299 * character before also specifies a folder name; what remains is
300 * treated as a mail address */
301 if (*addr == '|') {
302 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
303 goto jleave;
305 if (memchr(addr, '@', agp->ag_slen) == NULL) {
306 if (*addr == '+')
307 goto jisfile;
308 for (p = addr; (c.c = *p); ++p) {
309 if (c.c == '!' || c.c == '%')
310 break;
311 if (c.c == '/') {
312 jisfile:
313 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
314 goto jleave;
319 jaddr_check:
320 in_quote = FAL0;
321 in_domain = hadat = 0;
323 for (p = addr; (c.c = *p++) != '\0';) {
324 if (c.c == '"') {
325 in_quote = !in_quote;
326 } else if (c.u < 040 || c.u >= 0177) {
327 #ifdef HAVE_IDNA
328 if (in_domain && use_idna > 0) {
329 if (use_idna == 1)
330 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
331 c.u);
332 use_idna = 2;
333 } else
334 #endif
335 break;
336 } else if (in_domain == 2) {
337 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
338 break;
339 } else if (in_quote && in_domain == 0) {
340 /*EMPTY*/;
341 } else if (c.c == '\\' && *p != '\0') {
342 ++p;
343 } else if (c.c == '@') {
344 if (hadat++ > 0) {
345 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
346 c.u);
347 goto jleave;
349 agp->ag_sdom_start = PTR2SIZE(p - addr);
350 in_domain = (*p == '[') ? 2 : 1;
351 continue;
352 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
353 c.c == ',' || c.c == ';' || c.c == ':' || c.c == '\\' ||
354 c.c == '[' || c.c == ']')
355 break;
356 hadat = 0;
359 if (c.c != '\0') {
360 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
361 goto jleave;
363 #ifdef HAVE_IDNA
364 if (use_idna == 2)
365 agp = _idna_apply(agp);
366 #endif
367 jleave:
368 NYD_LEAVE;
369 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
372 static int
373 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
375 char *line2 = NULL, *cp, *cp2;
376 size_t line2size = 0;
377 int c, isenc;
378 NYD_ENTER;
380 if (*linebuf == NULL)
381 *linebuf = srealloc(*linebuf, *linesize = 1);
382 **linebuf = '\0';
383 for (;;) {
384 if (--rem < 0) {
385 rem = -1;
386 break;
388 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
389 rem = -1;
390 break;
392 for (cp = *linebuf; fieldnamechar(*cp & 0377); ++cp)
394 if (cp > *linebuf)
395 while (blankchar(*cp))
396 ++cp;
397 if (*cp != ':' || cp == *linebuf)
398 continue;
400 /* I guess we got a headline. Handle wraparound */
401 *colon = cp;
402 cp = *linebuf + c;
403 for (;;) {
404 isenc = 0;
405 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
407 cp++;
408 if (rem <= 0)
409 break;
410 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
411 isenc |= 1;
412 ungetc(c = getc(f), f);
413 if (!blankchar(c))
414 break;
415 c = readline_restart(f, &line2, &line2size, 0);
416 if (c < 0)
417 break;
418 --rem;
419 for (cp2 = line2; blankchar(*cp2); ++cp2)
421 c -= (int)PTR2SIZE(cp2 - line2);
422 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
423 isenc |= 2;
424 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
425 size_t diff = PTR2SIZE(cp - *linebuf),
426 colondiff = PTR2SIZE(*colon - *linebuf);
427 *linebuf = srealloc(*linebuf, *linesize += c + 2);
428 cp = &(*linebuf)[diff];
429 *colon = &(*linebuf)[colondiff];
431 if (isenc != 3)
432 *cp++ = ' ';
433 memcpy(cp, cp2, c);
434 cp += c;
436 *cp = '\0';
438 if (line2 != NULL)
439 free(line2);
440 break;
442 NYD_LEAVE;
443 return rem;
446 static int
447 msgidnextc(char const **cp, int *status)
449 int c;
450 NYD_ENTER;
452 for (;;) {
453 if (*status & 01) {
454 if (**cp == '"') {
455 *status &= ~01;
456 (*cp)++;
457 continue;
459 if (**cp == '\\') {
460 (*cp)++;
461 if (**cp == '\0')
462 goto jeof;
464 goto jdfl;
466 switch (**cp) {
467 case '(':
468 *cp = skip_comment(&(*cp)[1]);
469 continue;
470 case '>':
471 case '\0':
472 jeof:
473 c = '\0';
474 goto jleave;
475 case '"':
476 (*cp)++;
477 *status |= 01;
478 continue;
479 case '@':
480 *status |= 02;
481 /*FALLTHRU*/
482 default:
483 jdfl:
484 c = *(*cp)++ & 0377;
485 c = (*status & 02) ? lowerconv(c) : c;
486 goto jleave;
489 jleave:
490 NYD_LEAVE;
491 return c;
494 static int
495 charcount(char *str, int c)
497 char *cp;
498 int i;
499 NYD_ENTER;
501 for (i = 0, cp = str; *cp; ++cp)
502 if (*cp == c)
503 ++i;
504 NYD_LEAVE;
505 return i;
508 static char const *
509 nexttoken(char const *cp)
511 NYD_ENTER;
512 for (;;) {
513 if (*cp == '\0') {
514 cp = NULL;
515 break;
518 if (*cp == '(') {
519 size_t nesting = 1;
521 do switch (*++cp) {
522 case '(':
523 ++nesting;
524 break;
525 case ')':
526 --nesting;
527 break;
528 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
529 } else if (blankchar(*cp) || *cp == ',')
530 ++cp;
531 else
532 break;
534 NYD_LEAVE;
535 return cp;
538 FL char const *
539 myaddrs(struct header *hp)
541 struct name *np;
542 char *rv;
543 NYD_ENTER;
545 if (hp != NULL && (np = hp->h_from) != NULL) {
546 if ((rv = np->n_fullname) != NULL)
547 goto jleave;
548 if ((rv = np->n_name) != NULL)
549 goto jleave;
552 if ((rv = ok_vlook(from)) != NULL)
553 goto jleave;
555 /* When invoking *sendmail* directly, it's its task to generate an otherwise
556 * undeterminable From: address. However, if the user sets *hostname*,
557 * accept his desire */
558 if (ok_vlook(smtp) != NULL || ok_vlook(hostname) != NULL) {
559 char *hn = nodename(1);
560 size_t sz = strlen(myname) + strlen(hn) + 2;
561 rv = salloc(sz);
562 snprintf(rv, sz, "%s@%s", myname, hn);
564 jleave:
565 NYD_LEAVE;
566 return rv;
569 FL char const *
570 myorigin(struct header *hp)
572 char const *rv = NULL, *ccp;
573 struct name *np;
574 NYD_ENTER;
576 if ((ccp = myaddrs(hp)) != NULL &&
577 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
578 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
579 NYD_LEAVE;
580 return rv;
583 FL int
584 is_head(char const *linebuf, size_t linelen) /* XXX verbose WARN */
586 char date[FROM_DATEBUF];
587 int rv;
588 NYD_ENTER;
590 rv = ((linelen <= 5 || strncmp(linebuf, "From ", 5) != 0 ||
591 !extract_date_from_from_(linebuf, linelen, date) ||
592 !_is_date(date)) ? 0 : 1);
593 NYD_LEAVE;
594 return rv;
597 FL int
598 extract_date_from_from_(char const *line, size_t linelen,
599 char datebuf[FROM_DATEBUF])
601 int rv = 0;
602 char const *cp = line;
603 NYD_ENTER;
605 /* "From " */
606 cp = _from__skipword(cp);
607 if (cp == NULL)
608 goto jerr;
609 /* "addr-spec " */
610 cp = _from__skipword(cp);
611 if (cp == NULL)
612 goto jerr;
613 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
614 cp = _from__skipword(cp);
615 if (cp == NULL)
616 goto jerr;
619 linelen -= PTR2SIZE(cp - line);
620 if (linelen < _DATE_MINLEN)
621 goto jerr;
622 if (cp[linelen - 1] == '\n') {
623 --linelen;
624 /* (Rather IMAP/POP3 only) */
625 if (cp[linelen - 1] == '\r')
626 --linelen;
627 if (linelen < _DATE_MINLEN)
628 goto jerr;
630 if (linelen >= FROM_DATEBUF)
631 goto jerr;
633 rv = 1;
634 jleave:
635 memcpy(datebuf, cp, linelen);
636 datebuf[linelen] = '\0';
637 NYD_LEAVE;
638 return rv;
639 jerr:
640 cp = tr(213, "<Unknown date>");
641 linelen = strlen(cp);
642 if (linelen >= FROM_DATEBUF)
643 linelen = FROM_DATEBUF;
644 goto jleave;
647 FL void
648 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
650 struct header nh, *hq = &nh;
651 char *linebuf = NULL, *colon;
652 size_t linesize = 0, seenfields = 0;
653 int lc, c;
654 char const *val, *cp;
655 NYD_ENTER;
657 memset(hq, 0, sizeof *hq);
658 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
661 /* TODO yippieia, cat(check(lextract)) :-) */
662 rewind(fp);
663 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
664 if ((val = thisfield(linebuf, "to")) != NULL) {
665 ++seenfields;
666 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL)));
667 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
668 ++seenfields;
669 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL)));
670 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
671 ++seenfields;
672 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL)));
673 } else if ((val = thisfield(linebuf, "from")) != NULL) {
674 ++seenfields;
675 hq->h_from = cat(hq->h_from,
676 checkaddrs(lextract(val, GEXTRA | GFULL)));
677 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
678 ++seenfields;
679 hq->h_replyto = cat(hq->h_replyto,
680 checkaddrs(lextract(val, GEXTRA | GFULL)));
681 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
682 ++seenfields;
683 hq->h_sender = cat(hq->h_sender,
684 checkaddrs(lextract(val, GEXTRA | GFULL)));
685 } else if ((val = thisfield(linebuf, "organization")) != NULL) {
686 ++seenfields;
687 for (cp = val; blankchar(*cp); ++cp)
689 hq->h_organization = (hq->h_organization != NULL)
690 ? save2str(hq->h_organization, cp) : savestr(cp);
691 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
692 (val = thisfield(linebuf, "subj")) != NULL) {
693 ++seenfields;
694 for (cp = val; blankchar(*cp); ++cp)
696 hq->h_subject = (hq->h_subject != NULL)
697 ? save2str(hq->h_subject, cp) : savestr(cp);
698 } else
699 fprintf(stderr, tr(266, "Ignoring header field \"%s\"\n"), linebuf);
702 /* In case the blank line after the header has been edited out. Otherwise,
703 * fetch the header separator */
704 if (linebuf != NULL) {
705 if (linebuf[0] != '\0') {
706 for (cp = linebuf; *(++cp) != '\0';)
708 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
709 } else {
710 if ((c = getc(fp)) != '\n' && c != EOF)
711 ungetc(c, fp);
715 if (seenfields > 0) {
716 hp->h_to = hq->h_to;
717 hp->h_cc = hq->h_cc;
718 hp->h_bcc = hq->h_bcc;
719 hp->h_from = hq->h_from;
720 hp->h_replyto = hq->h_replyto;
721 hp->h_sender = hq->h_sender;
722 hp->h_organization = hq->h_organization;
723 hp->h_subject = hq->h_subject;
724 } else
725 fprintf(stderr, tr(267, "Restoring deleted header lines\n"));
727 if (linebuf != NULL)
728 free(linebuf);
729 NYD_LEAVE;
732 FL char *
733 hfield_mult(char const *field, struct message *mp, int mult)
735 FILE *ibuf;
736 int lc;
737 size_t linesize = 0;
738 char *linebuf = NULL, *colon, *oldhfield = NULL;
739 char const *hfield;
740 NYD_ENTER;
742 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
743 goto jleave;
744 if ((lc = mp->m_lines - 1) < 0)
745 goto jleave;
747 if ((mp->m_flag & MNOFROM) == 0 &&
748 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
749 goto jleave;
750 while (lc > 0) {
751 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
752 break;
753 if ((hfield = thisfield(linebuf, field)) != NULL) {
754 oldhfield = save2str(hfield, oldhfield);
755 if (mult == 0)
756 break;
760 jleave:
761 if (linebuf != NULL)
762 free(linebuf);
763 NYD_LEAVE;
764 return oldhfield;
767 FL char const *
768 thisfield(char const *linebuf, char const *field)
770 char const *rv = NULL;
771 NYD_ENTER;
773 while (lowerconv(*linebuf) == lowerconv(*field)) {
774 ++linebuf;
775 ++field;
777 if (*field != '\0')
778 goto jleave;
780 while (blankchar(*linebuf))
781 ++linebuf;
782 if (*linebuf++ != ':')
783 goto jleave;
785 while (blankchar(*linebuf))
786 ++linebuf;
787 rv = linebuf;
788 jleave:
789 NYD_LEAVE;
790 return rv;
793 FL char *
794 nameof(struct message *mp, int reptype)
796 char *cp, *cp2;
797 NYD_ENTER;
799 cp = skin(name1(mp, reptype));
800 if (reptype != 0 || charcount(cp, '!') < 2)
801 goto jleave;
802 cp2 = strrchr(cp, '!');
803 --cp2;
804 while (cp2 > cp && *cp2 != '!')
805 --cp2;
806 if (*cp2 == '!')
807 cp = cp2 + 1;
808 jleave:
809 NYD_LEAVE;
810 return cp;
813 FL char const *
814 skip_comment(char const *cp)
816 size_t nesting;
817 NYD_ENTER;
819 for (nesting = 1; nesting > 0 && *cp; ++cp) {
820 switch (*cp) {
821 case '\\':
822 if (cp[1])
823 ++cp;
824 break;
825 case '(':
826 ++nesting;
827 break;
828 case ')':
829 --nesting;
830 break;
833 NYD_LEAVE;
834 return cp;
837 FL char const *
838 routeaddr(char const *name)
840 char const *np, *rp = NULL;
841 NYD_ENTER;
843 for (np = name; *np; np++) {
844 switch (*np) {
845 case '(':
846 np = skip_comment(np + 1) - 1;
847 break;
848 case '"':
849 while (*np) {
850 if (*++np == '"')
851 break;
852 if (*np == '\\' && np[1])
853 np++;
855 break;
856 case '<':
857 rp = np;
858 break;
859 case '>':
860 goto jleave;
863 rp = NULL;
864 jleave:
865 NYD_LEAVE;
866 return rp;
869 FL int
870 is_addr_invalid(struct name *np, int putmsg)
872 char cbuf[sizeof "'\\U12340'"], *name = np->n_name;
873 int f = np->n_flags, ok8bit = 1;
874 ui_it c;
875 char const *fmt = "'\\x%02X'", *cs;
876 NYD_ENTER;
878 if ((f & NAME_ADDRSPEC_INVALID) == 0 || !putmsg ||
879 (f & NAME_ADDRSPEC_ERR_EMPTY) != 0)
880 goto jleave;
882 if (f & NAME_ADDRSPEC_ERR_IDNA)
883 cs = tr(284, "Invalid domain name: \"%s\", character %s\n"),
884 fmt = "'\\U%04X'",
885 ok8bit = 0;
886 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
887 cs = tr(142, "\"%s\" contains invalid %s sequence\n");
888 else
889 cs = tr(143, "\"%s\" contains invalid character %s\n");
891 c = NAME_ADDRSPEC_ERR_GETWC(f);
892 if (ok8bit && c >= 040 && c <= 0177)
893 snprintf(cbuf, sizeof cbuf, "'%c'", c);
894 else
895 snprintf(cbuf, sizeof cbuf, fmt, c);
897 fprintf(stderr, cs, name, cbuf);
898 jleave:
899 NYD_LEAVE;
900 return ((f & NAME_ADDRSPEC_INVALID) != 0);
903 FL char *
904 skin(char const *name)
906 struct addrguts ag;
907 char *ret = NULL;
908 NYD_ENTER;
910 if (name != NULL) {
911 addrspec_with_guts(1, name, &ag);
912 ret = ag.ag_skinned;
913 if ((ag.ag_n_flags & NAME_NAME_SALLOC) == 0)
914 ret = savestrbuf(ret, ag.ag_slen);
916 NYD_LEAVE;
917 return ret;
920 /* TODO addrspec_with_guts: RFC 5322 */
921 FL int
922 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
924 char const *cp;
925 char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp;
926 int rv = 1;
927 NYD_ENTER;
929 memset(agp, 0, sizeof *agp);
931 if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) {
932 agp->ag_skinned = UNCONST(""); /* ok: NAME_SALLOC is not set */
933 agp->ag_slen = 0;
934 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
935 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
936 goto jleave;
939 if (!doskin || !anyof(name, "(< ")) {
940 /*agp->ag_iaddr_start = 0;*/
941 agp->ag_iaddr_aend = agp->ag_ilen;
942 agp->ag_skinned = UNCONST(name); /* (NAME_SALLOC not set) */
943 agp->ag_slen = agp->ag_ilen;
944 agp->ag_n_flags = NAME_SKINNED;
945 goto jcheck;
948 /* Something makes us think we have to perform the skin operation */
949 nbuf = ac_alloc(agp->ag_ilen + 1);
950 /*agp->ag_iaddr_start = 0;*/
951 cp2 = bufend = nbuf;
952 gotlt = gotaddr = lastsp = 0;
954 for (cp = name++; (c = *cp++) != '\0'; ) {
955 switch (c) {
956 case '(':
957 cp = skip_comment(cp);
958 lastsp = 0;
959 break;
960 case '"':
961 /* Start of a "quoted-string".
962 * Copy it in its entirety */
963 /* XXX RFC: quotes are "semantically invisible"
964 * XXX But it was explicitly added (Changelog.Heirloom,
965 * XXX [9.23] released 11/15/00, "Do not remove quotes
966 * XXX when skinning names"? No more info.. */
967 *cp2++ = c;
968 while ((c = *cp) != '\0') { /* TODO improve */
969 cp++;
970 if (c == '"') {
971 *cp2++ = c;
972 break;
974 if (c != '\\')
975 *cp2++ = c;
976 else if ((c = *cp) != '\0') {
977 *cp2++ = c;
978 cp++;
981 lastsp = 0;
982 break;
983 case ' ':
984 case '\t':
985 if (gotaddr == 1) {
986 gotaddr = 2;
987 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
989 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
990 cp += 3, *cp2++ = '@';
991 else if (cp[0] == '@' && blankchar(cp[1]))
992 cp += 2, *cp2++ = '@';
993 else
994 lastsp = 1;
995 break;
996 case '<':
997 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
998 cp2 = bufend;
999 gotlt = gotaddr = 1;
1000 lastsp = 0;
1001 break;
1002 case '>':
1003 if (gotlt) {
1004 /* (_addrspec_check() verifies these later!) */
1005 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1006 gotlt = 0;
1007 while ((c = *cp) != '\0' && c != ',') {
1008 cp++;
1009 if (c == '(')
1010 cp = skip_comment(cp);
1011 else if (c == '"')
1012 while ((c = *cp) != '\0') {
1013 cp++;
1014 if (c == '"')
1015 break;
1016 if (c == '\\' && *cp != '\0')
1017 ++cp;
1020 lastsp = 0;
1021 break;
1023 /* FALLTRHOUGH */
1024 default:
1025 if (lastsp) {
1026 lastsp = 0;
1027 if (gotaddr)
1028 *cp2++ = ' ';
1030 *cp2++ = c;
1031 if (c == ',') {
1032 if (!gotlt) {
1033 *cp2++ = ' ';
1034 for (; blankchar(*cp); ++cp)
1036 lastsp = 0;
1037 bufend = cp2;
1039 } else if (!gotaddr) {
1040 gotaddr = 1;
1041 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1045 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1046 if (agp->ag_iaddr_aend == 0)
1047 agp->ag_iaddr_aend = agp->ag_ilen;
1049 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1050 ac_free(nbuf);
1051 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1052 jcheck:
1053 rv = _addrspec_check(doskin, agp);
1054 jleave:
1055 NYD_LEAVE;
1056 return rv;
1059 FL char *
1060 realname(char const *name)
1062 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1063 char *rname, *rp;
1064 struct str in, out;
1065 int quoted, good, nogood;
1066 NYD_ENTER;
1068 if ((cp = UNCONST(name)) == NULL)
1069 goto jleave;
1070 for (; *cp != '\0'; ++cp) {
1071 switch (*cp) {
1072 case '(':
1073 if (cstart != NULL) {
1074 /* More than one comment in address, doesn't make sense to display
1075 * it without context. Return the entire field */
1076 cp = mime_fromaddr(name);
1077 goto jleave;
1079 cstart = cp++;
1080 cp = skip_comment(cp);
1081 cend = cp--;
1082 if (cend <= cstart)
1083 cend = cstart = NULL;
1084 break;
1085 case '"':
1086 while (*cp) {
1087 if (*++cp == '"')
1088 break;
1089 if (*cp == '\\' && cp[1])
1090 ++cp;
1092 break;
1093 case '<':
1094 if (cp > name) {
1095 cstart = name;
1096 cend = cp;
1098 break;
1099 case ',':
1100 /* More than one address. Just use the first one */
1101 goto jbrk;
1105 jbrk:
1106 if (cstart == NULL) {
1107 if (*name == '<') {
1108 /* If name contains only a route-addr, the surrounding angle brackets
1109 * don't serve any useful purpose when displaying, so remove */
1110 cp = prstr(skin(name));
1111 } else
1112 cp = mime_fromaddr(name);
1113 goto jleave;
1116 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1117 * not stripped. The idea is to strip only syntactical relevant things (but
1118 * this is not necessarily the most sensible way in practice) */
1119 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1120 quoted = 0;
1121 for (cp = cstart; cp < cend; ++cp) {
1122 if (*cp == '(' && !quoted) {
1123 cq = skip_comment(++cp);
1124 if (PTRCMP(--cq, >, cend))
1125 cq = cend;
1126 while (cp < cq) {
1127 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1128 ++cp;
1129 *rp++ = *cp++;
1131 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1132 *rp++ = *++cp;
1133 else if (*cp == '"') {
1134 quoted = !quoted;
1135 continue;
1136 } else
1137 *rp++ = *cp;
1139 *rp = '\0';
1140 in.s = rname;
1141 in.l = rp - rname;
1142 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1143 ac_free(rname);
1144 rname = savestr(out.s);
1145 free(out.s);
1147 while (blankchar(*rname))
1148 ++rname;
1149 for (rp = rname; *rp != '\0'; ++rp)
1151 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1152 *rp = '\0';
1153 if (rp == rname) {
1154 cp = mime_fromaddr(name);
1155 goto jleave;
1158 /* mime_fromhdr() has converted all nonprintable characters to question
1159 * marks now. These and blanks are considered uninteresting; if the
1160 * displayed part of the real name contains more than 25% of them, it is
1161 * probably better to display the plain email address instead */
1162 good = 0;
1163 nogood = 0;
1164 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1165 if (*rp == '?' || blankchar(*rp))
1166 ++nogood;
1167 else
1168 ++good;
1169 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1170 jleave:
1171 NYD_LEAVE;
1172 return UNCONST(cp);
1175 FL char *
1176 name1(struct message *mp, int reptype)
1178 char *namebuf, *cp, *cp2, *linebuf = NULL;
1179 size_t namesize, linesize = 0;
1180 FILE *ibuf;
1181 int f1st = 1;
1182 NYD_ENTER;
1184 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1185 goto jleave;
1186 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1187 goto jleave;
1189 namebuf = smalloc(namesize = 1);
1190 namebuf[0] = 0;
1191 if (mp->m_flag & MNOFROM)
1192 goto jout;
1193 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1194 goto jout;
1195 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1196 goto jout;
1198 jnewname:
1199 if (namesize <= linesize)
1200 namebuf = srealloc(namebuf, namesize = linesize + 1);
1201 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1203 for (; blankchar(*cp); ++cp)
1205 for (cp2 = namebuf + strlen(namebuf);
1206 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize - 1);)
1207 *cp2++ = *cp++;
1208 *cp2 = '\0';
1210 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1211 goto jout;
1212 if ((cp = strchr(linebuf, 'F')) == NULL)
1213 goto jout;
1214 if (strncmp(cp, "From", 4) != 0)
1215 goto jout;
1216 if (namesize <= linesize)
1217 namebuf = srealloc(namebuf, namesize = linesize + 1);
1219 while ((cp = strchr(cp, 'r')) != NULL) {
1220 if (strncmp(cp, "remote", 6) == 0) {
1221 if ((cp = strchr(cp, 'f')) == NULL)
1222 break;
1223 if (strncmp(cp, "from", 4) != 0)
1224 break;
1225 if ((cp = strchr(cp, ' ')) == NULL)
1226 break;
1227 cp++;
1228 if (f1st) {
1229 strncpy(namebuf, cp, namesize);
1230 f1st = 0;
1231 } else {
1232 cp2 = strrchr(namebuf, '!') + 1;
1233 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1235 namebuf[namesize - 2] = '!';
1236 namebuf[namesize - 1] = '\0';
1237 goto jnewname;
1239 cp++;
1241 jout:
1242 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1243 *cp == '\0')
1244 cp = savestr(namebuf);
1246 if (linebuf != NULL)
1247 free(linebuf);
1248 free(namebuf);
1249 jleave:
1250 NYD_LEAVE;
1251 return cp;
1254 FL int
1255 msgidcmp(char const *s1, char const *s2)
1257 int q1 = 0, q2 = 0, c1, c2;
1258 NYD_ENTER;
1260 do {
1261 c1 = msgidnextc(&s1, &q1);
1262 c2 = msgidnextc(&s2, &q2);
1263 if (c1 != c2)
1264 break;
1265 } while (c1 && c2);
1266 NYD_LEAVE;
1267 return c1 - c2;
1270 FL int
1271 is_ign(char const *field, size_t fieldlen, struct ignoretab ignoret[2])
1273 char *realfld;
1274 int rv;
1275 NYD_ENTER;
1277 rv = 0;
1278 if (ignoret == NULL)
1279 goto jleave;
1280 rv = 1;
1281 if (ignoret == allignore)
1282 goto jleave;
1284 /* Lowercase it so that "Status" and "status" will hash to the same place */
1285 realfld = ac_alloc(fieldlen + 1);
1286 i_strcpy(realfld, field, fieldlen + 1);
1287 if (ignoret[1].i_count > 0)
1288 rv = !member(realfld, ignoret + 1);
1289 else
1290 rv = member(realfld, ignoret);
1291 ac_free(realfld);
1292 jleave:
1293 NYD_LEAVE;
1294 return rv;
1297 FL int
1298 member(char const *realfield, struct ignoretab *table)
1300 struct ignore *igp;
1301 int rv = 0;
1302 NYD_ENTER;
1304 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1305 if (*igp->i_field == *realfield && !strcmp(igp->i_field, realfield)) {
1306 rv = 1;
1307 break;
1309 NYD_LEAVE;
1310 return rv;
1313 FL char const *
1314 fakefrom(struct message *mp)
1316 char const *name;
1317 NYD_ENTER;
1319 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1320 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1321 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1322 * RFC 4155 however requires a RFC 5322 (2822) conforming
1323 * "addr-spec", but we simply can't provide that */
1324 name = "MAILER-DAEMON";
1325 NYD_LEAVE;
1326 return name;
1329 FL char const *
1330 fakedate(time_t t)
1332 char *cp, *cq;
1333 NYD_ENTER;
1335 cp = ctime(&t);
1336 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1338 *cq = '\0';
1339 cp = savestr(cp);
1340 NYD_LEAVE;
1341 return cp;
1344 FL time_t
1345 unixtime(char const *fromline)
1347 char const *fp;
1348 char *xp;
1349 time_t t;
1350 int i, year, month, day, hour, minute, second, tzdiff;
1351 struct tm *tmptr;
1352 NYD_ENTER;
1354 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1356 fp -= 24;
1357 if (PTR2SIZE(fp - fromline) < 7)
1358 goto jinvalid;
1359 if (fp[3] != ' ')
1360 goto jinvalid;
1361 for (i = 0;;) {
1362 if (strncmp(fp + 4, month_names[i], 3) == 0)
1363 break;
1364 if (month_names[++i][0] == '\0')
1365 goto jinvalid;
1367 month = i + 1;
1368 if (fp[7] != ' ')
1369 goto jinvalid;
1370 day = strtol(fp + 8, &xp, 10);
1371 if (*xp != ' ' || xp != fp + 10)
1372 goto jinvalid;
1373 hour = strtol(fp + 11, &xp, 10);
1374 if (*xp != ':' || xp != fp + 13)
1375 goto jinvalid;
1376 minute = strtol(fp + 14, &xp, 10);
1377 if (*xp != ':' || xp != fp + 16)
1378 goto jinvalid;
1379 second = strtol(fp + 17, &xp, 10);
1380 if (*xp != ' ' || xp != fp + 19)
1381 goto jinvalid;
1382 year = strtol(fp + 20, &xp, 10);
1383 if (xp != fp + 24)
1384 goto jinvalid;
1385 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1386 goto jinvalid;
1387 tzdiff = t - mktime(gmtime(&t));
1388 tmptr = localtime(&t);
1389 if (tmptr->tm_isdst > 0)
1390 tzdiff += 3600;
1391 t -= tzdiff;
1392 jleave:
1393 NYD_LEAVE;
1394 return t;
1395 jinvalid:
1396 time(&t);
1397 goto jleave;
1400 FL time_t
1401 rfctime(char const *date)
1403 char const *cp = date;
1404 char *x;
1405 time_t t;
1406 int i, year, month, day, hour, minute, second;
1407 NYD_ENTER;
1409 if ((cp = nexttoken(cp)) == NULL)
1410 goto jinvalid;
1411 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1412 cp[3] == ',') {
1413 if ((cp = nexttoken(&cp[4])) == NULL)
1414 goto jinvalid;
1416 day = strtol(cp, &x, 10); /* XXX strtol */
1417 if ((cp = nexttoken(x)) == NULL)
1418 goto jinvalid;
1419 for (i = 0;;) {
1420 if (strncmp(cp, month_names[i], 3) == 0)
1421 break;
1422 if (month_names[++i][0] == '\0')
1423 goto jinvalid;
1425 month = i + 1;
1426 if ((cp = nexttoken(&cp[3])) == NULL)
1427 goto jinvalid;
1428 /* RFC 5322, 4.3:
1429 * Where a two or three digit year occurs in a date, the year is to be
1430 * interpreted as follows: If a two digit year is encountered whose
1431 * value is between 00 and 49, the year is interpreted by adding 2000,
1432 * ending up with a value between 2000 and 2049. If a two digit year
1433 * is encountered with a value between 50 and 99, or any three digit
1434 * year is encountered, the year is interpreted by adding 1900 */
1435 year = strtol(cp, &x, 10); /* XXX strtol */
1436 i = (int)PTR2SIZE(x - cp);
1437 if (i == 2 && year >= 0 && year <= 49)
1438 year += 2000;
1439 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1440 year += 1900;
1441 if ((cp = nexttoken(x)) == NULL)
1442 goto jinvalid;
1443 hour = strtol(cp, &x, 10); /* XXX strtol */
1444 if (*x != ':')
1445 goto jinvalid;
1446 cp = &x[1];
1447 minute = strtol(cp, &x, 10);
1448 if (*x == ':') {
1449 cp = x + 1;
1450 second = strtol(cp, &x, 10);
1451 } else
1452 second = 0;
1453 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1454 goto jinvalid;
1455 if ((cp = nexttoken(x)) != NULL) {
1456 int sign = -1;
1457 char buf[3];
1459 switch (*cp) {
1460 case '-':
1461 sign = 1;
1462 /*FALLTHRU*/
1463 case '+':
1464 ++cp;
1465 break;
1467 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1468 digitchar(cp[3])) {
1469 buf[2] = '\0';
1470 buf[0] = cp[0];
1471 buf[1] = cp[1];
1472 t += strtol(buf, NULL, 10) * sign * 3600;/*XXX strtrol*/
1473 buf[0] = cp[2];
1474 buf[1] = cp[3];
1475 t += strtol(buf, NULL, 10) * sign * 60; /* XXX strtol*/
1477 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1478 * TODO once again, Christos Zoulas and NetBSD Mail have done
1479 * TODO a really good job already, but using strptime(3), which
1480 * TODO is not portable. Nonetheless, WE must improve, not
1481 * TODO at last because we simply ignore obsolete timezones!!
1482 * TODO See RFC 5322, 4.3! */
1484 jleave:
1485 NYD_LEAVE;
1486 return t;
1487 jinvalid:
1488 t = 0;
1489 goto jleave;
1492 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1494 FL time_t
1495 combinetime(int year, int month, int day, int hour, int minute, int second)
1497 time_t t;
1498 NYD_ENTER;
1500 if (second < 0 || minute < 0 || hour < 0 || day < 1) {
1501 t = (time_t)-1;
1502 goto jleave;
1505 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1506 if (month > 1)
1507 t += 86400 * 31;
1508 if (month > 2)
1509 t += 86400 * (is_leapyear(year) ? 29 : 28);
1510 if (month > 3)
1511 t += 86400 * 31;
1512 if (month > 4)
1513 t += 86400 * 30;
1514 if (month > 5)
1515 t += 86400 * 31;
1516 if (month > 6)
1517 t += 86400 * 30;
1518 if (month > 7)
1519 t += 86400 * 31;
1520 if (month > 8)
1521 t += 86400 * 31;
1522 if (month > 9)
1523 t += 86400 * 30;
1524 if (month > 10)
1525 t += 86400 * 31;
1526 if (month > 11)
1527 t += 86400 * 30;
1528 year -= 1900;
1529 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1530 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1531 jleave:
1532 NYD_LEAVE;
1533 return t;
1536 FL void
1537 substdate(struct message *m)
1539 char const *cp;
1540 NYD_ENTER;
1542 /* Determine the date to print in faked 'From ' lines. This is traditionally
1543 * the date the message was written to the mail file. Try to determine this
1544 * using RFC message header fields, or fall back to current time */
1545 if ((cp = hfield1("received", m)) != NULL) {
1546 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1548 ++cp;
1549 while (alnumchar(*cp));
1551 if (cp && *++cp)
1552 m->m_time = rfctime(cp);
1554 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
1555 if ((cp = hfield1("date", m)) != NULL)
1556 m->m_time = rfctime(cp);
1558 if (m->m_time == 0 || m->m_time > time_current.tc_time)
1559 m->m_time = time_current.tc_time;
1560 NYD_LEAVE;
1563 FL int
1564 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1566 int rv;
1567 NYD_ENTER;
1569 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1570 fprintf(stderr, tr(529, "A Sender: field is required with multiple "
1571 "addresses in From: field.\n"));
1572 rv = 1;
1573 } else if (senderfield && senderfield->n_flink) {
1574 fprintf(stderr, tr(530,
1575 "The Sender: field may contain only one address.\n"));
1576 rv = 2;
1577 } else
1578 rv = 0;
1579 NYD_LEAVE;
1580 return rv;
1583 FL char *
1584 getsender(struct message *mp)
1586 char *cp;
1587 struct name *np;
1588 NYD_ENTER;
1590 if ((cp = hfield1("from", mp)) == NULL ||
1591 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
1592 cp = NULL;
1593 else
1594 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
1595 NYD_LEAVE;
1596 return cp;
1599 FL int
1600 grab_headers(struct header *hp, enum gfield gflags, int subjfirst)
1602 /* TODO grab_headers: again, check counts etc. against RFC;
1603 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1604 int errs;
1605 int volatile comma;
1606 NYD_ENTER;
1608 errs = 0;
1609 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
1611 if (gflags & GTO)
1612 hp->h_to = grab_names("To: ", hp->h_to, comma, GTO | GFULL);
1613 if (subjfirst && (gflags & GSUBJECT))
1614 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1615 if (gflags & GCC)
1616 hp->h_cc = grab_names("Cc: ", hp->h_cc, comma, GCC | GFULL);
1617 if (gflags & GBCC)
1618 hp->h_bcc = grab_names("Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
1620 if (gflags & GEXTRA) {
1621 if (hp->h_from == NULL)
1622 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL);
1623 hp->h_from = grab_names("From: ", hp->h_from, comma, GEXTRA | GFULL);
1624 if (hp->h_replyto == NULL)
1625 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
1626 hp->h_replyto = grab_names("Reply-To: ", hp->h_replyto, comma,
1627 GEXTRA | GFULL);
1628 if (hp->h_sender == NULL)
1629 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
1630 hp->h_sender = grab_names("Sender: ", hp->h_sender, comma,
1631 GEXTRA | GFULL);
1632 if (hp->h_organization == NULL)
1633 hp->h_organization = ok_vlook(ORGANIZATION);
1634 hp->h_organization = readstr_input("Organization: ", hp->h_organization);
1637 if (!subjfirst && (gflags & GSUBJECT))
1638 hp->h_subject = readstr_input("Subject: ", hp->h_subject);
1640 NYD_LEAVE;
1641 return errs;
1644 /* vim:set fenc=utf-8:s-it-mode */