TODO: a few things are in
[s-mailx.git] / head.c
blob065c4cddcd48f3c1ed17e5f29de4b419857a764b
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 long a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem,
110 char **colon);
112 static int msgidnextc(char const **cp, int *status);
114 /* Count the occurances of c in str */
115 static int charcount(char *str, int c);
117 static char const * nexttoken(char const *cp);
119 static char const *
120 _from__skipword(char const *wp)
122 char c = 0;
123 NYD2_ENTER;
125 if (wp != NULL) {
126 while ((c = *wp++) != '\0' && !blankchar(c)) {
127 if (c == '"') {
128 while ((c = *wp++) != '\0' && c != '"')
130 if (c != '"')
131 --wp;
134 for (; blankchar(c); c = *wp++)
137 NYD2_LEAVE;
138 return (c == 0 ? NULL : wp - 1);
141 static int
142 _cmatch(size_t len, char const *date, char const *tp)
144 int ret = 0;
145 NYD2_ENTER;
147 while (len--) {
148 char c = date[len];
149 switch (tp[len]) {
150 case 'a':
151 if (!lowerchar(c))
152 goto jleave;
153 break;
154 case 'A':
155 if (!upperchar(c))
156 goto jleave;
157 break;
158 case ' ':
159 if (c != ' ')
160 goto jleave;
161 break;
162 case '0':
163 if (!digitchar(c))
164 goto jleave;
165 break;
166 case 'O':
167 if (c != ' ' && !digitchar(c))
168 goto jleave;
169 break;
170 case ':':
171 if (c != ':')
172 goto jleave;
173 break;
174 case '+':
175 if (c != '+' && c != '-')
176 goto jleave;
177 break;
180 ret = 1;
181 jleave:
182 NYD2_LEAVE;
183 return ret;
186 static int
187 _is_date(char const *date)
189 struct cmatch_data const *cmdp;
190 size_t dl;
191 int rv = 0;
192 NYD2_ENTER;
194 if ((dl = strlen(date)) >= a_HEAD_DATE_MINLEN)
195 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
196 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
197 break;
198 NYD2_LEAVE;
199 return rv;
202 static size_t
203 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
204 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
205 * (via third hand, plus adjustments).
206 * This algorithm is supposed to work for all dates in between 1582-10-15
207 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
208 size_t jdn;
209 NYD2_ENTER;
211 #if 0
212 if(y == 0)
213 y = 1;
214 if(m == 0)
215 m = 1;
216 if(d == 0)
217 d = 1;
218 #endif
220 if(m > 2)
221 m -= 3;
222 else{
223 m += 9;
224 --y;
226 jdn = y;
227 jdn /= 100;
228 y -= 100 * jdn;
229 y *= 1461;
230 y >>= 2;
231 jdn *= 146097;
232 jdn >>= 2;
233 jdn += y;
234 jdn += d;
235 jdn += 1721119;
236 m *= 153;
237 m += 2;
238 m /= 5;
239 jdn += m;
240 NYD2_LEAVE;
241 return jdn;
244 #if 0
245 static void
246 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
247 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
248 * (via third hand, plus adjustments) */
249 size_t y, x;
250 NYD2_ENTER;
252 jdn -= 1721119;
253 jdn <<= 2;
254 --jdn;
255 y = jdn / 146097;
256 jdn %= 146097;
257 jdn |= 3;
258 y *= 100;
259 y += jdn / 1461;
260 jdn %= 1461;
261 jdn += 4;
262 jdn >>= 2;
263 x = jdn;
264 jdn <<= 2;
265 jdn += x;
266 jdn -= 3;
267 x = jdn / 153; /* x -> month */
268 jdn %= 153;
269 jdn += 5;
270 jdn /= 5; /* jdn -> day */
271 if(x < 10)
272 x += 3;
273 else{
274 x -= 9;
275 ++y;
278 *yp = (ui32_t)(y & 0xFFFF);
279 *mp = (ui32_t)(x & 0xFF);
280 *dp = (ui32_t)(jdn & 0xFF);
281 NYD2_LEAVE;
283 #endif /* 0 */
285 #ifdef HAVE_IDNA
286 static struct n_addrguts *
287 a_head_idna_apply(struct n_addrguts *agp){
288 struct n_string idna_ascii;
289 NYD_ENTER;
291 n_string_creat_auto(&idna_ascii);
293 if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
294 agp->ag_slen - agp->ag_sdom_start))
295 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
296 else{
297 /* Replace the domain part of .ag_skinned with IDNA version */
298 n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
300 agp->ag_skinned = n_string_cp(&idna_ascii);
301 agp->ag_slen = idna_ascii.s_len;
302 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
303 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
305 NYD_LEAVE;
306 return agp;
308 #endif /* HAVE_IDNA */
310 static bool_t
311 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned)
313 char *addr, *p;
314 bool_t in_quote;
315 ui8_t in_domain, hadat;
316 union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
317 #ifdef HAVE_IDNA
318 ui8_t use_idna;
319 #endif
320 NYD_ENTER;
322 #ifdef HAVE_IDNA
323 use_idna = ok_blook(idna_disable) ? 0 : 1;
324 #endif
325 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
326 addr = agp->ag_skinned;
328 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
329 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
330 goto jleave;
333 /* If the field is not a recipient, it cannot be a file or a pipe */
334 if (!skinned)
335 goto jaddr_check;
337 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
338 * grep the latter for the complete picture */
339 if (*addr == '|') {
340 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
341 goto jleave;
343 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
344 (addr[0] == '-' && addr[1] == '\0'))
345 goto jisfile;
346 if (memchr(addr, '@', agp->ag_slen) == NULL) {
347 if (*addr == '+')
348 goto jisfile;
349 for (p = addr; (c.c = *p); ++p) {
350 if (c.c == '!' || c.c == '%')
351 break;
352 if (c.c == '/') {
353 jisfile:
354 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
355 goto jleave;
360 jaddr_check:
361 /* TODO This is false. If super correct this should work on wide
362 * TODO characters, just in case (some bytes of) the ASCII set is (are)
363 * TODO shared; it may yet tear apart multibyte sequences, possibly.
364 * TODO All this should interact with mime_enc_mustquote(), too!
365 * TODO That is: once this is an object, we need to do this in a way
366 * TODO that it is valid for the wire format (instead)! */
367 in_quote = FAL0;
368 in_domain = hadat = 0;
370 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser! */
371 for (p = addr; (c.c = *p++) != '\0';) {
372 if (c.c == '"') {
373 in_quote = !in_quote;
374 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
375 #ifdef HAVE_IDNA
376 if (in_domain && use_idna > 0) {
377 if (use_idna == 1)
378 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
379 c.u);
380 use_idna = 2;
381 } else
382 #endif
383 break;
384 } else if (in_domain == 2) {
385 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
386 break;
387 } else if (in_quote && in_domain == 0) {
388 /*EMPTY*/;
389 } else if (c.c == '\\' && *p != '\0') {
390 ++p;
391 } else if (c.c == '@') {
392 if (hadat++ > 0) {
393 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
394 c.u);
395 goto jleave;
397 agp->ag_sdom_start = PTR2SIZE(p - addr);
398 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
399 in_domain = (*p == '[') ? 2 : 1;
400 continue;
401 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
402 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
403 c.c == '\\' || c.c == ',')
404 break;
405 hadat = 0;
407 if (c.c != '\0') {
408 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
409 goto jleave;
412 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
413 /* TODO This may be an UUCP address */
414 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
415 if(!n_alias_is_valid_name(agp->ag_input))
416 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
417 }else{
418 /* If we seem to know that this is an address. Ensure this is correct
419 * according to RFC 5322 TODO the entire address parser should be like
420 * TODO that for one, and then we should know whether structured or
421 * TODO unstructured, and just parse correctly overall!
422 * TODO In addition, this can be optimised a lot.
423 * TODO And it is far from perfect: it should not forget whether no
424 * TODO whitespace followed some snippet, and it was written hastily */
425 struct a_token{
426 struct a_token *t_last;
427 struct a_token *t_next;
428 enum{
429 a_T_TATOM = 1<<0,
430 a_T_TCOMM = 1<<1,
431 a_T_TQUOTE = 1<<2,
432 a_T_TADDR = 1<<3,
433 a_T_TMASK = (1<<4) - 1,
435 a_T_SPECIAL = 1<<8 /* An atom actually needs to go TQUOTE */
436 } t_f;
437 ui8_t t__pad[4];
438 size_t t_start;
439 size_t t_end;
440 } *thead, *tcurr, *tp;
442 struct n_string ost, *ostp;
443 char const *cp, *cp1st, *cpmax, *xp;
444 void *lofi_snap;
446 /* Name and domain must be non-empty */
447 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
448 c.c = '@';
449 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
450 goto jleave;
453 #ifdef HAVE_IDNA
454 if(use_idna == 2)
455 agp = a_head_idna_apply(agp);
456 #endif
458 cp = agp->ag_input;
460 /* Nothing to do if there is only an address (in angle brackets) */
461 if(agp->ag_iaddr_start == 0){
462 if(agp->ag_iaddr_aend == agp->ag_ilen)
463 goto jleave;
464 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
465 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
466 cp[agp->ag_iaddr_aend] == '>')
467 goto jleave;
469 /* It is not, so parse off all tokens, then resort and rejoin */
470 lofi_snap = n_lofi_snap_create();
472 cp1st = cp;
473 if((c.ui32 = agp->ag_iaddr_start) > 0)
474 --c.ui32;
475 cpmax = &cp[c.ui32];
477 thead = tcurr = NULL;
478 hadat = FAL0;
479 jnode_redo:
480 for(tp = NULL; cp < cpmax;){
481 switch((c.c = *cp)){
482 case '(':
483 if(tp != NULL)
484 tp->t_end = PTR2SIZE(cp - cp1st);
485 tp = n_lofi_alloc(sizeof *tp);
486 tp->t_next = NULL;
487 if((tp->t_last = tcurr) != NULL)
488 tcurr->t_next = tp;
489 else
490 thead = tp;
491 tcurr = tp;
492 tp->t_f = a_T_TCOMM;
493 tp->t_start = PTR2SIZE(++cp - cp1st);
494 xp = skip_comment(cp);
495 tp->t_end = PTR2SIZE(xp - cp1st);
496 cp = xp;
497 if(tp->t_end > tp->t_start){
498 if(xp[-1] == ')')
499 --tp->t_end;
500 else{
501 /* No closing comment - strip trailing whitespace */
502 while(blankchar(*--xp))
503 if(--tp->t_end == tp->t_start)
504 break;
507 tp = NULL;
508 break;
510 case '"':
511 if(tp != NULL)
512 tp->t_end = PTR2SIZE(cp - cp1st);
513 tp = n_lofi_alloc(sizeof *tp);
514 tp->t_next = NULL;
515 if((tp->t_last = tcurr) != NULL)
516 tcurr->t_next = tp;
517 else
518 thead = tp;
519 tcurr = tp;
520 tp->t_f = a_T_TQUOTE;
521 tp->t_start = PTR2SIZE(++cp - cp1st);
522 for(xp = cp; xp < cpmax; ++xp){
523 if((c.c = *xp) == '"')
524 break;
525 if(c.c == '\\' && xp[1] != '\0')
526 ++xp;
528 tp->t_end = PTR2SIZE(xp - cp1st);
529 cp = &xp[1];
530 if(tp->t_end > tp->t_start){
531 /* No closing quote - strip trailing whitespace */
532 if(*xp != '"'){
533 while(blankchar(*xp--))
534 if(--tp->t_end == tp->t_start)
535 break;
538 tp = NULL;
539 break;
541 default:
542 if(blankchar(c.c)){
543 if(tp != NULL)
544 tp->t_end = PTR2SIZE(cp - cp1st);
545 tp = NULL;
546 ++cp;
547 break;
550 if(tp == NULL){
551 tp = n_lofi_alloc(sizeof *tp);
552 tp->t_next = NULL;
553 if((tp->t_last = tcurr) != NULL)
554 tcurr->t_next = tp;
555 else
556 thead = tp;
557 tcurr = tp;
558 tp->t_f = a_T_TATOM;
559 tp->t_start = PTR2SIZE(cp - cp1st);
561 ++cp;
563 /* Reverse solidus transforms the following into a quoted-pair, and
564 * therefore (must occur in comment or quoted-string only) the
565 * entire atom into a quoted string */
566 if(c.c == '\\'){
567 tp->t_f |= a_T_SPECIAL;
568 if(cp < cpmax)
569 ++cp;
570 break;
573 /* Is this plain RFC 5322 "atext", or "specials"?
574 * TODO Because we don't know structured/unstructured, nor anything
575 * TODO else, we need to treat "dot-atom" as being identical to
576 * TODO "specials".
577 * However, if the 8th bit is set, this will be RFC 2047 converted
578 * and the entire sequence is skipped */
579 if(!(c.u & 0x80) && !alnumchar(c.c) &&
580 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
581 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
582 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
583 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
584 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
585 tp->t_f |= a_T_SPECIAL;
586 break;
589 if(tp != NULL)
590 tp->t_end = PTR2SIZE(cp - cp1st);
592 if(hadat == FAL0){
593 hadat = TRU1;
594 tp = n_lofi_alloc(sizeof *tp);
595 tp->t_next = NULL;
596 if((tp->t_last = tcurr) != NULL)
597 tcurr->t_next = tp;
598 else
599 thead = tp;
600 tcurr = tp;
601 tp->t_f = a_T_TADDR;
602 tp->t_start = agp->ag_iaddr_start;
603 tp->t_end = agp->ag_iaddr_aend;
604 tp = NULL;
606 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
607 cpmax = &agp->ag_input[agp->ag_ilen];
608 if(cp < cpmax)
609 goto jnode_redo;
612 /* Nothing may follow the address, move it to the end */
613 if(!(tcurr->t_f & a_T_TADDR)){
614 for(tp = thead; tp != NULL; tp = tp->t_next){
615 if(tp->t_f & a_T_TADDR){
616 if(tp->t_last != NULL)
617 tp->t_last->t_next = tp->t_next;
618 else
619 thead = tp->t_next;
620 if(tp->t_next != NULL)
621 tp->t_next->t_last = tp->t_last;
623 tcurr = tp;
624 while(tp->t_next != NULL)
625 tp = tp->t_next;
626 tp->t_next = tcurr;
627 tcurr->t_last = tp;
628 tcurr->t_next = NULL;
629 break;
634 /* Make ranges contiguous: ensure a continuous range of atoms is converted
635 * to a SPECIAL one if at least one of them requires it */
636 for(tp = thead; tp != NULL; tp = tp->t_next){
637 if(tp->t_f & a_T_SPECIAL){
638 tcurr = tp;
639 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
640 tp->t_f |= a_T_SPECIAL;
641 tp = tcurr;
642 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
643 tp->t_f |= a_T_SPECIAL;
647 /* And yes, we want quotes to extend as much as possible */
648 for(tp = thead; tp != NULL; tp = tp->t_next){
649 if(tp->t_f & a_T_TQUOTE){
650 tcurr = tp;
651 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
652 tp->t_f |= a_T_SPECIAL;
653 tp = tcurr;
654 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
655 tp->t_f |= a_T_SPECIAL;
659 /* Then rejoin */
660 ostp = n_string_creat_auto(&ost);
661 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
662 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
664 for(tcurr = thead; tcurr != NULL;){
665 if(tcurr != thead)
666 ostp = n_string_push_c(ostp, ' ');
667 if(tcurr->t_f & a_T_TADDR){
668 ostp = n_string_push_c(ostp, '<');
669 agp->ag_iaddr_start = ostp->s_len;
670 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
671 (tcurr->t_end - tcurr->t_start));
672 agp->ag_iaddr_aend = ostp->s_len;
673 ostp = n_string_push_c(ostp, '>');
674 tcurr = tcurr->t_next;
675 }else if(tcurr->t_f & a_T_TCOMM){
676 ostp = n_string_push_c(ostp, '(');
677 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
678 (tcurr->t_end - tcurr->t_start));
679 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
680 tcurr = tp;
681 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
682 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
683 (tcurr->t_end - tcurr->t_start));
685 ostp = n_string_push_c(ostp, ')');
686 tcurr = tcurr->t_next;
687 }else if(tcurr->t_f & a_T_TQUOTE){
688 jput_quote:
689 ostp = n_string_push_c(ostp, '"');
690 tp = tcurr;
691 do/* while tcurr && TATOM||TQUOTE */{
692 cp = &cp1st[tcurr->t_start];
693 cpmax = &cp1st[tcurr->t_end];
694 if(cp == cpmax)
695 continue;
697 if(tcurr != tp)
698 ostp = n_string_push_c(ostp, ' ');
700 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
701 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
702 else{
703 bool_t esc;
705 for(esc = FAL0; cp < cpmax;){
706 if((c.c = *cp++) == '\\' && !esc){
707 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
708 esc = TRU1;
709 }else{
710 if(esc || c.c == '"'){
711 jput_quote_esc:
712 ostp = n_string_push_c(ostp, '\\');
714 ostp = n_string_push_c(ostp, c.c);
715 esc = FAL0;
718 if(esc){
719 c.c = '\\';
720 goto jput_quote_esc;
723 }while((tcurr = tcurr->t_next) != NULL &&
724 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
725 ostp = n_string_push_c(ostp, '"');
726 }else if(tcurr->t_f & a_T_SPECIAL)
727 goto jput_quote;
728 else{
729 /* Can we use a fast join mode? */
730 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
731 if(!(tcurr->t_f & a_T_TATOM))
732 break;
733 if(tcurr != tp)
734 ostp = n_string_push_c(ostp, ' ');
735 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
736 (tcurr->t_end - tcurr->t_start));
741 n_lofi_snap_unroll(lofi_snap);
743 agp->ag_input = n_string_cp(ostp);
744 agp->ag_ilen = ostp->s_len;
745 /*ostp = n_string_drop_ownership(ostp);*/
747 jleave:
748 NYD_LEAVE;
749 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) == 0);
752 static long
753 a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem, char **colon)
755 char *line2 = NULL, *cp, *cp2;
756 size_t line2size = 0;
757 int c, isenc;
758 NYD2_ENTER;
760 if (*linebuf == NULL)
761 *linebuf = srealloc(*linebuf, *linesize = 1);
762 **linebuf = '\0';
763 for (;;) {
764 if (--rem < 0) {
765 rem = -1;
766 break;
768 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
769 rem = -1;
770 break;
772 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
774 if (cp > *linebuf)
775 while (blankchar(*cp))
776 ++cp;
777 if (*cp != ':' || cp == *linebuf)
778 continue;
780 /* I guess we got a headline. Handle wraparound */
781 *colon = cp;
782 cp = *linebuf + c;
783 for (;;) {
784 isenc = 0;
785 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
787 cp++;
788 if (rem <= 0)
789 break;
790 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
791 isenc |= 1;
792 ungetc(c = getc(f), f);
793 if (!blankchar(c))
794 break;
795 c = readline_restart(f, &line2, &line2size, 0); /* TODO linepool! */
796 if (c < 0)
797 break;
798 --rem;
799 for (cp2 = line2; blankchar(*cp2); ++cp2)
801 c -= (int)PTR2SIZE(cp2 - line2);
802 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
803 isenc |= 2;
804 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
805 size_t diff = PTR2SIZE(cp - *linebuf),
806 colondiff = PTR2SIZE(*colon - *linebuf);
807 *linebuf = srealloc(*linebuf, *linesize += c + 2);
808 cp = &(*linebuf)[diff];
809 *colon = &(*linebuf)[colondiff];
811 if (isenc != 3)
812 *cp++ = ' ';
813 memcpy(cp, cp2, c);
814 cp += c;
816 *cp = '\0';
818 if (line2 != NULL)
819 free(line2);
820 break;
822 NYD2_LEAVE;
823 return rem;
826 static int
827 msgidnextc(char const **cp, int *status)
829 int c;
830 NYD2_ENTER;
832 assert(cp != NULL);
833 assert(*cp != NULL);
834 assert(status != NULL);
836 for (;;) {
837 if (*status & 01) {
838 if (**cp == '"') {
839 *status &= ~01;
840 (*cp)++;
841 continue;
843 if (**cp == '\\') {
844 (*cp)++;
845 if (**cp == '\0')
846 goto jeof;
848 goto jdfl;
850 switch (**cp) {
851 case '(':
852 *cp = skip_comment(&(*cp)[1]);
853 continue;
854 case '>':
855 case '\0':
856 jeof:
857 c = '\0';
858 goto jleave;
859 case '"':
860 (*cp)++;
861 *status |= 01;
862 continue;
863 case '@':
864 *status |= 02;
865 /*FALLTHRU*/
866 default:
867 jdfl:
868 c = *(*cp)++ & 0377;
869 c = (*status & 02) ? lowerconv(c) : c;
870 goto jleave;
873 jleave:
874 NYD2_LEAVE;
875 return c;
878 static int
879 charcount(char *str, int c)
881 char *cp;
882 int i;
883 NYD2_ENTER;
885 for (i = 0, cp = str; *cp; ++cp)
886 if (*cp == c)
887 ++i;
888 NYD2_LEAVE;
889 return i;
892 static char const *
893 nexttoken(char const *cp)
895 NYD2_ENTER;
896 for (;;) {
897 if (*cp == '\0') {
898 cp = NULL;
899 break;
902 if (*cp == '(') {
903 size_t nesting = 1;
905 do switch (*++cp) {
906 case '(':
907 ++nesting;
908 break;
909 case ')':
910 --nesting;
911 break;
912 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
913 } else if (blankchar(*cp) || *cp == ',')
914 ++cp;
915 else
916 break;
918 NYD2_LEAVE;
919 return cp;
922 FL char const *
923 myaddrs(struct header *hp) /* TODO */
925 struct name *np;
926 char const *rv, *mta;
927 NYD_ENTER;
929 if (hp != NULL && (np = hp->h_from) != NULL) {
930 if ((rv = np->n_fullname) != NULL)
931 goto jleave;
932 if ((rv = np->n_name) != NULL)
933 goto jleave;
936 if((rv = ok_vlook(from)) != NULL){
937 if((np = lextract(rv, GEXTRA | GFULL)) == NULL)
938 jefrom:
939 n_err(_("An address given in *from* is invalid: %s\n"), rv);
940 else for(; np != NULL; np = np->n_flink)
941 if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
942 goto jefrom;
943 goto jleave;
946 /* When invoking *sendmail* directly, it's its task to generate an otherwise
947 * undeterminable From: address. However, if the user sets *hostname*,
948 * accept his desire */
949 if (ok_vlook(hostname) != NULL)
950 goto jnodename;
951 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
952 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
953 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
954 goto jnodename;
955 jleave:
956 NYD_LEAVE;
957 return rv;
959 jnodename:{
960 char *cp;
961 char const *hn, *ln;
962 size_t i;
964 hn = n_nodename(TRU1);
965 ln = ok_vlook(LOGNAME);
966 i = strlen(ln) + strlen(hn) + 1 +1;
967 rv = cp = salloc(i);
968 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
970 goto jleave;
973 FL char const *
974 myorigin(struct header *hp) /* TODO */
976 char const *rv = NULL, *ccp;
977 struct name *np;
978 NYD_ENTER;
980 if((ccp = myaddrs(hp)) != NULL &&
981 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
982 if(np->n_flink == NULL)
983 rv = ccp;
984 else if((ccp = ok_vlook(sender)) != NULL) {
985 if((np = lextract(ccp, GEXTRA | GFULL)) == NULL ||
986 np->n_flink != NULL ||
987 is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
988 n_err(_("The address given in *sender* is invalid: %s\n"), ccp);
989 else
990 rv = ccp;
993 NYD_LEAVE;
994 return rv;
997 FL bool_t
998 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1000 char date[n_FROM_DATEBUF];
1001 bool_t rv;
1002 NYD2_ENTER;
1004 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1005 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1006 !_is_date(date)))
1007 rv = TRUM1;
1008 NYD2_LEAVE;
1009 return rv;
1012 FL int
1013 extract_date_from_from_(char const *line, size_t linelen,
1014 char datebuf[n_FROM_DATEBUF])
1016 int rv;
1017 char const *cp = line;
1018 NYD_ENTER;
1020 rv = 1;
1022 /* "From " */
1023 cp = _from__skipword(cp);
1024 if (cp == NULL)
1025 goto jerr;
1026 /* "addr-spec " */
1027 cp = _from__skipword(cp);
1028 if (cp == NULL)
1029 goto jerr;
1030 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1031 (cp[2] == 'y' || cp[2] == 'Y')){
1032 cp = _from__skipword(cp);
1033 if (cp == NULL)
1034 goto jerr;
1036 /* It seems there are invalid MBOX archives in the wild, compare
1037 * . http://bugs.debian.org/624111
1038 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1039 * What they do is that they obfuscate the address to "name at host",
1040 * and even "name at host dot dom dot dom.
1041 * The [Aa][Tt] is also RFC 733, so be tolerant */
1042 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1043 cp[2] == ' '){
1044 rv = -1;
1045 cp += 3;
1046 jat_dot:
1047 cp = _from__skipword(cp);
1048 if (cp == NULL)
1049 goto jerr;
1050 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1051 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1052 cp += 4;
1053 goto jat_dot;
1057 linelen -= PTR2SIZE(cp - line);
1058 if (linelen < a_HEAD_DATE_MINLEN)
1059 goto jerr;
1060 if (cp[linelen - 1] == '\n') {
1061 --linelen;
1062 /* (Rather IMAP/POP3 only) */
1063 if (cp[linelen - 1] == '\r')
1064 --linelen;
1065 if (linelen < a_HEAD_DATE_MINLEN)
1066 goto jerr;
1068 if (linelen >= n_FROM_DATEBUF)
1069 goto jerr;
1071 jleave:
1072 memcpy(datebuf, cp, linelen);
1073 datebuf[linelen] = '\0';
1074 NYD_LEAVE;
1075 return rv;
1076 jerr:
1077 cp = _("<Unknown date>");
1078 linelen = strlen(cp);
1079 if (linelen >= n_FROM_DATEBUF)
1080 linelen = n_FROM_DATEBUF;
1081 rv = 0;
1082 goto jleave;
1085 FL void
1086 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1088 /* See the prototype declaration for the hairy relationship of
1089 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1090 struct n_header_field **hftail;
1091 struct header nh, *hq = &nh;
1092 char *linebuf = NULL /* TODO line pool */, *colon;
1093 size_t linesize = 0, seenfields = 0;
1094 int c;
1095 long lc;
1096 char const *val, *cp;
1097 NYD_ENTER;
1099 memset(hq, 0, sizeof *hq);
1100 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1101 hq->h_to = hp->h_to;
1102 hq->h_cc = hp->h_cc;
1103 hq->h_bcc = hp->h_bcc;
1105 hftail = &hq->h_user_headers;
1107 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1110 /* TODO yippieia, cat(check(lextract)) :-) */
1111 rewind(fp);
1112 while ((lc = a_gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1113 struct name *np;
1115 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1116 * yet expanded when we parse these! */
1117 if ((val = thisfield(linebuf, "to")) != NULL) {
1118 ++seenfields;
1119 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1120 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1121 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1122 ++seenfields;
1123 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1124 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1125 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1126 ++seenfields;
1127 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1128 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1129 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1130 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1131 ++seenfields;
1132 hq->h_from = cat(hq->h_from,
1133 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1134 EACM_STRICT, NULL));
1136 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1137 ++seenfields;
1138 hq->h_reply_to = cat(hq->h_reply_to,
1139 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1140 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1141 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1142 ++seenfields;
1143 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1144 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1145 EACM_STRICT, NULL));
1146 } else
1147 goto jebadhead;
1148 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1149 (val = thisfield(linebuf, "subj")) != NULL) {
1150 ++seenfields;
1151 for (cp = val; blankchar(*cp); ++cp)
1153 hq->h_subject = (hq->h_subject != NULL)
1154 ? save2str(hq->h_subject, cp) : savestr(cp);
1156 /* The remaining are mostly hacked in and thus TODO -- at least in
1157 * TODO respect to their content checking */
1158 else if((val = thisfield(linebuf, "message-id")) != NULL){
1159 if(n_psonce & n_PSO_t_FLAG){
1160 np = checkaddrs(lextract(val, GREF),
1161 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1162 NULL);
1163 if (np == NULL || np->n_flink != NULL)
1164 goto jebadhead;
1165 ++seenfields;
1166 hq->h_message_id = np;
1167 }else
1168 goto jebadhead;
1169 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1170 if(n_psonce & n_PSO_t_FLAG){
1171 np = checkaddrs(lextract(val, GREF),
1172 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1173 NULL);
1174 ++seenfields;
1175 hq->h_in_reply_to = np;
1176 }else
1177 goto jebadhead;
1178 }else if((val = thisfield(linebuf, "references")) != NULL){
1179 if(n_psonce & n_PSO_t_FLAG){
1180 ++seenfields;
1181 /* TODO Limit number of references TODO better on parser side */
1182 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1183 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1184 NULL));
1185 }else
1186 goto jebadhead;
1188 /* and that is very hairy */
1189 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1190 if(n_psonce & n_PSO_t_FLAG){
1191 ++seenfields;
1192 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1193 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1194 checkaddr_err));
1195 }else
1196 goto jebadhead;
1198 /* A free-form header; a_gethfield() did some verification already.. */
1199 else{
1200 struct n_header_field *hfp;
1201 ui32_t nl, bl;
1202 char const *nstart;
1204 for(nstart = cp = linebuf;; ++cp)
1205 if(!fieldnamechar(*cp))
1206 break;
1207 nl = (ui32_t)PTR2SIZE(cp - nstart);
1209 while(blankchar(*cp))
1210 ++cp;
1211 if(*cp++ != ':'){
1212 jebadhead:
1213 n_err(_("Ignoring header field: %s\n"), linebuf);
1214 continue;
1216 while(blankchar(*cp))
1217 ++cp;
1218 bl = (ui32_t)strlen(cp) +1;
1220 ++seenfields;
1221 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1222 ) + nl +1 + bl);
1223 hftail = &hfp->hf_next;
1224 hfp->hf_next = NULL;
1225 hfp->hf_nl = nl;
1226 hfp->hf_bl = bl - 1;
1227 memcpy(hfp->hf_dat, nstart, nl);
1228 hfp->hf_dat[nl++] = '\0';
1229 memcpy(hfp->hf_dat + nl, cp, bl);
1233 /* In case the blank line after the header has been edited out. Otherwise,
1234 * fetch the header separator */
1235 if (linebuf != NULL) {
1236 if (linebuf[0] != '\0') {
1237 for (cp = linebuf; *(++cp) != '\0';)
1239 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1240 } else {
1241 if ((c = getc(fp)) != '\n' && c != EOF)
1242 ungetc(c, fp);
1246 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1247 hp->h_to = hq->h_to;
1248 hp->h_cc = hq->h_cc;
1249 hp->h_bcc = hq->h_bcc;
1250 hp->h_from = hq->h_from;
1251 hp->h_reply_to = hq->h_reply_to;
1252 hp->h_sender = hq->h_sender;
1253 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1254 !(n_poption & n_PO_t_FLAG))
1255 hp->h_subject = hq->h_subject;
1256 hp->h_user_headers = hq->h_user_headers;
1258 if (n_psonce & n_PSO_t_FLAG) {
1259 hp->h_ref = hq->h_ref;
1260 hp->h_message_id = hq->h_message_id;
1261 hp->h_in_reply_to = hq->h_in_reply_to;
1262 hp->h_mft = hq->h_mft;
1264 /* And perform additional validity checks so that we don't bail later
1265 * on TODO this is good and the place where this should occur,
1266 * TODO unfortunately a lot of other places do again and blabla */
1267 if (hp->h_from == NULL)
1268 hp->h_from = n_poption_arg_r;
1269 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1270 hp->h_sender = lextract(ok_vlook(sender),
1271 GEXTRA | GFULL | GFULLEXTRA);
1273 } else
1274 n_err(_("Restoring deleted header lines\n"));
1276 if (linebuf != NULL)
1277 free(linebuf);
1278 NYD_LEAVE;
1281 FL char *
1282 hfield_mult(char const *field, struct message *mp, int mult)
1284 FILE *ibuf;
1285 struct str hfs;
1286 long lc;
1287 size_t linesize = 0; /* TODO line pool */
1288 char *linebuf = NULL, *colon;
1289 char const *hfield;
1290 NYD_ENTER;
1292 /* There are (spam) messages which have header bytes which are many KB when
1293 * joined, so resize a single heap storage until we are done if we shall
1294 * collect a field that may have multiple bodies; only otherwise use the
1295 * string dope directly */
1296 memset(&hfs, 0, sizeof hfs);
1298 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1299 goto jleave;
1300 if ((lc = mp->m_lines - 1) < 0)
1301 goto jleave;
1303 if ((mp->m_flag & MNOFROM) == 0 &&
1304 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1305 goto jleave;
1306 while (lc > 0) {
1307 if ((lc = a_gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1308 break;
1309 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1310 if (mult)
1311 n_str_add_buf(&hfs, hfield, strlen(hfield));
1312 else {
1313 hfs.s = savestr(hfield);
1314 break;
1319 jleave:
1320 if (linebuf != NULL)
1321 free(linebuf);
1322 if (mult && hfs.s != NULL) {
1323 colon = savestrbuf(hfs.s, hfs.l);
1324 free(hfs.s);
1325 hfs.s = colon;
1327 NYD_LEAVE;
1328 return hfs.s;
1331 FL char const *
1332 thisfield(char const *linebuf, char const *field)
1334 char const *rv = NULL;
1335 NYD2_ENTER;
1337 while (lowerconv(*linebuf) == lowerconv(*field)) {
1338 ++linebuf;
1339 ++field;
1341 if (*field != '\0')
1342 goto jleave;
1344 while (blankchar(*linebuf))
1345 ++linebuf;
1346 if (*linebuf++ != ':')
1347 goto jleave;
1349 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1350 ++linebuf;
1351 rv = linebuf;
1352 jleave:
1353 NYD2_LEAVE;
1354 return rv;
1357 FL char *
1358 nameof(struct message *mp, int reptype)
1360 char *cp, *cp2;
1361 NYD_ENTER;
1363 cp = skin(name1(mp, reptype));
1364 if (reptype != 0 || charcount(cp, '!') < 2)
1365 goto jleave;
1366 cp2 = strrchr(cp, '!');
1367 --cp2;
1368 while (cp2 > cp && *cp2 != '!')
1369 --cp2;
1370 if (*cp2 == '!')
1371 cp = cp2 + 1;
1372 jleave:
1373 NYD_LEAVE;
1374 return cp;
1377 FL char const *
1378 skip_comment(char const *cp)
1380 size_t nesting;
1381 NYD_ENTER;
1383 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1384 switch (*cp) {
1385 case '\\':
1386 if (cp[1])
1387 ++cp;
1388 break;
1389 case '(':
1390 ++nesting;
1391 break;
1392 case ')':
1393 --nesting;
1394 break;
1397 NYD_LEAVE;
1398 return cp;
1401 FL char const *
1402 routeaddr(char const *name)
1404 char const *np, *rp = NULL;
1405 NYD_ENTER;
1407 for (np = name; *np; np++) {
1408 switch (*np) {
1409 case '(':
1410 np = skip_comment(np + 1) - 1;
1411 break;
1412 case '"':
1413 while (*np) {
1414 if (*++np == '"')
1415 break;
1416 if (*np == '\\' && np[1])
1417 np++;
1419 break;
1420 case '<':
1421 rp = np;
1422 break;
1423 case '>':
1424 goto jleave;
1427 rp = NULL;
1428 jleave:
1429 NYD_LEAVE;
1430 return rp;
1433 FL enum expand_addr_flags
1434 expandaddr_to_eaf(void)
1436 struct eafdesc {
1437 char const *eafd_name;
1438 bool_t eafd_is_target;
1439 ui8_t eafd_andoff;
1440 ui8_t eafd_or;
1441 } const eafa[] = {
1442 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1443 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1444 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1445 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1446 {"file", TRU1, EAF_NONE, EAF_FILE},
1447 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1448 {"name", TRU1, EAF_NONE, EAF_NAME},
1449 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1450 }, *eafp;
1452 char *buf;
1453 enum expand_addr_flags rv;
1454 char const *cp;
1455 NYD2_ENTER;
1457 if ((cp = ok_vlook(expandaddr)) == NULL)
1458 rv = EAF_RESTRICT_TARGETS;
1459 else if (*cp == '\0')
1460 rv = EAF_TARGET_MASK;
1461 else {
1462 rv = EAF_TARGET_MASK;
1464 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1465 bool_t minus;
1467 if ((minus = (*cp == '-')) || *cp == '+')
1468 ++cp;
1469 for (eafp = eafa;; ++eafp) {
1470 if (eafp == eafa + n_NELEM(eafa)) {
1471 if (n_poption & n_PO_D_V)
1472 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1473 break;
1474 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1475 if (!minus) {
1476 rv &= ~eafp->eafd_andoff;
1477 rv |= eafp->eafd_or;
1478 } else {
1479 if (eafp->eafd_is_target)
1480 rv &= ~eafp->eafd_or;
1481 else if (n_poption & n_PO_D_V)
1482 n_err(_("minus - prefix invalid for *expandaddr* value: "
1483 "%s\n"), --cp);
1485 break;
1486 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1487 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1488 rv &= ~EAF_NAME;
1489 break;
1494 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1495 (n_poption & n_PO_TILDE_FLAG)))
1496 rv |= EAF_TARGET_MASK;
1497 else if(n_poption & n_PO_D_V){
1498 if(!(rv & EAF_TARGET_MASK))
1499 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1500 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1501 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1504 NYD2_LEAVE;
1505 return rv;
1508 FL si8_t
1509 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1511 char cbuf[sizeof "'\\U12340'"];
1512 char const *cs;
1513 int f;
1514 si8_t rv;
1515 enum expand_addr_flags eaf;
1516 NYD_ENTER;
1518 eaf = expandaddr_to_eaf();
1519 f = np->n_flags;
1521 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1522 if (eaf & EAF_FAILINVADDR)
1523 rv = -rv;
1525 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1527 } else {
1528 ui32_t c;
1529 char const *fmt = "'\\x%02X'";
1530 bool_t ok8bit = TRU1;
1532 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1533 cs = _("Invalid domain name: %s, character %s\n");
1534 fmt = "'\\U%04X'";
1535 ok8bit = FAL0;
1536 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1537 cs = _("%s contains invalid %s sequence\n");
1538 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1539 cs = _("%s is an invalid alias name\n");
1540 } else
1541 cs = _("%s contains invalid byte %s\n");
1543 c = NAME_ADDRSPEC_ERR_GETWC(f);
1544 snprintf(cbuf, sizeof cbuf,
1545 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1546 goto jprint;
1548 goto jleave;
1551 /* *expandaddr* stuff */
1552 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1553 goto jleave;
1555 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1556 if (eaf & EAF_FAIL)
1557 rv = -rv;
1558 cs = _("%s%s: file or pipe addressees not allowed here\n");
1559 if (eacm & EACM_NOLOG)
1560 goto jleave;
1561 else
1562 goto j0print;
1565 eaf |= (eacm & EAF_TARGET_MASK);
1566 if (eacm & EACM_NONAME)
1567 eaf &= ~EAF_NAME;
1569 if (eaf == EAF_NONE) {
1570 rv = FAL0;
1571 goto jleave;
1573 if (eaf & EAF_FAIL)
1574 rv = -rv;
1576 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1577 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1578 if (eacm & EACM_NOLOG)
1579 goto jleave;
1580 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1581 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1582 if (eacm & EACM_NOLOG)
1583 goto jleave;
1584 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1585 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1586 if (eacm & EACM_NOLOG)
1587 goto jleave;
1588 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1589 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1590 if (eacm & EACM_NOLOG)
1591 goto jleave;
1592 } else {
1593 rv = FAL0;
1594 goto jleave;
1597 j0print:
1598 cbuf[0] = '\0';
1599 jprint:
1600 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1601 jleave:
1602 NYD_LEAVE;
1603 return rv;
1606 FL char *
1607 skin(char const *name)
1609 struct n_addrguts ag;
1610 char *rv;
1611 NYD_ENTER;
1613 if(name != NULL){
1614 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1615 rv = ag.ag_skinned;
1616 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1617 rv = savestrbuf(rv, ag.ag_slen);
1618 }else
1619 rv = NULL;
1620 NYD_LEAVE;
1621 return rv;
1624 /* TODO addrspec_with_guts: RFC 5322
1625 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1626 FL char const *
1627 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1628 bool_t issingle_hack){
1629 char const *cp;
1630 char *cp2, *bufend, *nbuf, c;
1631 enum{
1632 a_NONE,
1633 a_GOTLT = 1<<0,
1634 a_GOTADDR = 1<<1,
1635 a_GOTSPACE = 1<<2,
1636 a_LASTSP = 1<<3
1637 } flags;
1638 NYD_ENTER;
1640 memset(agp, 0, sizeof *agp);
1642 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1643 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1644 agp->ag_slen = 0;
1645 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1646 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1647 goto jleave;
1648 }else if(!doskin){
1649 /*agp->ag_iaddr_start = 0;*/
1650 agp->ag_iaddr_aend = agp->ag_ilen;
1651 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1652 agp->ag_slen = agp->ag_ilen;
1653 agp->ag_n_flags = NAME_SKINNED;
1654 goto jcheck;
1657 flags = a_NONE;
1658 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1659 /*agp->ag_iaddr_start = 0;*/
1660 cp2 = bufend = nbuf;
1662 /* TODO This is complete crap and should use a token parser */
1663 for(cp = name++; (c = *cp++) != '\0';){
1664 switch (c) {
1665 case '(':
1666 cp = skip_comment(cp);
1667 flags &= ~a_LASTSP;
1668 break;
1669 case '"':
1670 /* Start of a "quoted-string". Copy it in its entirety */
1671 /* XXX RFC: quotes are "semantically invisible"
1672 * XXX But it was explicitly added (Changelog.Heirloom,
1673 * XXX [9.23] released 11/15/00, "Do not remove quotes
1674 * XXX when skinning names"? No more info.. */
1675 *cp2++ = c;
1676 while ((c = *cp) != '\0') { /* TODO improve */
1677 ++cp;
1678 if (c == '"') {
1679 *cp2++ = c;
1680 break;
1682 if (c != '\\')
1683 *cp2++ = c;
1684 else if ((c = *cp) != '\0') {
1685 *cp2++ = c;
1686 ++cp;
1689 flags &= ~a_LASTSP;
1690 break;
1691 case ' ':
1692 case '\t':
1693 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1694 flags |= a_GOTSPACE;
1695 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1697 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1698 cp += 3, *cp2++ = '@';
1699 else if (cp[0] == '@' && blankchar(cp[1]))
1700 cp += 2, *cp2++ = '@';
1701 else
1702 flags |= a_LASTSP;
1703 break;
1704 case '<':
1705 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1706 cp2 = bufend;
1707 flags &= ~(a_GOTSPACE | a_LASTSP);
1708 flags |= a_GOTLT | a_GOTADDR;
1709 break;
1710 case '>':
1711 if(flags & a_GOTLT){
1712 /* (_addrspec_check() verifies these later!) */
1713 flags &= ~(a_GOTLT | a_LASTSP);
1714 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1716 /* Skip over the entire remaining field */
1717 while((c = *cp) != '\0' && c != ','){
1718 ++cp;
1719 if (c == '(')
1720 cp = skip_comment(cp);
1721 else if (c == '"')
1722 while ((c = *cp) != '\0') {
1723 ++cp;
1724 if (c == '"')
1725 break;
1726 if (c == '\\' && *cp != '\0')
1727 ++cp;
1730 break;
1732 /* FALLTHRU */
1733 default:
1734 if(flags & a_LASTSP){
1735 flags &= ~a_LASTSP;
1736 if(flags & a_GOTADDR)
1737 *cp2++ = ' ';
1739 *cp2++ = c;
1740 /* This character is forbidden here, but it may nonetheless be
1741 * present: ensure we turn this into something valid! (E.g., if the
1742 * next character would be a "..) */
1743 if(c == '\\' && *cp != '\0')
1744 *cp2++ = *cp++;
1745 if(c == ',' && !issingle_hack){
1746 if(!(flags & a_GOTLT)){
1747 *cp2++ = ' ';
1748 for(; blankchar(*cp); ++cp)
1750 flags &= ~a_LASTSP;
1751 bufend = cp2;
1753 }else if(!(flags & a_GOTADDR)){
1754 flags |= a_GOTADDR;
1755 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1759 --name;
1760 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1761 if (agp->ag_iaddr_aend == 0)
1762 agp->ag_iaddr_aend = agp->ag_ilen;
1763 /* Misses > */
1764 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1765 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1766 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1767 agp->ag_iaddr_aend = agp->ag_ilen;
1768 cp2[agp->ag_ilen++] = '>';
1769 cp2[agp->ag_ilen] = '\0';
1771 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1772 n_lofi_free(nbuf);
1773 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1774 jcheck:
1775 if(a_head_addrspec_check(agp, doskin) <= FAL0)
1776 name = NULL;
1777 else
1778 name = agp->ag_input;
1779 jleave:
1780 NYD_LEAVE;
1781 return name;
1784 FL char *
1785 realname(char const *name)
1787 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1788 char *rname, *rp;
1789 struct str in, out;
1790 int quoted, good, nogood;
1791 NYD_ENTER;
1793 if ((cp = n_UNCONST(name)) == NULL)
1794 goto jleave;
1795 for (; *cp != '\0'; ++cp) {
1796 switch (*cp) {
1797 case '(':
1798 if (cstart != NULL) {
1799 /* More than one comment in address, doesn't make sense to display
1800 * it without context. Return the entire field */
1801 cp = mime_fromaddr(name);
1802 goto jleave;
1804 cstart = cp++;
1805 cp = skip_comment(cp);
1806 cend = cp--;
1807 if (cend <= cstart)
1808 cend = cstart = NULL;
1809 break;
1810 case '"':
1811 while (*cp) {
1812 if (*++cp == '"')
1813 break;
1814 if (*cp == '\\' && cp[1])
1815 ++cp;
1817 break;
1818 case '<':
1819 if (cp > name) {
1820 cstart = name;
1821 cend = cp;
1823 break;
1824 case ',':
1825 /* More than one address. Just use the first one */
1826 goto jbrk;
1830 jbrk:
1831 if (cstart == NULL) {
1832 if (*name == '<') {
1833 /* If name contains only a route-addr, the surrounding angle brackets
1834 * don't serve any useful purpose when displaying, so remove */
1835 cp = prstr(skin(name));
1836 } else
1837 cp = mime_fromaddr(name);
1838 goto jleave;
1841 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1842 * not stripped. The idea is to strip only syntactical relevant things (but
1843 * this is not necessarily the most sensible way in practice) */
1844 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1845 quoted = 0;
1846 for (cp = cstart; cp < cend; ++cp) {
1847 if (*cp == '(' && !quoted) {
1848 cq = skip_comment(++cp);
1849 if (PTRCMP(--cq, >, cend))
1850 cq = cend;
1851 while (cp < cq) {
1852 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1853 ++cp;
1854 *rp++ = *cp++;
1856 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1857 *rp++ = *++cp;
1858 else if (*cp == '"') {
1859 quoted = !quoted;
1860 continue;
1861 } else
1862 *rp++ = *cp;
1864 *rp = '\0';
1865 in.s = rname;
1866 in.l = rp - rname;
1867 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1868 ac_free(rname);
1869 rname = savestr(out.s);
1870 free(out.s);
1872 while (blankchar(*rname))
1873 ++rname;
1874 for (rp = rname; *rp != '\0'; ++rp)
1876 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1877 *rp = '\0';
1878 if (rp == rname) {
1879 cp = mime_fromaddr(name);
1880 goto jleave;
1883 /* mime_fromhdr() has converted all nonprintable characters to question
1884 * marks now. These and blanks are considered uninteresting; if the
1885 * displayed part of the real name contains more than 25% of them, it is
1886 * probably better to display the plain email address instead */
1887 good = 0;
1888 nogood = 0;
1889 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1890 if (*rp == '?' || blankchar(*rp))
1891 ++nogood;
1892 else
1893 ++good;
1894 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1895 jleave:
1896 NYD_LEAVE;
1897 return n_UNCONST(cp);
1900 FL char *
1901 name1(struct message *mp, int reptype)
1903 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1904 size_t namesize, linesize = 0;
1905 FILE *ibuf;
1906 int f1st = 1;
1907 NYD_ENTER;
1909 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1910 goto jleave;
1911 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1912 goto jleave;
1914 namebuf = smalloc(namesize = 1);
1915 namebuf[0] = 0;
1916 if (mp->m_flag & MNOFROM)
1917 goto jout;
1918 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1919 goto jout;
1920 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1921 goto jout;
1923 jnewname:
1924 if (namesize <= linesize)
1925 namebuf = srealloc(namebuf, namesize = linesize +1);
1926 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1928 for (; blankchar(*cp); ++cp)
1930 for (cp2 = namebuf + strlen(namebuf);
1931 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1932 *cp2++ = *cp++;
1933 *cp2 = '\0';
1935 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1936 goto jout;
1937 if ((cp = strchr(linebuf, 'F')) == NULL)
1938 goto jout;
1939 if (strncmp(cp, "From", 4))
1940 goto jout;
1941 if (namesize <= linesize)
1942 namebuf = srealloc(namebuf, namesize = linesize + 1);
1944 while ((cp = strchr(cp, 'r')) != NULL) {
1945 if (!strncmp(cp, "remote", 6)) {
1946 if ((cp = strchr(cp, 'f')) == NULL)
1947 break;
1948 if (strncmp(cp, "from", 4) != 0)
1949 break;
1950 if ((cp = strchr(cp, ' ')) == NULL)
1951 break;
1952 cp++;
1953 if (f1st) {
1954 strncpy(namebuf, cp, namesize);
1955 f1st = 0;
1956 } else {
1957 cp2 = strrchr(namebuf, '!') + 1;
1958 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1960 namebuf[namesize - 2] = '!';
1961 namebuf[namesize - 1] = '\0';
1962 goto jnewname;
1964 cp++;
1966 jout:
1967 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1968 *cp == '\0')
1969 cp = savestr(namebuf);
1971 if (linebuf != NULL)
1972 free(linebuf);
1973 free(namebuf);
1974 jleave:
1975 NYD_LEAVE;
1976 return cp;
1979 FL char const *
1980 subject_re_trim(char const *s){
1981 struct{
1982 ui8_t len;
1983 char dat[7];
1984 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
1985 {3, "re:"},
1986 {3, "aw:"}, {5, "antw:"}, /* de */
1987 {3, "wg:"}, /* Seen too often in the wild */
1988 {0, ""}
1991 bool_t any;
1992 char *re_st, *re_st_x;
1993 char const *orig_s;
1994 size_t re_l;
1995 NYD_ENTER;
1997 any = FAL0;
1998 orig_s = s;
1999 re_st = NULL;
2000 n_UNINIT(re_l, 0);
2002 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2003 (re_l = strlen(re_st_x)) > 0){
2004 re_st = n_lofi_alloc(++re_l * 2);
2005 memcpy(re_st, re_st_x, re_l);
2008 jouter:
2009 while(*s != '\0'){
2010 while(spacechar(*s))
2011 ++s;
2013 for(pp = ignored; pp->len > 0; ++pp)
2014 if(is_asccaseprefix(pp->dat, s)){
2015 s += pp->len;
2016 any = TRU1;
2017 goto jouter;
2020 if(re_st != NULL){
2021 char *cp;
2023 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2024 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2025 if(is_asccaseprefix(cp, s)){
2026 s += strlen(cp);
2027 any = TRU1;
2028 goto jouter;
2031 break;
2034 if(re_st != NULL)
2035 n_lofi_free(re_st);
2036 NYD_LEAVE;
2037 return any ? s : orig_s;
2040 FL int
2041 msgidcmp(char const *s1, char const *s2)
2043 int q1 = 0, q2 = 0, c1, c2;
2044 NYD_ENTER;
2046 while(*s1 == '<')
2047 ++s1;
2048 while(*s2 == '<')
2049 ++s2;
2051 do {
2052 c1 = msgidnextc(&s1, &q1);
2053 c2 = msgidnextc(&s2, &q2);
2054 if (c1 != c2)
2055 break;
2056 } while (c1 && c2);
2057 NYD_LEAVE;
2058 return c1 - c2;
2061 FL char const *
2062 fakefrom(struct message *mp)
2064 char const *name;
2065 NYD_ENTER;
2067 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2068 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2069 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2070 * RFC 4155 however requires a RFC 5322 (2822) conforming
2071 * "addr-spec", but we simply can't provide that */
2072 name = "MAILER-DAEMON";
2073 NYD_LEAVE;
2074 return name;
2077 FL char const *
2078 fakedate(time_t t)
2080 char *cp, *cq;
2081 NYD_ENTER;
2083 cp = ctime(&t);
2084 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
2086 *cq = '\0';
2087 cp = savestr(cp);
2088 NYD_LEAVE;
2089 return cp;
2092 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2093 FL time_t
2094 unixtime(char const *fromline)
2096 char const *fp, *xp;
2097 time_t t;
2098 si32_t i, year, month, day, hour, minute, second, tzdiff;
2099 struct tm *tmptr;
2100 NYD2_ENTER;
2102 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2104 fp -= 24;
2105 if (PTR2SIZE(fp - fromline) < 7)
2106 goto jinvalid;
2107 if (fp[3] != ' ')
2108 goto jinvalid;
2109 for (i = 0;;) {
2110 if (!strncmp(fp + 4, n_month_names[i], 3))
2111 break;
2112 if (n_month_names[++i][0] == '\0')
2113 goto jinvalid;
2115 month = i + 1;
2116 if (fp[7] != ' ')
2117 goto jinvalid;
2118 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2119 if (*xp != ' ' || xp != fp + 10)
2120 goto jinvalid;
2121 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2122 if (*xp != ':' || xp != fp + 13)
2123 goto jinvalid;
2124 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2125 if (*xp != ':' || xp != fp + 16)
2126 goto jinvalid;
2127 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2128 if (*xp != ' ' || xp != fp + 19)
2129 goto jinvalid;
2130 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2131 if (xp != fp + 24)
2132 goto jinvalid;
2133 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2134 goto jinvalid;
2135 tzdiff = t - mktime(gmtime(&t));
2136 tmptr = localtime(&t);
2137 if (tmptr->tm_isdst > 0)
2138 tzdiff += 3600;
2139 t -= tzdiff;
2140 jleave:
2141 NYD2_LEAVE;
2142 return t;
2143 jinvalid:
2144 t = n_time_epoch();
2145 goto jleave;
2147 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2149 FL time_t
2150 rfctime(char const *date) /* TODO n_idec_ return tests */
2152 char const *cp, *x;
2153 time_t t;
2154 si32_t i, year, month, day, hour, minute, second;
2155 NYD2_ENTER;
2157 cp = date;
2159 if ((cp = nexttoken(cp)) == NULL)
2160 goto jinvalid;
2161 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2162 cp[3] == ',') {
2163 if ((cp = nexttoken(&cp[4])) == NULL)
2164 goto jinvalid;
2166 n_idec_si32_cp(&day, cp, 10, &x);
2167 if ((cp = nexttoken(x)) == NULL)
2168 goto jinvalid;
2169 for (i = 0;;) {
2170 if (!strncmp(cp, n_month_names[i], 3))
2171 break;
2172 if (n_month_names[++i][0] == '\0')
2173 goto jinvalid;
2175 month = i + 1;
2176 if ((cp = nexttoken(&cp[3])) == NULL)
2177 goto jinvalid;
2178 /* RFC 5322, 4.3:
2179 * Where a two or three digit year occurs in a date, the year is to be
2180 * interpreted as follows: If a two digit year is encountered whose
2181 * value is between 00 and 49, the year is interpreted by adding 2000,
2182 * ending up with a value between 2000 and 2049. If a two digit year
2183 * is encountered with a value between 50 and 99, or any three digit
2184 * year is encountered, the year is interpreted by adding 1900 */
2185 n_idec_si32_cp(&year, cp, 10, &x);
2186 i = (int)PTR2SIZE(x - cp);
2187 if (i == 2 && year >= 0 && year <= 49)
2188 year += 2000;
2189 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2190 year += 1900;
2191 if ((cp = nexttoken(x)) == NULL)
2192 goto jinvalid;
2193 n_idec_si32_cp(&hour, cp, 10, &x);
2194 if (*x != ':')
2195 goto jinvalid;
2196 cp = &x[1];
2197 n_idec_si32_cp(&minute, cp, 10, &x);
2198 if (*x == ':') {
2199 cp = &x[1];
2200 n_idec_si32_cp(&second, cp, 10, &x);
2201 } else
2202 second = 0;
2204 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2205 goto jinvalid;
2206 if ((cp = nexttoken(x)) != NULL) {
2207 char buf[3];
2208 int sign = 1;
2210 switch (*cp) {
2211 case '+':
2212 sign = -1;
2213 /* FALLTHRU */
2214 case '-':
2215 ++cp;
2216 break;
2218 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2219 digitchar(cp[3])) {
2220 si64_t tadj;
2222 buf[2] = '\0';
2223 buf[0] = cp[0];
2224 buf[1] = cp[1];
2225 n_idec_si32_cp(&i, buf, 10, NULL);
2226 tadj = (si64_t)i * 3600; /* XXX */
2227 buf[0] = cp[2];
2228 buf[1] = cp[3];
2229 n_idec_si32_cp(&i, buf, 10, NULL);
2230 tadj += (si64_t)i * 60; /* XXX */
2231 if (sign < 0)
2232 tadj = -tadj;
2233 t += (time_t)tadj;
2235 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2236 * TODO once again, Christos Zoulas and NetBSD Mail have done
2237 * TODO a really good job already, but using strptime(3), which
2238 * TODO is not portable. Nonetheless, WE must improve, not
2239 * TODO at last because we simply ignore obsolete timezones!!
2240 * TODO See RFC 5322, 4.3! */
2242 jleave:
2243 NYD2_LEAVE;
2244 return t;
2245 jinvalid:
2246 t = 0;
2247 goto jleave;
2250 FL time_t
2251 combinetime(int year, int month, int day, int hour, int minute, int second){
2252 size_t const jdn_epoch = 2440588;
2253 bool_t const y2038p = (sizeof(time_t) == 4);
2255 size_t jdn;
2256 time_t t;
2257 NYD2_ENTER;
2259 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2260 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2261 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2262 day < 1 || day > 31 ||
2263 month < 1 || month > 12 ||
2264 year < 1970)
2265 goto jerr;
2267 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2268 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2269 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2270 * test by stepping second-wise around the flip. Don't care otherwise */
2271 if(!y2038p)
2272 goto jerr;
2273 if(year > 2038 || month > 1 || day > 19 ||
2274 hour > 3 || minute > 14 || second > 7)
2275 goto jerr;
2278 t = second;
2279 t += minute * n_DATE_SECSMIN;
2280 t += hour * n_DATE_SECSHOUR;
2282 jdn = a_head_gregorian_to_jdn(year, month, day);
2283 jdn -= jdn_epoch;
2284 t += (time_t)jdn * n_DATE_SECSDAY;
2285 jleave:
2286 NYD2_LEAVE;
2287 return t;
2288 jerr:
2289 t = (time_t)-1;
2290 goto jleave;
2293 FL void
2294 substdate(struct message *m)
2296 char const *cp;
2297 NYD_ENTER;
2299 /* Determine the date to print in faked 'From ' lines. This is traditionally
2300 * the date the message was written to the mail file. Try to determine this
2301 * using RFC message header fields, or fall back to current time */
2302 m->m_time = 0;
2303 if ((cp = hfield1("received", m)) != NULL) {
2304 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2306 ++cp;
2307 while (alnumchar(*cp));
2309 if (cp && *++cp)
2310 m->m_time = rfctime(cp);
2312 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2313 if ((cp = hfield1("date", m)) != NULL)
2314 m->m_time = rfctime(cp);
2316 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2317 m->m_time = time_current.tc_time;
2318 NYD_LEAVE;
2321 FL void
2322 setup_from_and_sender(struct header *hp)
2324 char const *addr;
2325 struct name *np;
2326 NYD_ENTER;
2328 /* If -t parsed or composed From: then take it. With -t we otherwise
2329 * want -r to be honoured in favour of *from* in order to have
2330 * a behaviour that is compatible with what users would expect from e.g.
2331 * postfix(1) */
2332 if ((np = hp->h_from) != NULL ||
2333 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2335 } else if ((addr = myaddrs(hp)) != NULL)
2336 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2337 hp->h_from = np;
2339 if ((np = hp->h_sender) != NULL) {
2341 } else if ((addr = ok_vlook(sender)) != NULL)
2342 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2343 hp->h_sender = np;
2345 NYD_LEAVE;
2348 FL struct name const *
2349 check_from_and_sender(struct name const *fromfield,
2350 struct name const *senderfield)
2352 struct name const *rv = NULL;
2353 NYD_ENTER;
2355 if (senderfield != NULL) {
2356 if (senderfield->n_flink != NULL) {
2357 n_err(_("The Sender: field may contain only one address\n"));
2358 goto jleave;
2360 rv = senderfield;
2363 if (fromfield != NULL) {
2364 if (fromfield->n_flink != NULL && senderfield == NULL) {
2365 n_err(_("A Sender: is required when there are multiple "
2366 "addresses in From:\n"));
2367 goto jleave;
2369 if (rv == NULL)
2370 rv = fromfield;
2373 if (rv == NULL)
2374 rv = (struct name*)0x1;
2375 jleave:
2376 NYD_LEAVE;
2377 return rv;
2380 #ifdef HAVE_XSSL
2381 FL char *
2382 getsender(struct message *mp)
2384 char *cp;
2385 struct name *np;
2386 NYD_ENTER;
2388 if ((cp = hfield1("from", mp)) == NULL ||
2389 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2390 cp = NULL;
2391 else
2392 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2393 NYD_LEAVE;
2394 return cp;
2396 #endif
2398 FL int
2399 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2400 int subjfirst)
2402 /* TODO grab_headers: again, check counts etc. against RFC;
2403 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2404 int errs;
2405 int volatile comma;
2406 NYD_ENTER;
2408 errs = 0;
2409 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2411 if (gflags & GTO)
2412 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2413 if (subjfirst && (gflags & GSUBJECT))
2414 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2415 if (gflags & GCC)
2416 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2417 if (gflags & GBCC)
2418 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2420 if (gflags & GEXTRA) {
2421 if (hp->h_from == NULL)
2422 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2423 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2424 GEXTRA | GFULL | GFULLEXTRA);
2425 if (hp->h_reply_to == NULL) {
2426 struct name *v15compat;
2428 if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
2429 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2430 hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
2431 if(hp->h_reply_to == NULL) /* v15 */
2432 hp->h_reply_to = v15compat;
2434 hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
2435 GEXTRA | GFULL);
2436 if (hp->h_sender == NULL)
2437 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2438 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2439 GEXTRA | GFULL);
2442 if (!subjfirst && (gflags & GSUBJECT))
2443 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2445 NYD_LEAVE;
2446 return errs;
2449 FL bool_t
2450 n_header_match(struct message *mp, struct search_expr const *sep){
2451 struct str fiter, in, out;
2452 char const *field;
2453 long lc;
2454 FILE *ibuf;
2455 size_t *linesize;
2456 char **linebuf, *colon;
2457 enum {a_NONE, a_ALL, a_ITER, a_RE} match;
2458 bool_t rv;
2459 NYD_ENTER;
2461 rv = FAL0;
2462 match = a_NONE;
2463 linebuf = &termios_state.ts_linebuf; /* XXX line pool */
2464 linesize = &termios_state.ts_linesize; /* XXX line pool */
2465 n_UNINIT(fiter.l, 0);
2466 n_UNINIT(fiter.s, NULL);
2468 if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2469 goto jleave;
2470 if((lc = mp->m_lines - 1) < 0)
2471 goto jleave;
2473 if((mp->m_flag & MNOFROM) == 0 &&
2474 readline_restart(ibuf, linebuf, linesize, 0) < 0)
2475 goto jleave;
2477 /* */
2478 if((field = sep->ss_field) != NULL){
2479 if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
2480 match = a_ALL;
2481 else{
2482 fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
2483 match = a_ITER;
2485 #ifdef HAVE_REGEX
2486 }else if(sep->ss_fieldre != NULL){
2487 match = a_RE;
2488 #endif
2489 }else
2490 match = a_ALL;
2492 /* Iterate over all the headers */
2493 while(lc > 0){
2494 struct name *np;
2496 if((lc = a_gethfield(ibuf, linebuf, linesize, lc, &colon)) <= 0)
2497 break;
2499 /* Is this a header we are interested in? */
2500 if(match == a_ITER){
2501 char *itercp;
2503 memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
2504 while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
2505 /* It may be an abbreviation */
2506 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
2507 size_t i;
2508 char c1;
2510 if(field[0] != '\0' && field[1] == '\0'){
2511 c1 = lowerconv(field[0]);
2512 for(i = 0; i < n_NELEM(x); ++i){
2513 if(c1 == x[i][0]){
2514 field = x[i];
2515 break;
2520 if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
2521 break;
2523 if(field == NULL)
2524 continue;
2525 #ifdef HAVE_REGEX
2526 }else if(match == a_RE){
2527 char *cp;
2528 size_t i;
2530 i = PTR2SIZE(colon - *linebuf);
2531 cp = n_lofi_alloc(i +1);
2532 memcpy(cp, *linebuf, i);
2533 cp[i] = '\0';
2534 i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
2535 n_lofi_free(cp);
2536 if(!i)
2537 continue;
2538 #endif
2541 /* It could be a plain existence test */
2542 if(sep->ss_field_exists){
2543 rv = TRU1;
2544 break;
2547 /* Need to check the body */
2548 while(blankchar(*++colon))
2550 in.s = colon;
2552 /* Shall we split into address list and match as/the addresses only?
2553 * TODO at some later time we should ignore and log efforts to search
2554 * TODO a skinned address list if we know the header has none such */
2555 if(sep->ss_skin){
2556 if((np = lextract(in.s, GSKIN)) == NULL)
2557 continue;
2558 out.s = np->n_name;
2559 }else{
2560 np = NULL;
2561 in.l = strlen(in.s);
2562 mime_fromhdr(&in, &out, TD_ICONV);
2565 jnext_name:
2566 #ifdef HAVE_REGEX
2567 if(sep->ss_bodyre != NULL)
2568 rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
2569 else
2570 #endif
2571 rv = substr(out.s, sep->ss_body);
2573 if(np == NULL)
2574 free(out.s);
2575 if(rv)
2576 break;
2577 if(np != NULL && (np = np->n_flink) != NULL){
2578 out.s = np->n_name;
2579 goto jnext_name;
2583 jleave:
2584 if(match == a_ITER)
2585 n_lofi_free(fiter.s);
2586 NYD_LEAVE;
2587 return rv;
2590 FL struct n_header_field *
2591 n_customhdr_query(void){
2592 char const *vp;
2593 struct n_header_field *rv, **tail, *hfp;
2594 NYD_ENTER;
2596 rv = NULL;
2598 if((vp = ok_vlook(customhdr)) != NULL){
2599 char *buf;
2601 tail = &rv;
2602 buf = savestr(vp);
2603 jch_outer:
2604 while((vp = n_strsep_esc(&buf, ',', TRU1)) != NULL){
2605 ui32_t nl, bl;
2606 char const *nstart, *cp;
2608 for(nstart = cp = vp;; ++cp){
2609 if(fieldnamechar(*cp))
2610 continue;
2611 if(*cp == '\0'){
2612 if(cp == nstart){
2613 n_err(_("Invalid nameless *customhdr* entry\n"));
2614 goto jch_outer;
2616 }else if(*cp != ':' && !blankchar(*cp)){
2617 jch_badent:
2618 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2619 goto jch_outer;
2621 break;
2623 nl = (ui32_t)PTR2SIZE(cp - nstart);
2625 while(blankchar(*cp))
2626 ++cp;
2627 if(*cp++ != ':')
2628 goto jch_badent;
2629 while(blankchar(*cp))
2630 ++cp;
2631 bl = (ui32_t)strlen(cp) +1;
2633 *tail =
2634 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2635 nl +1 + bl);
2636 tail = &hfp->hf_next;
2637 hfp->hf_next = NULL;
2638 hfp->hf_nl = nl;
2639 hfp->hf_bl = bl - 1;
2640 memcpy(hfp->hf_dat, nstart, nl);
2641 hfp->hf_dat[nl++] = '\0';
2642 memcpy(hfp->hf_dat + nl, cp, bl);
2645 NYD_LEAVE;
2646 return rv;
2649 /* s-it-mode */