cmd_tab.h+: fix: ARG_R should always imply ARG_S!
[s-mailx.git] / head.c
blob3620e182fcbba805d2468768b9bceb97b7e67443
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 static char const *
129 _from__skipword(char const *wp)
131 char c = 0;
132 NYD2_ENTER;
134 if (wp != NULL) {
135 while ((c = *wp++) != '\0' && !blankchar(c)) {
136 if (c == '"') {
137 while ((c = *wp++) != '\0' && c != '"')
139 if (c != '"')
140 --wp;
143 for (; blankchar(c); c = *wp++)
146 NYD2_LEAVE;
147 return (c == 0 ? NULL : wp - 1);
150 static int
151 _cmatch(size_t len, char const *date, char const *tp)
153 int ret = 0;
154 NYD2_ENTER;
156 while (len--) {
157 char c = date[len];
158 switch (tp[len]) {
159 case 'a':
160 if (!lowerchar(c))
161 goto jleave;
162 break;
163 case 'A':
164 if (!upperchar(c))
165 goto jleave;
166 break;
167 case ' ':
168 if (c != ' ')
169 goto jleave;
170 break;
171 case '0':
172 if (!digitchar(c))
173 goto jleave;
174 break;
175 case 'O':
176 if (c != ' ' && !digitchar(c))
177 goto jleave;
178 break;
179 case ':':
180 if (c != ':')
181 goto jleave;
182 break;
183 case '+':
184 if (c != '+' && c != '-')
185 goto jleave;
186 break;
189 ret = 1;
190 jleave:
191 NYD2_LEAVE;
192 return ret;
195 static int
196 _is_date(char const *date)
198 struct cmatch_data const *cmdp;
199 size_t dl;
200 int rv = 0;
201 NYD2_ENTER;
203 if ((dl = strlen(date)) >= _DATE_MINLEN)
204 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
205 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
206 break;
207 NYD2_LEAVE;
208 return rv;
211 static size_t
212 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
213 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
214 * (via third hand, plus adjustments).
215 * This algorithm is supposed to work for all dates in between 1582-10-15
216 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
217 size_t jdn;
218 NYD2_ENTER;
220 #if 0
221 if(y == 0)
222 y = 1;
223 if(m == 0)
224 m = 1;
225 if(d == 0)
226 d = 1;
227 #endif
229 if(m > 2)
230 m -= 3;
231 else{
232 m += 9;
233 --y;
235 jdn = y;
236 jdn /= 100;
237 y -= 100 * jdn;
238 y *= 1461;
239 y >>= 2;
240 jdn *= 146097;
241 jdn >>= 2;
242 jdn += y;
243 jdn += d;
244 jdn += 1721119;
245 m *= 153;
246 m += 2;
247 m /= 5;
248 jdn += m;
249 NYD2_LEAVE;
250 return jdn;
253 #if 0
254 static void
255 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
256 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
257 * (via third hand, plus adjustments) */
258 size_t y, x;
259 NYD2_ENTER;
261 jdn -= 1721119;
262 jdn <<= 2;
263 --jdn;
264 y = jdn / 146097;
265 jdn %= 146097;
266 jdn |= 3;
267 y *= 100;
268 y += jdn / 1461;
269 jdn %= 1461;
270 jdn += 4;
271 jdn >>= 2;
272 x = jdn;
273 jdn <<= 2;
274 jdn += x;
275 jdn -= 3;
276 x = jdn / 153; /* x -> month */
277 jdn %= 153;
278 jdn += 5;
279 jdn /= 5; /* jdn -> day */
280 if(x < 10)
281 x += 3;
282 else{
283 x -= 9;
284 ++y;
287 *yp = (ui32_t)(y & 0xFFFF);
288 *mp = (ui32_t)(x & 0xFF);
289 *dp = (ui32_t)(jdn & 0xFF);
290 NYD2_LEAVE;
292 #endif /* 0 */
294 #ifdef HAVE_IDNA
295 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
296 static struct addrguts *
297 _idna_apply(struct addrguts *agp)
299 char *idna_utf8, *idna_ascii, *cs;
300 size_t sz, i;
301 NYD_ENTER;
303 sz = agp->ag_slen - agp->ag_sdom_start;
304 assert(sz > 0);
305 idna_utf8 = ac_alloc(sz +1);
306 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
307 idna_utf8[sz] = '\0';
309 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
310 * let it perform the charset conversion, if any should be necessary */
311 if (!(options & OPT_UNICODE)) {
312 char const *tcs = charset_get_lc();
313 idna_ascii = idna_utf8;
314 idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", tcs);
315 i = (idna_utf8 == NULL && errno == EINVAL);
316 ac_free(idna_ascii);
318 if (idna_utf8 == NULL) {
319 if (i)
320 n_err(_("Cannot convert from %s to %s\n"), tcs, "UTF-8");
321 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
322 goto jleave;
326 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
327 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
328 goto jleave1;
331 /* Replace the domain part of .ag_skinned with IDNA version */
332 sz = strlen(idna_ascii);
333 i = agp->ag_sdom_start;
334 cs = salloc(agp->ag_slen - i + sz +1);
335 memcpy(cs, agp->ag_skinned, i);
336 memcpy(cs + i, idna_ascii, sz);
337 i += sz;
338 cs[i] = '\0';
340 agp->ag_skinned = cs;
341 agp->ag_slen = i;
342 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
343 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
345 idn_free(idna_ascii);
346 jleave1:
347 if (options & OPT_UNICODE)
348 ac_free(idna_utf8);
349 else
350 idn_free(idna_utf8);
351 jleave:
352 NYD_LEAVE;
353 return agp;
356 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */
357 static struct addrguts *
358 _idna_apply(struct addrguts *agp)
360 char *idna_in, *idna_out, *cs;
361 size_t sz, i;
362 idn_result_t r;
363 NYD_ENTER;
365 sz = agp->ag_slen - agp->ag_sdom_start;
366 assert(sz > 0);
367 idna_in = ac_alloc(sz +1);
368 memcpy(idna_in, agp->ag_skinned + agp->ag_sdom_start, sz);
369 idna_in[sz] = '\0';
371 for (idna_out = NULL, sz = HOST_NAME_MAX +1;; sz += HOST_NAME_MAX) {
372 idna_out = ac_alloc(sz);
374 r = idn_encodename(IDN_ENCODE_APP, idna_in, idna_out, sz);
375 switch (r) {
376 case idn_success:
377 case idn_buffer_overflow:
378 break;
379 case idn_invalid_encoding:
380 n_err(_("Cannot convert from %s to %s\n"), charset_get_lc(), "UTF-8");
381 /* FALLTHRU */
382 default:
383 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
384 goto jleave;
387 if (r == idn_success)
388 break;
389 ac_free(idna_out);
392 /* Replace the domain part of .ag_skinned with IDNA version */
393 sz = strlen(idna_out);
394 i = agp->ag_sdom_start;
395 cs = salloc(agp->ag_slen - i + sz +1);
396 memcpy(cs, agp->ag_skinned, i);
397 memcpy(cs + i, idna_out, sz);
398 i += sz;
399 cs[i] = '\0';
401 agp->ag_skinned = cs;
402 agp->ag_slen = i;
403 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
404 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
406 jleave:
407 ac_free(idna_out);
408 ac_free(idna_in);
409 NYD_LEAVE;
410 return agp;
412 # endif /* IDNA==IDNKIT */
413 #endif /* HAVE_IDNA */
415 static int
416 _addrspec_check(int skinned, struct addrguts *agp)
418 char *addr, *p;
419 bool_t in_quote;
420 ui8_t in_domain, hadat;
421 union {char c; unsigned char u;} c;
422 #ifdef HAVE_IDNA
423 ui8_t use_idna;
424 #endif
425 NYD_ENTER;
427 #ifdef HAVE_IDNA
428 use_idna = ok_blook(idna_disable) ? 0 : 1;
429 #endif
430 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
431 addr = agp->ag_skinned;
433 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
434 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
435 goto jleave;
438 /* If the field is not a recipient, it cannot be a file or a pipe */
439 if (!skinned)
440 goto jaddr_check;
442 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
443 * grep the latter for the complete picture */
444 if (*addr == '|') {
445 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
446 goto jleave;
448 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
449 (addr[0] == '-' && addr[1] == '\0'))
450 goto jisfile;
451 if (memchr(addr, '@', agp->ag_slen) == NULL) {
452 if (*addr == '+')
453 goto jisfile;
454 for (p = addr; (c.c = *p); ++p) {
455 if (c.c == '!' || c.c == '%')
456 break;
457 if (c.c == '/') {
458 jisfile:
459 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
460 goto jleave;
465 jaddr_check:
466 in_quote = FAL0;
467 in_domain = hadat = 0;
469 for (p = addr; (c.c = *p++) != '\0';) {
470 if (c.c == '"') {
471 in_quote = !in_quote;
472 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
473 #ifdef HAVE_IDNA
474 if (in_domain && use_idna > 0) {
475 if (use_idna == 1)
476 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
477 c.u);
478 use_idna = 2;
479 } else
480 #endif
481 break;
482 } else if (in_domain == 2) {
483 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
484 break;
485 } else if (in_quote && in_domain == 0) {
486 /*EMPTY*/;
487 } else if (c.c == '\\' && *p != '\0') {
488 ++p;
489 } else if (c.c == '@') {
490 if (hadat++ > 0) {
491 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
492 c.u);
493 goto jleave;
495 agp->ag_sdom_start = PTR2SIZE(p - addr);
496 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
497 in_domain = (*p == '[') ? 2 : 1;
498 continue;
499 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
500 c.c == ',' || c.c == ';' || c.c == ':' || c.c == '\\' ||
501 c.c == '[' || c.c == ']')
502 break;
503 hadat = 0;
505 if (c.c != '\0') {
506 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
507 goto jleave;
510 if (!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
511 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
513 #ifdef HAVE_IDNA
514 if (use_idna == 2)
515 agp = _idna_apply(agp);
516 #endif
517 jleave:
518 NYD_LEAVE;
519 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
522 static int
523 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
525 char *line2 = NULL, *cp, *cp2;
526 size_t line2size = 0;
527 int c, isenc;
528 NYD2_ENTER;
530 if (*linebuf == NULL)
531 *linebuf = srealloc(*linebuf, *linesize = 1);
532 **linebuf = '\0';
533 for (;;) {
534 if (--rem < 0) {
535 rem = -1;
536 break;
538 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
539 rem = -1;
540 break;
542 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
544 if (cp > *linebuf)
545 while (blankchar(*cp))
546 ++cp;
547 if (*cp != ':' || cp == *linebuf)
548 continue;
550 /* I guess we got a headline. Handle wraparound */
551 *colon = cp;
552 cp = *linebuf + c;
553 for (;;) {
554 isenc = 0;
555 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
557 cp++;
558 if (rem <= 0)
559 break;
560 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
561 isenc |= 1;
562 ungetc(c = getc(f), f);
563 if (!blankchar(c))
564 break;
565 c = readline_restart(f, &line2, &line2size, 0);
566 if (c < 0)
567 break;
568 --rem;
569 for (cp2 = line2; blankchar(*cp2); ++cp2)
571 c -= (int)PTR2SIZE(cp2 - line2);
572 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
573 isenc |= 2;
574 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
575 size_t diff = PTR2SIZE(cp - *linebuf),
576 colondiff = PTR2SIZE(*colon - *linebuf);
577 *linebuf = srealloc(*linebuf, *linesize += c + 2);
578 cp = &(*linebuf)[diff];
579 *colon = &(*linebuf)[colondiff];
581 if (isenc != 3)
582 *cp++ = ' ';
583 memcpy(cp, cp2, c);
584 cp += c;
586 *cp = '\0';
588 if (line2 != NULL)
589 free(line2);
590 break;
592 NYD2_LEAVE;
593 return rem;
596 static int
597 msgidnextc(char const **cp, int *status)
599 int c;
600 NYD2_ENTER;
602 assert(cp != NULL);
603 assert(*cp != NULL);
604 assert(status != NULL);
606 for (;;) {
607 if (*status & 01) {
608 if (**cp == '"') {
609 *status &= ~01;
610 (*cp)++;
611 continue;
613 if (**cp == '\\') {
614 (*cp)++;
615 if (**cp == '\0')
616 goto jeof;
618 goto jdfl;
620 switch (**cp) {
621 case '(':
622 *cp = skip_comment(&(*cp)[1]);
623 continue;
624 case '>':
625 case '\0':
626 jeof:
627 c = '\0';
628 goto jleave;
629 case '"':
630 (*cp)++;
631 *status |= 01;
632 continue;
633 case '@':
634 *status |= 02;
635 /*FALLTHRU*/
636 default:
637 jdfl:
638 c = *(*cp)++ & 0377;
639 c = (*status & 02) ? lowerconv(c) : c;
640 goto jleave;
643 jleave:
644 NYD2_LEAVE;
645 return c;
648 static int
649 charcount(char *str, int c)
651 char *cp;
652 int i;
653 NYD2_ENTER;
655 for (i = 0, cp = str; *cp; ++cp)
656 if (*cp == c)
657 ++i;
658 NYD2_LEAVE;
659 return i;
662 static char const *
663 nexttoken(char const *cp)
665 NYD2_ENTER;
666 for (;;) {
667 if (*cp == '\0') {
668 cp = NULL;
669 break;
672 if (*cp == '(') {
673 size_t nesting = 1;
675 do switch (*++cp) {
676 case '(':
677 ++nesting;
678 break;
679 case ')':
680 --nesting;
681 break;
682 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
683 } else if (blankchar(*cp) || *cp == ',')
684 ++cp;
685 else
686 break;
688 NYD2_LEAVE;
689 return cp;
692 FL char const *
693 myaddrs(struct header *hp)
695 struct name *np;
696 char const *rv, *mta;
697 NYD_ENTER;
699 if (hp != NULL && (np = hp->h_from) != NULL) {
700 if ((rv = np->n_fullname) != NULL)
701 goto jleave;
702 if ((rv = np->n_name) != NULL)
703 goto jleave;
706 if ((rv = ok_vlook(from)) != NULL)
707 goto jleave;
709 /* When invoking *sendmail* directly, it's its task to generate an otherwise
710 * undeterminable From: address. However, if the user sets *hostname*,
711 * accept his desire */
712 if (ok_vlook(hostname) != NULL)
713 goto jnodename;
714 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
715 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
716 ((mta = ok_vlook(mta)) != NULL &&
717 (mta = n_servbyname(mta, NULL)) != NULL && *mta != '\0'))
718 goto jnodename;
719 jleave:
720 NYD_LEAVE;
721 return rv;
723 jnodename:{
724 char *hn, *cp;
725 size_t i;
727 hn = nodename(1);
728 i = strlen(myname) + strlen(hn) + 1 +1;
729 rv = cp = salloc(i);
730 sstpcpy(sstpcpy(sstpcpy(cp, myname), "@"), hn);
732 goto jleave;
735 FL char const *
736 myorigin(struct header *hp)
738 char const *rv = NULL, *ccp;
739 struct name *np;
740 NYD_ENTER;
742 if ((ccp = myaddrs(hp)) != NULL &&
743 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
744 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
745 NYD_LEAVE;
746 return rv;
749 FL bool_t
750 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
752 char date[FROM_DATEBUF];
753 bool_t rv;
754 NYD2_ENTER;
756 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
757 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
758 !_is_date(date)))
759 rv = TRUM1;
760 NYD2_LEAVE;
761 return rv;
764 FL int
765 extract_date_from_from_(char const *line, size_t linelen,
766 char datebuf[FROM_DATEBUF])
768 int rv;
769 char const *cp = line;
770 NYD_ENTER;
772 rv = 1;
774 /* "From " */
775 cp = _from__skipword(cp);
776 if (cp == NULL)
777 goto jerr;
778 /* "addr-spec " */
779 cp = _from__skipword(cp);
780 if (cp == NULL)
781 goto jerr;
782 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
783 cp = _from__skipword(cp);
784 if (cp == NULL)
785 goto jerr;
787 /* It seems there are invalid MBOX archives in the wild, compare
788 * . http://bugs.debian.org/624111
789 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
790 * What they do is that they obfuscate the address to "name at host",
791 * and even "name at host dot dom dot dom. I think we should handle that */
792 else if(cp[0] == 'a' && cp[1] == 't' && cp[2] == ' '){
793 rv = -1;
794 cp += 3;
795 jat_dot:
796 cp = _from__skipword(cp);
797 if (cp == NULL)
798 goto jerr;
799 if(cp[0] == 'd' && cp[1] == 'o' && cp[2] == 't' && cp[3] == ' '){
800 cp += 4;
801 goto jat_dot;
805 linelen -= PTR2SIZE(cp - line);
806 if (linelen < _DATE_MINLEN)
807 goto jerr;
808 if (cp[linelen - 1] == '\n') {
809 --linelen;
810 /* (Rather IMAP/POP3 only) */
811 if (cp[linelen - 1] == '\r')
812 --linelen;
813 if (linelen < _DATE_MINLEN)
814 goto jerr;
816 if (linelen >= FROM_DATEBUF)
817 goto jerr;
819 jleave:
820 memcpy(datebuf, cp, linelen);
821 datebuf[linelen] = '\0';
822 NYD_LEAVE;
823 return rv;
824 jerr:
825 cp = _("<Unknown date>");
826 linelen = strlen(cp);
827 if (linelen >= FROM_DATEBUF)
828 linelen = FROM_DATEBUF;
829 rv = 0;
830 goto jleave;
833 FL void
834 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
836 /* See the prototype declaration for the hairy relationship of
837 * options&OPT_t_FLAG and/or pstate&PS_t_FLAG in here */
838 struct n_header_field **hftail;
839 struct header nh, *hq = &nh;
840 char *linebuf = NULL /* TODO line pool */, *colon;
841 size_t linesize = 0, seenfields = 0;
842 int lc, c;
843 char const *val, *cp;
844 NYD_ENTER;
846 memset(hq, 0, sizeof *hq);
847 if ((pstate & PS_t_FLAG) && (options & OPT_t_FLAG)) {
848 hq->h_to = hp->h_to;
849 hq->h_cc = hp->h_cc;
850 hq->h_bcc = hp->h_bcc;
852 hftail = &hq->h_user_headers;
854 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
857 /* TODO yippieia, cat(check(lextract)) :-) */
858 rewind(fp);
859 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
860 struct name *np;
862 /* We explicitly allow EAF_NAME for some addressees since aliases are not
863 * yet expanded when we parse these! */
864 if ((val = thisfield(linebuf, "to")) != NULL) {
865 ++seenfields;
866 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
867 EACM_NORMAL | EAF_NAME, checkaddr_err));
868 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
869 ++seenfields;
870 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
871 EACM_NORMAL | EAF_NAME, checkaddr_err));
872 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
873 ++seenfields;
874 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
875 EACM_NORMAL | EAF_NAME, checkaddr_err));
876 } else if ((val = thisfield(linebuf, "from")) != NULL) {
877 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
878 ++seenfields;
879 hq->h_from = cat(hq->h_from,
880 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
881 EACM_STRICT, NULL));
883 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
884 ++seenfields;
885 hq->h_replyto = cat(hq->h_replyto,
886 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
887 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
888 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
889 ++seenfields;
890 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
891 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
892 EACM_STRICT, NULL));
893 } else
894 goto jebadhead;
895 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
896 (val = thisfield(linebuf, "subj")) != NULL) {
897 ++seenfields;
898 for (cp = val; blankchar(*cp); ++cp)
900 hq->h_subject = (hq->h_subject != NULL)
901 ? save2str(hq->h_subject, cp) : savestr(cp);
903 /* The remaining are mostly hacked in and thus TODO -- at least in
904 * TODO respect to their content checking */
905 else if((val = thisfield(linebuf, "message-id")) != NULL){
906 if(pstate & PS_t_FLAG){
907 np = checkaddrs(lextract(val, GREF),
908 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
909 NULL);
910 if (np == NULL || np->n_flink != NULL)
911 goto jebadhead;
912 ++seenfields;
913 hq->h_message_id = np;
914 }else
915 goto jebadhead;
916 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
917 if(pstate & PS_t_FLAG){
918 np = checkaddrs(lextract(val, GREF),
919 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
920 NULL);
921 ++seenfields;
922 hq->h_in_reply_to = np;
923 }else
924 goto jebadhead;
925 }else if((val = thisfield(linebuf, "references")) != NULL){
926 if(pstate & PS_t_FLAG){
927 ++seenfields;
928 /* TODO Limit number of references TODO better on parser side */
929 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
930 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
931 NULL));
932 }else
933 goto jebadhead;
935 /* and that is very hairy */
936 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
937 if(pstate & PS_t_FLAG){
938 ++seenfields;
939 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
940 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
941 checkaddr_err));
942 }else
943 goto jebadhead;
945 /* A free-form user header; gethfield() did some verification already.. */
946 else{
947 struct n_header_field *hfp;
948 ui32_t nl, bl;
949 char const *nstart;
951 for(nstart = cp = linebuf;; ++cp)
952 if(!fieldnamechar(*cp))
953 break;
954 nl = (ui32_t)PTR2SIZE(cp - nstart);
956 while(blankchar(*cp))
957 ++cp;
958 if(*cp++ != ':'){
959 jebadhead:
960 n_err(_("Ignoring header field: %s\n"), linebuf);
961 continue;
963 while(blankchar(*cp))
964 ++cp;
965 bl = (ui32_t)strlen(cp) +1;
967 ++seenfields;
968 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
969 ) + nl +1 + bl);
970 hftail = &hfp->hf_next;
971 hfp->hf_next = NULL;
972 hfp->hf_nl = nl;
973 hfp->hf_bl = bl - 1;
974 memcpy(hfp->hf_dat, nstart, nl);
975 hfp->hf_dat[nl++] = '\0';
976 memcpy(hfp->hf_dat + nl, cp, bl);
980 /* In case the blank line after the header has been edited out. Otherwise,
981 * fetch the header separator */
982 if (linebuf != NULL) {
983 if (linebuf[0] != '\0') {
984 for (cp = linebuf; *(++cp) != '\0';)
986 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
987 } else {
988 if ((c = getc(fp)) != '\n' && c != EOF)
989 ungetc(c, fp);
993 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
994 hp->h_to = hq->h_to;
995 hp->h_cc = hq->h_cc;
996 hp->h_bcc = hq->h_bcc;
997 hp->h_from = hq->h_from;
998 hp->h_replyto = hq->h_replyto;
999 hp->h_sender = hq->h_sender;
1000 if (hq->h_subject != NULL || !(pstate & PS_t_FLAG) ||
1001 !(options & OPT_t_FLAG))
1002 hp->h_subject = hq->h_subject;
1003 hp->h_user_headers = hq->h_user_headers;
1005 if (pstate & PS_t_FLAG) {
1006 hp->h_ref = hq->h_ref;
1007 hp->h_message_id = hq->h_message_id;
1008 hp->h_in_reply_to = hq->h_in_reply_to;
1009 hp->h_mft = hq->h_mft;
1011 /* And perform additional validity checks so that we don't bail later
1012 * on TODO this is good and the place where this should occur,
1013 * TODO unfortunately a lot of other places do again and blabla */
1014 if (pstate & PS_t_FLAG) {
1015 if (hp->h_from == NULL)
1016 hp->h_from = option_r_arg;
1017 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1018 hp->h_sender = lextract(ok_vlook(sender),
1019 GEXTRA | GFULL | GFULLEXTRA);
1022 } else
1023 n_err(_("Restoring deleted header lines\n"));
1025 if (linebuf != NULL)
1026 free(linebuf);
1027 NYD_LEAVE;
1030 FL char *
1031 hfield_mult(char const *field, struct message *mp, int mult)
1033 FILE *ibuf;
1034 int lc;
1035 struct str hfs;
1036 size_t linesize = 0; /* TODO line pool */
1037 char *linebuf = NULL, *colon;
1038 char const *hfield;
1039 NYD_ENTER;
1041 /* There are (spam) messages which have header bytes which are many KB when
1042 * joined, so resize a single heap storage until we are done if we shall
1043 * collect a field that may have multiple bodies; only otherwise use the
1044 * string dope directly */
1045 memset(&hfs, 0, sizeof hfs);
1047 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1048 goto jleave;
1049 if ((lc = mp->m_lines - 1) < 0)
1050 goto jleave;
1052 if ((mp->m_flag & MNOFROM) == 0 &&
1053 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1054 goto jleave;
1055 while (lc > 0) {
1056 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1057 break;
1058 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1059 if (mult)
1060 n_str_add_buf(&hfs, hfield, strlen(hfield));
1061 else {
1062 hfs.s = savestr(hfield);
1063 break;
1068 jleave:
1069 if (linebuf != NULL)
1070 free(linebuf);
1071 if (mult && hfs.s != NULL) {
1072 colon = savestrbuf(hfs.s, hfs.l);
1073 free(hfs.s);
1074 hfs.s = colon;
1076 NYD_LEAVE;
1077 return hfs.s;
1080 FL char const *
1081 thisfield(char const *linebuf, char const *field)
1083 char const *rv = NULL;
1084 NYD2_ENTER;
1086 while (lowerconv(*linebuf) == lowerconv(*field)) {
1087 ++linebuf;
1088 ++field;
1090 if (*field != '\0')
1091 goto jleave;
1093 while (blankchar(*linebuf))
1094 ++linebuf;
1095 if (*linebuf++ != ':')
1096 goto jleave;
1098 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1099 ++linebuf;
1100 rv = linebuf;
1101 jleave:
1102 NYD2_LEAVE;
1103 return rv;
1106 FL char *
1107 nameof(struct message *mp, int reptype)
1109 char *cp, *cp2;
1110 NYD_ENTER;
1112 cp = skin(name1(mp, reptype));
1113 if (reptype != 0 || charcount(cp, '!') < 2)
1114 goto jleave;
1115 cp2 = strrchr(cp, '!');
1116 --cp2;
1117 while (cp2 > cp && *cp2 != '!')
1118 --cp2;
1119 if (*cp2 == '!')
1120 cp = cp2 + 1;
1121 jleave:
1122 NYD_LEAVE;
1123 return cp;
1126 FL char const *
1127 skip_comment(char const *cp)
1129 size_t nesting;
1130 NYD_ENTER;
1132 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1133 switch (*cp) {
1134 case '\\':
1135 if (cp[1])
1136 ++cp;
1137 break;
1138 case '(':
1139 ++nesting;
1140 break;
1141 case ')':
1142 --nesting;
1143 break;
1146 NYD_LEAVE;
1147 return cp;
1150 FL char const *
1151 routeaddr(char const *name)
1153 char const *np, *rp = NULL;
1154 NYD_ENTER;
1156 for (np = name; *np; np++) {
1157 switch (*np) {
1158 case '(':
1159 np = skip_comment(np + 1) - 1;
1160 break;
1161 case '"':
1162 while (*np) {
1163 if (*++np == '"')
1164 break;
1165 if (*np == '\\' && np[1])
1166 np++;
1168 break;
1169 case '<':
1170 rp = np;
1171 break;
1172 case '>':
1173 goto jleave;
1176 rp = NULL;
1177 jleave:
1178 NYD_LEAVE;
1179 return rp;
1182 FL enum expand_addr_flags
1183 expandaddr_to_eaf(void)
1185 struct eafdesc {
1186 char const *eafd_name;
1187 bool_t eafd_is_target;
1188 ui8_t eafd_andoff;
1189 ui8_t eafd_or;
1190 } const eafa[] = {
1191 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1192 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1193 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1194 {"file", TRU1, EAF_NONE, EAF_FILE},
1195 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1196 {"name", TRU1, EAF_NONE, EAF_NAME},
1197 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1198 }, *eafp;
1200 char *buf;
1201 enum expand_addr_flags rv;
1202 char const *cp;
1203 NYD2_ENTER;
1205 if ((cp = ok_vlook(expandaddr)) == NULL)
1206 rv = EAF_RESTRICT_TARGETS;
1207 else if (*cp == '\0')
1208 rv = EAF_TARGET_MASK;
1209 else {
1210 rv = EAF_TARGET_MASK;
1212 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1213 bool_t minus;
1215 if ((minus = (*cp == '-')) || *cp == '+')
1216 ++cp;
1217 for (eafp = eafa;; ++eafp) {
1218 if (eafp == eafa + n_NELEM(eafa)) {
1219 if (options & OPT_D_V)
1220 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1221 break;
1222 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1223 if (!minus) {
1224 rv &= ~eafp->eafd_andoff;
1225 rv |= eafp->eafd_or;
1226 } else {
1227 if (eafp->eafd_is_target)
1228 rv &= ~eafp->eafd_or;
1229 else if (options & OPT_D_V)
1230 n_err(_("minus - prefix invalid for *expandaddr* value: "
1231 "%s\n"), --cp);
1233 break;
1234 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1235 OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1236 rv &= ~EAF_NAME;
1237 break;
1242 if ((rv & EAF_RESTRICT) && (options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)))
1243 rv |= EAF_TARGET_MASK;
1244 else if (options & OPT_D_V) {
1245 if (!(rv & EAF_TARGET_MASK))
1246 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1247 else if ((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1248 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1251 NYD2_LEAVE;
1252 return rv;
1255 FL si8_t
1256 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1258 char cbuf[sizeof "'\\U12340'"];
1259 enum expand_addr_flags eaf;
1260 char const *cs;
1261 int f;
1262 si8_t rv;
1263 NYD_ENTER;
1265 f = np->n_flags;
1267 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1268 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1270 } else {
1271 ui32_t c;
1272 char const *fmt = "'\\x%02X'";
1273 bool_t ok8bit = TRU1;
1275 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1276 cs = _("Invalid domain name: %s, character %s\n");
1277 fmt = "'\\U%04X'";
1278 ok8bit = FAL0;
1279 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1280 cs = _("%s contains invalid %s sequence\n");
1281 else
1282 cs = _("%s contains invalid non-ASCII byte %s\n");
1284 c = NAME_ADDRSPEC_ERR_GETWC(f);
1285 snprintf(cbuf, sizeof cbuf,
1286 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1287 goto jprint;
1289 goto jleave;
1292 /* *expandaddr* stuff */
1293 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1294 goto jleave;
1296 eaf = expandaddr_to_eaf();
1298 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1299 if (eaf & EAF_FAIL)
1300 rv = -rv;
1301 cs = _("%s%s: file or pipe addressees not allowed here\n");
1302 if (eacm & EACM_NOLOG)
1303 goto jleave;
1304 else
1305 goto j0print;
1308 eaf |= (eacm & EAF_TARGET_MASK);
1309 if (eacm & EACM_NONAME)
1310 eaf &= ~EAF_NAME;
1312 if (eaf == EAF_NONE) {
1313 rv = FAL0;
1314 goto jleave;
1316 if (eaf & EAF_FAIL)
1317 rv = -rv;
1319 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1320 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1321 if (eacm & EACM_NOLOG)
1322 goto jleave;
1323 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1324 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1325 if (eacm & EACM_NOLOG)
1326 goto jleave;
1327 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1328 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1329 if (eacm & EACM_NOLOG)
1330 goto jleave;
1331 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1332 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1333 if (eacm & EACM_NOLOG)
1334 goto jleave;
1335 } else {
1336 rv = FAL0;
1337 goto jleave;
1340 j0print:
1341 cbuf[0] = '\0';
1342 jprint:
1343 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1344 jleave:
1345 NYD_LEAVE;
1346 return rv;
1349 FL char *
1350 skin(char const *name)
1352 struct addrguts ag;
1353 char *ret = NULL;
1354 NYD_ENTER;
1356 if (name != NULL) {
1357 addrspec_with_guts(1, name, &ag);
1358 ret = ag.ag_skinned;
1359 if (!(ag.ag_n_flags & NAME_NAME_SALLOC))
1360 ret = savestrbuf(ret, ag.ag_slen);
1362 NYD_LEAVE;
1363 return ret;
1366 /* TODO addrspec_with_guts: RFC 5322
1367 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1368 FL int
1369 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
1371 char const *cp;
1372 char *cp2, *bufend, *nbuf, c, gotlt, gotaddr, lastsp;
1373 int rv = 1;
1374 NYD_ENTER;
1376 memset(agp, 0, sizeof *agp);
1378 if ((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0) {
1379 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1380 agp->ag_slen = 0;
1381 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1382 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1383 goto jleave;
1386 if (!doskin || !anyof(name, "(< ")) {
1387 /*agp->ag_iaddr_start = 0;*/
1388 agp->ag_iaddr_aend = agp->ag_ilen;
1389 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1390 agp->ag_slen = agp->ag_ilen;
1391 agp->ag_n_flags = NAME_SKINNED;
1392 goto jcheck;
1395 /* Something makes us think we have to perform the skin operation */
1396 nbuf = ac_alloc(agp->ag_ilen + 1);
1397 /*agp->ag_iaddr_start = 0;*/
1398 cp2 = bufend = nbuf;
1399 gotlt = gotaddr = lastsp = 0;
1401 for (cp = name++; (c = *cp++) != '\0'; ) {
1402 switch (c) {
1403 case '(':
1404 cp = skip_comment(cp);
1405 lastsp = 0;
1406 break;
1407 case '"':
1408 /* Start of a "quoted-string".
1409 * Copy it in its entirety */
1410 /* XXX RFC: quotes are "semantically invisible"
1411 * XXX But it was explicitly added (Changelog.Heirloom,
1412 * XXX [9.23] released 11/15/00, "Do not remove quotes
1413 * XXX when skinning names"? No more info.. */
1414 *cp2++ = c;
1415 while ((c = *cp) != '\0') { /* TODO improve */
1416 cp++;
1417 if (c == '"') {
1418 *cp2++ = c;
1419 break;
1421 if (c != '\\')
1422 *cp2++ = c;
1423 else if ((c = *cp) != '\0') {
1424 *cp2++ = c;
1425 cp++;
1428 lastsp = 0;
1429 break;
1430 case ' ':
1431 case '\t':
1432 if (gotaddr == 1) {
1433 gotaddr = 2;
1434 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1436 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1437 cp += 3, *cp2++ = '@';
1438 else if (cp[0] == '@' && blankchar(cp[1]))
1439 cp += 2, *cp2++ = '@';
1440 else
1441 lastsp = 1;
1442 break;
1443 case '<':
1444 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1445 cp2 = bufend;
1446 gotlt = gotaddr = 1;
1447 lastsp = 0;
1448 break;
1449 case '>':
1450 if (gotlt) {
1451 /* (_addrspec_check() verifies these later!) */
1452 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1453 gotlt = 0;
1454 while ((c = *cp) != '\0' && c != ',') {
1455 cp++;
1456 if (c == '(')
1457 cp = skip_comment(cp);
1458 else if (c == '"')
1459 while ((c = *cp) != '\0') {
1460 cp++;
1461 if (c == '"')
1462 break;
1463 if (c == '\\' && *cp != '\0')
1464 ++cp;
1467 lastsp = 0;
1468 break;
1470 /* FALLTRHOUGH */
1471 default:
1472 if (lastsp) {
1473 lastsp = 0;
1474 if (gotaddr)
1475 *cp2++ = ' ';
1477 *cp2++ = c;
1478 if (c == ',') {
1479 if (!gotlt) {
1480 *cp2++ = ' ';
1481 for (; blankchar(*cp); ++cp)
1483 lastsp = 0;
1484 bufend = cp2;
1486 } else if (!gotaddr) {
1487 gotaddr = 1;
1488 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1492 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1493 if (agp->ag_iaddr_aend == 0)
1494 agp->ag_iaddr_aend = agp->ag_ilen;
1496 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1497 ac_free(nbuf);
1498 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1499 jcheck:
1500 rv = _addrspec_check(doskin, agp);
1501 jleave:
1502 NYD_LEAVE;
1503 return rv;
1506 FL char *
1507 realname(char const *name)
1509 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1510 char *rname, *rp;
1511 struct str in, out;
1512 int quoted, good, nogood;
1513 NYD_ENTER;
1515 if ((cp = n_UNCONST(name)) == NULL)
1516 goto jleave;
1517 for (; *cp != '\0'; ++cp) {
1518 switch (*cp) {
1519 case '(':
1520 if (cstart != NULL) {
1521 /* More than one comment in address, doesn't make sense to display
1522 * it without context. Return the entire field */
1523 cp = mime_fromaddr(name);
1524 goto jleave;
1526 cstart = cp++;
1527 cp = skip_comment(cp);
1528 cend = cp--;
1529 if (cend <= cstart)
1530 cend = cstart = NULL;
1531 break;
1532 case '"':
1533 while (*cp) {
1534 if (*++cp == '"')
1535 break;
1536 if (*cp == '\\' && cp[1])
1537 ++cp;
1539 break;
1540 case '<':
1541 if (cp > name) {
1542 cstart = name;
1543 cend = cp;
1545 break;
1546 case ',':
1547 /* More than one address. Just use the first one */
1548 goto jbrk;
1552 jbrk:
1553 if (cstart == NULL) {
1554 if (*name == '<') {
1555 /* If name contains only a route-addr, the surrounding angle brackets
1556 * don't serve any useful purpose when displaying, so remove */
1557 cp = prstr(skin(name));
1558 } else
1559 cp = mime_fromaddr(name);
1560 goto jleave;
1563 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1564 * not stripped. The idea is to strip only syntactical relevant things (but
1565 * this is not necessarily the most sensible way in practice) */
1566 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1567 quoted = 0;
1568 for (cp = cstart; cp < cend; ++cp) {
1569 if (*cp == '(' && !quoted) {
1570 cq = skip_comment(++cp);
1571 if (PTRCMP(--cq, >, cend))
1572 cq = cend;
1573 while (cp < cq) {
1574 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1575 ++cp;
1576 *rp++ = *cp++;
1578 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1579 *rp++ = *++cp;
1580 else if (*cp == '"') {
1581 quoted = !quoted;
1582 continue;
1583 } else
1584 *rp++ = *cp;
1586 *rp = '\0';
1587 in.s = rname;
1588 in.l = rp - rname;
1589 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1590 ac_free(rname);
1591 rname = savestr(out.s);
1592 free(out.s);
1594 while (blankchar(*rname))
1595 ++rname;
1596 for (rp = rname; *rp != '\0'; ++rp)
1598 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1599 *rp = '\0';
1600 if (rp == rname) {
1601 cp = mime_fromaddr(name);
1602 goto jleave;
1605 /* mime_fromhdr() has converted all nonprintable characters to question
1606 * marks now. These and blanks are considered uninteresting; if the
1607 * displayed part of the real name contains more than 25% of them, it is
1608 * probably better to display the plain email address instead */
1609 good = 0;
1610 nogood = 0;
1611 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1612 if (*rp == '?' || blankchar(*rp))
1613 ++nogood;
1614 else
1615 ++good;
1616 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1617 jleave:
1618 NYD_LEAVE;
1619 return n_UNCONST(cp);
1622 FL char *
1623 name1(struct message *mp, int reptype)
1625 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1626 size_t namesize, linesize = 0;
1627 FILE *ibuf;
1628 int f1st = 1;
1629 NYD_ENTER;
1631 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1632 goto jleave;
1633 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1634 goto jleave;
1636 namebuf = smalloc(namesize = 1);
1637 namebuf[0] = 0;
1638 if (mp->m_flag & MNOFROM)
1639 goto jout;
1640 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1641 goto jout;
1642 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1643 goto jout;
1645 jnewname:
1646 if (namesize <= linesize)
1647 namebuf = srealloc(namebuf, namesize = linesize +1);
1648 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1650 for (; blankchar(*cp); ++cp)
1652 for (cp2 = namebuf + strlen(namebuf);
1653 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1654 *cp2++ = *cp++;
1655 *cp2 = '\0';
1657 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1658 goto jout;
1659 if ((cp = strchr(linebuf, 'F')) == NULL)
1660 goto jout;
1661 if (strncmp(cp, "From", 4))
1662 goto jout;
1663 if (namesize <= linesize)
1664 namebuf = srealloc(namebuf, namesize = linesize + 1);
1666 while ((cp = strchr(cp, 'r')) != NULL) {
1667 if (!strncmp(cp, "remote", 6)) {
1668 if ((cp = strchr(cp, 'f')) == NULL)
1669 break;
1670 if (strncmp(cp, "from", 4) != 0)
1671 break;
1672 if ((cp = strchr(cp, ' ')) == NULL)
1673 break;
1674 cp++;
1675 if (f1st) {
1676 strncpy(namebuf, cp, namesize);
1677 f1st = 0;
1678 } else {
1679 cp2 = strrchr(namebuf, '!') + 1;
1680 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1682 namebuf[namesize - 2] = '!';
1683 namebuf[namesize - 1] = '\0';
1684 goto jnewname;
1686 cp++;
1688 jout:
1689 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1690 *cp == '\0')
1691 cp = savestr(namebuf);
1693 if (linebuf != NULL)
1694 free(linebuf);
1695 free(namebuf);
1696 jleave:
1697 NYD_LEAVE;
1698 return cp;
1701 FL char *
1702 subject_re_trim(char *s)
1704 struct {
1705 ui8_t len;
1706 char dat[7];
1707 } const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
1708 { 3, "re:" },
1709 { 3, "aw:" }, { 5, "antw:" }, /* de */
1710 { 0, "" }
1713 bool_t any = FAL0;
1714 char *orig_s = s, *re_st = NULL, *re_st_x;
1715 size_t re_l = 0 /* pacify CC */;
1716 NYD_ENTER;
1718 if ((re_st_x = ok_vlook(reply_strings)) != NULL &&
1719 (re_l = strlen(re_st_x)) > 0) {
1720 re_st = ac_alloc(++re_l * 2);
1721 memcpy(re_st, re_st_x, re_l);
1724 jouter:
1725 while (*s != '\0') {
1726 while (spacechar(*s))
1727 ++s;
1729 for (pp = ignored; pp->len > 0; ++pp)
1730 if (is_asccaseprefix(s, pp->dat)) {
1731 s += pp->len;
1732 any = TRU1;
1733 goto jouter;
1736 if (re_st != NULL) {
1737 char *cp;
1739 memcpy(re_st_x = re_st + re_l, re_st, re_l);
1740 while ((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
1741 if (is_asccaseprefix(s, cp)) {
1742 s += strlen(cp);
1743 any = TRU1;
1744 goto jouter;
1747 break;
1750 if (re_st != NULL)
1751 ac_free(re_st);
1752 NYD_LEAVE;
1753 return any ? s : orig_s;
1756 FL int
1757 msgidcmp(char const *s1, char const *s2)
1759 int q1 = 0, q2 = 0, c1, c2;
1760 NYD_ENTER;
1762 while(*s1 == '<')
1763 ++s1;
1764 while(*s2 == '<')
1765 ++s2;
1767 do {
1768 c1 = msgidnextc(&s1, &q1);
1769 c2 = msgidnextc(&s2, &q2);
1770 if (c1 != c2)
1771 break;
1772 } while (c1 && c2);
1773 NYD_LEAVE;
1774 return c1 - c2;
1777 FL char const *
1778 fakefrom(struct message *mp)
1780 char const *name;
1781 NYD_ENTER;
1783 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
1784 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
1785 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1786 * RFC 4155 however requires a RFC 5322 (2822) conforming
1787 * "addr-spec", but we simply can't provide that */
1788 name = "MAILER-DAEMON";
1789 NYD_LEAVE;
1790 return name;
1793 FL char const *
1794 fakedate(time_t t)
1796 char *cp, *cq;
1797 NYD_ENTER;
1799 cp = ctime(&t);
1800 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
1802 *cq = '\0';
1803 cp = savestr(cp);
1804 NYD_LEAVE;
1805 return cp;
1808 #ifdef HAVE_IMAP_SEARCH
1809 FL time_t
1810 unixtime(char const *fromline)
1812 char const *fp;
1813 char *xp;
1814 time_t t;
1815 int i, year, month, day, hour, minute, second, tzdiff;
1816 struct tm *tmptr;
1817 NYD2_ENTER;
1819 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
1821 fp -= 24;
1822 if (PTR2SIZE(fp - fromline) < 7)
1823 goto jinvalid;
1824 if (fp[3] != ' ')
1825 goto jinvalid;
1826 for (i = 0;;) {
1827 if (!strncmp(fp + 4, month_names[i], 3))
1828 break;
1829 if (month_names[++i][0] == '\0')
1830 goto jinvalid;
1832 month = i + 1;
1833 if (fp[7] != ' ')
1834 goto jinvalid;
1835 day = strtol(fp + 8, &xp, 10);
1836 if (*xp != ' ' || xp != fp + 10)
1837 goto jinvalid;
1838 hour = strtol(fp + 11, &xp, 10);
1839 if (*xp != ':' || xp != fp + 13)
1840 goto jinvalid;
1841 minute = strtol(fp + 14, &xp, 10);
1842 if (*xp != ':' || xp != fp + 16)
1843 goto jinvalid;
1844 second = strtol(fp + 17, &xp, 10);
1845 if (*xp != ' ' || xp != fp + 19)
1846 goto jinvalid;
1847 year = strtol(fp + 20, &xp, 10);
1848 if (xp != fp + 24)
1849 goto jinvalid;
1850 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1851 goto jinvalid;
1852 tzdiff = t - mktime(gmtime(&t));
1853 tmptr = localtime(&t);
1854 if (tmptr->tm_isdst > 0)
1855 tzdiff += 3600;
1856 t -= tzdiff;
1857 jleave:
1858 NYD2_LEAVE;
1859 return t;
1860 jinvalid:
1861 t = n_time_epoch();
1862 goto jleave;
1864 #endif /* HAVE_IMAP_SEARCH */
1866 FL time_t
1867 rfctime(char const *date)
1869 char const *cp = date;
1870 char *x;
1871 time_t t;
1872 int i, year, month, day, hour, minute, second;
1873 NYD2_ENTER;
1875 if ((cp = nexttoken(cp)) == NULL)
1876 goto jinvalid;
1877 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
1878 cp[3] == ',') {
1879 if ((cp = nexttoken(&cp[4])) == NULL)
1880 goto jinvalid;
1882 day = strtol(cp, &x, 10); /* XXX strtol */
1883 if ((cp = nexttoken(x)) == NULL)
1884 goto jinvalid;
1885 for (i = 0;;) {
1886 if (!strncmp(cp, month_names[i], 3))
1887 break;
1888 if (month_names[++i][0] == '\0')
1889 goto jinvalid;
1891 month = i + 1;
1892 if ((cp = nexttoken(&cp[3])) == NULL)
1893 goto jinvalid;
1894 /* RFC 5322, 4.3:
1895 * Where a two or three digit year occurs in a date, the year is to be
1896 * interpreted as follows: If a two digit year is encountered whose
1897 * value is between 00 and 49, the year is interpreted by adding 2000,
1898 * ending up with a value between 2000 and 2049. If a two digit year
1899 * is encountered with a value between 50 and 99, or any three digit
1900 * year is encountered, the year is interpreted by adding 1900 */
1901 year = strtol(cp, &x, 10); /* XXX strtol */
1902 i = (int)PTR2SIZE(x - cp);
1903 if (i == 2 && year >= 0 && year <= 49)
1904 year += 2000;
1905 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
1906 year += 1900;
1907 if ((cp = nexttoken(x)) == NULL)
1908 goto jinvalid;
1909 hour = strtol(cp, &x, 10); /* XXX strtol */
1910 if (*x != ':')
1911 goto jinvalid;
1912 cp = &x[1];
1913 minute = strtol(cp, &x, 10);
1914 if (*x == ':') {
1915 cp = x + 1;
1916 second = strtol(cp, &x, 10);
1917 } else
1918 second = 0;
1920 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
1921 goto jinvalid;
1922 if ((cp = nexttoken(x)) != NULL) {
1923 char buf[3];
1924 int sign = 1;
1926 switch (*cp) {
1927 case '+':
1928 sign = -1;
1929 /* FALLTHRU */
1930 case '-':
1931 ++cp;
1932 break;
1934 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
1935 digitchar(cp[3])) {
1936 long tadj;
1937 buf[2] = '\0';
1938 buf[0] = cp[0];
1939 buf[1] = cp[1];
1940 tadj = strtol(buf, NULL, 10) * 3600;/*XXX strtrol*/
1941 buf[0] = cp[2];
1942 buf[1] = cp[3];
1943 tadj += strtol(buf, NULL, 10) * 60; /* XXX strtol*/
1944 if (sign < 0)
1945 tadj = -tadj;
1946 t += tadj;
1948 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1949 * TODO once again, Christos Zoulas and NetBSD Mail have done
1950 * TODO a really good job already, but using strptime(3), which
1951 * TODO is not portable. Nonetheless, WE must improve, not
1952 * TODO at last because we simply ignore obsolete timezones!!
1953 * TODO See RFC 5322, 4.3! */
1955 jleave:
1956 NYD2_LEAVE;
1957 return t;
1958 jinvalid:
1959 t = 0;
1960 goto jleave;
1963 FL time_t
1964 combinetime(int year, int month, int day, int hour, int minute, int second){
1965 size_t const jdn_epoch = 2440588;
1966 bool_t const y2038p = (sizeof(time_t) == 4);
1968 size_t jdn;
1969 time_t t;
1970 NYD2_ENTER;
1972 if(UICMP(32, second, >=, DATE_SECSMIN) || /* XXX (leap- */
1973 UICMP(32, minute, >=, DATE_MINSHOUR) ||
1974 UICMP(32, hour, >=, DATE_HOURSDAY) ||
1975 day < 1 || day > 31 ||
1976 month < 1 || month > 12 ||
1977 year < 1970)
1978 goto jerr;
1980 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
1981 (DATE_SECSDAY * DATE_DAYSYEAR))){
1982 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
1983 * test by stepping second-wise around the flip. Don't care otherwise */
1984 if(!y2038p)
1985 goto jerr;
1986 if(year > 2038 || month > 1 || day > 19 ||
1987 hour > 3 || minute > 14 || second > 7)
1988 goto jerr;
1991 t = second;
1992 t += minute * DATE_SECSMIN;
1993 t += hour * DATE_SECSHOUR;
1995 jdn = a_head_gregorian_to_jdn(year, month, day);
1996 jdn -= jdn_epoch;
1997 t += (time_t)jdn * DATE_SECSDAY;
1998 jleave:
1999 NYD2_LEAVE;
2000 return t;
2001 jerr:
2002 t = (time_t)-1;
2003 goto jleave;
2006 FL void
2007 substdate(struct message *m)
2009 char const *cp;
2010 NYD_ENTER;
2012 /* Determine the date to print in faked 'From ' lines. This is traditionally
2013 * the date the message was written to the mail file. Try to determine this
2014 * using RFC message header fields, or fall back to current time */
2015 if ((cp = hfield1("received", m)) != NULL) {
2016 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2018 ++cp;
2019 while (alnumchar(*cp));
2021 if (cp && *++cp)
2022 m->m_time = rfctime(cp);
2024 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2025 if ((cp = hfield1("date", m)) != NULL)
2026 m->m_time = rfctime(cp);
2028 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2029 m->m_time = time_current.tc_time;
2030 NYD_LEAVE;
2033 FL void
2034 setup_from_and_sender(struct header *hp)
2036 char const *addr;
2037 struct name *np;
2038 NYD_ENTER;
2040 /* If -t parsed or composed From: then take it. With -t we otherwise
2041 * want -r to be honoured in favour of *from* in order to have
2042 * a behaviour that is compatible with what users would expect from e.g.
2043 * postfix(1) */
2044 if ((np = hp->h_from) != NULL ||
2045 ((pstate & PS_t_FLAG) && (np = option_r_arg) != NULL)) {
2047 } else if ((addr = myaddrs(hp)) != NULL)
2048 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2049 hp->h_from = np;
2051 if ((np = hp->h_sender) != NULL) {
2053 } else if ((addr = ok_vlook(sender)) != NULL)
2054 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2055 hp->h_sender = np;
2057 NYD_LEAVE;
2060 FL struct name const *
2061 check_from_and_sender(struct name const *fromfield,
2062 struct name const *senderfield)
2064 struct name const *rv = NULL;
2065 NYD_ENTER;
2067 if (senderfield != NULL) {
2068 if (senderfield->n_flink != NULL) {
2069 n_err(_("The Sender: field may contain only one address\n"));
2070 goto jleave;
2072 rv = senderfield;
2075 if (fromfield != NULL) {
2076 if (fromfield->n_flink != NULL && senderfield == NULL) {
2077 n_err(_("A Sender: is required when there are multiple "
2078 "addresses in From:\n"));
2079 goto jleave;
2081 if (rv == NULL)
2082 rv = fromfield;
2085 if (rv == NULL)
2086 rv = (struct name*)0x1;
2087 jleave:
2088 NYD_LEAVE;
2089 return rv;
2092 #ifdef HAVE_XSSL
2093 FL char *
2094 getsender(struct message *mp)
2096 char *cp;
2097 struct name *np;
2098 NYD_ENTER;
2100 if ((cp = hfield1("from", mp)) == NULL ||
2101 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2102 cp = NULL;
2103 else
2104 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2105 NYD_LEAVE;
2106 return cp;
2108 #endif
2110 FL int
2111 grab_headers(enum n_lexinput_flags lif, struct header *hp, enum gfield gflags,
2112 int subjfirst)
2114 /* TODO grab_headers: again, check counts etc. against RFC;
2115 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2116 int errs;
2117 int volatile comma;
2118 NYD_ENTER;
2120 errs = 0;
2121 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2123 if (gflags & GTO)
2124 hp->h_to = grab_names(lif, "To: ", hp->h_to, comma, GTO | GFULL);
2125 if (subjfirst && (gflags & GSUBJECT))
2126 hp->h_subject = n_lex_input_cp(lif, "Subject: ", hp->h_subject);
2127 if (gflags & GCC)
2128 hp->h_cc = grab_names(lif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2129 if (gflags & GBCC)
2130 hp->h_bcc = grab_names(lif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2132 if (gflags & GEXTRA) {
2133 if (hp->h_from == NULL)
2134 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2135 hp->h_from = grab_names(lif, "From: ", hp->h_from, comma,
2136 GEXTRA | GFULL | GFULLEXTRA);
2137 if (hp->h_replyto == NULL)
2138 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
2139 hp->h_replyto = grab_names(lif, "Reply-To: ", hp->h_replyto, comma,
2140 GEXTRA | GFULL);
2141 if (hp->h_sender == NULL)
2142 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2143 hp->h_sender = grab_names(lif, "Sender: ", hp->h_sender, comma,
2144 GEXTRA | GFULL);
2147 if (!subjfirst && (gflags & GSUBJECT))
2148 hp->h_subject = n_lex_input_cp(lif, "Subject: ", hp->h_subject);
2150 NYD_LEAVE;
2151 return errs;
2154 FL bool_t
2155 header_match(struct message *mp, struct search_expr const *sep)
2157 struct str in, out;
2158 FILE *ibuf;
2159 int lc;
2160 size_t linesize = 0; /* TODO line pool */
2161 char *linebuf = NULL, *colon;
2162 bool_t rv = FAL0;
2163 NYD_ENTER;
2165 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2166 goto jleave;
2167 if ((lc = mp->m_lines - 1) < 0)
2168 goto jleave;
2170 if ((mp->m_flag & MNOFROM) == 0 &&
2171 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2172 goto jleave;
2173 while (lc > 0) {
2174 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2175 break;
2176 if (blankchar(*++colon))
2177 ++colon;
2178 in.l = strlen(in.s = colon);
2179 mime_fromhdr(&in, &out, TD_ICONV);
2180 #ifdef HAVE_REGEX
2181 if (sep->ss_sexpr == NULL)
2182 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2183 else
2184 #endif
2185 rv = substr(out.s, sep->ss_sexpr);
2186 free(out.s);
2187 if (rv)
2188 break;
2191 jleave:
2192 if (linebuf != NULL)
2193 free(linebuf);
2194 NYD_LEAVE;
2195 return rv;
2198 /* s-it-mode */