nail.1: last fixes
[s-mailx.git] / head.c
blob6f99b01b3cb63d7931b6b5d8b34dd1c5429e57d9
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
3 *@ TODO Mostly a hackery, we need RFC compliant parsers instead.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE head
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 struct cmatch_data {
44 size_t tlen; /* Length of .tdata */
45 char const *tdata; /* Template date - see _cmatch_data[] */
48 /* Template characters for cmatch_data.tdata:
49 * 'A' An upper case char
50 * 'a' A lower case char
51 * ' ' A space
52 * '0' A digit
53 * 'O' An optional digit or space
54 * ':' A colon
55 * '+' Either a plus or a minus sign */
56 static struct cmatch_data const _cmatch_data[] = {
57 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
58 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
59 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
60 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
61 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
62 * in the wild (by UW-imap) */
63 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
64 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
65 * zone strings; note that 1. is strictly speaking not correct as some
66 * letters are not used, and 2. is not because only "UT" is defined */
67 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
68 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
69 { 0, NULL }
71 #define a_HEAD_DATE_MINLEN 21
73 /* Skip over "word" as found in From_ line */
74 static char const * _from__skipword(char const *wp);
76 /* Match the date string against the date template (tp), return if match.
77 * See _cmatch_data[] for template character description */
78 static int _cmatch(size_t len, char const *date,
79 char const *tp);
81 /* Check whether date is a valid 'From_' date.
82 * (Rather ctime(3) generated dates, according to RFC 4155) */
83 static int _is_date(char const *date);
85 /* JulianDayNumber converter(s) */
86 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
87 #if 0
88 static void a_head_jdn_to_gregorian(size_t jdn,
89 ui32_t *yp, ui32_t *mp, ui32_t *dp);
90 #endif
92 /* Convert the domain part of a skinned address to IDNA.
93 * If an error occurs before Unicode information is available, revert the IDNA
94 * error to a normal CHAR one so that the error message doesn't talk Unicode */
95 #ifdef HAVE_IDNA
96 static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp);
97 #endif
99 /* Classify and check a (possibly skinned) header body according to RFC
100 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
101 * also a file or a pipe command, so check that first, then.
102 * Otherwise perform content checking and isolate the domain part (for IDNA) */
103 static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned);
105 /* Return the next header field found in the given message.
106 * Return >= 0 if something found, < 0 elsewise.
107 * "colon" is set to point to the colon in the header.
108 * Must deal with \ continuations & other such fraud */
109 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
110 int rem, char **colon);
112 static int msgidnextc(char const **cp, int *status);
114 /* Count the occurances of c in str */
115 static int charcount(char *str, int c);
117 static char const * nexttoken(char const *cp);
119 /* TODO v15: change *customhdr* syntax and use shell tokens?! */
120 static char *a_head_customhdr__sep(char **iolist);
122 static char const *
123 _from__skipword(char const *wp)
125 char c = 0;
126 NYD2_ENTER;
128 if (wp != NULL) {
129 while ((c = *wp++) != '\0' && !blankchar(c)) {
130 if (c == '"') {
131 while ((c = *wp++) != '\0' && c != '"')
133 if (c != '"')
134 --wp;
137 for (; blankchar(c); c = *wp++)
140 NYD2_LEAVE;
141 return (c == 0 ? NULL : wp - 1);
144 static int
145 _cmatch(size_t len, char const *date, char const *tp)
147 int ret = 0;
148 NYD2_ENTER;
150 while (len--) {
151 char c = date[len];
152 switch (tp[len]) {
153 case 'a':
154 if (!lowerchar(c))
155 goto jleave;
156 break;
157 case 'A':
158 if (!upperchar(c))
159 goto jleave;
160 break;
161 case ' ':
162 if (c != ' ')
163 goto jleave;
164 break;
165 case '0':
166 if (!digitchar(c))
167 goto jleave;
168 break;
169 case 'O':
170 if (c != ' ' && !digitchar(c))
171 goto jleave;
172 break;
173 case ':':
174 if (c != ':')
175 goto jleave;
176 break;
177 case '+':
178 if (c != '+' && c != '-')
179 goto jleave;
180 break;
183 ret = 1;
184 jleave:
185 NYD2_LEAVE;
186 return ret;
189 static int
190 _is_date(char const *date)
192 struct cmatch_data const *cmdp;
193 size_t dl;
194 int rv = 0;
195 NYD2_ENTER;
197 if ((dl = strlen(date)) >= a_HEAD_DATE_MINLEN)
198 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
199 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
200 break;
201 NYD2_LEAVE;
202 return rv;
205 static size_t
206 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
207 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
208 * (via third hand, plus adjustments).
209 * This algorithm is supposed to work for all dates in between 1582-10-15
210 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
211 size_t jdn;
212 NYD2_ENTER;
214 #if 0
215 if(y == 0)
216 y = 1;
217 if(m == 0)
218 m = 1;
219 if(d == 0)
220 d = 1;
221 #endif
223 if(m > 2)
224 m -= 3;
225 else{
226 m += 9;
227 --y;
229 jdn = y;
230 jdn /= 100;
231 y -= 100 * jdn;
232 y *= 1461;
233 y >>= 2;
234 jdn *= 146097;
235 jdn >>= 2;
236 jdn += y;
237 jdn += d;
238 jdn += 1721119;
239 m *= 153;
240 m += 2;
241 m /= 5;
242 jdn += m;
243 NYD2_LEAVE;
244 return jdn;
247 #if 0
248 static void
249 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
250 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
251 * (via third hand, plus adjustments) */
252 size_t y, x;
253 NYD2_ENTER;
255 jdn -= 1721119;
256 jdn <<= 2;
257 --jdn;
258 y = jdn / 146097;
259 jdn %= 146097;
260 jdn |= 3;
261 y *= 100;
262 y += jdn / 1461;
263 jdn %= 1461;
264 jdn += 4;
265 jdn >>= 2;
266 x = jdn;
267 jdn <<= 2;
268 jdn += x;
269 jdn -= 3;
270 x = jdn / 153; /* x -> month */
271 jdn %= 153;
272 jdn += 5;
273 jdn /= 5; /* jdn -> day */
274 if(x < 10)
275 x += 3;
276 else{
277 x -= 9;
278 ++y;
281 *yp = (ui32_t)(y & 0xFFFF);
282 *mp = (ui32_t)(x & 0xFF);
283 *dp = (ui32_t)(jdn & 0xFF);
284 NYD2_LEAVE;
286 #endif /* 0 */
288 #ifdef HAVE_IDNA
289 static struct n_addrguts *
290 a_head_idna_apply(struct n_addrguts *agp){
291 struct n_string idna_ascii;
292 NYD_ENTER;
294 n_string_creat_auto(&idna_ascii);
296 if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
297 agp->ag_slen - agp->ag_sdom_start))
298 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
299 else{
300 /* Replace the domain part of .ag_skinned with IDNA version */
301 n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
303 agp->ag_skinned = n_string_cp(&idna_ascii);
304 agp->ag_slen = idna_ascii.s_len;
305 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
306 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
308 NYD_LEAVE;
309 return agp;
311 #endif /* HAVE_IDNA */
313 static bool_t
314 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned)
316 char *addr, *p;
317 bool_t in_quote;
318 ui8_t in_domain, hadat;
319 union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
320 #ifdef HAVE_IDNA
321 ui8_t use_idna;
322 #endif
323 NYD_ENTER;
325 #ifdef HAVE_IDNA
326 use_idna = ok_blook(idna_disable) ? 0 : 1;
327 #endif
328 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
329 addr = agp->ag_skinned;
331 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
332 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
333 goto jleave;
336 /* If the field is not a recipient, it cannot be a file or a pipe */
337 if (!skinned)
338 goto jaddr_check;
340 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
341 * grep the latter for the complete picture */
342 if (*addr == '|') {
343 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
344 goto jleave;
346 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
347 (addr[0] == '-' && addr[1] == '\0'))
348 goto jisfile;
349 if (memchr(addr, '@', agp->ag_slen) == NULL) {
350 if (*addr == '+')
351 goto jisfile;
352 for (p = addr; (c.c = *p); ++p) {
353 if (c.c == '!' || c.c == '%')
354 break;
355 if (c.c == '/') {
356 jisfile:
357 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
358 goto jleave;
363 jaddr_check:
364 in_quote = FAL0;
365 in_domain = hadat = 0;
367 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser! */
368 for (p = addr; (c.c = *p++) != '\0';) {
369 if (c.c == '"') {
370 in_quote = !in_quote;
371 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
372 #ifdef HAVE_IDNA
373 if (in_domain && use_idna > 0) {
374 if (use_idna == 1)
375 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
376 c.u);
377 use_idna = 2;
378 } else
379 #endif
380 break;
381 } else if (in_domain == 2) {
382 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
383 break;
384 } else if (in_quote && in_domain == 0) {
385 /*EMPTY*/;
386 } else if (c.c == '\\' && *p != '\0') {
387 ++p;
388 } else if (c.c == '@') {
389 if (hadat++ > 0) {
390 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
391 c.u);
392 goto jleave;
394 agp->ag_sdom_start = PTR2SIZE(p - addr);
395 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
396 in_domain = (*p == '[') ? 2 : 1;
397 continue;
398 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
399 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
400 c.c == '\\' || c.c == ',')
401 break;
402 hadat = 0;
404 if (c.c != '\0') {
405 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
406 goto jleave;
409 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
410 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
411 if(!n_alias_is_valid_name(agp->ag_input))
412 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
413 }else{
414 /* If we seem to know that this is an address. Ensure this is correct
415 * according to RFC 5322 TODO the entire address parser should be like
416 * TODO that for one, and then we should know whether structured or
417 * TODO unstructured, and just parse correctly overall!
418 * TODO In addition, this can be optimised a lot.
419 * TODO And it is far from perfect: it should not forget whether no
420 * TODO whitespace followed some snippet, and it was written hastily */
421 struct a_token{
422 struct a_token *t_last;
423 struct a_token *t_next;
424 enum{
425 a_T_TATOM = 1<<0,
426 a_T_TCOMM = 1<<1,
427 a_T_TQUOTE = 1<<2,
428 a_T_TADDR = 1<<3,
429 a_T_TMASK = (1<<4) - 1,
431 a_T_SPECIAL = 1<<8 /* An atom actually needs to go TQUOTE */
432 } t_f;
433 ui8_t t__pad[4];
434 size_t t_start;
435 size_t t_end;
436 } *thead, *tcurr, *tp;
438 struct n_string ost, *ostp;
439 char const *cp, *cp1st, *cpmax, *xp;
440 void *lofi_snap;
442 /* Name and domain must be non-empty */
443 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
444 c.c = '@';
445 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
446 goto jleave;
449 #ifdef HAVE_IDNA
450 if(use_idna == 2)
451 agp = a_head_idna_apply(agp);
452 #endif
454 cp = agp->ag_input;
456 /* Nothing to do if there is only an address (in angle brackets) */
457 if(agp->ag_iaddr_start == 0){
458 if(agp->ag_iaddr_aend == agp->ag_ilen)
459 goto jleave;
460 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
461 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
462 cp[agp->ag_iaddr_aend] == '>')
463 goto jleave;
465 /* It is not, so parse off all tokens, then resort and rejoin */
466 lofi_snap = n_lofi_snap_create();
468 cp1st = cp;
469 if((c.ui32 = agp->ag_iaddr_start) > 0)
470 --c.ui32;
471 cpmax = &cp[c.ui32];
473 thead = tcurr = NULL;
474 hadat = FAL0;
475 jnode_redo:
476 for(tp = NULL; cp < cpmax;){
477 switch((c.c = *cp)){
478 case '(':
479 if(tp != NULL)
480 tp->t_end = PTR2SIZE(cp - cp1st);
481 tp = n_lofi_alloc(sizeof *tp);
482 tp->t_next = NULL;
483 if((tp->t_last = tcurr) != NULL)
484 tcurr->t_next = tp;
485 else
486 thead = tp;
487 tcurr = tp;
488 tp->t_f = a_T_TCOMM;
489 tp->t_start = PTR2SIZE(++cp - cp1st);
490 xp = skip_comment(cp);
491 tp->t_end = PTR2SIZE(xp - cp1st);
492 cp = xp;
493 if(tp->t_end > tp->t_start){
494 if(xp[-1] == ')')
495 --tp->t_end;
496 else{
497 /* No closing comment - strip trailing whitespace */
498 while(blankchar(*--xp))
499 if(--tp->t_end == tp->t_start)
500 break;
503 tp = NULL;
504 break;
506 case '"':
507 if(tp != NULL)
508 tp->t_end = PTR2SIZE(cp - cp1st);
509 tp = n_lofi_alloc(sizeof *tp);
510 tp->t_next = NULL;
511 if((tp->t_last = tcurr) != NULL)
512 tcurr->t_next = tp;
513 else
514 thead = tp;
515 tcurr = tp;
516 tp->t_f = a_T_TQUOTE;
517 tp->t_start = PTR2SIZE(++cp - cp1st);
518 for(xp = cp; xp < cpmax; ++xp){
519 if((c.c = *xp) == '"')
520 break;
521 if(c.c == '\\' && xp[1] != '\0')
522 ++xp;
524 tp->t_end = PTR2SIZE(xp - cp1st);
525 cp = &xp[1];
526 if(tp->t_end > tp->t_start){
527 /* No closing quote - strip trailing whitespace */
528 if(*xp != '"'){
529 while(blankchar(*xp--))
530 if(--tp->t_end == tp->t_start)
531 break;
534 tp = NULL;
535 break;
537 default:
538 if(blankchar(c.c)){
539 if(tp != NULL)
540 tp->t_end = PTR2SIZE(cp - cp1st);
541 tp = NULL;
542 ++cp;
543 break;
546 if(tp == NULL){
547 tp = n_lofi_alloc(sizeof *tp);
548 tp->t_next = NULL;
549 if((tp->t_last = tcurr) != NULL)
550 tcurr->t_next = tp;
551 else
552 thead = tp;
553 tcurr = tp;
554 tp->t_f = a_T_TATOM;
555 tp->t_start = PTR2SIZE(cp - cp1st);
557 ++cp;
559 /* Reverse solidus transforms the following into a quoted-pair, and
560 * therefore (must occur in comment or quoted-string only) the
561 * entire atom into a quoted string */
562 if(c.c == '\\'){
563 tp->t_f |= a_T_SPECIAL;
564 if(cp < cpmax)
565 ++cp;
567 /* Is this plain RFC 5322 "atext", or "specials"? Because we don't
568 * TODO know structured/unstructured, nor anything else, we need to
569 * TODO treat "dot-atom" as being identical to "specials" */
570 else if(!alnumchar(c.c) &&
571 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
572 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
573 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
574 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
575 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
576 tp->t_f |= a_T_SPECIAL;
577 break;
580 if(tp != NULL)
581 tp->t_end = PTR2SIZE(cp - cp1st);
583 if(hadat == FAL0){
584 hadat = TRU1;
585 tp = n_lofi_alloc(sizeof *tp);
586 tp->t_next = NULL;
587 if((tp->t_last = tcurr) != NULL)
588 tcurr->t_next = tp;
589 else
590 thead = tp;
591 tcurr = tp;
592 tp->t_f = a_T_TADDR;
593 tp->t_start = agp->ag_iaddr_start;
594 tp->t_end = agp->ag_iaddr_aend;
595 tp = NULL;
597 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
598 cpmax = &agp->ag_input[agp->ag_ilen];
599 if(cp < cpmax)
600 goto jnode_redo;
603 /* Nothing may follow the address, move it to the end */
604 if(!(tcurr->t_f & a_T_TADDR)){
605 for(tp = thead; tp != NULL; tp = tp->t_next){
606 if(tp->t_f & a_T_TADDR){
607 if(tp->t_last != NULL)
608 tp->t_last->t_next = tp->t_next;
609 else
610 thead = tp->t_next;
611 if(tp->t_next != NULL)
612 tp->t_next->t_last = tp->t_last;
614 tcurr = tp;
615 while(tp->t_next != NULL)
616 tp = tp->t_next;
617 tp->t_next = tcurr;
618 tcurr->t_last = tp;
619 tcurr->t_next = NULL;
620 break;
625 /* Make ranges contiguous: ensure a continuous range of atoms is converted
626 * to a SPECIAL one if at least one of them requires it */
627 for(tp = thead; tp != NULL; tp = tp->t_next){
628 if(tp->t_f & a_T_SPECIAL){
629 tcurr = tp;
630 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
631 tp->t_f |= a_T_SPECIAL;
632 tp = tcurr;
633 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
634 tp->t_f |= a_T_SPECIAL;
638 /* And yes, we want quotes to extend as much as possible */
639 for(tp = thead; tp != NULL; tp = tp->t_next){
640 if(tp->t_f & a_T_TQUOTE){
641 tcurr = tp;
642 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
643 tp->t_f |= a_T_SPECIAL;
644 tp = tcurr;
645 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
646 tp->t_f |= a_T_SPECIAL;
650 /* Then rejoin */
651 ostp = n_string_creat_auto(&ost);
652 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
653 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
655 for(tcurr = thead; tcurr != NULL;){
656 if(tcurr != thead)
657 ostp = n_string_push_c(ostp, ' ');
658 if(tcurr->t_f & a_T_TADDR){
659 ostp = n_string_push_c(ostp, '<');
660 agp->ag_iaddr_start = ostp->s_len;
661 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
662 (tcurr->t_end - tcurr->t_start));
663 agp->ag_iaddr_aend = ostp->s_len;
664 ostp = n_string_push_c(ostp, '>');
665 tcurr = tcurr->t_next;
666 }else if(tcurr->t_f & a_T_TCOMM){
667 ostp = n_string_push_c(ostp, '(');
668 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
669 (tcurr->t_end - tcurr->t_start));
670 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
671 tcurr = tp;
672 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
673 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
674 (tcurr->t_end - tcurr->t_start));
676 ostp = n_string_push_c(ostp, ')');
677 tcurr = tcurr->t_next;
678 }else if(tcurr->t_f & a_T_TQUOTE){
679 jput_quote:
680 ostp = n_string_push_c(ostp, '"');
681 tp = tcurr;
682 do/* while tcurr && TATOM||TQUOTE */{
683 cp = &cp1st[tcurr->t_start];
684 cpmax = &cp1st[tcurr->t_end];
685 if(cp == cpmax)
686 continue;
688 if(tcurr != tp)
689 ostp = n_string_push_c(ostp, ' ');
691 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
692 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
693 else{
694 bool_t esc;
696 for(esc = FAL0; cp < cpmax;){
697 if((c.c = *cp++) == '\\' && !esc){
698 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
699 esc = TRU1;
700 }else{
701 if(esc || c.c == '"'){
702 jput_quote_esc:
703 ostp = n_string_push_c(ostp, '\\');
705 ostp = n_string_push_c(ostp, c.c);
706 esc = FAL0;
709 if(esc){
710 c.c = '\\';
711 goto jput_quote_esc;
714 }while((tcurr = tcurr->t_next) != NULL &&
715 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
716 ostp = n_string_push_c(ostp, '"');
717 }else if(tcurr->t_f & a_T_SPECIAL)
718 goto jput_quote;
719 else{
720 /* Can we use a fast join mode? */
721 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
722 if(!(tcurr->t_f & a_T_TATOM))
723 break;
724 if(tcurr != tp)
725 ostp = n_string_push_c(ostp, ' ');
726 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
727 (tcurr->t_end - tcurr->t_start));
732 n_lofi_snap_unroll(lofi_snap);
734 agp->ag_input = n_string_cp(ostp);
735 agp->ag_ilen = ostp->s_len;
736 /*ostp = n_string_drop_ownership(ostp);*/
738 jleave:
739 NYD_LEAVE;
740 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) == 0);
743 static int
744 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
746 char *line2 = NULL, *cp, *cp2;
747 size_t line2size = 0;
748 int c, isenc;
749 NYD2_ENTER;
751 if (*linebuf == NULL)
752 *linebuf = srealloc(*linebuf, *linesize = 1);
753 **linebuf = '\0';
754 for (;;) {
755 if (--rem < 0) {
756 rem = -1;
757 break;
759 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
760 rem = -1;
761 break;
763 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
765 if (cp > *linebuf)
766 while (blankchar(*cp))
767 ++cp;
768 if (*cp != ':' || cp == *linebuf)
769 continue;
771 /* I guess we got a headline. Handle wraparound */
772 *colon = cp;
773 cp = *linebuf + c;
774 for (;;) {
775 isenc = 0;
776 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
778 cp++;
779 if (rem <= 0)
780 break;
781 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
782 isenc |= 1;
783 ungetc(c = getc(f), f);
784 if (!blankchar(c))
785 break;
786 c = readline_restart(f, &line2, &line2size, 0);
787 if (c < 0)
788 break;
789 --rem;
790 for (cp2 = line2; blankchar(*cp2); ++cp2)
792 c -= (int)PTR2SIZE(cp2 - line2);
793 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
794 isenc |= 2;
795 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
796 size_t diff = PTR2SIZE(cp - *linebuf),
797 colondiff = PTR2SIZE(*colon - *linebuf);
798 *linebuf = srealloc(*linebuf, *linesize += c + 2);
799 cp = &(*linebuf)[diff];
800 *colon = &(*linebuf)[colondiff];
802 if (isenc != 3)
803 *cp++ = ' ';
804 memcpy(cp, cp2, c);
805 cp += c;
807 *cp = '\0';
809 if (line2 != NULL)
810 free(line2);
811 break;
813 NYD2_LEAVE;
814 return rem;
817 static int
818 msgidnextc(char const **cp, int *status)
820 int c;
821 NYD2_ENTER;
823 assert(cp != NULL);
824 assert(*cp != NULL);
825 assert(status != NULL);
827 for (;;) {
828 if (*status & 01) {
829 if (**cp == '"') {
830 *status &= ~01;
831 (*cp)++;
832 continue;
834 if (**cp == '\\') {
835 (*cp)++;
836 if (**cp == '\0')
837 goto jeof;
839 goto jdfl;
841 switch (**cp) {
842 case '(':
843 *cp = skip_comment(&(*cp)[1]);
844 continue;
845 case '>':
846 case '\0':
847 jeof:
848 c = '\0';
849 goto jleave;
850 case '"':
851 (*cp)++;
852 *status |= 01;
853 continue;
854 case '@':
855 *status |= 02;
856 /*FALLTHRU*/
857 default:
858 jdfl:
859 c = *(*cp)++ & 0377;
860 c = (*status & 02) ? lowerconv(c) : c;
861 goto jleave;
864 jleave:
865 NYD2_LEAVE;
866 return c;
869 static int
870 charcount(char *str, int c)
872 char *cp;
873 int i;
874 NYD2_ENTER;
876 for (i = 0, cp = str; *cp; ++cp)
877 if (*cp == c)
878 ++i;
879 NYD2_LEAVE;
880 return i;
883 static char const *
884 nexttoken(char const *cp)
886 NYD2_ENTER;
887 for (;;) {
888 if (*cp == '\0') {
889 cp = NULL;
890 break;
893 if (*cp == '(') {
894 size_t nesting = 1;
896 do switch (*++cp) {
897 case '(':
898 ++nesting;
899 break;
900 case ')':
901 --nesting;
902 break;
903 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
904 } else if (blankchar(*cp) || *cp == ',')
905 ++cp;
906 else
907 break;
909 NYD2_LEAVE;
910 return cp;
913 static char *
914 a_head_customhdr__sep(char **iolist){
915 char *cp, c, *base;
916 bool_t isesc, anyesc;
917 NYD2_ENTER;
919 for(base = *iolist; base != NULL; base = *iolist){
920 while((c = *base) != '\0' && blankspacechar(c))
921 ++base;
923 for(isesc = anyesc = FAL0, cp = base;; ++cp){
924 if(n_UNLIKELY((c = *cp) == '\0')){
925 *iolist = NULL;
926 break;
927 }else if(!isesc){
928 if(c == ','){
929 *iolist = cp + 1;
930 break;
932 isesc = (c == '\\');
933 }else{
934 isesc = FAL0;
935 anyesc |= (c == ',');
939 while(cp > base && blankspacechar(cp[-1]))
940 --cp;
941 *cp = '\0';
943 if(*base != '\0'){
944 if(anyesc){
945 char *ins;
947 for(ins = cp = base;; ++ins)
948 if((c = *cp) == '\\' && cp[1] == ','){
949 *ins = ',';
950 cp += 2;
951 }else if((*ins = (++cp, c)) == '\0')
952 break;
954 break;
957 NYD2_LEAVE;
958 return base;
961 FL char const *
962 myaddrs(struct header *hp) /* TODO */
964 struct name *np;
965 char const *rv, *mta;
966 NYD_ENTER;
968 if (hp != NULL && (np = hp->h_from) != NULL) {
969 if ((rv = np->n_fullname) != NULL)
970 goto jleave;
971 if ((rv = np->n_name) != NULL)
972 goto jleave;
975 if((rv = ok_vlook(from)) != NULL){
976 if((np = lextract(rv, GEXTRA | GFULL)) == NULL)
977 jefrom:
978 n_err(_("An address given in *from* is invalid: %s\n"), rv);
979 else for(; np != NULL; np = np->n_flink)
980 if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
981 goto jefrom;
982 goto jleave;
985 /* When invoking *sendmail* directly, it's its task to generate an otherwise
986 * undeterminable From: address. However, if the user sets *hostname*,
987 * accept his desire */
988 if (ok_vlook(hostname) != NULL)
989 goto jnodename;
990 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
991 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
992 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
993 goto jnodename;
994 jleave:
995 NYD_LEAVE;
996 return rv;
998 jnodename:{
999 char *cp;
1000 char const *hn, *ln;
1001 size_t i;
1003 hn = n_nodename(TRU1);
1004 ln = ok_vlook(LOGNAME);
1005 i = strlen(ln) + strlen(hn) + 1 +1;
1006 rv = cp = salloc(i);
1007 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1009 goto jleave;
1012 FL char const *
1013 myorigin(struct header *hp) /* TODO */
1015 char const *rv = NULL, *ccp;
1016 struct name *np;
1017 NYD_ENTER;
1019 if((ccp = myaddrs(hp)) != NULL &&
1020 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1021 if(np->n_flink == NULL)
1022 rv = ccp;
1023 else if((ccp = ok_vlook(sender)) != NULL) {
1024 if((np = lextract(ccp, GEXTRA | GFULL)) == NULL ||
1025 np->n_flink != NULL ||
1026 is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1027 n_err(_("The address given in *sender* is invalid: %s\n"), ccp);
1028 else
1029 rv = ccp;
1032 NYD_LEAVE;
1033 return rv;
1036 FL bool_t
1037 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1039 char date[n_FROM_DATEBUF];
1040 bool_t rv;
1041 NYD2_ENTER;
1043 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1044 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1045 !_is_date(date)))
1046 rv = TRUM1;
1047 NYD2_LEAVE;
1048 return rv;
1051 FL int
1052 extract_date_from_from_(char const *line, size_t linelen,
1053 char datebuf[n_FROM_DATEBUF])
1055 int rv;
1056 char const *cp = line;
1057 NYD_ENTER;
1059 rv = 1;
1061 /* "From " */
1062 cp = _from__skipword(cp);
1063 if (cp == NULL)
1064 goto jerr;
1065 /* "addr-spec " */
1066 cp = _from__skipword(cp);
1067 if (cp == NULL)
1068 goto jerr;
1069 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1070 (cp[2] == 'y' || cp[2] == 'Y')){
1071 cp = _from__skipword(cp);
1072 if (cp == NULL)
1073 goto jerr;
1075 /* It seems there are invalid MBOX archives in the wild, compare
1076 * . http://bugs.debian.org/624111
1077 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1078 * What they do is that they obfuscate the address to "name at host",
1079 * and even "name at host dot dom dot dom.
1080 * The [Aa][Tt] is also RFC 733, so be tolerant */
1081 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1082 cp[2] == ' '){
1083 rv = -1;
1084 cp += 3;
1085 jat_dot:
1086 cp = _from__skipword(cp);
1087 if (cp == NULL)
1088 goto jerr;
1089 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1090 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1091 cp += 4;
1092 goto jat_dot;
1096 linelen -= PTR2SIZE(cp - line);
1097 if (linelen < a_HEAD_DATE_MINLEN)
1098 goto jerr;
1099 if (cp[linelen - 1] == '\n') {
1100 --linelen;
1101 /* (Rather IMAP/POP3 only) */
1102 if (cp[linelen - 1] == '\r')
1103 --linelen;
1104 if (linelen < a_HEAD_DATE_MINLEN)
1105 goto jerr;
1107 if (linelen >= n_FROM_DATEBUF)
1108 goto jerr;
1110 jleave:
1111 memcpy(datebuf, cp, linelen);
1112 datebuf[linelen] = '\0';
1113 NYD_LEAVE;
1114 return rv;
1115 jerr:
1116 cp = _("<Unknown date>");
1117 linelen = strlen(cp);
1118 if (linelen >= n_FROM_DATEBUF)
1119 linelen = n_FROM_DATEBUF;
1120 rv = 0;
1121 goto jleave;
1124 FL void
1125 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1127 /* See the prototype declaration for the hairy relationship of
1128 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1129 struct n_header_field **hftail;
1130 struct header nh, *hq = &nh;
1131 char *linebuf = NULL /* TODO line pool */, *colon;
1132 size_t linesize = 0, seenfields = 0;
1133 int lc, c;
1134 char const *val, *cp;
1135 NYD_ENTER;
1137 memset(hq, 0, sizeof *hq);
1138 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1139 hq->h_to = hp->h_to;
1140 hq->h_cc = hp->h_cc;
1141 hq->h_bcc = hp->h_bcc;
1143 hftail = &hq->h_user_headers;
1145 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1148 /* TODO yippieia, cat(check(lextract)) :-) */
1149 rewind(fp);
1150 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1151 struct name *np;
1153 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1154 * yet expanded when we parse these! */
1155 if ((val = thisfield(linebuf, "to")) != NULL) {
1156 ++seenfields;
1157 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1158 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1159 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1160 ++seenfields;
1161 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1162 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1163 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1164 ++seenfields;
1165 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1166 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1167 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1168 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1169 ++seenfields;
1170 hq->h_from = cat(hq->h_from,
1171 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1172 EACM_STRICT, NULL));
1174 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1175 ++seenfields;
1176 hq->h_replyto = cat(hq->h_replyto,
1177 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1178 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1179 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1180 ++seenfields;
1181 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1182 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1183 EACM_STRICT, NULL));
1184 } else
1185 goto jebadhead;
1186 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1187 (val = thisfield(linebuf, "subj")) != NULL) {
1188 ++seenfields;
1189 for (cp = val; blankchar(*cp); ++cp)
1191 hq->h_subject = (hq->h_subject != NULL)
1192 ? save2str(hq->h_subject, cp) : savestr(cp);
1194 /* The remaining are mostly hacked in and thus TODO -- at least in
1195 * TODO respect to their content checking */
1196 else if((val = thisfield(linebuf, "message-id")) != NULL){
1197 if(n_psonce & n_PSO_t_FLAG){
1198 np = checkaddrs(lextract(val, GREF),
1199 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1200 NULL);
1201 if (np == NULL || np->n_flink != NULL)
1202 goto jebadhead;
1203 ++seenfields;
1204 hq->h_message_id = np;
1205 }else
1206 goto jebadhead;
1207 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1208 if(n_psonce & n_PSO_t_FLAG){
1209 np = checkaddrs(lextract(val, GREF),
1210 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1211 NULL);
1212 ++seenfields;
1213 hq->h_in_reply_to = np;
1214 }else
1215 goto jebadhead;
1216 }else if((val = thisfield(linebuf, "references")) != NULL){
1217 if(n_psonce & n_PSO_t_FLAG){
1218 ++seenfields;
1219 /* TODO Limit number of references TODO better on parser side */
1220 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1221 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1222 NULL));
1223 }else
1224 goto jebadhead;
1226 /* and that is very hairy */
1227 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1228 if(n_psonce & n_PSO_t_FLAG){
1229 ++seenfields;
1230 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1231 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1232 checkaddr_err));
1233 }else
1234 goto jebadhead;
1236 /* A free-form user header; gethfield() did some verification already.. */
1237 else{
1238 struct n_header_field *hfp;
1239 ui32_t nl, bl;
1240 char const *nstart;
1242 for(nstart = cp = linebuf;; ++cp)
1243 if(!fieldnamechar(*cp))
1244 break;
1245 nl = (ui32_t)PTR2SIZE(cp - nstart);
1247 while(blankchar(*cp))
1248 ++cp;
1249 if(*cp++ != ':'){
1250 jebadhead:
1251 n_err(_("Ignoring header field: %s\n"), linebuf);
1252 continue;
1254 while(blankchar(*cp))
1255 ++cp;
1256 bl = (ui32_t)strlen(cp) +1;
1258 ++seenfields;
1259 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1260 ) + nl +1 + bl);
1261 hftail = &hfp->hf_next;
1262 hfp->hf_next = NULL;
1263 hfp->hf_nl = nl;
1264 hfp->hf_bl = bl - 1;
1265 memcpy(hfp->hf_dat, nstart, nl);
1266 hfp->hf_dat[nl++] = '\0';
1267 memcpy(hfp->hf_dat + nl, cp, bl);
1271 /* In case the blank line after the header has been edited out. Otherwise,
1272 * fetch the header separator */
1273 if (linebuf != NULL) {
1274 if (linebuf[0] != '\0') {
1275 for (cp = linebuf; *(++cp) != '\0';)
1277 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1278 } else {
1279 if ((c = getc(fp)) != '\n' && c != EOF)
1280 ungetc(c, fp);
1284 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1285 hp->h_to = hq->h_to;
1286 hp->h_cc = hq->h_cc;
1287 hp->h_bcc = hq->h_bcc;
1288 hp->h_from = hq->h_from;
1289 hp->h_replyto = hq->h_replyto;
1290 hp->h_sender = hq->h_sender;
1291 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1292 !(n_poption & n_PO_t_FLAG))
1293 hp->h_subject = hq->h_subject;
1294 hp->h_user_headers = hq->h_user_headers;
1296 if (n_psonce & n_PSO_t_FLAG) {
1297 hp->h_ref = hq->h_ref;
1298 hp->h_message_id = hq->h_message_id;
1299 hp->h_in_reply_to = hq->h_in_reply_to;
1300 hp->h_mft = hq->h_mft;
1302 /* And perform additional validity checks so that we don't bail later
1303 * on TODO this is good and the place where this should occur,
1304 * TODO unfortunately a lot of other places do again and blabla */
1305 if (hp->h_from == NULL)
1306 hp->h_from = n_poption_arg_r;
1307 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1308 hp->h_sender = lextract(ok_vlook(sender),
1309 GEXTRA | GFULL | GFULLEXTRA);
1311 } else
1312 n_err(_("Restoring deleted header lines\n"));
1314 if (linebuf != NULL)
1315 free(linebuf);
1316 NYD_LEAVE;
1319 FL char *
1320 hfield_mult(char const *field, struct message *mp, int mult)
1322 FILE *ibuf;
1323 int lc;
1324 struct str hfs;
1325 size_t linesize = 0; /* TODO line pool */
1326 char *linebuf = NULL, *colon;
1327 char const *hfield;
1328 NYD_ENTER;
1330 /* There are (spam) messages which have header bytes which are many KB when
1331 * joined, so resize a single heap storage until we are done if we shall
1332 * collect a field that may have multiple bodies; only otherwise use the
1333 * string dope directly */
1334 memset(&hfs, 0, sizeof hfs);
1336 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1337 goto jleave;
1338 if ((lc = mp->m_lines - 1) < 0)
1339 goto jleave;
1341 if ((mp->m_flag & MNOFROM) == 0 &&
1342 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1343 goto jleave;
1344 while (lc > 0) {
1345 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1346 break;
1347 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1348 if (mult)
1349 n_str_add_buf(&hfs, hfield, strlen(hfield));
1350 else {
1351 hfs.s = savestr(hfield);
1352 break;
1357 jleave:
1358 if (linebuf != NULL)
1359 free(linebuf);
1360 if (mult && hfs.s != NULL) {
1361 colon = savestrbuf(hfs.s, hfs.l);
1362 free(hfs.s);
1363 hfs.s = colon;
1365 NYD_LEAVE;
1366 return hfs.s;
1369 FL char const *
1370 thisfield(char const *linebuf, char const *field)
1372 char const *rv = NULL;
1373 NYD2_ENTER;
1375 while (lowerconv(*linebuf) == lowerconv(*field)) {
1376 ++linebuf;
1377 ++field;
1379 if (*field != '\0')
1380 goto jleave;
1382 while (blankchar(*linebuf))
1383 ++linebuf;
1384 if (*linebuf++ != ':')
1385 goto jleave;
1387 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1388 ++linebuf;
1389 rv = linebuf;
1390 jleave:
1391 NYD2_LEAVE;
1392 return rv;
1395 FL char *
1396 nameof(struct message *mp, int reptype)
1398 char *cp, *cp2;
1399 NYD_ENTER;
1401 cp = skin(name1(mp, reptype));
1402 if (reptype != 0 || charcount(cp, '!') < 2)
1403 goto jleave;
1404 cp2 = strrchr(cp, '!');
1405 --cp2;
1406 while (cp2 > cp && *cp2 != '!')
1407 --cp2;
1408 if (*cp2 == '!')
1409 cp = cp2 + 1;
1410 jleave:
1411 NYD_LEAVE;
1412 return cp;
1415 FL char const *
1416 skip_comment(char const *cp)
1418 size_t nesting;
1419 NYD_ENTER;
1421 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1422 switch (*cp) {
1423 case '\\':
1424 if (cp[1])
1425 ++cp;
1426 break;
1427 case '(':
1428 ++nesting;
1429 break;
1430 case ')':
1431 --nesting;
1432 break;
1435 NYD_LEAVE;
1436 return cp;
1439 FL char const *
1440 routeaddr(char const *name)
1442 char const *np, *rp = NULL;
1443 NYD_ENTER;
1445 for (np = name; *np; np++) {
1446 switch (*np) {
1447 case '(':
1448 np = skip_comment(np + 1) - 1;
1449 break;
1450 case '"':
1451 while (*np) {
1452 if (*++np == '"')
1453 break;
1454 if (*np == '\\' && np[1])
1455 np++;
1457 break;
1458 case '<':
1459 rp = np;
1460 break;
1461 case '>':
1462 goto jleave;
1465 rp = NULL;
1466 jleave:
1467 NYD_LEAVE;
1468 return rp;
1471 FL enum expand_addr_flags
1472 expandaddr_to_eaf(void)
1474 struct eafdesc {
1475 char const *eafd_name;
1476 bool_t eafd_is_target;
1477 ui8_t eafd_andoff;
1478 ui8_t eafd_or;
1479 } const eafa[] = {
1480 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1481 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1482 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1483 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1484 {"file", TRU1, EAF_NONE, EAF_FILE},
1485 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1486 {"name", TRU1, EAF_NONE, EAF_NAME},
1487 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1488 }, *eafp;
1490 char *buf;
1491 enum expand_addr_flags rv;
1492 char const *cp;
1493 NYD2_ENTER;
1495 if ((cp = ok_vlook(expandaddr)) == NULL)
1496 rv = EAF_RESTRICT_TARGETS;
1497 else if (*cp == '\0')
1498 rv = EAF_TARGET_MASK;
1499 else {
1500 rv = EAF_TARGET_MASK;
1502 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1503 bool_t minus;
1505 if ((minus = (*cp == '-')) || *cp == '+')
1506 ++cp;
1507 for (eafp = eafa;; ++eafp) {
1508 if (eafp == eafa + n_NELEM(eafa)) {
1509 if (n_poption & n_PO_D_V)
1510 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1511 break;
1512 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1513 if (!minus) {
1514 rv &= ~eafp->eafd_andoff;
1515 rv |= eafp->eafd_or;
1516 } else {
1517 if (eafp->eafd_is_target)
1518 rv &= ~eafp->eafd_or;
1519 else if (n_poption & n_PO_D_V)
1520 n_err(_("minus - prefix invalid for *expandaddr* value: "
1521 "%s\n"), --cp);
1523 break;
1524 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1525 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1526 rv &= ~EAF_NAME;
1527 break;
1532 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1533 (n_poption & n_PO_TILDE_FLAG)))
1534 rv |= EAF_TARGET_MASK;
1535 else if(n_poption & n_PO_D_V){
1536 if(!(rv & EAF_TARGET_MASK))
1537 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1538 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1539 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1542 NYD2_LEAVE;
1543 return rv;
1546 FL si8_t
1547 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1549 char cbuf[sizeof "'\\U12340'"];
1550 char const *cs;
1551 int f;
1552 si8_t rv;
1553 enum expand_addr_flags eaf;
1554 NYD_ENTER;
1556 eaf = expandaddr_to_eaf();
1557 f = np->n_flags;
1559 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1560 if (eaf & EAF_FAILINVADDR)
1561 rv = -rv;
1563 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1565 } else {
1566 ui32_t c;
1567 char const *fmt = "'\\x%02X'";
1568 bool_t ok8bit = TRU1;
1570 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1571 cs = _("Invalid domain name: %s, character %s\n");
1572 fmt = "'\\U%04X'";
1573 ok8bit = FAL0;
1574 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1575 cs = _("%s contains invalid %s sequence\n");
1576 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1577 cs = _("%s is an invalid alias name\n");
1578 } else
1579 cs = _("%s contains invalid byte %s\n");
1581 c = NAME_ADDRSPEC_ERR_GETWC(f);
1582 snprintf(cbuf, sizeof cbuf,
1583 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1584 goto jprint;
1586 goto jleave;
1589 /* *expandaddr* stuff */
1590 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1591 goto jleave;
1593 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1594 if (eaf & EAF_FAIL)
1595 rv = -rv;
1596 cs = _("%s%s: file or pipe addressees not allowed here\n");
1597 if (eacm & EACM_NOLOG)
1598 goto jleave;
1599 else
1600 goto j0print;
1603 eaf |= (eacm & EAF_TARGET_MASK);
1604 if (eacm & EACM_NONAME)
1605 eaf &= ~EAF_NAME;
1607 if (eaf == EAF_NONE) {
1608 rv = FAL0;
1609 goto jleave;
1611 if (eaf & EAF_FAIL)
1612 rv = -rv;
1614 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1615 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1616 if (eacm & EACM_NOLOG)
1617 goto jleave;
1618 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1619 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1620 if (eacm & EACM_NOLOG)
1621 goto jleave;
1622 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1623 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1624 if (eacm & EACM_NOLOG)
1625 goto jleave;
1626 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1627 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1628 if (eacm & EACM_NOLOG)
1629 goto jleave;
1630 } else {
1631 rv = FAL0;
1632 goto jleave;
1635 j0print:
1636 cbuf[0] = '\0';
1637 jprint:
1638 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1639 jleave:
1640 NYD_LEAVE;
1641 return rv;
1644 FL char *
1645 skin(char const *name)
1647 struct n_addrguts ag;
1648 char *rv;
1649 NYD_ENTER;
1651 if(name != NULL){
1652 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1653 rv = ag.ag_skinned;
1654 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1655 rv = savestrbuf(rv, ag.ag_slen);
1656 }else
1657 rv = NULL;
1658 NYD_LEAVE;
1659 return rv;
1662 /* TODO addrspec_with_guts: RFC 5322
1663 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1664 FL char const *
1665 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1666 bool_t issingle_hack){
1667 char const *cp;
1668 char *cp2, *bufend, *nbuf, c;
1669 enum{
1670 a_NONE,
1671 a_GOTLT = 1<<0,
1672 a_GOTADDR = 1<<1,
1673 a_GOTSPACE = 1<<2,
1674 a_LASTSP = 1<<3
1675 } flags;
1676 NYD_ENTER;
1678 memset(agp, 0, sizeof *agp);
1680 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1681 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1682 agp->ag_slen = 0;
1683 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1684 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1685 goto jleave;
1686 }else if(!doskin){
1687 /*agp->ag_iaddr_start = 0;*/
1688 agp->ag_iaddr_aend = agp->ag_ilen;
1689 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1690 agp->ag_slen = agp->ag_ilen;
1691 agp->ag_n_flags = NAME_SKINNED;
1692 goto jcheck;
1695 flags = a_NONE;
1696 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1697 /*agp->ag_iaddr_start = 0;*/
1698 cp2 = bufend = nbuf;
1700 /* TODO This is complete crap and should use a token parser */
1701 for(cp = name++; (c = *cp++) != '\0';){
1702 switch (c) {
1703 case '(':
1704 cp = skip_comment(cp);
1705 flags &= ~a_LASTSP;
1706 break;
1707 case '"':
1708 /* Start of a "quoted-string". Copy it in its entirety */
1709 /* XXX RFC: quotes are "semantically invisible"
1710 * XXX But it was explicitly added (Changelog.Heirloom,
1711 * XXX [9.23] released 11/15/00, "Do not remove quotes
1712 * XXX when skinning names"? No more info.. */
1713 *cp2++ = c;
1714 while ((c = *cp) != '\0') { /* TODO improve */
1715 ++cp;
1716 if (c == '"') {
1717 *cp2++ = c;
1718 break;
1720 if (c != '\\')
1721 *cp2++ = c;
1722 else if ((c = *cp) != '\0') {
1723 *cp2++ = c;
1724 ++cp;
1727 flags &= ~a_LASTSP;
1728 break;
1729 case ' ':
1730 case '\t':
1731 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1732 flags |= a_GOTSPACE;
1733 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1735 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1736 cp += 3, *cp2++ = '@';
1737 else if (cp[0] == '@' && blankchar(cp[1]))
1738 cp += 2, *cp2++ = '@';
1739 else
1740 flags |= a_LASTSP;
1741 break;
1742 case '<':
1743 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1744 cp2 = bufend;
1745 flags &= ~(a_GOTSPACE | a_LASTSP);
1746 flags |= a_GOTLT | a_GOTADDR;
1747 break;
1748 case '>':
1749 if(flags & a_GOTLT){
1750 /* (_addrspec_check() verifies these later!) */
1751 flags &= ~(a_GOTLT | a_LASTSP);
1752 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1754 /* Skip over the entire remaining field */
1755 while((c = *cp) != '\0' && c != ','){
1756 ++cp;
1757 if (c == '(')
1758 cp = skip_comment(cp);
1759 else if (c == '"')
1760 while ((c = *cp) != '\0') {
1761 ++cp;
1762 if (c == '"')
1763 break;
1764 if (c == '\\' && *cp != '\0')
1765 ++cp;
1768 break;
1770 /* FALLTHRU */
1771 default:
1772 if(flags & a_LASTSP){
1773 flags &= ~a_LASTSP;
1774 if(flags & a_GOTADDR)
1775 *cp2++ = ' ';
1777 *cp2++ = c;
1778 /* This character is forbidden here, but it may nonetheless be
1779 * present: ensure we turn this into something valid! (E.g., if the
1780 * next character would be a "..) */
1781 if(c == '\\' && *cp != '\0')
1782 *cp2++ = *cp++;
1783 if(c == ',' && !issingle_hack){
1784 if(!(flags & a_GOTLT)){
1785 *cp2++ = ' ';
1786 for(; blankchar(*cp); ++cp)
1788 flags &= ~a_LASTSP;
1789 bufend = cp2;
1791 }else if(!(flags & a_GOTADDR)){
1792 flags |= a_GOTADDR;
1793 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1797 --name;
1798 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1799 if (agp->ag_iaddr_aend == 0)
1800 agp->ag_iaddr_aend = agp->ag_ilen;
1801 /* Misses > */
1802 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1803 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1804 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1805 agp->ag_iaddr_aend = agp->ag_ilen;
1806 cp2[agp->ag_ilen++] = '>';
1807 cp2[agp->ag_ilen] = '\0';
1809 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1810 n_lofi_free(nbuf);
1811 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1812 jcheck:
1813 if(a_head_addrspec_check(agp, doskin) <= FAL0)
1814 name = NULL;
1815 else
1816 name = agp->ag_input;
1817 jleave:
1818 NYD_LEAVE;
1819 return name;
1822 FL char *
1823 realname(char const *name)
1825 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1826 char *rname, *rp;
1827 struct str in, out;
1828 int quoted, good, nogood;
1829 NYD_ENTER;
1831 if ((cp = n_UNCONST(name)) == NULL)
1832 goto jleave;
1833 for (; *cp != '\0'; ++cp) {
1834 switch (*cp) {
1835 case '(':
1836 if (cstart != NULL) {
1837 /* More than one comment in address, doesn't make sense to display
1838 * it without context. Return the entire field */
1839 cp = mime_fromaddr(name);
1840 goto jleave;
1842 cstart = cp++;
1843 cp = skip_comment(cp);
1844 cend = cp--;
1845 if (cend <= cstart)
1846 cend = cstart = NULL;
1847 break;
1848 case '"':
1849 while (*cp) {
1850 if (*++cp == '"')
1851 break;
1852 if (*cp == '\\' && cp[1])
1853 ++cp;
1855 break;
1856 case '<':
1857 if (cp > name) {
1858 cstart = name;
1859 cend = cp;
1861 break;
1862 case ',':
1863 /* More than one address. Just use the first one */
1864 goto jbrk;
1868 jbrk:
1869 if (cstart == NULL) {
1870 if (*name == '<') {
1871 /* If name contains only a route-addr, the surrounding angle brackets
1872 * don't serve any useful purpose when displaying, so remove */
1873 cp = prstr(skin(name));
1874 } else
1875 cp = mime_fromaddr(name);
1876 goto jleave;
1879 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1880 * not stripped. The idea is to strip only syntactical relevant things (but
1881 * this is not necessarily the most sensible way in practice) */
1882 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1883 quoted = 0;
1884 for (cp = cstart; cp < cend; ++cp) {
1885 if (*cp == '(' && !quoted) {
1886 cq = skip_comment(++cp);
1887 if (PTRCMP(--cq, >, cend))
1888 cq = cend;
1889 while (cp < cq) {
1890 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1891 ++cp;
1892 *rp++ = *cp++;
1894 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1895 *rp++ = *++cp;
1896 else if (*cp == '"') {
1897 quoted = !quoted;
1898 continue;
1899 } else
1900 *rp++ = *cp;
1902 *rp = '\0';
1903 in.s = rname;
1904 in.l = rp - rname;
1905 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1906 ac_free(rname);
1907 rname = savestr(out.s);
1908 free(out.s);
1910 while (blankchar(*rname))
1911 ++rname;
1912 for (rp = rname; *rp != '\0'; ++rp)
1914 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1915 *rp = '\0';
1916 if (rp == rname) {
1917 cp = mime_fromaddr(name);
1918 goto jleave;
1921 /* mime_fromhdr() has converted all nonprintable characters to question
1922 * marks now. These and blanks are considered uninteresting; if the
1923 * displayed part of the real name contains more than 25% of them, it is
1924 * probably better to display the plain email address instead */
1925 good = 0;
1926 nogood = 0;
1927 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1928 if (*rp == '?' || blankchar(*rp))
1929 ++nogood;
1930 else
1931 ++good;
1932 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1933 jleave:
1934 NYD_LEAVE;
1935 return n_UNCONST(cp);
1938 FL char *
1939 name1(struct message *mp, int reptype)
1941 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1942 size_t namesize, linesize = 0;
1943 FILE *ibuf;
1944 int f1st = 1;
1945 NYD_ENTER;
1947 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1948 goto jleave;
1949 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1950 goto jleave;
1952 namebuf = smalloc(namesize = 1);
1953 namebuf[0] = 0;
1954 if (mp->m_flag & MNOFROM)
1955 goto jout;
1956 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1957 goto jout;
1958 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1959 goto jout;
1961 jnewname:
1962 if (namesize <= linesize)
1963 namebuf = srealloc(namebuf, namesize = linesize +1);
1964 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1966 for (; blankchar(*cp); ++cp)
1968 for (cp2 = namebuf + strlen(namebuf);
1969 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1970 *cp2++ = *cp++;
1971 *cp2 = '\0';
1973 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1974 goto jout;
1975 if ((cp = strchr(linebuf, 'F')) == NULL)
1976 goto jout;
1977 if (strncmp(cp, "From", 4))
1978 goto jout;
1979 if (namesize <= linesize)
1980 namebuf = srealloc(namebuf, namesize = linesize + 1);
1982 while ((cp = strchr(cp, 'r')) != NULL) {
1983 if (!strncmp(cp, "remote", 6)) {
1984 if ((cp = strchr(cp, 'f')) == NULL)
1985 break;
1986 if (strncmp(cp, "from", 4) != 0)
1987 break;
1988 if ((cp = strchr(cp, ' ')) == NULL)
1989 break;
1990 cp++;
1991 if (f1st) {
1992 strncpy(namebuf, cp, namesize);
1993 f1st = 0;
1994 } else {
1995 cp2 = strrchr(namebuf, '!') + 1;
1996 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1998 namebuf[namesize - 2] = '!';
1999 namebuf[namesize - 1] = '\0';
2000 goto jnewname;
2002 cp++;
2004 jout:
2005 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2006 *cp == '\0')
2007 cp = savestr(namebuf);
2009 if (linebuf != NULL)
2010 free(linebuf);
2011 free(namebuf);
2012 jleave:
2013 NYD_LEAVE;
2014 return cp;
2017 FL char const *
2018 subject_re_trim(char const *s){
2019 struct{
2020 ui8_t len;
2021 char dat[7];
2022 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2023 {3, "re:"},
2024 {3, "aw:"}, {5, "antw:"}, /* de */
2025 {3, "wg:"}, /* Seen too often in the wild */
2026 {0, ""}
2029 bool_t any;
2030 char *re_st, *re_st_x;
2031 char const *orig_s;
2032 size_t re_l;
2033 NYD_ENTER;
2035 any = FAL0;
2036 orig_s = s;
2037 re_st = NULL;
2038 n_UNINIT(re_l, 0);
2040 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2041 (re_l = strlen(re_st_x)) > 0){
2042 re_st = n_lofi_alloc(++re_l * 2);
2043 memcpy(re_st, re_st_x, re_l);
2046 jouter:
2047 while(*s != '\0'){
2048 while(spacechar(*s))
2049 ++s;
2051 for(pp = ignored; pp->len > 0; ++pp)
2052 if(is_asccaseprefix(pp->dat, s)){
2053 s += pp->len;
2054 any = TRU1;
2055 goto jouter;
2058 if(re_st != NULL){
2059 char *cp;
2061 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2062 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2063 if(is_asccaseprefix(cp, s)){
2064 s += strlen(cp);
2065 any = TRU1;
2066 goto jouter;
2069 break;
2072 if(re_st != NULL)
2073 n_lofi_free(re_st);
2074 NYD_LEAVE;
2075 return any ? s : orig_s;
2078 FL int
2079 msgidcmp(char const *s1, char const *s2)
2081 int q1 = 0, q2 = 0, c1, c2;
2082 NYD_ENTER;
2084 while(*s1 == '<')
2085 ++s1;
2086 while(*s2 == '<')
2087 ++s2;
2089 do {
2090 c1 = msgidnextc(&s1, &q1);
2091 c2 = msgidnextc(&s2, &q2);
2092 if (c1 != c2)
2093 break;
2094 } while (c1 && c2);
2095 NYD_LEAVE;
2096 return c1 - c2;
2099 FL char const *
2100 fakefrom(struct message *mp)
2102 char const *name;
2103 NYD_ENTER;
2105 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2106 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2107 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2108 * RFC 4155 however requires a RFC 5322 (2822) conforming
2109 * "addr-spec", but we simply can't provide that */
2110 name = "MAILER-DAEMON";
2111 NYD_LEAVE;
2112 return name;
2115 FL char const *
2116 fakedate(time_t t)
2118 char *cp, *cq;
2119 NYD_ENTER;
2121 cp = ctime(&t);
2122 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
2124 *cq = '\0';
2125 cp = savestr(cp);
2126 NYD_LEAVE;
2127 return cp;
2130 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2131 FL time_t
2132 unixtime(char const *fromline)
2134 char const *fp, *xp;
2135 time_t t;
2136 si32_t i, year, month, day, hour, minute, second, tzdiff;
2137 struct tm *tmptr;
2138 NYD2_ENTER;
2140 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2142 fp -= 24;
2143 if (PTR2SIZE(fp - fromline) < 7)
2144 goto jinvalid;
2145 if (fp[3] != ' ')
2146 goto jinvalid;
2147 for (i = 0;;) {
2148 if (!strncmp(fp + 4, n_month_names[i], 3))
2149 break;
2150 if (n_month_names[++i][0] == '\0')
2151 goto jinvalid;
2153 month = i + 1;
2154 if (fp[7] != ' ')
2155 goto jinvalid;
2156 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2157 if (*xp != ' ' || xp != fp + 10)
2158 goto jinvalid;
2159 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2160 if (*xp != ':' || xp != fp + 13)
2161 goto jinvalid;
2162 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2163 if (*xp != ':' || xp != fp + 16)
2164 goto jinvalid;
2165 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2166 if (*xp != ' ' || xp != fp + 19)
2167 goto jinvalid;
2168 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2169 if (xp != fp + 24)
2170 goto jinvalid;
2171 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2172 goto jinvalid;
2173 tzdiff = t - mktime(gmtime(&t));
2174 tmptr = localtime(&t);
2175 if (tmptr->tm_isdst > 0)
2176 tzdiff += 3600;
2177 t -= tzdiff;
2178 jleave:
2179 NYD2_LEAVE;
2180 return t;
2181 jinvalid:
2182 t = n_time_epoch();
2183 goto jleave;
2185 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2187 FL time_t
2188 rfctime(char const *date) /* TODO n_idec_ return tests */
2190 char const *cp, *x;
2191 time_t t;
2192 si32_t i, year, month, day, hour, minute, second;
2193 NYD2_ENTER;
2195 cp = date;
2197 if ((cp = nexttoken(cp)) == NULL)
2198 goto jinvalid;
2199 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2200 cp[3] == ',') {
2201 if ((cp = nexttoken(&cp[4])) == NULL)
2202 goto jinvalid;
2204 n_idec_si32_cp(&day, cp, 10, &x);
2205 if ((cp = nexttoken(x)) == NULL)
2206 goto jinvalid;
2207 for (i = 0;;) {
2208 if (!strncmp(cp, n_month_names[i], 3))
2209 break;
2210 if (n_month_names[++i][0] == '\0')
2211 goto jinvalid;
2213 month = i + 1;
2214 if ((cp = nexttoken(&cp[3])) == NULL)
2215 goto jinvalid;
2216 /* RFC 5322, 4.3:
2217 * Where a two or three digit year occurs in a date, the year is to be
2218 * interpreted as follows: If a two digit year is encountered whose
2219 * value is between 00 and 49, the year is interpreted by adding 2000,
2220 * ending up with a value between 2000 and 2049. If a two digit year
2221 * is encountered with a value between 50 and 99, or any three digit
2222 * year is encountered, the year is interpreted by adding 1900 */
2223 n_idec_si32_cp(&year, cp, 10, &x);
2224 i = (int)PTR2SIZE(x - cp);
2225 if (i == 2 && year >= 0 && year <= 49)
2226 year += 2000;
2227 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2228 year += 1900;
2229 if ((cp = nexttoken(x)) == NULL)
2230 goto jinvalid;
2231 n_idec_si32_cp(&hour, cp, 10, &x);
2232 if (*x != ':')
2233 goto jinvalid;
2234 cp = &x[1];
2235 n_idec_si32_cp(&minute, cp, 10, &x);
2236 if (*x == ':') {
2237 cp = &x[1];
2238 n_idec_si32_cp(&second, cp, 10, &x);
2239 } else
2240 second = 0;
2242 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2243 goto jinvalid;
2244 if ((cp = nexttoken(x)) != NULL) {
2245 char buf[3];
2246 int sign = 1;
2248 switch (*cp) {
2249 case '+':
2250 sign = -1;
2251 /* FALLTHRU */
2252 case '-':
2253 ++cp;
2254 break;
2256 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2257 digitchar(cp[3])) {
2258 si64_t tadj;
2260 buf[2] = '\0';
2261 buf[0] = cp[0];
2262 buf[1] = cp[1];
2263 n_idec_si32_cp(&i, buf, 10, NULL);
2264 tadj = (si64_t)i * 3600; /* XXX */
2265 buf[0] = cp[2];
2266 buf[1] = cp[3];
2267 n_idec_si32_cp(&i, buf, 10, NULL);
2268 tadj += (si64_t)i * 60; /* XXX */
2269 if (sign < 0)
2270 tadj = -tadj;
2271 t += (time_t)tadj;
2273 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2274 * TODO once again, Christos Zoulas and NetBSD Mail have done
2275 * TODO a really good job already, but using strptime(3), which
2276 * TODO is not portable. Nonetheless, WE must improve, not
2277 * TODO at last because we simply ignore obsolete timezones!!
2278 * TODO See RFC 5322, 4.3! */
2280 jleave:
2281 NYD2_LEAVE;
2282 return t;
2283 jinvalid:
2284 t = 0;
2285 goto jleave;
2288 FL time_t
2289 combinetime(int year, int month, int day, int hour, int minute, int second){
2290 size_t const jdn_epoch = 2440588;
2291 bool_t const y2038p = (sizeof(time_t) == 4);
2293 size_t jdn;
2294 time_t t;
2295 NYD2_ENTER;
2297 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2298 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2299 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2300 day < 1 || day > 31 ||
2301 month < 1 || month > 12 ||
2302 year < 1970)
2303 goto jerr;
2305 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2306 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2307 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2308 * test by stepping second-wise around the flip. Don't care otherwise */
2309 if(!y2038p)
2310 goto jerr;
2311 if(year > 2038 || month > 1 || day > 19 ||
2312 hour > 3 || minute > 14 || second > 7)
2313 goto jerr;
2316 t = second;
2317 t += minute * n_DATE_SECSMIN;
2318 t += hour * n_DATE_SECSHOUR;
2320 jdn = a_head_gregorian_to_jdn(year, month, day);
2321 jdn -= jdn_epoch;
2322 t += (time_t)jdn * n_DATE_SECSDAY;
2323 jleave:
2324 NYD2_LEAVE;
2325 return t;
2326 jerr:
2327 t = (time_t)-1;
2328 goto jleave;
2331 FL void
2332 substdate(struct message *m)
2334 char const *cp;
2335 NYD_ENTER;
2337 /* Determine the date to print in faked 'From ' lines. This is traditionally
2338 * the date the message was written to the mail file. Try to determine this
2339 * using RFC message header fields, or fall back to current time */
2340 m->m_time = 0;
2341 if ((cp = hfield1("received", m)) != NULL) {
2342 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2344 ++cp;
2345 while (alnumchar(*cp));
2347 if (cp && *++cp)
2348 m->m_time = rfctime(cp);
2350 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2351 if ((cp = hfield1("date", m)) != NULL)
2352 m->m_time = rfctime(cp);
2354 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2355 m->m_time = time_current.tc_time;
2356 NYD_LEAVE;
2359 FL void
2360 setup_from_and_sender(struct header *hp)
2362 char const *addr;
2363 struct name *np;
2364 NYD_ENTER;
2366 /* If -t parsed or composed From: then take it. With -t we otherwise
2367 * want -r to be honoured in favour of *from* in order to have
2368 * a behaviour that is compatible with what users would expect from e.g.
2369 * postfix(1) */
2370 if ((np = hp->h_from) != NULL ||
2371 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2373 } else if ((addr = myaddrs(hp)) != NULL)
2374 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2375 hp->h_from = np;
2377 if ((np = hp->h_sender) != NULL) {
2379 } else if ((addr = ok_vlook(sender)) != NULL)
2380 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2381 hp->h_sender = np;
2383 NYD_LEAVE;
2386 FL struct name const *
2387 check_from_and_sender(struct name const *fromfield,
2388 struct name const *senderfield)
2390 struct name const *rv = NULL;
2391 NYD_ENTER;
2393 if (senderfield != NULL) {
2394 if (senderfield->n_flink != NULL) {
2395 n_err(_("The Sender: field may contain only one address\n"));
2396 goto jleave;
2398 rv = senderfield;
2401 if (fromfield != NULL) {
2402 if (fromfield->n_flink != NULL && senderfield == NULL) {
2403 n_err(_("A Sender: is required when there are multiple "
2404 "addresses in From:\n"));
2405 goto jleave;
2407 if (rv == NULL)
2408 rv = fromfield;
2411 if (rv == NULL)
2412 rv = (struct name*)0x1;
2413 jleave:
2414 NYD_LEAVE;
2415 return rv;
2418 #ifdef HAVE_XSSL
2419 FL char *
2420 getsender(struct message *mp)
2422 char *cp;
2423 struct name *np;
2424 NYD_ENTER;
2426 if ((cp = hfield1("from", mp)) == NULL ||
2427 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2428 cp = NULL;
2429 else
2430 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2431 NYD_LEAVE;
2432 return cp;
2434 #endif
2436 FL int
2437 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2438 int subjfirst)
2440 /* TODO grab_headers: again, check counts etc. against RFC;
2441 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2442 int errs;
2443 int volatile comma;
2444 NYD_ENTER;
2446 errs = 0;
2447 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2449 if (gflags & GTO)
2450 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2451 if (subjfirst && (gflags & GSUBJECT))
2452 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2453 if (gflags & GCC)
2454 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2455 if (gflags & GBCC)
2456 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2458 if (gflags & GEXTRA) {
2459 if (hp->h_from == NULL)
2460 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2461 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2462 GEXTRA | GFULL | GFULLEXTRA);
2463 if (hp->h_replyto == NULL)
2464 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
2465 hp->h_replyto = grab_names(gif, "Reply-To: ", hp->h_replyto, comma,
2466 GEXTRA | GFULL);
2467 if (hp->h_sender == NULL)
2468 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2469 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2470 GEXTRA | GFULL);
2473 if (!subjfirst && (gflags & GSUBJECT))
2474 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2476 NYD_LEAVE;
2477 return errs;
2480 FL bool_t
2481 header_match(struct message *mp, struct search_expr const *sep)
2483 struct str in, out;
2484 FILE *ibuf;
2485 int lc;
2486 size_t linesize = 0; /* TODO line pool */
2487 char *linebuf = NULL, *colon;
2488 bool_t rv = FAL0;
2489 NYD_ENTER;
2491 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2492 goto jleave;
2493 if ((lc = mp->m_lines - 1) < 0)
2494 goto jleave;
2496 if ((mp->m_flag & MNOFROM) == 0 &&
2497 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2498 goto jleave;
2499 while (lc > 0) {
2500 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2501 break;
2502 if (blankchar(*++colon))
2503 ++colon;
2504 in.l = strlen(in.s = colon);
2505 mime_fromhdr(&in, &out, TD_ICONV);
2506 #ifdef HAVE_REGEX
2507 if (sep->ss_sexpr == NULL)
2508 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2509 else
2510 #endif
2511 rv = substr(out.s, sep->ss_sexpr);
2512 free(out.s);
2513 if (rv)
2514 break;
2517 jleave:
2518 if (linebuf != NULL)
2519 free(linebuf);
2520 NYD_LEAVE;
2521 return rv;
2524 FL struct n_header_field *
2525 n_customhdr_query(void){
2526 char const *vp;
2527 struct n_header_field *rv, **tail, *hfp;
2528 NYD_ENTER;
2530 rv = NULL;
2532 if((vp = ok_vlook(customhdr)) != NULL){
2533 char *buf;
2535 tail = &rv;
2536 buf = savestr(vp);
2537 jch_outer:
2538 while((vp = a_head_customhdr__sep(&buf)) != NULL){
2539 ui32_t nl, bl;
2540 char const *nstart, *cp;
2542 for(nstart = cp = vp;; ++cp){
2543 if(fieldnamechar(*cp))
2544 continue;
2545 if(*cp == '\0'){
2546 if(cp == nstart){
2547 n_err(_("Invalid nameless *customhdr* entry\n"));
2548 goto jch_outer;
2550 }else if(*cp != ':' && !blankchar(*cp)){
2551 jch_badent:
2552 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2553 goto jch_outer;
2555 break;
2557 nl = (ui32_t)PTR2SIZE(cp - nstart);
2559 while(blankchar(*cp))
2560 ++cp;
2561 if(*cp++ != ':')
2562 goto jch_badent;
2563 while(blankchar(*cp))
2564 ++cp;
2565 bl = (ui32_t)strlen(cp) +1;
2567 *tail =
2568 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2569 nl +1 + bl);
2570 tail = &hfp->hf_next;
2571 hfp->hf_next = NULL;
2572 hfp->hf_nl = nl;
2573 hfp->hf_bl = bl - 1;
2574 memcpy(hfp->hf_dat, nstart, nl);
2575 hfp->hf_dat[nl++] = '\0';
2576 memcpy(hfp->hf_dat + nl, cp, bl);
2579 NYD_LEAVE;
2580 return rv;
2583 /* s-it-mode */