a_nag_group_lookup(): FIX used case-i?sensitive hash function!
[s-mailx.git] / head.c
blob8ce9dcf81e0ad007544856f1cfe0241305324c89
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 /* TODO This is false. If super correct this should work on wide
365 * TODO characters, just in case (some bytes of) the ASCII set is (are)
366 * TODO shared; it may yet tear apart multibyte sequences, possibly.
367 * TODO All this should interact with mime_enc_mustquote(), too!
368 * TODO That is: once this is an object, we need to do this in a way
369 * TODO that it is valid for the wire format (instead)! */
370 in_quote = FAL0;
371 in_domain = hadat = 0;
373 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser! */
374 for (p = addr; (c.c = *p++) != '\0';) {
375 if (c.c == '"') {
376 in_quote = !in_quote;
377 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
378 #ifdef HAVE_IDNA
379 if (in_domain && use_idna > 0) {
380 if (use_idna == 1)
381 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
382 c.u);
383 use_idna = 2;
384 } else
385 #endif
386 break;
387 } else if (in_domain == 2) {
388 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
389 break;
390 } else if (in_quote && in_domain == 0) {
391 /*EMPTY*/;
392 } else if (c.c == '\\' && *p != '\0') {
393 ++p;
394 } else if (c.c == '@') {
395 if (hadat++ > 0) {
396 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
397 c.u);
398 goto jleave;
400 agp->ag_sdom_start = PTR2SIZE(p - addr);
401 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
402 in_domain = (*p == '[') ? 2 : 1;
403 continue;
404 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
405 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
406 c.c == '\\' || c.c == ',')
407 break;
408 hadat = 0;
410 if (c.c != '\0') {
411 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
412 goto jleave;
415 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
416 /* TODO This may be an UUCP address */
417 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
418 if(!n_alias_is_valid_name(agp->ag_input))
419 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
420 }else{
421 /* If we seem to know that this is an address. Ensure this is correct
422 * according to RFC 5322 TODO the entire address parser should be like
423 * TODO that for one, and then we should know whether structured or
424 * TODO unstructured, and just parse correctly overall!
425 * TODO In addition, this can be optimised a lot.
426 * TODO And it is far from perfect: it should not forget whether no
427 * TODO whitespace followed some snippet, and it was written hastily */
428 struct a_token{
429 struct a_token *t_last;
430 struct a_token *t_next;
431 enum{
432 a_T_TATOM = 1<<0,
433 a_T_TCOMM = 1<<1,
434 a_T_TQUOTE = 1<<2,
435 a_T_TADDR = 1<<3,
436 a_T_TMASK = (1<<4) - 1,
438 a_T_SPECIAL = 1<<8 /* An atom actually needs to go TQUOTE */
439 } t_f;
440 ui8_t t__pad[4];
441 size_t t_start;
442 size_t t_end;
443 } *thead, *tcurr, *tp;
445 struct n_string ost, *ostp;
446 char const *cp, *cp1st, *cpmax, *xp;
447 void *lofi_snap;
449 /* Name and domain must be non-empty */
450 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
451 c.c = '@';
452 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
453 goto jleave;
456 #ifdef HAVE_IDNA
457 if(use_idna == 2)
458 agp = a_head_idna_apply(agp);
459 #endif
461 cp = agp->ag_input;
463 /* Nothing to do if there is only an address (in angle brackets) */
464 if(agp->ag_iaddr_start == 0){
465 if(agp->ag_iaddr_aend == agp->ag_ilen)
466 goto jleave;
467 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
468 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
469 cp[agp->ag_iaddr_aend] == '>')
470 goto jleave;
472 /* It is not, so parse off all tokens, then resort and rejoin */
473 lofi_snap = n_lofi_snap_create();
475 cp1st = cp;
476 if((c.ui32 = agp->ag_iaddr_start) > 0)
477 --c.ui32;
478 cpmax = &cp[c.ui32];
480 thead = tcurr = NULL;
481 hadat = FAL0;
482 jnode_redo:
483 for(tp = NULL; cp < cpmax;){
484 switch((c.c = *cp)){
485 case '(':
486 if(tp != NULL)
487 tp->t_end = PTR2SIZE(cp - cp1st);
488 tp = n_lofi_alloc(sizeof *tp);
489 tp->t_next = NULL;
490 if((tp->t_last = tcurr) != NULL)
491 tcurr->t_next = tp;
492 else
493 thead = tp;
494 tcurr = tp;
495 tp->t_f = a_T_TCOMM;
496 tp->t_start = PTR2SIZE(++cp - cp1st);
497 xp = skip_comment(cp);
498 tp->t_end = PTR2SIZE(xp - cp1st);
499 cp = xp;
500 if(tp->t_end > tp->t_start){
501 if(xp[-1] == ')')
502 --tp->t_end;
503 else{
504 /* No closing comment - strip trailing whitespace */
505 while(blankchar(*--xp))
506 if(--tp->t_end == tp->t_start)
507 break;
510 tp = NULL;
511 break;
513 case '"':
514 if(tp != NULL)
515 tp->t_end = PTR2SIZE(cp - cp1st);
516 tp = n_lofi_alloc(sizeof *tp);
517 tp->t_next = NULL;
518 if((tp->t_last = tcurr) != NULL)
519 tcurr->t_next = tp;
520 else
521 thead = tp;
522 tcurr = tp;
523 tp->t_f = a_T_TQUOTE;
524 tp->t_start = PTR2SIZE(++cp - cp1st);
525 for(xp = cp; xp < cpmax; ++xp){
526 if((c.c = *xp) == '"')
527 break;
528 if(c.c == '\\' && xp[1] != '\0')
529 ++xp;
531 tp->t_end = PTR2SIZE(xp - cp1st);
532 cp = &xp[1];
533 if(tp->t_end > tp->t_start){
534 /* No closing quote - strip trailing whitespace */
535 if(*xp != '"'){
536 while(blankchar(*xp--))
537 if(--tp->t_end == tp->t_start)
538 break;
541 tp = NULL;
542 break;
544 default:
545 if(blankchar(c.c)){
546 if(tp != NULL)
547 tp->t_end = PTR2SIZE(cp - cp1st);
548 tp = NULL;
549 ++cp;
550 break;
553 if(tp == NULL){
554 tp = n_lofi_alloc(sizeof *tp);
555 tp->t_next = NULL;
556 if((tp->t_last = tcurr) != NULL)
557 tcurr->t_next = tp;
558 else
559 thead = tp;
560 tcurr = tp;
561 tp->t_f = a_T_TATOM;
562 tp->t_start = PTR2SIZE(cp - cp1st);
564 ++cp;
566 /* Reverse solidus transforms the following into a quoted-pair, and
567 * therefore (must occur in comment or quoted-string only) the
568 * entire atom into a quoted string */
569 if(c.c == '\\'){
570 tp->t_f |= a_T_SPECIAL;
571 if(cp < cpmax)
572 ++cp;
573 break;
576 /* Is this plain RFC 5322 "atext", or "specials"?
577 * TODO Because we don't know structured/unstructured, nor anything
578 * TODO else, we need to treat "dot-atom" as being identical to
579 * TODO "specials".
580 * However, if the 8th bit is set, this will be RFC 2047 converted
581 * and the entire sequence is skipped */
582 if(!(c.u & 0x80) && !alnumchar(c.c) &&
583 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
584 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
585 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
586 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
587 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
588 tp->t_f |= a_T_SPECIAL;
589 break;
592 if(tp != NULL)
593 tp->t_end = PTR2SIZE(cp - cp1st);
595 if(hadat == FAL0){
596 hadat = TRU1;
597 tp = n_lofi_alloc(sizeof *tp);
598 tp->t_next = NULL;
599 if((tp->t_last = tcurr) != NULL)
600 tcurr->t_next = tp;
601 else
602 thead = tp;
603 tcurr = tp;
604 tp->t_f = a_T_TADDR;
605 tp->t_start = agp->ag_iaddr_start;
606 tp->t_end = agp->ag_iaddr_aend;
607 tp = NULL;
609 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
610 cpmax = &agp->ag_input[agp->ag_ilen];
611 if(cp < cpmax)
612 goto jnode_redo;
615 /* Nothing may follow the address, move it to the end */
616 if(!(tcurr->t_f & a_T_TADDR)){
617 for(tp = thead; tp != NULL; tp = tp->t_next){
618 if(tp->t_f & a_T_TADDR){
619 if(tp->t_last != NULL)
620 tp->t_last->t_next = tp->t_next;
621 else
622 thead = tp->t_next;
623 if(tp->t_next != NULL)
624 tp->t_next->t_last = tp->t_last;
626 tcurr = tp;
627 while(tp->t_next != NULL)
628 tp = tp->t_next;
629 tp->t_next = tcurr;
630 tcurr->t_last = tp;
631 tcurr->t_next = NULL;
632 break;
637 /* Make ranges contiguous: ensure a continuous range of atoms is converted
638 * to a SPECIAL one if at least one of them requires it */
639 for(tp = thead; tp != NULL; tp = tp->t_next){
640 if(tp->t_f & a_T_SPECIAL){
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 /* And yes, we want quotes to extend as much as possible */
651 for(tp = thead; tp != NULL; tp = tp->t_next){
652 if(tp->t_f & a_T_TQUOTE){
653 tcurr = tp;
654 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
655 tp->t_f |= a_T_SPECIAL;
656 tp = tcurr;
657 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
658 tp->t_f |= a_T_SPECIAL;
662 /* Then rejoin */
663 ostp = n_string_creat_auto(&ost);
664 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
665 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
667 for(tcurr = thead; tcurr != NULL;){
668 if(tcurr != thead)
669 ostp = n_string_push_c(ostp, ' ');
670 if(tcurr->t_f & a_T_TADDR){
671 ostp = n_string_push_c(ostp, '<');
672 agp->ag_iaddr_start = ostp->s_len;
673 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
674 (tcurr->t_end - tcurr->t_start));
675 agp->ag_iaddr_aend = ostp->s_len;
676 ostp = n_string_push_c(ostp, '>');
677 tcurr = tcurr->t_next;
678 }else if(tcurr->t_f & a_T_TCOMM){
679 ostp = n_string_push_c(ostp, '(');
680 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
681 (tcurr->t_end - tcurr->t_start));
682 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
683 tcurr = tp;
684 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
685 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
686 (tcurr->t_end - tcurr->t_start));
688 ostp = n_string_push_c(ostp, ')');
689 tcurr = tcurr->t_next;
690 }else if(tcurr->t_f & a_T_TQUOTE){
691 jput_quote:
692 ostp = n_string_push_c(ostp, '"');
693 tp = tcurr;
694 do/* while tcurr && TATOM||TQUOTE */{
695 cp = &cp1st[tcurr->t_start];
696 cpmax = &cp1st[tcurr->t_end];
697 if(cp == cpmax)
698 continue;
700 if(tcurr != tp)
701 ostp = n_string_push_c(ostp, ' ');
703 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
704 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
705 else{
706 bool_t esc;
708 for(esc = FAL0; cp < cpmax;){
709 if((c.c = *cp++) == '\\' && !esc){
710 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
711 esc = TRU1;
712 }else{
713 if(esc || c.c == '"'){
714 jput_quote_esc:
715 ostp = n_string_push_c(ostp, '\\');
717 ostp = n_string_push_c(ostp, c.c);
718 esc = FAL0;
721 if(esc){
722 c.c = '\\';
723 goto jput_quote_esc;
726 }while((tcurr = tcurr->t_next) != NULL &&
727 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
728 ostp = n_string_push_c(ostp, '"');
729 }else if(tcurr->t_f & a_T_SPECIAL)
730 goto jput_quote;
731 else{
732 /* Can we use a fast join mode? */
733 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
734 if(!(tcurr->t_f & a_T_TATOM))
735 break;
736 if(tcurr != tp)
737 ostp = n_string_push_c(ostp, ' ');
738 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
739 (tcurr->t_end - tcurr->t_start));
744 n_lofi_snap_unroll(lofi_snap);
746 agp->ag_input = n_string_cp(ostp);
747 agp->ag_ilen = ostp->s_len;
748 /*ostp = n_string_drop_ownership(ostp);*/
750 jleave:
751 NYD_LEAVE;
752 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) == 0);
755 static int
756 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
758 char *line2 = NULL, *cp, *cp2;
759 size_t line2size = 0;
760 int c, isenc;
761 NYD2_ENTER;
763 if (*linebuf == NULL)
764 *linebuf = srealloc(*linebuf, *linesize = 1);
765 **linebuf = '\0';
766 for (;;) {
767 if (--rem < 0) {
768 rem = -1;
769 break;
771 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
772 rem = -1;
773 break;
775 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
777 if (cp > *linebuf)
778 while (blankchar(*cp))
779 ++cp;
780 if (*cp != ':' || cp == *linebuf)
781 continue;
783 /* I guess we got a headline. Handle wraparound */
784 *colon = cp;
785 cp = *linebuf + c;
786 for (;;) {
787 isenc = 0;
788 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
790 cp++;
791 if (rem <= 0)
792 break;
793 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
794 isenc |= 1;
795 ungetc(c = getc(f), f);
796 if (!blankchar(c))
797 break;
798 c = readline_restart(f, &line2, &line2size, 0);
799 if (c < 0)
800 break;
801 --rem;
802 for (cp2 = line2; blankchar(*cp2); ++cp2)
804 c -= (int)PTR2SIZE(cp2 - line2);
805 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
806 isenc |= 2;
807 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
808 size_t diff = PTR2SIZE(cp - *linebuf),
809 colondiff = PTR2SIZE(*colon - *linebuf);
810 *linebuf = srealloc(*linebuf, *linesize += c + 2);
811 cp = &(*linebuf)[diff];
812 *colon = &(*linebuf)[colondiff];
814 if (isenc != 3)
815 *cp++ = ' ';
816 memcpy(cp, cp2, c);
817 cp += c;
819 *cp = '\0';
821 if (line2 != NULL)
822 free(line2);
823 break;
825 NYD2_LEAVE;
826 return rem;
829 static int
830 msgidnextc(char const **cp, int *status)
832 int c;
833 NYD2_ENTER;
835 assert(cp != NULL);
836 assert(*cp != NULL);
837 assert(status != NULL);
839 for (;;) {
840 if (*status & 01) {
841 if (**cp == '"') {
842 *status &= ~01;
843 (*cp)++;
844 continue;
846 if (**cp == '\\') {
847 (*cp)++;
848 if (**cp == '\0')
849 goto jeof;
851 goto jdfl;
853 switch (**cp) {
854 case '(':
855 *cp = skip_comment(&(*cp)[1]);
856 continue;
857 case '>':
858 case '\0':
859 jeof:
860 c = '\0';
861 goto jleave;
862 case '"':
863 (*cp)++;
864 *status |= 01;
865 continue;
866 case '@':
867 *status |= 02;
868 /*FALLTHRU*/
869 default:
870 jdfl:
871 c = *(*cp)++ & 0377;
872 c = (*status & 02) ? lowerconv(c) : c;
873 goto jleave;
876 jleave:
877 NYD2_LEAVE;
878 return c;
881 static int
882 charcount(char *str, int c)
884 char *cp;
885 int i;
886 NYD2_ENTER;
888 for (i = 0, cp = str; *cp; ++cp)
889 if (*cp == c)
890 ++i;
891 NYD2_LEAVE;
892 return i;
895 static char const *
896 nexttoken(char const *cp)
898 NYD2_ENTER;
899 for (;;) {
900 if (*cp == '\0') {
901 cp = NULL;
902 break;
905 if (*cp == '(') {
906 size_t nesting = 1;
908 do switch (*++cp) {
909 case '(':
910 ++nesting;
911 break;
912 case ')':
913 --nesting;
914 break;
915 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
916 } else if (blankchar(*cp) || *cp == ',')
917 ++cp;
918 else
919 break;
921 NYD2_LEAVE;
922 return cp;
925 static char *
926 a_head_customhdr__sep(char **iolist){
927 char *cp, c, *base;
928 bool_t isesc, anyesc;
929 NYD2_ENTER;
931 for(base = *iolist; base != NULL; base = *iolist){
932 while((c = *base) != '\0' && blankspacechar(c))
933 ++base;
935 for(isesc = anyesc = FAL0, cp = base;; ++cp){
936 if(n_UNLIKELY((c = *cp) == '\0')){
937 *iolist = NULL;
938 break;
939 }else if(!isesc){
940 if(c == ','){
941 *iolist = cp + 1;
942 break;
944 isesc = (c == '\\');
945 }else{
946 isesc = FAL0;
947 anyesc |= (c == ',');
951 while(cp > base && blankspacechar(cp[-1]))
952 --cp;
953 *cp = '\0';
955 if(*base != '\0'){
956 if(anyesc){
957 char *ins;
959 for(ins = cp = base;; ++ins)
960 if((c = *cp) == '\\' && cp[1] == ','){
961 *ins = ',';
962 cp += 2;
963 }else if((*ins = (++cp, c)) == '\0')
964 break;
966 break;
969 NYD2_LEAVE;
970 return base;
973 FL char const *
974 myaddrs(struct header *hp) /* TODO */
976 struct name *np;
977 char const *rv, *mta;
978 NYD_ENTER;
980 if (hp != NULL && (np = hp->h_from) != NULL) {
981 if ((rv = np->n_fullname) != NULL)
982 goto jleave;
983 if ((rv = np->n_name) != NULL)
984 goto jleave;
987 if((rv = ok_vlook(from)) != NULL){
988 if((np = lextract(rv, GEXTRA | GFULL)) == NULL)
989 jefrom:
990 n_err(_("An address given in *from* is invalid: %s\n"), rv);
991 else for(; np != NULL; np = np->n_flink)
992 if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
993 goto jefrom;
994 goto jleave;
997 /* When invoking *sendmail* directly, it's its task to generate an otherwise
998 * undeterminable From: address. However, if the user sets *hostname*,
999 * accept his desire */
1000 if (ok_vlook(hostname) != NULL)
1001 goto jnodename;
1002 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
1003 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1004 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
1005 goto jnodename;
1006 jleave:
1007 NYD_LEAVE;
1008 return rv;
1010 jnodename:{
1011 char *cp;
1012 char const *hn, *ln;
1013 size_t i;
1015 hn = n_nodename(TRU1);
1016 ln = ok_vlook(LOGNAME);
1017 i = strlen(ln) + strlen(hn) + 1 +1;
1018 rv = cp = salloc(i);
1019 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1021 goto jleave;
1024 FL char const *
1025 myorigin(struct header *hp) /* TODO */
1027 char const *rv = NULL, *ccp;
1028 struct name *np;
1029 NYD_ENTER;
1031 if((ccp = myaddrs(hp)) != NULL &&
1032 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1033 if(np->n_flink == NULL)
1034 rv = ccp;
1035 else if((ccp = ok_vlook(sender)) != NULL) {
1036 if((np = lextract(ccp, GEXTRA | GFULL)) == NULL ||
1037 np->n_flink != NULL ||
1038 is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1039 n_err(_("The address given in *sender* is invalid: %s\n"), ccp);
1040 else
1041 rv = ccp;
1044 NYD_LEAVE;
1045 return rv;
1048 FL bool_t
1049 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1051 char date[n_FROM_DATEBUF];
1052 bool_t rv;
1053 NYD2_ENTER;
1055 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1056 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1057 !_is_date(date)))
1058 rv = TRUM1;
1059 NYD2_LEAVE;
1060 return rv;
1063 FL int
1064 extract_date_from_from_(char const *line, size_t linelen,
1065 char datebuf[n_FROM_DATEBUF])
1067 int rv;
1068 char const *cp = line;
1069 NYD_ENTER;
1071 rv = 1;
1073 /* "From " */
1074 cp = _from__skipword(cp);
1075 if (cp == NULL)
1076 goto jerr;
1077 /* "addr-spec " */
1078 cp = _from__skipword(cp);
1079 if (cp == NULL)
1080 goto jerr;
1081 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1082 (cp[2] == 'y' || cp[2] == 'Y')){
1083 cp = _from__skipword(cp);
1084 if (cp == NULL)
1085 goto jerr;
1087 /* It seems there are invalid MBOX archives in the wild, compare
1088 * . http://bugs.debian.org/624111
1089 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1090 * What they do is that they obfuscate the address to "name at host",
1091 * and even "name at host dot dom dot dom.
1092 * The [Aa][Tt] is also RFC 733, so be tolerant */
1093 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1094 cp[2] == ' '){
1095 rv = -1;
1096 cp += 3;
1097 jat_dot:
1098 cp = _from__skipword(cp);
1099 if (cp == NULL)
1100 goto jerr;
1101 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1102 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1103 cp += 4;
1104 goto jat_dot;
1108 linelen -= PTR2SIZE(cp - line);
1109 if (linelen < a_HEAD_DATE_MINLEN)
1110 goto jerr;
1111 if (cp[linelen - 1] == '\n') {
1112 --linelen;
1113 /* (Rather IMAP/POP3 only) */
1114 if (cp[linelen - 1] == '\r')
1115 --linelen;
1116 if (linelen < a_HEAD_DATE_MINLEN)
1117 goto jerr;
1119 if (linelen >= n_FROM_DATEBUF)
1120 goto jerr;
1122 jleave:
1123 memcpy(datebuf, cp, linelen);
1124 datebuf[linelen] = '\0';
1125 NYD_LEAVE;
1126 return rv;
1127 jerr:
1128 cp = _("<Unknown date>");
1129 linelen = strlen(cp);
1130 if (linelen >= n_FROM_DATEBUF)
1131 linelen = n_FROM_DATEBUF;
1132 rv = 0;
1133 goto jleave;
1136 FL void
1137 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1139 /* See the prototype declaration for the hairy relationship of
1140 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1141 struct n_header_field **hftail;
1142 struct header nh, *hq = &nh;
1143 char *linebuf = NULL /* TODO line pool */, *colon;
1144 size_t linesize = 0, seenfields = 0;
1145 int lc, c;
1146 char const *val, *cp;
1147 NYD_ENTER;
1149 memset(hq, 0, sizeof *hq);
1150 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1151 hq->h_to = hp->h_to;
1152 hq->h_cc = hp->h_cc;
1153 hq->h_bcc = hp->h_bcc;
1155 hftail = &hq->h_user_headers;
1157 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1160 /* TODO yippieia, cat(check(lextract)) :-) */
1161 rewind(fp);
1162 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1163 struct name *np;
1165 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1166 * yet expanded when we parse these! */
1167 if ((val = thisfield(linebuf, "to")) != NULL) {
1168 ++seenfields;
1169 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1170 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1171 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1172 ++seenfields;
1173 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1174 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1175 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1176 ++seenfields;
1177 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1178 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1179 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1180 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1181 ++seenfields;
1182 hq->h_from = cat(hq->h_from,
1183 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1184 EACM_STRICT, NULL));
1186 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1187 ++seenfields;
1188 hq->h_reply_to = cat(hq->h_reply_to,
1189 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1190 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1191 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1192 ++seenfields;
1193 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1194 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1195 EACM_STRICT, NULL));
1196 } else
1197 goto jebadhead;
1198 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1199 (val = thisfield(linebuf, "subj")) != NULL) {
1200 ++seenfields;
1201 for (cp = val; blankchar(*cp); ++cp)
1203 hq->h_subject = (hq->h_subject != NULL)
1204 ? save2str(hq->h_subject, cp) : savestr(cp);
1206 /* The remaining are mostly hacked in and thus TODO -- at least in
1207 * TODO respect to their content checking */
1208 else if((val = thisfield(linebuf, "message-id")) != NULL){
1209 if(n_psonce & n_PSO_t_FLAG){
1210 np = checkaddrs(lextract(val, GREF),
1211 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1212 NULL);
1213 if (np == NULL || np->n_flink != NULL)
1214 goto jebadhead;
1215 ++seenfields;
1216 hq->h_message_id = np;
1217 }else
1218 goto jebadhead;
1219 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1220 if(n_psonce & n_PSO_t_FLAG){
1221 np = checkaddrs(lextract(val, GREF),
1222 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1223 NULL);
1224 ++seenfields;
1225 hq->h_in_reply_to = np;
1226 }else
1227 goto jebadhead;
1228 }else if((val = thisfield(linebuf, "references")) != NULL){
1229 if(n_psonce & n_PSO_t_FLAG){
1230 ++seenfields;
1231 /* TODO Limit number of references TODO better on parser side */
1232 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1233 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1234 NULL));
1235 }else
1236 goto jebadhead;
1238 /* and that is very hairy */
1239 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1240 if(n_psonce & n_PSO_t_FLAG){
1241 ++seenfields;
1242 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1243 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1244 checkaddr_err));
1245 }else
1246 goto jebadhead;
1248 /* A free-form user header; gethfield() did some verification already.. */
1249 else{
1250 struct n_header_field *hfp;
1251 ui32_t nl, bl;
1252 char const *nstart;
1254 for(nstart = cp = linebuf;; ++cp)
1255 if(!fieldnamechar(*cp))
1256 break;
1257 nl = (ui32_t)PTR2SIZE(cp - nstart);
1259 while(blankchar(*cp))
1260 ++cp;
1261 if(*cp++ != ':'){
1262 jebadhead:
1263 n_err(_("Ignoring header field: %s\n"), linebuf);
1264 continue;
1266 while(blankchar(*cp))
1267 ++cp;
1268 bl = (ui32_t)strlen(cp) +1;
1270 ++seenfields;
1271 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1272 ) + nl +1 + bl);
1273 hftail = &hfp->hf_next;
1274 hfp->hf_next = NULL;
1275 hfp->hf_nl = nl;
1276 hfp->hf_bl = bl - 1;
1277 memcpy(hfp->hf_dat, nstart, nl);
1278 hfp->hf_dat[nl++] = '\0';
1279 memcpy(hfp->hf_dat + nl, cp, bl);
1283 /* In case the blank line after the header has been edited out. Otherwise,
1284 * fetch the header separator */
1285 if (linebuf != NULL) {
1286 if (linebuf[0] != '\0') {
1287 for (cp = linebuf; *(++cp) != '\0';)
1289 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1290 } else {
1291 if ((c = getc(fp)) != '\n' && c != EOF)
1292 ungetc(c, fp);
1296 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1297 hp->h_to = hq->h_to;
1298 hp->h_cc = hq->h_cc;
1299 hp->h_bcc = hq->h_bcc;
1300 hp->h_from = hq->h_from;
1301 hp->h_reply_to = hq->h_reply_to;
1302 hp->h_sender = hq->h_sender;
1303 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1304 !(n_poption & n_PO_t_FLAG))
1305 hp->h_subject = hq->h_subject;
1306 hp->h_user_headers = hq->h_user_headers;
1308 if (n_psonce & n_PSO_t_FLAG) {
1309 hp->h_ref = hq->h_ref;
1310 hp->h_message_id = hq->h_message_id;
1311 hp->h_in_reply_to = hq->h_in_reply_to;
1312 hp->h_mft = hq->h_mft;
1314 /* And perform additional validity checks so that we don't bail later
1315 * on TODO this is good and the place where this should occur,
1316 * TODO unfortunately a lot of other places do again and blabla */
1317 if (hp->h_from == NULL)
1318 hp->h_from = n_poption_arg_r;
1319 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1320 hp->h_sender = lextract(ok_vlook(sender),
1321 GEXTRA | GFULL | GFULLEXTRA);
1323 } else
1324 n_err(_("Restoring deleted header lines\n"));
1326 if (linebuf != NULL)
1327 free(linebuf);
1328 NYD_LEAVE;
1331 FL char *
1332 hfield_mult(char const *field, struct message *mp, int mult)
1334 FILE *ibuf;
1335 int lc;
1336 struct str hfs;
1337 size_t linesize = 0; /* TODO line pool */
1338 char *linebuf = NULL, *colon;
1339 char const *hfield;
1340 NYD_ENTER;
1342 /* There are (spam) messages which have header bytes which are many KB when
1343 * joined, so resize a single heap storage until we are done if we shall
1344 * collect a field that may have multiple bodies; only otherwise use the
1345 * string dope directly */
1346 memset(&hfs, 0, sizeof hfs);
1348 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1349 goto jleave;
1350 if ((lc = mp->m_lines - 1) < 0)
1351 goto jleave;
1353 if ((mp->m_flag & MNOFROM) == 0 &&
1354 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1355 goto jleave;
1356 while (lc > 0) {
1357 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1358 break;
1359 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1360 if (mult)
1361 n_str_add_buf(&hfs, hfield, strlen(hfield));
1362 else {
1363 hfs.s = savestr(hfield);
1364 break;
1369 jleave:
1370 if (linebuf != NULL)
1371 free(linebuf);
1372 if (mult && hfs.s != NULL) {
1373 colon = savestrbuf(hfs.s, hfs.l);
1374 free(hfs.s);
1375 hfs.s = colon;
1377 NYD_LEAVE;
1378 return hfs.s;
1381 FL char const *
1382 thisfield(char const *linebuf, char const *field)
1384 char const *rv = NULL;
1385 NYD2_ENTER;
1387 while (lowerconv(*linebuf) == lowerconv(*field)) {
1388 ++linebuf;
1389 ++field;
1391 if (*field != '\0')
1392 goto jleave;
1394 while (blankchar(*linebuf))
1395 ++linebuf;
1396 if (*linebuf++ != ':')
1397 goto jleave;
1399 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1400 ++linebuf;
1401 rv = linebuf;
1402 jleave:
1403 NYD2_LEAVE;
1404 return rv;
1407 FL char *
1408 nameof(struct message *mp, int reptype)
1410 char *cp, *cp2;
1411 NYD_ENTER;
1413 cp = skin(name1(mp, reptype));
1414 if (reptype != 0 || charcount(cp, '!') < 2)
1415 goto jleave;
1416 cp2 = strrchr(cp, '!');
1417 --cp2;
1418 while (cp2 > cp && *cp2 != '!')
1419 --cp2;
1420 if (*cp2 == '!')
1421 cp = cp2 + 1;
1422 jleave:
1423 NYD_LEAVE;
1424 return cp;
1427 FL char const *
1428 skip_comment(char const *cp)
1430 size_t nesting;
1431 NYD_ENTER;
1433 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1434 switch (*cp) {
1435 case '\\':
1436 if (cp[1])
1437 ++cp;
1438 break;
1439 case '(':
1440 ++nesting;
1441 break;
1442 case ')':
1443 --nesting;
1444 break;
1447 NYD_LEAVE;
1448 return cp;
1451 FL char const *
1452 routeaddr(char const *name)
1454 char const *np, *rp = NULL;
1455 NYD_ENTER;
1457 for (np = name; *np; np++) {
1458 switch (*np) {
1459 case '(':
1460 np = skip_comment(np + 1) - 1;
1461 break;
1462 case '"':
1463 while (*np) {
1464 if (*++np == '"')
1465 break;
1466 if (*np == '\\' && np[1])
1467 np++;
1469 break;
1470 case '<':
1471 rp = np;
1472 break;
1473 case '>':
1474 goto jleave;
1477 rp = NULL;
1478 jleave:
1479 NYD_LEAVE;
1480 return rp;
1483 FL enum expand_addr_flags
1484 expandaddr_to_eaf(void)
1486 struct eafdesc {
1487 char const *eafd_name;
1488 bool_t eafd_is_target;
1489 ui8_t eafd_andoff;
1490 ui8_t eafd_or;
1491 } const eafa[] = {
1492 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1493 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1494 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1495 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1496 {"file", TRU1, EAF_NONE, EAF_FILE},
1497 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1498 {"name", TRU1, EAF_NONE, EAF_NAME},
1499 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1500 }, *eafp;
1502 char *buf;
1503 enum expand_addr_flags rv;
1504 char const *cp;
1505 NYD2_ENTER;
1507 if ((cp = ok_vlook(expandaddr)) == NULL)
1508 rv = EAF_RESTRICT_TARGETS;
1509 else if (*cp == '\0')
1510 rv = EAF_TARGET_MASK;
1511 else {
1512 rv = EAF_TARGET_MASK;
1514 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1515 bool_t minus;
1517 if ((minus = (*cp == '-')) || *cp == '+')
1518 ++cp;
1519 for (eafp = eafa;; ++eafp) {
1520 if (eafp == eafa + n_NELEM(eafa)) {
1521 if (n_poption & n_PO_D_V)
1522 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1523 break;
1524 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1525 if (!minus) {
1526 rv &= ~eafp->eafd_andoff;
1527 rv |= eafp->eafd_or;
1528 } else {
1529 if (eafp->eafd_is_target)
1530 rv &= ~eafp->eafd_or;
1531 else if (n_poption & n_PO_D_V)
1532 n_err(_("minus - prefix invalid for *expandaddr* value: "
1533 "%s\n"), --cp);
1535 break;
1536 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1537 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1538 rv &= ~EAF_NAME;
1539 break;
1544 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1545 (n_poption & n_PO_TILDE_FLAG)))
1546 rv |= EAF_TARGET_MASK;
1547 else if(n_poption & n_PO_D_V){
1548 if(!(rv & EAF_TARGET_MASK))
1549 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1550 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1551 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1554 NYD2_LEAVE;
1555 return rv;
1558 FL si8_t
1559 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1561 char cbuf[sizeof "'\\U12340'"];
1562 char const *cs;
1563 int f;
1564 si8_t rv;
1565 enum expand_addr_flags eaf;
1566 NYD_ENTER;
1568 eaf = expandaddr_to_eaf();
1569 f = np->n_flags;
1571 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1572 if (eaf & EAF_FAILINVADDR)
1573 rv = -rv;
1575 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1577 } else {
1578 ui32_t c;
1579 char const *fmt = "'\\x%02X'";
1580 bool_t ok8bit = TRU1;
1582 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1583 cs = _("Invalid domain name: %s, character %s\n");
1584 fmt = "'\\U%04X'";
1585 ok8bit = FAL0;
1586 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1587 cs = _("%s contains invalid %s sequence\n");
1588 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1589 cs = _("%s is an invalid alias name\n");
1590 } else
1591 cs = _("%s contains invalid byte %s\n");
1593 c = NAME_ADDRSPEC_ERR_GETWC(f);
1594 snprintf(cbuf, sizeof cbuf,
1595 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1596 goto jprint;
1598 goto jleave;
1601 /* *expandaddr* stuff */
1602 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1603 goto jleave;
1605 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1606 if (eaf & EAF_FAIL)
1607 rv = -rv;
1608 cs = _("%s%s: file or pipe addressees not allowed here\n");
1609 if (eacm & EACM_NOLOG)
1610 goto jleave;
1611 else
1612 goto j0print;
1615 eaf |= (eacm & EAF_TARGET_MASK);
1616 if (eacm & EACM_NONAME)
1617 eaf &= ~EAF_NAME;
1619 if (eaf == EAF_NONE) {
1620 rv = FAL0;
1621 goto jleave;
1623 if (eaf & EAF_FAIL)
1624 rv = -rv;
1626 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1627 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1628 if (eacm & EACM_NOLOG)
1629 goto jleave;
1630 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1631 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1632 if (eacm & EACM_NOLOG)
1633 goto jleave;
1634 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1635 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1636 if (eacm & EACM_NOLOG)
1637 goto jleave;
1638 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1639 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1640 if (eacm & EACM_NOLOG)
1641 goto jleave;
1642 } else {
1643 rv = FAL0;
1644 goto jleave;
1647 j0print:
1648 cbuf[0] = '\0';
1649 jprint:
1650 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1651 jleave:
1652 NYD_LEAVE;
1653 return rv;
1656 FL char *
1657 skin(char const *name)
1659 struct n_addrguts ag;
1660 char *rv;
1661 NYD_ENTER;
1663 if(name != NULL){
1664 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1665 rv = ag.ag_skinned;
1666 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1667 rv = savestrbuf(rv, ag.ag_slen);
1668 }else
1669 rv = NULL;
1670 NYD_LEAVE;
1671 return rv;
1674 /* TODO addrspec_with_guts: RFC 5322
1675 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1676 FL char const *
1677 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1678 bool_t issingle_hack){
1679 char const *cp;
1680 char *cp2, *bufend, *nbuf, c;
1681 enum{
1682 a_NONE,
1683 a_GOTLT = 1<<0,
1684 a_GOTADDR = 1<<1,
1685 a_GOTSPACE = 1<<2,
1686 a_LASTSP = 1<<3
1687 } flags;
1688 NYD_ENTER;
1690 memset(agp, 0, sizeof *agp);
1692 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1693 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1694 agp->ag_slen = 0;
1695 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1696 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1697 goto jleave;
1698 }else if(!doskin){
1699 /*agp->ag_iaddr_start = 0;*/
1700 agp->ag_iaddr_aend = agp->ag_ilen;
1701 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1702 agp->ag_slen = agp->ag_ilen;
1703 agp->ag_n_flags = NAME_SKINNED;
1704 goto jcheck;
1707 flags = a_NONE;
1708 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1709 /*agp->ag_iaddr_start = 0;*/
1710 cp2 = bufend = nbuf;
1712 /* TODO This is complete crap and should use a token parser */
1713 for(cp = name++; (c = *cp++) != '\0';){
1714 switch (c) {
1715 case '(':
1716 cp = skip_comment(cp);
1717 flags &= ~a_LASTSP;
1718 break;
1719 case '"':
1720 /* Start of a "quoted-string". Copy it in its entirety */
1721 /* XXX RFC: quotes are "semantically invisible"
1722 * XXX But it was explicitly added (Changelog.Heirloom,
1723 * XXX [9.23] released 11/15/00, "Do not remove quotes
1724 * XXX when skinning names"? No more info.. */
1725 *cp2++ = c;
1726 while ((c = *cp) != '\0') { /* TODO improve */
1727 ++cp;
1728 if (c == '"') {
1729 *cp2++ = c;
1730 break;
1732 if (c != '\\')
1733 *cp2++ = c;
1734 else if ((c = *cp) != '\0') {
1735 *cp2++ = c;
1736 ++cp;
1739 flags &= ~a_LASTSP;
1740 break;
1741 case ' ':
1742 case '\t':
1743 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1744 flags |= a_GOTSPACE;
1745 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1747 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1748 cp += 3, *cp2++ = '@';
1749 else if (cp[0] == '@' && blankchar(cp[1]))
1750 cp += 2, *cp2++ = '@';
1751 else
1752 flags |= a_LASTSP;
1753 break;
1754 case '<':
1755 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1756 cp2 = bufend;
1757 flags &= ~(a_GOTSPACE | a_LASTSP);
1758 flags |= a_GOTLT | a_GOTADDR;
1759 break;
1760 case '>':
1761 if(flags & a_GOTLT){
1762 /* (_addrspec_check() verifies these later!) */
1763 flags &= ~(a_GOTLT | a_LASTSP);
1764 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1766 /* Skip over the entire remaining field */
1767 while((c = *cp) != '\0' && c != ','){
1768 ++cp;
1769 if (c == '(')
1770 cp = skip_comment(cp);
1771 else if (c == '"')
1772 while ((c = *cp) != '\0') {
1773 ++cp;
1774 if (c == '"')
1775 break;
1776 if (c == '\\' && *cp != '\0')
1777 ++cp;
1780 break;
1782 /* FALLTHRU */
1783 default:
1784 if(flags & a_LASTSP){
1785 flags &= ~a_LASTSP;
1786 if(flags & a_GOTADDR)
1787 *cp2++ = ' ';
1789 *cp2++ = c;
1790 /* This character is forbidden here, but it may nonetheless be
1791 * present: ensure we turn this into something valid! (E.g., if the
1792 * next character would be a "..) */
1793 if(c == '\\' && *cp != '\0')
1794 *cp2++ = *cp++;
1795 if(c == ',' && !issingle_hack){
1796 if(!(flags & a_GOTLT)){
1797 *cp2++ = ' ';
1798 for(; blankchar(*cp); ++cp)
1800 flags &= ~a_LASTSP;
1801 bufend = cp2;
1803 }else if(!(flags & a_GOTADDR)){
1804 flags |= a_GOTADDR;
1805 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1809 --name;
1810 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1811 if (agp->ag_iaddr_aend == 0)
1812 agp->ag_iaddr_aend = agp->ag_ilen;
1813 /* Misses > */
1814 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1815 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1816 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1817 agp->ag_iaddr_aend = agp->ag_ilen;
1818 cp2[agp->ag_ilen++] = '>';
1819 cp2[agp->ag_ilen] = '\0';
1821 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1822 n_lofi_free(nbuf);
1823 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1824 jcheck:
1825 if(a_head_addrspec_check(agp, doskin) <= FAL0)
1826 name = NULL;
1827 else
1828 name = agp->ag_input;
1829 jleave:
1830 NYD_LEAVE;
1831 return name;
1834 FL char *
1835 realname(char const *name)
1837 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1838 char *rname, *rp;
1839 struct str in, out;
1840 int quoted, good, nogood;
1841 NYD_ENTER;
1843 if ((cp = n_UNCONST(name)) == NULL)
1844 goto jleave;
1845 for (; *cp != '\0'; ++cp) {
1846 switch (*cp) {
1847 case '(':
1848 if (cstart != NULL) {
1849 /* More than one comment in address, doesn't make sense to display
1850 * it without context. Return the entire field */
1851 cp = mime_fromaddr(name);
1852 goto jleave;
1854 cstart = cp++;
1855 cp = skip_comment(cp);
1856 cend = cp--;
1857 if (cend <= cstart)
1858 cend = cstart = NULL;
1859 break;
1860 case '"':
1861 while (*cp) {
1862 if (*++cp == '"')
1863 break;
1864 if (*cp == '\\' && cp[1])
1865 ++cp;
1867 break;
1868 case '<':
1869 if (cp > name) {
1870 cstart = name;
1871 cend = cp;
1873 break;
1874 case ',':
1875 /* More than one address. Just use the first one */
1876 goto jbrk;
1880 jbrk:
1881 if (cstart == NULL) {
1882 if (*name == '<') {
1883 /* If name contains only a route-addr, the surrounding angle brackets
1884 * don't serve any useful purpose when displaying, so remove */
1885 cp = prstr(skin(name));
1886 } else
1887 cp = mime_fromaddr(name);
1888 goto jleave;
1891 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1892 * not stripped. The idea is to strip only syntactical relevant things (but
1893 * this is not necessarily the most sensible way in practice) */
1894 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1895 quoted = 0;
1896 for (cp = cstart; cp < cend; ++cp) {
1897 if (*cp == '(' && !quoted) {
1898 cq = skip_comment(++cp);
1899 if (PTRCMP(--cq, >, cend))
1900 cq = cend;
1901 while (cp < cq) {
1902 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1903 ++cp;
1904 *rp++ = *cp++;
1906 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1907 *rp++ = *++cp;
1908 else if (*cp == '"') {
1909 quoted = !quoted;
1910 continue;
1911 } else
1912 *rp++ = *cp;
1914 *rp = '\0';
1915 in.s = rname;
1916 in.l = rp - rname;
1917 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1918 ac_free(rname);
1919 rname = savestr(out.s);
1920 free(out.s);
1922 while (blankchar(*rname))
1923 ++rname;
1924 for (rp = rname; *rp != '\0'; ++rp)
1926 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1927 *rp = '\0';
1928 if (rp == rname) {
1929 cp = mime_fromaddr(name);
1930 goto jleave;
1933 /* mime_fromhdr() has converted all nonprintable characters to question
1934 * marks now. These and blanks are considered uninteresting; if the
1935 * displayed part of the real name contains more than 25% of them, it is
1936 * probably better to display the plain email address instead */
1937 good = 0;
1938 nogood = 0;
1939 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1940 if (*rp == '?' || blankchar(*rp))
1941 ++nogood;
1942 else
1943 ++good;
1944 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1945 jleave:
1946 NYD_LEAVE;
1947 return n_UNCONST(cp);
1950 FL char *
1951 name1(struct message *mp, int reptype)
1953 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1954 size_t namesize, linesize = 0;
1955 FILE *ibuf;
1956 int f1st = 1;
1957 NYD_ENTER;
1959 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1960 goto jleave;
1961 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1962 goto jleave;
1964 namebuf = smalloc(namesize = 1);
1965 namebuf[0] = 0;
1966 if (mp->m_flag & MNOFROM)
1967 goto jout;
1968 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1969 goto jout;
1970 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1971 goto jout;
1973 jnewname:
1974 if (namesize <= linesize)
1975 namebuf = srealloc(namebuf, namesize = linesize +1);
1976 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1978 for (; blankchar(*cp); ++cp)
1980 for (cp2 = namebuf + strlen(namebuf);
1981 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1982 *cp2++ = *cp++;
1983 *cp2 = '\0';
1985 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1986 goto jout;
1987 if ((cp = strchr(linebuf, 'F')) == NULL)
1988 goto jout;
1989 if (strncmp(cp, "From", 4))
1990 goto jout;
1991 if (namesize <= linesize)
1992 namebuf = srealloc(namebuf, namesize = linesize + 1);
1994 while ((cp = strchr(cp, 'r')) != NULL) {
1995 if (!strncmp(cp, "remote", 6)) {
1996 if ((cp = strchr(cp, 'f')) == NULL)
1997 break;
1998 if (strncmp(cp, "from", 4) != 0)
1999 break;
2000 if ((cp = strchr(cp, ' ')) == NULL)
2001 break;
2002 cp++;
2003 if (f1st) {
2004 strncpy(namebuf, cp, namesize);
2005 f1st = 0;
2006 } else {
2007 cp2 = strrchr(namebuf, '!') + 1;
2008 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
2010 namebuf[namesize - 2] = '!';
2011 namebuf[namesize - 1] = '\0';
2012 goto jnewname;
2014 cp++;
2016 jout:
2017 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2018 *cp == '\0')
2019 cp = savestr(namebuf);
2021 if (linebuf != NULL)
2022 free(linebuf);
2023 free(namebuf);
2024 jleave:
2025 NYD_LEAVE;
2026 return cp;
2029 FL char const *
2030 subject_re_trim(char const *s){
2031 struct{
2032 ui8_t len;
2033 char dat[7];
2034 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2035 {3, "re:"},
2036 {3, "aw:"}, {5, "antw:"}, /* de */
2037 {3, "wg:"}, /* Seen too often in the wild */
2038 {0, ""}
2041 bool_t any;
2042 char *re_st, *re_st_x;
2043 char const *orig_s;
2044 size_t re_l;
2045 NYD_ENTER;
2047 any = FAL0;
2048 orig_s = s;
2049 re_st = NULL;
2050 n_UNINIT(re_l, 0);
2052 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2053 (re_l = strlen(re_st_x)) > 0){
2054 re_st = n_lofi_alloc(++re_l * 2);
2055 memcpy(re_st, re_st_x, re_l);
2058 jouter:
2059 while(*s != '\0'){
2060 while(spacechar(*s))
2061 ++s;
2063 for(pp = ignored; pp->len > 0; ++pp)
2064 if(is_asccaseprefix(pp->dat, s)){
2065 s += pp->len;
2066 any = TRU1;
2067 goto jouter;
2070 if(re_st != NULL){
2071 char *cp;
2073 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2074 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2075 if(is_asccaseprefix(cp, s)){
2076 s += strlen(cp);
2077 any = TRU1;
2078 goto jouter;
2081 break;
2084 if(re_st != NULL)
2085 n_lofi_free(re_st);
2086 NYD_LEAVE;
2087 return any ? s : orig_s;
2090 FL int
2091 msgidcmp(char const *s1, char const *s2)
2093 int q1 = 0, q2 = 0, c1, c2;
2094 NYD_ENTER;
2096 while(*s1 == '<')
2097 ++s1;
2098 while(*s2 == '<')
2099 ++s2;
2101 do {
2102 c1 = msgidnextc(&s1, &q1);
2103 c2 = msgidnextc(&s2, &q2);
2104 if (c1 != c2)
2105 break;
2106 } while (c1 && c2);
2107 NYD_LEAVE;
2108 return c1 - c2;
2111 FL char const *
2112 fakefrom(struct message *mp)
2114 char const *name;
2115 NYD_ENTER;
2117 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2118 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2119 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2120 * RFC 4155 however requires a RFC 5322 (2822) conforming
2121 * "addr-spec", but we simply can't provide that */
2122 name = "MAILER-DAEMON";
2123 NYD_LEAVE;
2124 return name;
2127 FL char const *
2128 fakedate(time_t t)
2130 char *cp, *cq;
2131 NYD_ENTER;
2133 cp = ctime(&t);
2134 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
2136 *cq = '\0';
2137 cp = savestr(cp);
2138 NYD_LEAVE;
2139 return cp;
2142 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2143 FL time_t
2144 unixtime(char const *fromline)
2146 char const *fp, *xp;
2147 time_t t;
2148 si32_t i, year, month, day, hour, minute, second, tzdiff;
2149 struct tm *tmptr;
2150 NYD2_ENTER;
2152 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2154 fp -= 24;
2155 if (PTR2SIZE(fp - fromline) < 7)
2156 goto jinvalid;
2157 if (fp[3] != ' ')
2158 goto jinvalid;
2159 for (i = 0;;) {
2160 if (!strncmp(fp + 4, n_month_names[i], 3))
2161 break;
2162 if (n_month_names[++i][0] == '\0')
2163 goto jinvalid;
2165 month = i + 1;
2166 if (fp[7] != ' ')
2167 goto jinvalid;
2168 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2169 if (*xp != ' ' || xp != fp + 10)
2170 goto jinvalid;
2171 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2172 if (*xp != ':' || xp != fp + 13)
2173 goto jinvalid;
2174 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2175 if (*xp != ':' || xp != fp + 16)
2176 goto jinvalid;
2177 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2178 if (*xp != ' ' || xp != fp + 19)
2179 goto jinvalid;
2180 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2181 if (xp != fp + 24)
2182 goto jinvalid;
2183 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2184 goto jinvalid;
2185 tzdiff = t - mktime(gmtime(&t));
2186 tmptr = localtime(&t);
2187 if (tmptr->tm_isdst > 0)
2188 tzdiff += 3600;
2189 t -= tzdiff;
2190 jleave:
2191 NYD2_LEAVE;
2192 return t;
2193 jinvalid:
2194 t = n_time_epoch();
2195 goto jleave;
2197 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2199 FL time_t
2200 rfctime(char const *date) /* TODO n_idec_ return tests */
2202 char const *cp, *x;
2203 time_t t;
2204 si32_t i, year, month, day, hour, minute, second;
2205 NYD2_ENTER;
2207 cp = date;
2209 if ((cp = nexttoken(cp)) == NULL)
2210 goto jinvalid;
2211 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2212 cp[3] == ',') {
2213 if ((cp = nexttoken(&cp[4])) == NULL)
2214 goto jinvalid;
2216 n_idec_si32_cp(&day, cp, 10, &x);
2217 if ((cp = nexttoken(x)) == NULL)
2218 goto jinvalid;
2219 for (i = 0;;) {
2220 if (!strncmp(cp, n_month_names[i], 3))
2221 break;
2222 if (n_month_names[++i][0] == '\0')
2223 goto jinvalid;
2225 month = i + 1;
2226 if ((cp = nexttoken(&cp[3])) == NULL)
2227 goto jinvalid;
2228 /* RFC 5322, 4.3:
2229 * Where a two or three digit year occurs in a date, the year is to be
2230 * interpreted as follows: If a two digit year is encountered whose
2231 * value is between 00 and 49, the year is interpreted by adding 2000,
2232 * ending up with a value between 2000 and 2049. If a two digit year
2233 * is encountered with a value between 50 and 99, or any three digit
2234 * year is encountered, the year is interpreted by adding 1900 */
2235 n_idec_si32_cp(&year, cp, 10, &x);
2236 i = (int)PTR2SIZE(x - cp);
2237 if (i == 2 && year >= 0 && year <= 49)
2238 year += 2000;
2239 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2240 year += 1900;
2241 if ((cp = nexttoken(x)) == NULL)
2242 goto jinvalid;
2243 n_idec_si32_cp(&hour, cp, 10, &x);
2244 if (*x != ':')
2245 goto jinvalid;
2246 cp = &x[1];
2247 n_idec_si32_cp(&minute, cp, 10, &x);
2248 if (*x == ':') {
2249 cp = &x[1];
2250 n_idec_si32_cp(&second, cp, 10, &x);
2251 } else
2252 second = 0;
2254 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2255 goto jinvalid;
2256 if ((cp = nexttoken(x)) != NULL) {
2257 char buf[3];
2258 int sign = 1;
2260 switch (*cp) {
2261 case '+':
2262 sign = -1;
2263 /* FALLTHRU */
2264 case '-':
2265 ++cp;
2266 break;
2268 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2269 digitchar(cp[3])) {
2270 si64_t tadj;
2272 buf[2] = '\0';
2273 buf[0] = cp[0];
2274 buf[1] = cp[1];
2275 n_idec_si32_cp(&i, buf, 10, NULL);
2276 tadj = (si64_t)i * 3600; /* XXX */
2277 buf[0] = cp[2];
2278 buf[1] = cp[3];
2279 n_idec_si32_cp(&i, buf, 10, NULL);
2280 tadj += (si64_t)i * 60; /* XXX */
2281 if (sign < 0)
2282 tadj = -tadj;
2283 t += (time_t)tadj;
2285 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2286 * TODO once again, Christos Zoulas and NetBSD Mail have done
2287 * TODO a really good job already, but using strptime(3), which
2288 * TODO is not portable. Nonetheless, WE must improve, not
2289 * TODO at last because we simply ignore obsolete timezones!!
2290 * TODO See RFC 5322, 4.3! */
2292 jleave:
2293 NYD2_LEAVE;
2294 return t;
2295 jinvalid:
2296 t = 0;
2297 goto jleave;
2300 FL time_t
2301 combinetime(int year, int month, int day, int hour, int minute, int second){
2302 size_t const jdn_epoch = 2440588;
2303 bool_t const y2038p = (sizeof(time_t) == 4);
2305 size_t jdn;
2306 time_t t;
2307 NYD2_ENTER;
2309 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2310 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2311 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2312 day < 1 || day > 31 ||
2313 month < 1 || month > 12 ||
2314 year < 1970)
2315 goto jerr;
2317 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2318 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2319 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2320 * test by stepping second-wise around the flip. Don't care otherwise */
2321 if(!y2038p)
2322 goto jerr;
2323 if(year > 2038 || month > 1 || day > 19 ||
2324 hour > 3 || minute > 14 || second > 7)
2325 goto jerr;
2328 t = second;
2329 t += minute * n_DATE_SECSMIN;
2330 t += hour * n_DATE_SECSHOUR;
2332 jdn = a_head_gregorian_to_jdn(year, month, day);
2333 jdn -= jdn_epoch;
2334 t += (time_t)jdn * n_DATE_SECSDAY;
2335 jleave:
2336 NYD2_LEAVE;
2337 return t;
2338 jerr:
2339 t = (time_t)-1;
2340 goto jleave;
2343 FL void
2344 substdate(struct message *m)
2346 char const *cp;
2347 NYD_ENTER;
2349 /* Determine the date to print in faked 'From ' lines. This is traditionally
2350 * the date the message was written to the mail file. Try to determine this
2351 * using RFC message header fields, or fall back to current time */
2352 m->m_time = 0;
2353 if ((cp = hfield1("received", m)) != NULL) {
2354 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2356 ++cp;
2357 while (alnumchar(*cp));
2359 if (cp && *++cp)
2360 m->m_time = rfctime(cp);
2362 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2363 if ((cp = hfield1("date", m)) != NULL)
2364 m->m_time = rfctime(cp);
2366 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2367 m->m_time = time_current.tc_time;
2368 NYD_LEAVE;
2371 FL void
2372 setup_from_and_sender(struct header *hp)
2374 char const *addr;
2375 struct name *np;
2376 NYD_ENTER;
2378 /* If -t parsed or composed From: then take it. With -t we otherwise
2379 * want -r to be honoured in favour of *from* in order to have
2380 * a behaviour that is compatible with what users would expect from e.g.
2381 * postfix(1) */
2382 if ((np = hp->h_from) != NULL ||
2383 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2385 } else if ((addr = myaddrs(hp)) != NULL)
2386 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2387 hp->h_from = np;
2389 if ((np = hp->h_sender) != NULL) {
2391 } else if ((addr = ok_vlook(sender)) != NULL)
2392 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2393 hp->h_sender = np;
2395 NYD_LEAVE;
2398 FL struct name const *
2399 check_from_and_sender(struct name const *fromfield,
2400 struct name const *senderfield)
2402 struct name const *rv = NULL;
2403 NYD_ENTER;
2405 if (senderfield != NULL) {
2406 if (senderfield->n_flink != NULL) {
2407 n_err(_("The Sender: field may contain only one address\n"));
2408 goto jleave;
2410 rv = senderfield;
2413 if (fromfield != NULL) {
2414 if (fromfield->n_flink != NULL && senderfield == NULL) {
2415 n_err(_("A Sender: is required when there are multiple "
2416 "addresses in From:\n"));
2417 goto jleave;
2419 if (rv == NULL)
2420 rv = fromfield;
2423 if (rv == NULL)
2424 rv = (struct name*)0x1;
2425 jleave:
2426 NYD_LEAVE;
2427 return rv;
2430 #ifdef HAVE_XSSL
2431 FL char *
2432 getsender(struct message *mp)
2434 char *cp;
2435 struct name *np;
2436 NYD_ENTER;
2438 if ((cp = hfield1("from", mp)) == NULL ||
2439 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2440 cp = NULL;
2441 else
2442 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2443 NYD_LEAVE;
2444 return cp;
2446 #endif
2448 FL int
2449 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2450 int subjfirst)
2452 /* TODO grab_headers: again, check counts etc. against RFC;
2453 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2454 int errs;
2455 int volatile comma;
2456 NYD_ENTER;
2458 errs = 0;
2459 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2461 if (gflags & GTO)
2462 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2463 if (subjfirst && (gflags & GSUBJECT))
2464 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2465 if (gflags & GCC)
2466 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2467 if (gflags & GBCC)
2468 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2470 if (gflags & GEXTRA) {
2471 if (hp->h_from == NULL)
2472 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2473 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2474 GEXTRA | GFULL | GFULLEXTRA);
2475 if (hp->h_reply_to == NULL) {
2476 struct name *v15compat;
2478 if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
2479 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2480 hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
2481 if(hp->h_reply_to == NULL) /* v15 */
2482 hp->h_reply_to = v15compat;
2484 hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
2485 GEXTRA | GFULL);
2486 if (hp->h_sender == NULL)
2487 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2488 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2489 GEXTRA | GFULL);
2492 if (!subjfirst && (gflags & GSUBJECT))
2493 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2495 NYD_LEAVE;
2496 return errs;
2499 FL bool_t
2500 header_match(struct message *mp, struct search_expr const *sep)
2502 struct str in, out;
2503 FILE *ibuf;
2504 int lc;
2505 size_t linesize = 0; /* TODO line pool */
2506 char *linebuf = NULL, *colon;
2507 bool_t rv = FAL0;
2508 NYD_ENTER;
2510 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2511 goto jleave;
2512 if ((lc = mp->m_lines - 1) < 0)
2513 goto jleave;
2515 if ((mp->m_flag & MNOFROM) == 0 &&
2516 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2517 goto jleave;
2518 while (lc > 0) {
2519 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2520 break;
2521 if (blankchar(*++colon))
2522 ++colon;
2523 in.l = strlen(in.s = colon);
2524 mime_fromhdr(&in, &out, TD_ICONV);
2525 #ifdef HAVE_REGEX
2526 if (sep->ss_sexpr == NULL)
2527 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2528 else
2529 #endif
2530 rv = substr(out.s, sep->ss_sexpr);
2531 free(out.s);
2532 if (rv)
2533 break;
2536 jleave:
2537 if (linebuf != NULL)
2538 free(linebuf);
2539 NYD_LEAVE;
2540 return rv;
2543 FL struct n_header_field *
2544 n_customhdr_query(void){
2545 char const *vp;
2546 struct n_header_field *rv, **tail, *hfp;
2547 NYD_ENTER;
2549 rv = NULL;
2551 if((vp = ok_vlook(customhdr)) != NULL){
2552 char *buf;
2554 tail = &rv;
2555 buf = savestr(vp);
2556 jch_outer:
2557 while((vp = a_head_customhdr__sep(&buf)) != NULL){
2558 ui32_t nl, bl;
2559 char const *nstart, *cp;
2561 for(nstart = cp = vp;; ++cp){
2562 if(fieldnamechar(*cp))
2563 continue;
2564 if(*cp == '\0'){
2565 if(cp == nstart){
2566 n_err(_("Invalid nameless *customhdr* entry\n"));
2567 goto jch_outer;
2569 }else if(*cp != ':' && !blankchar(*cp)){
2570 jch_badent:
2571 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2572 goto jch_outer;
2574 break;
2576 nl = (ui32_t)PTR2SIZE(cp - nstart);
2578 while(blankchar(*cp))
2579 ++cp;
2580 if(*cp++ != ':')
2581 goto jch_badent;
2582 while(blankchar(*cp))
2583 ++cp;
2584 bl = (ui32_t)strlen(cp) +1;
2586 *tail =
2587 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2588 nl +1 + bl);
2589 tail = &hfp->hf_next;
2590 hfp->hf_next = NULL;
2591 hfp->hf_nl = nl;
2592 hfp->hf_bl = bl - 1;
2593 memcpy(hfp->hf_dat, nstart, nl);
2594 hfp->hf_dat[nl++] = '\0';
2595 memcpy(hfp->hf_dat + nl, cp, bl);
2598 NYD_LEAVE;
2599 return rv;
2602 /* s-it-mode */