Drop `customhdr' again (keep *customhdr*): `~^' is better!
[s-mailx.git] / head.c
bloba59338d004f901ee2f8333e804681cb2d0e4f69f
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 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE head
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 #ifdef HAVE_IDNA
43 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
44 # include <idna.h>
45 # include <idn-free.h>
46 # include <stringprep.h>
47 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT
48 # include <idn/api.h>
49 # endif
50 #endif
52 struct cmatch_data {
53 size_t tlen; /* Length of .tdata */
54 char const *tdata; /* Template date - see _cmatch_data[] */
57 /* Template characters for cmatch_data.tdata:
58 * 'A' An upper case char
59 * 'a' A lower case char
60 * ' ' A space
61 * '0' A digit
62 * 'O' An optional digit or space
63 * ':' A colon
64 * '+' Either a plus or a minus sign */
65 static struct cmatch_data const _cmatch_data[] = {
66 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
67 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
68 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
69 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
70 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
71 * in the wild (by UW-imap) */
72 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
73 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
74 * zone strings; note that 1. is strictly speaking not correct as some
75 * letters are not used, and 2. is not because only "UT" is defined */
76 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
77 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
78 { 0, NULL }
80 #define _DATE_MINLEN 21
82 /* Skip over "word" as found in From_ line */
83 static char const * _from__skipword(char const *wp);
85 /* Match the date string against the date template (tp), return if match.
86 * See _cmatch_data[] for template character description */
87 static int _cmatch(size_t len, char const *date,
88 char const *tp);
90 /* Check whether date is a valid 'From_' date.
91 * (Rather ctime(3) generated dates, according to RFC 4155) */
92 static int _is_date(char const *date);
94 /* JulianDayNumber converter(s) */
95 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
96 #if 0
97 static void a_head_jdn_to_gregorian(size_t jdn,
98 ui32_t *yp, ui32_t *mp, ui32_t *dp);
99 #endif
101 /* Convert the domain part of a skinned address to IDNA.
102 * If an error occurs before Unicode information is available, revert the IDNA
103 * error to a normal CHAR one so that the error message doesn't talk Unicode */
104 #ifdef HAVE_IDNA
105 static struct addrguts * _idna_apply(struct addrguts *agp);
106 #endif
108 /* Classify and check a (possibly skinned) header body according to RFC
109 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
110 * also a file or a pipe command, so check that first, then.
111 * Otherwise perform content checking and isolate the domain part (for IDNA) */
112 static int _addrspec_check(int doskin, struct addrguts *agp);
114 /* Return the next header field found in the given message.
115 * Return >= 0 if something found, < 0 elsewise.
116 * "colon" is set to point to the colon in the header.
117 * Must deal with \ continuations & other such fraud */
118 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
119 int rem, char **colon);
121 static int msgidnextc(char const **cp, int *status);
123 /* Count the occurances of c in str */
124 static int charcount(char *str, int c);
126 static char const * nexttoken(char const *cp);
128 /* TODO v15: change *customhdr* syntax and use shell tokens?! */
129 static char *a_head_customhdr__sep(char **iolist);
131 static char const *
132 _from__skipword(char const *wp)
134 char c = 0;
135 NYD2_ENTER;
137 if (wp != NULL) {
138 while ((c = *wp++) != '\0' && !blankchar(c)) {
139 if (c == '"') {
140 while ((c = *wp++) != '\0' && c != '"')
142 if (c != '"')
143 --wp;
146 for (; blankchar(c); c = *wp++)
149 NYD2_LEAVE;
150 return (c == 0 ? NULL : wp - 1);
153 static int
154 _cmatch(size_t len, char const *date, char const *tp)
156 int ret = 0;
157 NYD2_ENTER;
159 while (len--) {
160 char c = date[len];
161 switch (tp[len]) {
162 case 'a':
163 if (!lowerchar(c))
164 goto jleave;
165 break;
166 case 'A':
167 if (!upperchar(c))
168 goto jleave;
169 break;
170 case ' ':
171 if (c != ' ')
172 goto jleave;
173 break;
174 case '0':
175 if (!digitchar(c))
176 goto jleave;
177 break;
178 case 'O':
179 if (c != ' ' && !digitchar(c))
180 goto jleave;
181 break;
182 case ':':
183 if (c != ':')
184 goto jleave;
185 break;
186 case '+':
187 if (c != '+' && c != '-')
188 goto jleave;
189 break;
192 ret = 1;
193 jleave:
194 NYD2_LEAVE;
195 return ret;
198 static int
199 _is_date(char const *date)
201 struct cmatch_data const *cmdp;
202 size_t dl;
203 int rv = 0;
204 NYD2_ENTER;
206 if ((dl = strlen(date)) >= _DATE_MINLEN)
207 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
208 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
209 break;
210 NYD2_LEAVE;
211 return rv;
214 static size_t
215 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
216 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
217 * (via third hand, plus adjustments).
218 * This algorithm is supposed to work for all dates in between 1582-10-15
219 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
220 size_t jdn;
221 NYD2_ENTER;
223 #if 0
224 if(y == 0)
225 y = 1;
226 if(m == 0)
227 m = 1;
228 if(d == 0)
229 d = 1;
230 #endif
232 if(m > 2)
233 m -= 3;
234 else{
235 m += 9;
236 --y;
238 jdn = y;
239 jdn /= 100;
240 y -= 100 * jdn;
241 y *= 1461;
242 y >>= 2;
243 jdn *= 146097;
244 jdn >>= 2;
245 jdn += y;
246 jdn += d;
247 jdn += 1721119;
248 m *= 153;
249 m += 2;
250 m /= 5;
251 jdn += m;
252 NYD2_LEAVE;
253 return jdn;
256 #if 0
257 static void
258 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
259 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
260 * (via third hand, plus adjustments) */
261 size_t y, x;
262 NYD2_ENTER;
264 jdn -= 1721119;
265 jdn <<= 2;
266 --jdn;
267 y = jdn / 146097;
268 jdn %= 146097;
269 jdn |= 3;
270 y *= 100;
271 y += jdn / 1461;
272 jdn %= 1461;
273 jdn += 4;
274 jdn >>= 2;
275 x = jdn;
276 jdn <<= 2;
277 jdn += x;
278 jdn -= 3;
279 x = jdn / 153; /* x -> month */
280 jdn %= 153;
281 jdn += 5;
282 jdn /= 5; /* jdn -> day */
283 if(x < 10)
284 x += 3;
285 else{
286 x -= 9;
287 ++y;
290 *yp = (ui32_t)(y & 0xFFFF);
291 *mp = (ui32_t)(x & 0xFF);
292 *dp = (ui32_t)(jdn & 0xFF);
293 NYD2_LEAVE;
295 #endif /* 0 */
297 #ifdef HAVE_IDNA
298 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
299 static struct addrguts *
300 _idna_apply(struct addrguts *agp)
302 char *idna_utf8, *idna_ascii, *cs;
303 size_t sz, i;
304 NYD_ENTER;
306 sz = agp->ag_slen - agp->ag_sdom_start;
307 assert(sz > 0);
308 idna_utf8 = ac_alloc(sz +1);
309 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
310 idna_utf8[sz] = '\0';
312 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
313 * let it perform the charset conversion, if any should be necessary */
314 if (!(options & OPT_UNICODE)) {
315 char const *tcs = charset_get_lc();
316 idna_ascii = idna_utf8;
317 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
318 i = (idna_utf8 == NULL && errno == EINVAL);
319 ac_free(idna_ascii);
321 if (idna_utf8 == NULL) {
322 if (i)
323 n_err(_("Cannot convert from %s to %s\n"), tcs, "UTF-8");
324 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
325 goto jleave;
329 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
330 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
331 goto jleave1;
334 /* Replace the domain part of .ag_skinned with IDNA version */
335 sz = strlen(idna_ascii);
336 i = agp->ag_sdom_start;
337 cs = salloc(agp->ag_slen - i + sz +1);
338 memcpy(cs, agp->ag_skinned, i);
339 memcpy(cs + i, idna_ascii, sz);
340 i += sz;
341 cs[i] = '\0';
343 agp->ag_skinned = cs;
344 agp->ag_slen = i;
345 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
346 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
348 idn_free(idna_ascii);
349 jleave1:
350 if (options & OPT_UNICODE)
351 ac_free(idna_utf8);
352 else
353 idn_free(idna_utf8);
354 jleave:
355 NYD_LEAVE;
356 return agp;
359 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */
360 static struct addrguts *
361 _idna_apply(struct addrguts *agp)
363 char *idna_in, *idna_out, *cs;
364 size_t sz, i;
365 idn_result_t r;
366 NYD_ENTER;
368 sz = agp->ag_slen - agp->ag_sdom_start;
369 assert(sz > 0);
370 idna_in = ac_alloc(sz +1);
371 memcpy(idna_in, agp->ag_skinned + agp->ag_sdom_start, sz);
372 idna_in[sz] = '\0';
374 for (idna_out = NULL, sz = HOST_NAME_MAX +1;; sz += HOST_NAME_MAX) {
375 idna_out = ac_alloc(sz);
377 r = idn_encodename(IDN_ENCODE_APP, idna_in, idna_out, sz);
378 switch (r) {
379 case idn_success:
380 case idn_buffer_overflow:
381 break;
382 case idn_invalid_encoding:
383 n_err(_("Cannot convert from %s to %s\n"), charset_get_lc(), "UTF-8");
384 /* FALLTHRU */
385 default:
386 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
387 goto jleave;
390 if (r == idn_success)
391 break;
392 ac_free(idna_out);
395 /* Replace the domain part of .ag_skinned with IDNA version */
396 sz = strlen(idna_out);
397 i = agp->ag_sdom_start;
398 cs = salloc(agp->ag_slen - i + sz +1);
399 memcpy(cs, agp->ag_skinned, i);
400 memcpy(cs + i, idna_out, sz);
401 i += sz;
402 cs[i] = '\0';
404 agp->ag_skinned = cs;
405 agp->ag_slen = i;
406 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
407 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
409 jleave:
410 ac_free(idna_out);
411 ac_free(idna_in);
412 NYD_LEAVE;
413 return agp;
415 # endif /* IDNA==IDNKIT */
416 #endif /* HAVE_IDNA */
418 static int
419 _addrspec_check(int skinned, struct addrguts *agp)
421 char *addr, *p;
422 bool_t in_quote;
423 ui8_t in_domain, hadat;
424 union {char c; unsigned char u;} c;
425 #ifdef HAVE_IDNA
426 ui8_t use_idna;
427 #endif
428 NYD_ENTER;
430 #ifdef HAVE_IDNA
431 use_idna = ok_blook(idna_disable) ? 0 : 1;
432 #endif
433 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
434 addr = agp->ag_skinned;
436 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
437 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
438 goto jleave;
441 /* If the field is not a recipient, it cannot be a file or a pipe */
442 if (!skinned)
443 goto jaddr_check;
445 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
446 * grep the latter for the complete picture */
447 if (*addr == '|') {
448 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
449 goto jleave;
451 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
452 (addr[0] == '-' && addr[1] == '\0'))
453 goto jisfile;
454 if (memchr(addr, '@', agp->ag_slen) == NULL) {
455 if (*addr == '+')
456 goto jisfile;
457 for (p = addr; (c.c = *p); ++p) {
458 if (c.c == '!' || c.c == '%')
459 break;
460 if (c.c == '/') {
461 jisfile:
462 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
463 goto jleave;
468 jaddr_check:
469 in_quote = FAL0;
470 in_domain = hadat = 0;
472 for (p = addr; (c.c = *p++) != '\0';) {
473 if (c.c == '"') {
474 in_quote = !in_quote;
475 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
476 #ifdef HAVE_IDNA
477 if (in_domain && use_idna > 0) {
478 if (use_idna == 1)
479 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
480 c.u);
481 use_idna = 2;
482 } else
483 #endif
484 break;
485 } else if (in_domain == 2) {
486 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
487 break;
488 } else if (in_quote && in_domain == 0) {
489 /*EMPTY*/;
490 } else if (c.c == '\\' && *p != '\0') {
491 ++p;
492 } else if (c.c == '@') {
493 if (hadat++ > 0) {
494 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
495 c.u);
496 goto jleave;
498 agp->ag_sdom_start = PTR2SIZE(p - addr);
499 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
500 in_domain = (*p == '[') ? 2 : 1;
501 continue;
502 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
503 c.c == ',' || c.c == ';' || c.c == ':' || c.c == '\\' ||
504 c.c == '[' || c.c == ']')
505 break;
506 hadat = 0;
508 if (c.c != '\0') {
509 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
510 goto jleave;
513 if (!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
514 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
516 #ifdef HAVE_IDNA
517 if (use_idna == 2)
518 agp = _idna_apply(agp);
519 #endif
520 jleave:
521 NYD_LEAVE;
522 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
525 static int
526 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
528 char *line2 = NULL, *cp, *cp2;
529 size_t line2size = 0;
530 int c, isenc;
531 NYD2_ENTER;
533 if (*linebuf == NULL)
534 *linebuf = srealloc(*linebuf, *linesize = 1);
535 **linebuf = '\0';
536 for (;;) {
537 if (--rem < 0) {
538 rem = -1;
539 break;
541 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
542 rem = -1;
543 break;
545 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
547 if (cp > *linebuf)
548 while (blankchar(*cp))
549 ++cp;
550 if (*cp != ':' || cp == *linebuf)
551 continue;
553 /* I guess we got a headline. Handle wraparound */
554 *colon = cp;
555 cp = *linebuf + c;
556 for (;;) {
557 isenc = 0;
558 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
560 cp++;
561 if (rem <= 0)
562 break;
563 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
564 isenc |= 1;
565 ungetc(c = getc(f), f);
566 if (!blankchar(c))
567 break;
568 c = readline_restart(f, &line2, &line2size, 0);
569 if (c < 0)
570 break;
571 --rem;
572 for (cp2 = line2; blankchar(*cp2); ++cp2)
574 c -= (int)PTR2SIZE(cp2 - line2);
575 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
576 isenc |= 2;
577 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
578 size_t diff = PTR2SIZE(cp - *linebuf),
579 colondiff = PTR2SIZE(*colon - *linebuf);
580 *linebuf = srealloc(*linebuf, *linesize += c + 2);
581 cp = &(*linebuf)[diff];
582 *colon = &(*linebuf)[colondiff];
584 if (isenc != 3)
585 *cp++ = ' ';
586 memcpy(cp, cp2, c);
587 cp += c;
589 *cp = '\0';
591 if (line2 != NULL)
592 free(line2);
593 break;
595 NYD2_LEAVE;
596 return rem;
599 static int
600 msgidnextc(char const **cp, int *status)
602 int c;
603 NYD2_ENTER;
605 assert(cp != NULL);
606 assert(*cp != NULL);
607 assert(status != NULL);
609 for (;;) {
610 if (*status & 01) {
611 if (**cp == '"') {
612 *status &= ~01;
613 (*cp)++;
614 continue;
616 if (**cp == '\\') {
617 (*cp)++;
618 if (**cp == '\0')
619 goto jeof;
621 goto jdfl;
623 switch (**cp) {
624 case '(':
625 *cp = skip_comment(&(*cp)[1]);
626 continue;
627 case '>':
628 case '\0':
629 jeof:
630 c = '\0';
631 goto jleave;
632 case '"':
633 (*cp)++;
634 *status |= 01;
635 continue;
636 case '@':
637 *status |= 02;
638 /*FALLTHRU*/
639 default:
640 jdfl:
641 c = *(*cp)++ & 0377;
642 c = (*status & 02) ? lowerconv(c) : c;
643 goto jleave;
646 jleave:
647 NYD2_LEAVE;
648 return c;
651 static int
652 charcount(char *str, int c)
654 char *cp;
655 int i;
656 NYD2_ENTER;
658 for (i = 0, cp = str; *cp; ++cp)
659 if (*cp == c)
660 ++i;
661 NYD2_LEAVE;
662 return i;
665 static char const *
666 nexttoken(char const *cp)
668 NYD2_ENTER;
669 for (;;) {
670 if (*cp == '\0') {
671 cp = NULL;
672 break;
675 if (*cp == '(') {
676 size_t nesting = 1;
678 do switch (*++cp) {
679 case '(':
680 ++nesting;
681 break;
682 case ')':
683 --nesting;
684 break;
685 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
686 } else if (blankchar(*cp) || *cp == ',')
687 ++cp;
688 else
689 break;
691 NYD2_LEAVE;
692 return cp;
695 static char *
696 a_head_customhdr__sep(char **iolist){
697 char *cp, c, *base;
698 bool_t isesc, anyesc;
699 NYD2_ENTER;
701 for(base = *iolist; base != NULL; base = *iolist){
702 while((c = *base) != '\0' && blankspacechar(c))
703 ++base;
705 for(isesc = anyesc = FAL0, cp = base;; ++cp){
706 if(n_UNLIKELY((c = *cp) == '\0')){
707 *iolist = NULL;
708 break;
709 }else if(!isesc){
710 if(c == ','){
711 *iolist = cp + 1;
712 break;
714 isesc = (c == '\\');
715 }else{
716 isesc = FAL0;
717 anyesc |= (c == ',');
721 while(cp > base && blankspacechar(cp[-1]))
722 --cp;
723 *cp = '\0';
725 if(*base != '\0'){
726 if(anyesc){
727 char *ins;
729 for(ins = cp = base;; ++ins)
730 if((c = *cp) == '\\' && cp[1] == ','){
731 *ins = ',';
732 cp += 2;
733 }else if((*ins = (++cp, c)) == '\0')
734 break;
736 break;
739 NYD2_LEAVE;
740 return base;
743 FL char const *
744 myaddrs(struct header *hp)
746 struct name *np;
747 char const *rv, *mta;
748 NYD_ENTER;
750 if (hp != NULL && (np = hp->h_from) != NULL) {
751 if ((rv = np->n_fullname) != NULL)
752 goto jleave;
753 if ((rv = np->n_name) != NULL)
754 goto jleave;
757 if ((rv = ok_vlook(from)) != NULL)
758 goto jleave;
760 /* When invoking *sendmail* directly, it's its task to generate an otherwise
761 * undeterminable From: address. However, if the user sets *hostname*,
762 * accept his desire */
763 if (ok_vlook(hostname) != NULL)
764 goto jnodename;
765 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
766 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
767 ((mta = ok_vlook(mta)) != NULL &&
768 (mta = n_servbyname(mta, NULL)) != NULL && *mta != '\0'))
769 goto jnodename;
770 jleave:
771 NYD_LEAVE;
772 return rv;
774 jnodename:{
775 char *hn, *cp;
776 size_t i;
778 hn = nodename(1);
779 i = strlen(myname) + strlen(hn) + 1 +1;
780 rv = cp = salloc(i);
781 sstpcpy(sstpcpy(sstpcpy(cp, myname), "@"), hn);
783 goto jleave;
786 FL char const *
787 myorigin(struct header *hp)
789 char const *rv = NULL, *ccp;
790 struct name *np;
791 NYD_ENTER;
793 if ((ccp = myaddrs(hp)) != NULL &&
794 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
795 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
796 NYD_LEAVE;
797 return rv;
800 FL bool_t
801 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
803 char date[FROM_DATEBUF];
804 bool_t rv;
805 NYD2_ENTER;
807 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
808 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
809 !_is_date(date)))
810 rv = TRUM1;
811 NYD2_LEAVE;
812 return rv;
815 FL int
816 extract_date_from_from_(char const *line, size_t linelen,
817 char datebuf[FROM_DATEBUF])
819 int rv;
820 char const *cp = line;
821 NYD_ENTER;
823 rv = 1;
825 /* "From " */
826 cp = _from__skipword(cp);
827 if (cp == NULL)
828 goto jerr;
829 /* "addr-spec " */
830 cp = _from__skipword(cp);
831 if (cp == NULL)
832 goto jerr;
833 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
834 cp = _from__skipword(cp);
835 if (cp == NULL)
836 goto jerr;
838 /* It seems there are invalid MBOX archives in the wild, compare
839 * . http://bugs.debian.org/624111
840 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
841 * What they do is that they obfuscate the address to "name at host",
842 * and even "name at host dot dom dot dom. I think we should handle that */
843 else if(cp[0] == 'a' && cp[1] == 't' && cp[2] == ' '){
844 rv = -1;
845 cp += 3;
846 jat_dot:
847 cp = _from__skipword(cp);
848 if (cp == NULL)
849 goto jerr;
850 if(cp[0] == 'd' && cp[1] == 'o' && cp[2] == 't' && cp[3] == ' '){
851 cp += 4;
852 goto jat_dot;
856 linelen -= PTR2SIZE(cp - line);
857 if (linelen < _DATE_MINLEN)
858 goto jerr;
859 if (cp[linelen - 1] == '\n') {
860 --linelen;
861 /* (Rather IMAP/POP3 only) */
862 if (cp[linelen - 1] == '\r')
863 --linelen;
864 if (linelen < _DATE_MINLEN)
865 goto jerr;
867 if (linelen >= FROM_DATEBUF)
868 goto jerr;
870 jleave:
871 memcpy(datebuf, cp, linelen);
872 datebuf[linelen] = '\0';
873 NYD_LEAVE;
874 return rv;
875 jerr:
876 cp = _("<Unknown date>");
877 linelen = strlen(cp);
878 if (linelen >= FROM_DATEBUF)
879 linelen = FROM_DATEBUF;
880 rv = 0;
881 goto jleave;
884 FL void
885 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
887 /* See the prototype declaration for the hairy relationship of
888 * options&OPT_t_FLAG and/or pstate&PS_t_FLAG in here */
889 struct n_header_field **hftail;
890 struct header nh, *hq = &nh;
891 char *linebuf = NULL /* TODO line pool */, *colon;
892 size_t linesize = 0, seenfields = 0;
893 int lc, c;
894 char const *val, *cp;
895 NYD_ENTER;
897 memset(hq, 0, sizeof *hq);
898 if ((pstate & PS_t_FLAG) && (options & OPT_t_FLAG)) {
899 hq->h_to = hp->h_to;
900 hq->h_cc = hp->h_cc;
901 hq->h_bcc = hp->h_bcc;
903 hftail = &hq->h_user_headers;
905 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
908 /* TODO yippieia, cat(check(lextract)) :-) */
909 rewind(fp);
910 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
911 struct name *np;
913 /* We explicitly allow EAF_NAME for some addressees since aliases are not
914 * yet expanded when we parse these! */
915 if ((val = thisfield(linebuf, "to")) != NULL) {
916 ++seenfields;
917 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
918 EACM_NORMAL | EAF_NAME, checkaddr_err));
919 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
920 ++seenfields;
921 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
922 EACM_NORMAL | EAF_NAME, checkaddr_err));
923 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
924 ++seenfields;
925 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
926 EACM_NORMAL | EAF_NAME, checkaddr_err));
927 } else if ((val = thisfield(linebuf, "from")) != NULL) {
928 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
929 ++seenfields;
930 hq->h_from = cat(hq->h_from,
931 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
932 EACM_STRICT, NULL));
934 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
935 ++seenfields;
936 hq->h_replyto = cat(hq->h_replyto,
937 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
938 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
939 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
940 ++seenfields;
941 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
942 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
943 EACM_STRICT, NULL));
944 } else
945 goto jebadhead;
946 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
947 (val = thisfield(linebuf, "subj")) != NULL) {
948 ++seenfields;
949 for (cp = val; blankchar(*cp); ++cp)
951 hq->h_subject = (hq->h_subject != NULL)
952 ? save2str(hq->h_subject, cp) : savestr(cp);
954 /* The remaining are mostly hacked in and thus TODO -- at least in
955 * TODO respect to their content checking */
956 else if((val = thisfield(linebuf, "message-id")) != NULL){
957 if(pstate & PS_t_FLAG){
958 np = checkaddrs(lextract(val, GREF),
959 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
960 NULL);
961 if (np == NULL || np->n_flink != NULL)
962 goto jebadhead;
963 ++seenfields;
964 hq->h_message_id = np;
965 }else
966 goto jebadhead;
967 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
968 if(pstate & PS_t_FLAG){
969 np = checkaddrs(lextract(val, GREF),
970 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
971 NULL);
972 ++seenfields;
973 hq->h_in_reply_to = np;
974 }else
975 goto jebadhead;
976 }else if((val = thisfield(linebuf, "references")) != NULL){
977 if(pstate & PS_t_FLAG){
978 ++seenfields;
979 /* TODO Limit number of references TODO better on parser side */
980 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
981 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
982 NULL));
983 }else
984 goto jebadhead;
986 /* and that is very hairy */
987 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
988 if(pstate & PS_t_FLAG){
989 ++seenfields;
990 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
991 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
992 checkaddr_err));
993 }else
994 goto jebadhead;
996 /* A free-form user header; gethfield() did some verification already.. */
997 else{
998 struct n_header_field *hfp;
999 ui32_t nl, bl;
1000 char const *nstart;
1002 for(nstart = cp = linebuf;; ++cp)
1003 if(!fieldnamechar(*cp))
1004 break;
1005 nl = (ui32_t)PTR2SIZE(cp - nstart);
1007 while(blankchar(*cp))
1008 ++cp;
1009 if(*cp++ != ':'){
1010 jebadhead:
1011 n_err(_("Ignoring header field: %s\n"), linebuf);
1012 continue;
1014 while(blankchar(*cp))
1015 ++cp;
1016 bl = (ui32_t)strlen(cp) +1;
1018 ++seenfields;
1019 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1020 ) + nl +1 + bl);
1021 hftail = &hfp->hf_next;
1022 hfp->hf_next = NULL;
1023 hfp->hf_nl = nl;
1024 hfp->hf_bl = bl - 1;
1025 memcpy(hfp->hf_dat, nstart, nl);
1026 hfp->hf_dat[nl++] = '\0';
1027 memcpy(hfp->hf_dat + nl, cp, bl);
1031 /* In case the blank line after the header has been edited out. Otherwise,
1032 * fetch the header separator */
1033 if (linebuf != NULL) {
1034 if (linebuf[0] != '\0') {
1035 for (cp = linebuf; *(++cp) != '\0';)
1037 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1038 } else {
1039 if ((c = getc(fp)) != '\n' && c != EOF)
1040 ungetc(c, fp);
1044 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1045 hp->h_to = hq->h_to;
1046 hp->h_cc = hq->h_cc;
1047 hp->h_bcc = hq->h_bcc;
1048 hp->h_from = hq->h_from;
1049 hp->h_replyto = hq->h_replyto;
1050 hp->h_sender = hq->h_sender;
1051 if (hq->h_subject != NULL || !(pstate & PS_t_FLAG) ||
1052 !(options & OPT_t_FLAG))
1053 hp->h_subject = hq->h_subject;
1054 hp->h_user_headers = hq->h_user_headers;
1056 if (pstate & PS_t_FLAG) {
1057 hp->h_ref = hq->h_ref;
1058 hp->h_message_id = hq->h_message_id;
1059 hp->h_in_reply_to = hq->h_in_reply_to;
1060 hp->h_mft = hq->h_mft;
1062 /* And perform additional validity checks so that we don't bail later
1063 * on TODO this is good and the place where this should occur,
1064 * TODO unfortunately a lot of other places do again and blabla */
1065 if (pstate & PS_t_FLAG) {
1066 if (hp->h_from == NULL)
1067 hp->h_from = option_r_arg;
1068 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1069 hp->h_sender = lextract(ok_vlook(sender),
1070 GEXTRA | GFULL | GFULLEXTRA);
1073 } else
1074 n_err(_("Restoring deleted header lines\n"));
1076 if (linebuf != NULL)
1077 free(linebuf);
1078 NYD_LEAVE;
1081 FL char *
1082 hfield_mult(char const *field, struct message *mp, int mult)
1084 FILE *ibuf;
1085 int lc;
1086 struct str hfs;
1087 size_t linesize = 0; /* TODO line pool */
1088 char *linebuf = NULL, *colon;
1089 char const *hfield;
1090 NYD_ENTER;
1092 /* There are (spam) messages which have header bytes which are many KB when
1093 * joined, so resize a single heap storage until we are done if we shall
1094 * collect a field that may have multiple bodies; only otherwise use the
1095 * string dope directly */
1096 memset(&hfs, 0, sizeof hfs);
1098 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1099 goto jleave;
1100 if ((lc = mp->m_lines - 1) < 0)
1101 goto jleave;
1103 if ((mp->m_flag & MNOFROM) == 0 &&
1104 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1105 goto jleave;
1106 while (lc > 0) {
1107 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1108 break;
1109 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1110 if (mult)
1111 n_str_add_buf(&hfs, hfield, strlen(hfield));
1112 else {
1113 hfs.s = savestr(hfield);
1114 break;
1119 jleave:
1120 if (linebuf != NULL)
1121 free(linebuf);
1122 if (mult && hfs.s != NULL) {
1123 colon = savestrbuf(hfs.s, hfs.l);
1124 free(hfs.s);
1125 hfs.s = colon;
1127 NYD_LEAVE;
1128 return hfs.s;
1131 FL char const *
1132 thisfield(char const *linebuf, char const *field)
1134 char const *rv = NULL;
1135 NYD2_ENTER;
1137 while (lowerconv(*linebuf) == lowerconv(*field)) {
1138 ++linebuf;
1139 ++field;
1141 if (*field != '\0')
1142 goto jleave;
1144 while (blankchar(*linebuf))
1145 ++linebuf;
1146 if (*linebuf++ != ':')
1147 goto jleave;
1149 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1150 ++linebuf;
1151 rv = linebuf;
1152 jleave:
1153 NYD2_LEAVE;
1154 return rv;
1157 FL char *
1158 nameof(struct message *mp, int reptype)
1160 char *cp, *cp2;
1161 NYD_ENTER;
1163 cp = skin(name1(mp, reptype));
1164 if (reptype != 0 || charcount(cp, '!') < 2)
1165 goto jleave;
1166 cp2 = strrchr(cp, '!');
1167 --cp2;
1168 while (cp2 > cp && *cp2 != '!')
1169 --cp2;
1170 if (*cp2 == '!')
1171 cp = cp2 + 1;
1172 jleave:
1173 NYD_LEAVE;
1174 return cp;
1177 FL char const *
1178 skip_comment(char const *cp)
1180 size_t nesting;
1181 NYD_ENTER;
1183 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1184 switch (*cp) {
1185 case '\\':
1186 if (cp[1])
1187 ++cp;
1188 break;
1189 case '(':
1190 ++nesting;
1191 break;
1192 case ')':
1193 --nesting;
1194 break;
1197 NYD_LEAVE;
1198 return cp;
1201 FL char const *
1202 routeaddr(char const *name)
1204 char const *np, *rp = NULL;
1205 NYD_ENTER;
1207 for (np = name; *np; np++) {
1208 switch (*np) {
1209 case '(':
1210 np = skip_comment(np + 1) - 1;
1211 break;
1212 case '"':
1213 while (*np) {
1214 if (*++np == '"')
1215 break;
1216 if (*np == '\\' && np[1])
1217 np++;
1219 break;
1220 case '<':
1221 rp = np;
1222 break;
1223 case '>':
1224 goto jleave;
1227 rp = NULL;
1228 jleave:
1229 NYD_LEAVE;
1230 return rp;
1233 FL enum expand_addr_flags
1234 expandaddr_to_eaf(void)
1236 struct eafdesc {
1237 char const *eafd_name;
1238 bool_t eafd_is_target;
1239 ui8_t eafd_andoff;
1240 ui8_t eafd_or;
1241 } const eafa[] = {
1242 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1243 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1244 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1245 {"file", TRU1, EAF_NONE, EAF_FILE},
1246 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1247 {"name", TRU1, EAF_NONE, EAF_NAME},
1248 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1249 }, *eafp;
1251 char *buf;
1252 enum expand_addr_flags rv;
1253 char const *cp;
1254 NYD2_ENTER;
1256 if ((cp = ok_vlook(expandaddr)) == NULL)
1257 rv = EAF_RESTRICT_TARGETS;
1258 else if (*cp == '\0')
1259 rv = EAF_TARGET_MASK;
1260 else {
1261 rv = EAF_TARGET_MASK;
1263 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1264 bool_t minus;
1266 if ((minus = (*cp == '-')) || *cp == '+')
1267 ++cp;
1268 for (eafp = eafa;; ++eafp) {
1269 if (eafp == eafa + n_NELEM(eafa)) {
1270 if (options & OPT_D_V)
1271 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1272 break;
1273 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1274 if (!minus) {
1275 rv &= ~eafp->eafd_andoff;
1276 rv |= eafp->eafd_or;
1277 } else {
1278 if (eafp->eafd_is_target)
1279 rv &= ~eafp->eafd_or;
1280 else if (options & OPT_D_V)
1281 n_err(_("minus - prefix invalid for *expandaddr* value: "
1282 "%s\n"), --cp);
1284 break;
1285 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1286 OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1287 rv &= ~EAF_NAME;
1288 break;
1293 if ((rv & EAF_RESTRICT) && (options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)))
1294 rv |= EAF_TARGET_MASK;
1295 else if (options & OPT_D_V) {
1296 if (!(rv & EAF_TARGET_MASK))
1297 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1298 else if ((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1299 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1302 NYD2_LEAVE;
1303 return rv;
1306 FL si8_t
1307 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1309 char cbuf[sizeof "'\\U12340'"];
1310 enum expand_addr_flags eaf;
1311 char const *cs;
1312 int f;
1313 si8_t rv;
1314 NYD_ENTER;
1316 f = np->n_flags;
1318 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1319 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1321 } else {
1322 ui32_t c;
1323 char const *fmt = "'\\x%02X'";
1324 bool_t ok8bit = TRU1;
1326 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1327 cs = _("Invalid domain name: %s, character %s\n");
1328 fmt = "'\\U%04X'";
1329 ok8bit = FAL0;
1330 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1331 cs = _("%s contains invalid %s sequence\n");
1332 else
1333 cs = _("%s contains invalid non-ASCII byte %s\n");
1335 c = NAME_ADDRSPEC_ERR_GETWC(f);
1336 snprintf(cbuf, sizeof cbuf,
1337 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1338 goto jprint;
1340 goto jleave;
1343 /* *expandaddr* stuff */
1344 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1345 goto jleave;
1347 eaf = expandaddr_to_eaf();
1349 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1350 if (eaf & EAF_FAIL)
1351 rv = -rv;
1352 cs = _("%s%s: file or pipe addressees not allowed here\n");
1353 if (eacm & EACM_NOLOG)
1354 goto jleave;
1355 else
1356 goto j0print;
1359 eaf |= (eacm & EAF_TARGET_MASK);
1360 if (eacm & EACM_NONAME)
1361 eaf &= ~EAF_NAME;
1363 if (eaf == EAF_NONE) {
1364 rv = FAL0;
1365 goto jleave;
1367 if (eaf & EAF_FAIL)
1368 rv = -rv;
1370 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1371 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1372 if (eacm & EACM_NOLOG)
1373 goto jleave;
1374 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1375 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1376 if (eacm & EACM_NOLOG)
1377 goto jleave;
1378 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1379 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1380 if (eacm & EACM_NOLOG)
1381 goto jleave;
1382 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1383 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1384 if (eacm & EACM_NOLOG)
1385 goto jleave;
1386 } else {
1387 rv = FAL0;
1388 goto jleave;
1391 j0print:
1392 cbuf[0] = '\0';
1393 jprint:
1394 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1395 jleave:
1396 NYD_LEAVE;
1397 return rv;
1400 FL char *
1401 skin(char const *name)
1403 struct addrguts ag;
1404 char *ret = NULL;
1405 NYD_ENTER;
1407 if (name != NULL) {
1408 addrspec_with_guts(1, name, &ag);
1409 ret = ag.ag_skinned;
1410 if (!(ag.ag_n_flags & NAME_NAME_SALLOC))
1411 ret = savestrbuf(ret, ag.ag_slen);
1413 NYD_LEAVE;
1414 return ret;
1417 /* TODO addrspec_with_guts: RFC 5322
1418 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1419 FL int
1420 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
1422 char const *cp;
1423 char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp;
1424 int rv = 1;
1425 NYD_ENTER;
1427 memset(agp, 0, sizeof *agp);
1429 if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) {
1430 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1431 agp->ag_slen = 0;
1432 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1433 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1434 goto jleave;
1437 if (!doskin || !anyof(name, "(< ")) {
1438 /*agp->ag_iaddr_start = 0;*/
1439 agp->ag_iaddr_aend = agp->ag_ilen;
1440 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1441 agp->ag_slen = agp->ag_ilen;
1442 agp->ag_n_flags = NAME_SKINNED;
1443 goto jcheck;
1446 /* Something makes us think we have to perform the skin operation */
1447 nbuf = ac_alloc(agp->ag_ilen + 1);
1448 /*agp->ag_iaddr_start = 0;*/
1449 cp2 = bufend = nbuf;
1450 gotlt = gotaddr = lastsp = 0;
1452 for (cp = name++; (c = *cp++) != '\0'; ) {
1453 switch (c) {
1454 case '(':
1455 cp = skip_comment(cp);
1456 lastsp = 0;
1457 break;
1458 case '"':
1459 /* Start of a "quoted-string".
1460 * Copy it in its entirety */
1461 /* XXX RFC: quotes are "semantically invisible"
1462 * XXX But it was explicitly added (Changelog.Heirloom,
1463 * XXX [9.23] released 11/15/00, "Do not remove quotes
1464 * XXX when skinning names"? No more info.. */
1465 *cp2++ = c;
1466 while ((c = *cp) != '\0') { /* TODO improve */
1467 cp++;
1468 if (c == '"') {
1469 *cp2++ = c;
1470 break;
1472 if (c != '\\')
1473 *cp2++ = c;
1474 else if ((c = *cp) != '\0') {
1475 *cp2++ = c;
1476 cp++;
1479 lastsp = 0;
1480 break;
1481 case ' ':
1482 case '\t':
1483 if (gotaddr == 1) {
1484 gotaddr = 2;
1485 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1487 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1488 cp += 3, *cp2++ = '@';
1489 else if (cp[0] == '@' && blankchar(cp[1]))
1490 cp += 2, *cp2++ = '@';
1491 else
1492 lastsp = 1;
1493 break;
1494 case '<':
1495 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1496 cp2 = bufend;
1497 gotlt = gotaddr = 1;
1498 lastsp = 0;
1499 break;
1500 case '>':
1501 if (gotlt) {
1502 /* (_addrspec_check() verifies these later!) */
1503 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1504 gotlt = 0;
1505 while ((c = *cp) != '\0' && c != ',') {
1506 cp++;
1507 if (c == '(')
1508 cp = skip_comment(cp);
1509 else if (c == '"')
1510 while ((c = *cp) != '\0') {
1511 cp++;
1512 if (c == '"')
1513 break;
1514 if (c == '\\' && *cp != '\0')
1515 ++cp;
1518 lastsp = 0;
1519 break;
1521 /* FALLTRHOUGH */
1522 default:
1523 if (lastsp) {
1524 lastsp = 0;
1525 if (gotaddr)
1526 *cp2++ = ' ';
1528 *cp2++ = c;
1529 if (c == ',') {
1530 if (!gotlt) {
1531 *cp2++ = ' ';
1532 for (; blankchar(*cp); ++cp)
1534 lastsp = 0;
1535 bufend = cp2;
1537 } else if (!gotaddr) {
1538 gotaddr = 1;
1539 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1543 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1544 if (agp->ag_iaddr_aend == 0)
1545 agp->ag_iaddr_aend = agp->ag_ilen;
1547 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1548 ac_free(nbuf);
1549 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1550 jcheck:
1551 rv = _addrspec_check(doskin, agp);
1552 jleave:
1553 NYD_LEAVE;
1554 return rv;
1557 FL char *
1558 realname(char const *name)
1560 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1561 char *rname, *rp;
1562 struct str in, out;
1563 int quoted, good, nogood;
1564 NYD_ENTER;
1566 if ((cp = n_UNCONST(name)) == NULL)
1567 goto jleave;
1568 for (; *cp != '\0'; ++cp) {
1569 switch (*cp) {
1570 case '(':
1571 if (cstart != NULL) {
1572 /* More than one comment in address, doesn't make sense to display
1573 * it without context. Return the entire field */
1574 cp = mime_fromaddr(name);
1575 goto jleave;
1577 cstart = cp++;
1578 cp = skip_comment(cp);
1579 cend = cp--;
1580 if (cend <= cstart)
1581 cend = cstart = NULL;
1582 break;
1583 case '"':
1584 while (*cp) {
1585 if (*++cp == '"')
1586 break;
1587 if (*cp == '\\' && cp[1])
1588 ++cp;
1590 break;
1591 case '<':
1592 if (cp > name) {
1593 cstart = name;
1594 cend = cp;
1596 break;
1597 case ',':
1598 /* More than one address. Just use the first one */
1599 goto jbrk;
1603 jbrk:
1604 if (cstart == NULL) {
1605 if (*name == '<') {
1606 /* If name contains only a route-addr, the surrounding angle brackets
1607 * don't serve any useful purpose when displaying, so remove */
1608 cp = prstr(skin(name));
1609 } else
1610 cp = mime_fromaddr(name);
1611 goto jleave;
1614 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1615 * not stripped. The idea is to strip only syntactical relevant things (but
1616 * this is not necessarily the most sensible way in practice) */
1617 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1618 quoted = 0;
1619 for (cp = cstart; cp < cend; ++cp) {
1620 if (*cp == '(' && !quoted) {
1621 cq = skip_comment(++cp);
1622 if (PTRCMP(--cq, >, cend))
1623 cq = cend;
1624 while (cp < cq) {
1625 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1626 ++cp;
1627 *rp++ = *cp++;
1629 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1630 *rp++ = *++cp;
1631 else if (*cp == '"') {
1632 quoted = !quoted;
1633 continue;
1634 } else
1635 *rp++ = *cp;
1637 *rp = '\0';
1638 in.s = rname;
1639 in.l = rp - rname;
1640 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1641 ac_free(rname);
1642 rname = savestr(out.s);
1643 free(out.s);
1645 while (blankchar(*rname))
1646 ++rname;
1647 for (rp = rname; *rp != '\0'; ++rp)
1649 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1650 *rp = '\0';
1651 if (rp == rname) {
1652 cp = mime_fromaddr(name);
1653 goto jleave;
1656 /* mime_fromhdr() has converted all nonprintable characters to question
1657 * marks now. These and blanks are considered uninteresting; if the
1658 * displayed part of the real name contains more than 25% of them, it is
1659 * probably better to display the plain email address instead */
1660 good = 0;
1661 nogood = 0;
1662 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1663 if (*rp == '?' || blankchar(*rp))
1664 ++nogood;
1665 else
1666 ++good;
1667 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1668 jleave:
1669 NYD_LEAVE;
1670 return n_UNCONST(cp);
1673 FL char *
1674 name1(struct message *mp, int reptype)
1676 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1677 size_t namesize, linesize = 0;
1678 FILE *ibuf;
1679 int f1st = 1;
1680 NYD_ENTER;
1682 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1683 goto jleave;
1684 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1685 goto jleave;
1687 namebuf = smalloc(namesize = 1);
1688 namebuf[0] = 0;
1689 if (mp->m_flag & MNOFROM)
1690 goto jout;
1691 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1692 goto jout;
1693 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1694 goto jout;
1696 jnewname:
1697 if (namesize <= linesize)
1698 namebuf = srealloc(namebuf, namesize = linesize +1);
1699 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1701 for (; blankchar(*cp); ++cp)
1703 for (cp2 = namebuf + strlen(namebuf);
1704 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1705 *cp2++ = *cp++;
1706 *cp2 = '\0';
1708 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1709 goto jout;
1710 if ((cp = strchr(linebuf, 'F')) == NULL)
1711 goto jout;
1712 if (strncmp(cp, "From", 4))
1713 goto jout;
1714 if (namesize <= linesize)
1715 namebuf = srealloc(namebuf, namesize = linesize + 1);
1717 while ((cp = strchr(cp, 'r')) != NULL) {
1718 if (!strncmp(cp, "remote", 6)) {
1719 if ((cp = strchr(cp, 'f')) == NULL)
1720 break;
1721 if (strncmp(cp, "from", 4) != 0)
1722 break;
1723 if ((cp = strchr(cp, ' ')) == NULL)
1724 break;
1725 cp++;
1726 if (f1st) {
1727 strncpy(namebuf, cp, namesize);
1728 f1st = 0;
1729 } else {
1730 cp2 = strrchr(namebuf, '!') + 1;
1731 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1733 namebuf[namesize - 2] = '!';
1734 namebuf[namesize - 1] = '\0';
1735 goto jnewname;
1737 cp++;
1739 jout:
1740 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1741 *cp == '\0')
1742 cp = savestr(namebuf);
1744 if (linebuf != NULL)
1745 free(linebuf);
1746 free(namebuf);
1747 jleave:
1748 NYD_LEAVE;
1749 return cp;
1752 FL char *
1753 subject_re_trim(char *s)
1755 struct {
1756 ui8_t len;
1757 char dat[7];
1758 } const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
1759 { 3, "re:" },
1760 { 3, "aw:" }, { 5, "antw:" }, /* de */
1761 { 0, "" }
1764 bool_t any = FAL0;
1765 char *orig_s = s, *re_st = NULL, *re_st_x;
1766 size_t re_l = 0 /* pacify CC */;
1767 NYD_ENTER;
1769 if ((re_st_x = ok_vlook(reply_strings)) != NULL &&
1770 (re_l = strlen(re_st_x)) > 0) {
1771 re_st = ac_alloc(++re_l * 2);
1772 memcpy(re_st, re_st_x, re_l);
1775 jouter:
1776 while (*s != '\0') {
1777 while (spacechar(*s))
1778 ++s;
1780 for (pp = ignored; pp->len > 0; ++pp)
1781 if (is_asccaseprefix(s, pp->dat)) {
1782 s += pp->len;
1783 any = TRU1;
1784 goto jouter;
1787 if (re_st != NULL) {
1788 char *cp;
1790 memcpy(re_st_x = re_st + re_l, re_st, re_l);
1791 while ((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
1792 if (is_asccaseprefix(s, cp)) {
1793 s += strlen(cp);
1794 any = TRU1;
1795 goto jouter;
1798 break;
1801 if (re_st != NULL)
1802 ac_free(re_st);
1803 NYD_LEAVE;
1804 return any ? s : orig_s;
1807 FL int
1808 msgidcmp(char const *s1, char const *s2)
1810 int q1 = 0, q2 = 0, c1, c2;
1811 NYD_ENTER;
1813 while(*s1 == '<')
1814 ++s1;
1815 while(*s2 == '<')
1816 ++s2;
1818 do {
1819 c1 = msgidnextc(&s1, &q1);
1820 c2 = msgidnextc(&s2, &q2);
1821 if (c1 != c2)
1822 break;
1823 } while (c1 && c2);
1824 NYD_LEAVE;
1825 return c1 - c2;
1828 FL char const *
1829 fakefrom(struct message *mp)
1831 char const *name;
1832 NYD_ENTER;
1834 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1835 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1836 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1837 * RFC 4155 however requires a RFC 5322 (2822) conforming
1838 * "addr-spec", but we simply can't provide that */
1839 name = "MAILER-DAEMON";
1840 NYD_LEAVE;
1841 return name;
1844 FL char const *
1845 fakedate(time_t t)
1847 char *cp, *cq;
1848 NYD_ENTER;
1850 cp = ctime(&t);
1851 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1853 *cq = '\0';
1854 cp = savestr(cp);
1855 NYD_LEAVE;
1856 return cp;
1859 #ifdef HAVE_IMAP_SEARCH
1860 FL time_t
1861 unixtime(char const *fromline)
1863 char const *fp;
1864 char *xp;
1865 time_t t;
1866 int i, year, month, day, hour, minute, second, tzdiff;
1867 struct tm *tmptr;
1868 NYD2_ENTER;
1870 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1872 fp -= 24;
1873 if (PTR2SIZE(fp - fromline) < 7)
1874 goto jinvalid;
1875 if (fp[3] != ' ')
1876 goto jinvalid;
1877 for (i = 0;;) {
1878 if (!strncmp(fp + 4, month_names[i], 3))
1879 break;
1880 if (month_names[++i][0] == '\0')
1881 goto jinvalid;
1883 month = i + 1;
1884 if (fp[7] != ' ')
1885 goto jinvalid;
1886 day = strtol(fp + 8, &xp, 10);
1887 if (*xp != ' ' || xp != fp + 10)
1888 goto jinvalid;
1889 hour = strtol(fp + 11, &xp, 10);
1890 if (*xp != ':' || xp != fp + 13)
1891 goto jinvalid;
1892 minute = strtol(fp + 14, &xp, 10);
1893 if (*xp != ':' || xp != fp + 16)
1894 goto jinvalid;
1895 second = strtol(fp + 17, &xp, 10);
1896 if (*xp != ' ' || xp != fp + 19)
1897 goto jinvalid;
1898 year = strtol(fp + 20, &xp, 10);
1899 if (xp != fp + 24)
1900 goto jinvalid;
1901 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1902 goto jinvalid;
1903 tzdiff = t - mktime(gmtime(&t));
1904 tmptr = localtime(&t);
1905 if (tmptr->tm_isdst > 0)
1906 tzdiff += 3600;
1907 t -= tzdiff;
1908 jleave:
1909 NYD2_LEAVE;
1910 return t;
1911 jinvalid:
1912 t = n_time_epoch();
1913 goto jleave;
1915 #endif /* HAVE_IMAP_SEARCH */
1917 FL time_t
1918 rfctime(char const *date)
1920 char const *cp = date;
1921 char *x;
1922 time_t t;
1923 int i, year, month, day, hour, minute, second;
1924 NYD2_ENTER;
1926 if ((cp = nexttoken(cp)) == NULL)
1927 goto jinvalid;
1928 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1929 cp[3] == ',') {
1930 if ((cp = nexttoken(&cp[4])) == NULL)
1931 goto jinvalid;
1933 day = strtol(cp, &x, 10); /* XXX strtol */
1934 if ((cp = nexttoken(x)) == NULL)
1935 goto jinvalid;
1936 for (i = 0;;) {
1937 if (!strncmp(cp, month_names[i], 3))
1938 break;
1939 if (month_names[++i][0] == '\0')
1940 goto jinvalid;
1942 month = i + 1;
1943 if ((cp = nexttoken(&cp[3])) == NULL)
1944 goto jinvalid;
1945 /* RFC 5322, 4.3:
1946 * Where a two or three digit year occurs in a date, the year is to be
1947 * interpreted as follows: If a two digit year is encountered whose
1948 * value is between 00 and 49, the year is interpreted by adding 2000,
1949 * ending up with a value between 2000 and 2049. If a two digit year
1950 * is encountered with a value between 50 and 99, or any three digit
1951 * year is encountered, the year is interpreted by adding 1900 */
1952 year = strtol(cp, &x, 10); /* XXX strtol */
1953 i = (int)PTR2SIZE(x - cp);
1954 if (i == 2 && year >= 0 && year <= 49)
1955 year += 2000;
1956 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1957 year += 1900;
1958 if ((cp = nexttoken(x)) == NULL)
1959 goto jinvalid;
1960 hour = strtol(cp, &x, 10); /* XXX strtol */
1961 if (*x != ':')
1962 goto jinvalid;
1963 cp = &x[1];
1964 minute = strtol(cp, &x, 10);
1965 if (*x == ':') {
1966 cp = x + 1;
1967 second = strtol(cp, &x, 10);
1968 } else
1969 second = 0;
1971 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1972 goto jinvalid;
1973 if ((cp = nexttoken(x)) != NULL) {
1974 char buf[3];
1975 int sign = 1;
1977 switch (*cp) {
1978 case '+':
1979 sign = -1;
1980 /* FALLTHRU */
1981 case '-':
1982 ++cp;
1983 break;
1985 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1986 digitchar(cp[3])) {
1987 long tadj;
1988 buf[2] = '\0';
1989 buf[0] = cp[0];
1990 buf[1] = cp[1];
1991 tadj = strtol(buf, NULL, 10) * 3600;/*XXX strtrol*/
1992 buf[0] = cp[2];
1993 buf[1] = cp[3];
1994 tadj += strtol(buf, NULL, 10) * 60; /* XXX strtol*/
1995 if (sign < 0)
1996 tadj = -tadj;
1997 t += tadj;
1999 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2000 * TODO once again, Christos Zoulas and NetBSD Mail have done
2001 * TODO a really good job already, but using strptime(3), which
2002 * TODO is not portable. Nonetheless, WE must improve, not
2003 * TODO at last because we simply ignore obsolete timezones!!
2004 * TODO See RFC 5322, 4.3! */
2006 jleave:
2007 NYD2_LEAVE;
2008 return t;
2009 jinvalid:
2010 t = 0;
2011 goto jleave;
2014 FL time_t
2015 combinetime(int year, int month, int day, int hour, int minute, int second){
2016 size_t const jdn_epoch = 2440588;
2017 bool_t const y2038p = (sizeof(time_t) == 4);
2019 size_t jdn;
2020 time_t t;
2021 NYD2_ENTER;
2023 if(UICMP(32, second, >=, DATE_SECSMIN) || /* XXX (leap- */
2024 UICMP(32, minute, >=, DATE_MINSHOUR) ||
2025 UICMP(32, hour, >=, DATE_HOURSDAY) ||
2026 day < 1 || day > 31 ||
2027 month < 1 || month > 12 ||
2028 year < 1970)
2029 goto jerr;
2031 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2032 (DATE_SECSDAY * DATE_DAYSYEAR))){
2033 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2034 * test by stepping second-wise around the flip. Don't care otherwise */
2035 if(!y2038p)
2036 goto jerr;
2037 if(year > 2038 || month > 1 || day > 19 ||
2038 hour > 3 || minute > 14 || second > 7)
2039 goto jerr;
2042 t = second;
2043 t += minute * DATE_SECSMIN;
2044 t += hour * DATE_SECSHOUR;
2046 jdn = a_head_gregorian_to_jdn(year, month, day);
2047 jdn -= jdn_epoch;
2048 t += (time_t)jdn * DATE_SECSDAY;
2049 jleave:
2050 NYD2_LEAVE;
2051 return t;
2052 jerr:
2053 t = (time_t)-1;
2054 goto jleave;
2057 FL void
2058 substdate(struct message *m)
2060 char const *cp;
2061 NYD_ENTER;
2063 /* Determine the date to print in faked 'From ' lines. This is traditionally
2064 * the date the message was written to the mail file. Try to determine this
2065 * using RFC message header fields, or fall back to current time */
2066 if ((cp = hfield1("received", m)) != NULL) {
2067 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2069 ++cp;
2070 while (alnumchar(*cp));
2072 if (cp && *++cp)
2073 m->m_time = rfctime(cp);
2075 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2076 if ((cp = hfield1("date", m)) != NULL)
2077 m->m_time = rfctime(cp);
2079 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2080 m->m_time = time_current.tc_time;
2081 NYD_LEAVE;
2084 FL void
2085 setup_from_and_sender(struct header *hp)
2087 char const *addr;
2088 struct name *np;
2089 NYD_ENTER;
2091 /* If -t parsed or composed From: then take it. With -t we otherwise
2092 * want -r to be honoured in favour of *from* in order to have
2093 * a behaviour that is compatible with what users would expect from e.g.
2094 * postfix(1) */
2095 if ((np = hp->h_from) != NULL ||
2096 ((pstate & PS_t_FLAG) && (np = option_r_arg) != NULL)) {
2098 } else if ((addr = myaddrs(hp)) != NULL)
2099 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2100 hp->h_from = np;
2102 if ((np = hp->h_sender) != NULL) {
2104 } else if ((addr = ok_vlook(sender)) != NULL)
2105 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2106 hp->h_sender = np;
2108 NYD_LEAVE;
2111 FL struct name const *
2112 check_from_and_sender(struct name const *fromfield,
2113 struct name const *senderfield)
2115 struct name const *rv = NULL;
2116 NYD_ENTER;
2118 if (senderfield != NULL) {
2119 if (senderfield->n_flink != NULL) {
2120 n_err(_("The Sender: field may contain only one address\n"));
2121 goto jleave;
2123 rv = senderfield;
2126 if (fromfield != NULL) {
2127 if (fromfield->n_flink != NULL && senderfield == NULL) {
2128 n_err(_("A Sender: is required when there are multiple "
2129 "addresses in From:\n"));
2130 goto jleave;
2132 if (rv == NULL)
2133 rv = fromfield;
2136 if (rv == NULL)
2137 rv = (struct name*)0x1;
2138 jleave:
2139 NYD_LEAVE;
2140 return rv;
2143 #ifdef HAVE_XSSL
2144 FL char *
2145 getsender(struct message *mp)
2147 char *cp;
2148 struct name *np;
2149 NYD_ENTER;
2151 if ((cp = hfield1("from", mp)) == NULL ||
2152 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2153 cp = NULL;
2154 else
2155 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2156 NYD_LEAVE;
2157 return cp;
2159 #endif
2161 FL int
2162 grab_headers(enum n_lexinput_flags lif, struct header *hp, enum gfield gflags,
2163 int subjfirst)
2165 /* TODO grab_headers: again, check counts etc. against RFC;
2166 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2167 int errs;
2168 int volatile comma;
2169 NYD_ENTER;
2171 errs = 0;
2172 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2174 if (gflags & GTO)
2175 hp->h_to = grab_names(lif, "To: ", hp->h_to, comma, GTO | GFULL);
2176 if (subjfirst && (gflags & GSUBJECT))
2177 hp->h_subject = n_lex_input_cp(lif, "Subject: ", hp->h_subject);
2178 if (gflags & GCC)
2179 hp->h_cc = grab_names(lif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2180 if (gflags & GBCC)
2181 hp->h_bcc = grab_names(lif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2183 if (gflags & GEXTRA) {
2184 if (hp->h_from == NULL)
2185 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2186 hp->h_from = grab_names(lif, "From: ", hp->h_from, comma,
2187 GEXTRA | GFULL | GFULLEXTRA);
2188 if (hp->h_replyto == NULL)
2189 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
2190 hp->h_replyto = grab_names(lif, "Reply-To: ", hp->h_replyto, comma,
2191 GEXTRA | GFULL);
2192 if (hp->h_sender == NULL)
2193 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2194 hp->h_sender = grab_names(lif, "Sender: ", hp->h_sender, comma,
2195 GEXTRA | GFULL);
2198 if (!subjfirst && (gflags & GSUBJECT))
2199 hp->h_subject = n_lex_input_cp(lif, "Subject: ", hp->h_subject);
2201 NYD_LEAVE;
2202 return errs;
2205 FL bool_t
2206 header_match(struct message *mp, struct search_expr const *sep)
2208 struct str in, out;
2209 FILE *ibuf;
2210 int lc;
2211 size_t linesize = 0; /* TODO line pool */
2212 char *linebuf = NULL, *colon;
2213 bool_t rv = FAL0;
2214 NYD_ENTER;
2216 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2217 goto jleave;
2218 if ((lc = mp->m_lines - 1) < 0)
2219 goto jleave;
2221 if ((mp->m_flag & MNOFROM) == 0 &&
2222 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2223 goto jleave;
2224 while (lc > 0) {
2225 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2226 break;
2227 if (blankchar(*++colon))
2228 ++colon;
2229 in.l = strlen(in.s = colon);
2230 mime_fromhdr(&in, &out, TD_ICONV);
2231 #ifdef HAVE_REGEX
2232 if (sep->ss_sexpr == NULL)
2233 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2234 else
2235 #endif
2236 rv = substr(out.s, sep->ss_sexpr);
2237 free(out.s);
2238 if (rv)
2239 break;
2242 jleave:
2243 if (linebuf != NULL)
2244 free(linebuf);
2245 NYD_LEAVE;
2246 return rv;
2249 FL struct n_header_field *
2250 n_customhdr_query(void){
2251 char const *vp;
2252 struct n_header_field *rv, **tail, *hfp;
2253 NYD_ENTER;
2255 rv = NULL;
2257 if((vp = ok_vlook(customhdr)) != NULL){
2258 char *buf;
2260 tail = &rv;
2261 buf = savestr(vp);
2262 jch_outer:
2263 while((vp = a_head_customhdr__sep(&buf)) != NULL){
2264 ui32_t nl, bl;
2265 char const *nstart, *cp;
2267 for(nstart = cp = vp;; ++cp){
2268 if(fieldnamechar(*cp))
2269 continue;
2270 if(*cp == '\0'){
2271 if(cp == nstart){
2272 n_err(_("Invalid nameless *customhdr* entry\n"));
2273 goto jch_outer;
2275 }else if(*cp != ':' && !blankchar(*cp)){
2276 jch_badent:
2277 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2278 goto jch_outer;
2280 break;
2282 nl = (ui32_t)PTR2SIZE(cp - nstart);
2284 while(blankchar(*cp))
2285 ++cp;
2286 if(*cp++ != ':')
2287 goto jch_badent;
2288 while(blankchar(*cp))
2289 ++cp;
2290 bl = (ui32_t)strlen(cp) +1;
2292 *tail =
2293 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2294 nl +1 + bl);
2295 tail = &hfp->hf_next;
2296 hfp->hf_next = NULL;
2297 hfp->hf_nl = nl;
2298 hfp->hf_bl = bl - 1;
2299 memcpy(hfp->hf_dat, nstart, nl);
2300 hfp->hf_dat[nl++] = '\0';
2301 memcpy(hfp->hf_dat + nl, cp, bl);
2304 NYD_LEAVE;
2305 return rv;
2308 /* s-it-mode */