(BWDIC!) Fix *imap-delim* behaviour..
[s-mailx.git] / head.c
blob6d4e9ebc47e6152cf441ccdb7786bfeb12e5cf32
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 #ifdef HAVE_IDNA
44 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
45 # include <idna.h>
46 # include <idn-free.h>
47 # include <stringprep.h>
48 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT
49 # include <idn/api.h>
50 # endif
51 #endif
53 struct cmatch_data {
54 size_t tlen; /* Length of .tdata */
55 char const *tdata; /* Template date - see _cmatch_data[] */
58 /* Template characters for cmatch_data.tdata:
59 * 'A' An upper case char
60 * 'a' A lower case char
61 * ' ' A space
62 * '0' A digit
63 * 'O' An optional digit or space
64 * ':' A colon
65 * '+' Either a plus or a minus sign */
66 static struct cmatch_data const _cmatch_data[] = {
67 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
68 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
69 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
70 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
71 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
72 * in the wild (by UW-imap) */
73 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
74 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
75 * zone strings; note that 1. is strictly speaking not correct as some
76 * letters are not used, and 2. is not because only "UT" is defined */
77 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
78 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
79 { 0, NULL }
81 #define a_HEAD_DATE_MINLEN 21
83 /* Skip over "word" as found in From_ line */
84 static char const * _from__skipword(char const *wp);
86 /* Match the date string against the date template (tp), return if match.
87 * See _cmatch_data[] for template character description */
88 static int _cmatch(size_t len, char const *date,
89 char const *tp);
91 /* Check whether date is a valid 'From_' date.
92 * (Rather ctime(3) generated dates, according to RFC 4155) */
93 static int _is_date(char const *date);
95 /* JulianDayNumber converter(s) */
96 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
97 #if 0
98 static void a_head_jdn_to_gregorian(size_t jdn,
99 ui32_t *yp, ui32_t *mp, ui32_t *dp);
100 #endif
102 /* Convert the domain part of a skinned address to IDNA.
103 * If an error occurs before Unicode information is available, revert the IDNA
104 * error to a normal CHAR one so that the error message doesn't talk Unicode */
105 #ifdef HAVE_IDNA
106 static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp);
107 #endif
109 /* Classify and check a (possibly skinned) header body according to RFC
110 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
111 * also a file or a pipe command, so check that first, then.
112 * Otherwise perform content checking and isolate the domain part (for IDNA) */
113 static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned);
115 /* Return the next header field found in the given message.
116 * Return >= 0 if something found, < 0 elsewise.
117 * "colon" is set to point to the colon in the header.
118 * Must deal with \ continuations & other such fraud */
119 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
120 int rem, char **colon);
122 static int msgidnextc(char const **cp, int *status);
124 /* Count the occurances of c in str */
125 static int charcount(char *str, int c);
127 static char const * nexttoken(char const *cp);
129 /* TODO v15: change *customhdr* syntax and use shell tokens?! */
130 static char *a_head_customhdr__sep(char **iolist);
132 static char const *
133 _from__skipword(char const *wp)
135 char c = 0;
136 NYD2_ENTER;
138 if (wp != NULL) {
139 while ((c = *wp++) != '\0' && !blankchar(c)) {
140 if (c == '"') {
141 while ((c = *wp++) != '\0' && c != '"')
143 if (c != '"')
144 --wp;
147 for (; blankchar(c); c = *wp++)
150 NYD2_LEAVE;
151 return (c == 0 ? NULL : wp - 1);
154 static int
155 _cmatch(size_t len, char const *date, char const *tp)
157 int ret = 0;
158 NYD2_ENTER;
160 while (len--) {
161 char c = date[len];
162 switch (tp[len]) {
163 case 'a':
164 if (!lowerchar(c))
165 goto jleave;
166 break;
167 case 'A':
168 if (!upperchar(c))
169 goto jleave;
170 break;
171 case ' ':
172 if (c != ' ')
173 goto jleave;
174 break;
175 case '0':
176 if (!digitchar(c))
177 goto jleave;
178 break;
179 case 'O':
180 if (c != ' ' && !digitchar(c))
181 goto jleave;
182 break;
183 case ':':
184 if (c != ':')
185 goto jleave;
186 break;
187 case '+':
188 if (c != '+' && c != '-')
189 goto jleave;
190 break;
193 ret = 1;
194 jleave:
195 NYD2_LEAVE;
196 return ret;
199 static int
200 _is_date(char const *date)
202 struct cmatch_data const *cmdp;
203 size_t dl;
204 int rv = 0;
205 NYD2_ENTER;
207 if ((dl = strlen(date)) >= a_HEAD_DATE_MINLEN)
208 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
209 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
210 break;
211 NYD2_LEAVE;
212 return rv;
215 static size_t
216 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
217 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
218 * (via third hand, plus adjustments).
219 * This algorithm is supposed to work for all dates in between 1582-10-15
220 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
221 size_t jdn;
222 NYD2_ENTER;
224 #if 0
225 if(y == 0)
226 y = 1;
227 if(m == 0)
228 m = 1;
229 if(d == 0)
230 d = 1;
231 #endif
233 if(m > 2)
234 m -= 3;
235 else{
236 m += 9;
237 --y;
239 jdn = y;
240 jdn /= 100;
241 y -= 100 * jdn;
242 y *= 1461;
243 y >>= 2;
244 jdn *= 146097;
245 jdn >>= 2;
246 jdn += y;
247 jdn += d;
248 jdn += 1721119;
249 m *= 153;
250 m += 2;
251 m /= 5;
252 jdn += m;
253 NYD2_LEAVE;
254 return jdn;
257 #if 0
258 static void
259 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
260 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
261 * (via third hand, plus adjustments) */
262 size_t y, x;
263 NYD2_ENTER;
265 jdn -= 1721119;
266 jdn <<= 2;
267 --jdn;
268 y = jdn / 146097;
269 jdn %= 146097;
270 jdn |= 3;
271 y *= 100;
272 y += jdn / 1461;
273 jdn %= 1461;
274 jdn += 4;
275 jdn >>= 2;
276 x = jdn;
277 jdn <<= 2;
278 jdn += x;
279 jdn -= 3;
280 x = jdn / 153; /* x -> month */
281 jdn %= 153;
282 jdn += 5;
283 jdn /= 5; /* jdn -> day */
284 if(x < 10)
285 x += 3;
286 else{
287 x -= 9;
288 ++y;
291 *yp = (ui32_t)(y & 0xFFFF);
292 *mp = (ui32_t)(x & 0xFF);
293 *dp = (ui32_t)(jdn & 0xFF);
294 NYD2_LEAVE;
296 #endif /* 0 */
298 #ifdef HAVE_IDNA
299 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
300 static struct n_addrguts *
301 a_head_idna_apply(struct n_addrguts *agp)
303 char *idna_utf8, *idna_ascii, *cs;
304 size_t sz, i;
305 NYD_ENTER;
307 sz = agp->ag_slen - agp->ag_sdom_start;
308 assert(sz > 0);
309 idna_utf8 = ac_alloc(sz +1);
310 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
311 idna_utf8[sz] = '\0';
313 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
314 * let it perform the charset conversion, if any should be necessary */
315 if (!(n_psonce & n_PSO_UNICODE)) {
316 char const *tcs = ok_vlook(ttycharset);
317 idna_ascii = idna_utf8;
318 idna_utf8 = stringprep_convert(idna_ascii, "utf-8", tcs);
319 i = (idna_utf8 == NULL && n_err_no == n_ERR_INVAL);
320 ac_free(idna_ascii);
322 if (idna_utf8 == NULL) {
323 if (i)
324 n_err(_("Cannot convert from %s to %s\n"), tcs, "utf-8");
325 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
326 goto jleave;
330 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
331 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
332 goto jleave1;
335 /* Replace the domain part of .ag_skinned with IDNA version */
336 sz = strlen(idna_ascii);
337 i = agp->ag_sdom_start;
338 cs = salloc(i + sz +1);
339 memcpy(cs, agp->ag_skinned, i);
340 memcpy(&cs[i], idna_ascii, sz);
341 i += sz;
342 cs[i] = '\0';
344 agp->ag_skinned = cs;
345 agp->ag_slen = i;
346 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
347 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
349 idn_free(idna_ascii);
350 jleave1:
351 if (n_psonce & n_PSO_UNICODE)
352 ac_free(idna_utf8);
353 else
354 idn_free(idna_utf8);
355 jleave:
356 NYD_LEAVE;
357 return agp;
360 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */
361 static struct n_addrguts *
362 a_head_idna_apply(struct n_addrguts *agp)
364 char *idna_in, *idna_out, *cs;
365 size_t sz, i;
366 idn_result_t r;
367 NYD_ENTER;
369 sz = agp->ag_slen - agp->ag_sdom_start;
370 assert(sz > 0);
371 idna_in = ac_alloc(sz +1);
372 memcpy(idna_in, agp->ag_skinned + agp->ag_sdom_start, sz);
373 idna_in[sz] = '\0';
375 for (idna_out = NULL, sz = HOST_NAME_MAX +1;; sz += HOST_NAME_MAX) {
376 idna_out = ac_alloc(sz);
378 r = idn_encodename(IDN_ENCODE_APP, idna_in, idna_out, sz);
379 switch (r) {
380 case idn_success:
381 case idn_buffer_overflow:
382 break;
383 case idn_invalid_encoding:
384 n_err(_("Cannot convert from %s to %s\n"),
385 ok_vlook(ttycharset), "utf-8");
386 /* FALLTHRU */
387 default:
388 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
389 goto jleave;
392 if (r == idn_success)
393 break;
394 ac_free(idna_out);
397 /* Replace the domain part of .ag_skinned with IDNA version */
398 sz = strlen(idna_out);
399 i = agp->ag_sdom_start;
400 cs = salloc(i + sz +1);
401 memcpy(cs, agp->ag_skinned, i);
402 memcpy(&cs[i], idna_out, sz);
403 i += sz;
404 cs[i] = '\0';
406 agp->ag_skinned = cs;
407 agp->ag_slen = i;
408 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
409 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
411 jleave:
412 ac_free(idna_out);
413 ac_free(idna_in);
414 NYD_LEAVE;
415 return agp;
417 # endif /* IDNA==IDNKIT */
418 #endif /* HAVE_IDNA */
420 static bool_t
421 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned)
423 char *addr, *p;
424 bool_t in_quote;
425 ui8_t in_domain, hadat;
426 union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
427 #ifdef HAVE_IDNA
428 ui8_t use_idna;
429 #endif
430 NYD_ENTER;
432 #ifdef HAVE_IDNA
433 use_idna = ok_blook(idna_disable) ? 0 : 1;
434 #endif
435 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
436 addr = agp->ag_skinned;
438 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
439 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
440 goto jleave;
443 /* If the field is not a recipient, it cannot be a file or a pipe */
444 if (!skinned)
445 goto jaddr_check;
447 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
448 * grep the latter for the complete picture */
449 if (*addr == '|') {
450 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
451 goto jleave;
453 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
454 (addr[0] == '-' && addr[1] == '\0'))
455 goto jisfile;
456 if (memchr(addr, '@', agp->ag_slen) == NULL) {
457 if (*addr == '+')
458 goto jisfile;
459 for (p = addr; (c.c = *p); ++p) {
460 if (c.c == '!' || c.c == '%')
461 break;
462 if (c.c == '/') {
463 jisfile:
464 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
465 goto jleave;
470 jaddr_check:
471 in_quote = FAL0;
472 in_domain = hadat = 0;
474 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser! */
475 for (p = addr; (c.c = *p++) != '\0';) {
476 if (c.c == '"') {
477 in_quote = !in_quote;
478 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
479 #ifdef HAVE_IDNA
480 if (in_domain && use_idna > 0) {
481 if (use_idna == 1)
482 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
483 c.u);
484 use_idna = 2;
485 } else
486 #endif
487 break;
488 } else if (in_domain == 2) {
489 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
490 break;
491 } else if (in_quote && in_domain == 0) {
492 /*EMPTY*/;
493 } else if (c.c == '\\' && *p != '\0') {
494 ++p;
495 } else if (c.c == '@') {
496 if (hadat++ > 0) {
497 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
498 c.u);
499 goto jleave;
501 agp->ag_sdom_start = PTR2SIZE(p - addr);
502 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
503 in_domain = (*p == '[') ? 2 : 1;
504 continue;
505 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
506 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
507 c.c == '\\' || c.c == ',')
508 break;
509 hadat = 0;
511 if (c.c != '\0') {
512 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
513 goto jleave;
516 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
517 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
518 if(!n_alias_is_valid_name(agp->ag_input))
519 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
520 }else{
521 /* If we seem to know that this is an address. Ensure this is correct
522 * according to RFC 5322 TODO the entire address parser should be like
523 * TODO that for one, and then we should know whether structured or
524 * TODO unstructured, and just parse correctly overall!
525 * TODO In addition, this can be optimised a lot.
526 * TODO And it is far from perfect: it should not forget whether no
527 * TODO whitespace followed some snippet, and it was written hastily */
528 struct a_token{
529 struct a_token *t_last;
530 struct a_token *t_next;
531 enum{
532 a_T_TATOM = 1<<0,
533 a_T_TCOMM = 1<<1,
534 a_T_TQUOTE = 1<<2,
535 a_T_TADDR = 1<<3,
536 a_T_TMASK = (1<<4) - 1,
538 a_T_SPECIAL = 1<<8 /* An atom actually needs to go TQUOTE */
539 } t_f;
540 ui8_t t__pad[4];
541 size_t t_start;
542 size_t t_end;
543 } *thead, *tcurr, *tp;
545 struct n_string ost, *ostp;
546 char const *cp, *cp1st, *cpmax, *xp;
547 void *lofi_snap;
549 /* Name and domain must be non-empty */
550 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
551 c.c = '@';
552 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
553 goto jleave;
556 #ifdef HAVE_IDNA
557 if(use_idna == 2)
558 agp = a_head_idna_apply(agp);
559 #endif
561 cp = agp->ag_input;
563 /* Nothing to do if there is only an address (in angle brackets) */
564 if(agp->ag_iaddr_start == 0){
565 if(agp->ag_iaddr_aend == agp->ag_ilen)
566 goto jleave;
567 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
568 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
569 cp[agp->ag_iaddr_aend] == '>')
570 goto jleave;
572 /* It is not, so parse off all tokens, then resort and rejoin */
573 lofi_snap = n_lofi_snap_create();
575 cp1st = cp;
576 if((c.ui32 = agp->ag_iaddr_start) > 0)
577 --c.ui32;
578 cpmax = &cp[c.ui32];
580 thead = tcurr = NULL;
581 hadat = FAL0;
582 jnode_redo:
583 for(tp = NULL; cp < cpmax;){
584 switch((c.c = *cp)){
585 case '(':
586 if(tp != NULL)
587 tp->t_end = PTR2SIZE(cp - cp1st);
588 tp = n_lofi_alloc(sizeof *tp);
589 tp->t_next = NULL;
590 if((tp->t_last = tcurr) != NULL)
591 tcurr->t_next = tp;
592 else
593 thead = tp;
594 tcurr = tp;
595 tp->t_f = a_T_TCOMM;
596 tp->t_start = PTR2SIZE(++cp - cp1st);
597 xp = skip_comment(cp);
598 tp->t_end = PTR2SIZE(xp - cp1st);
599 cp = xp;
600 if(tp->t_end > tp->t_start){
601 if(xp[-1] == ')')
602 --tp->t_end;
603 else{
604 /* No closing comment - strip trailing whitespace */
605 while(blankchar(*--xp))
606 if(--tp->t_end == tp->t_start)
607 break;
610 tp = NULL;
611 break;
613 case '"':
614 if(tp != NULL)
615 tp->t_end = PTR2SIZE(cp - cp1st);
616 tp = n_lofi_alloc(sizeof *tp);
617 tp->t_next = NULL;
618 if((tp->t_last = tcurr) != NULL)
619 tcurr->t_next = tp;
620 else
621 thead = tp;
622 tcurr = tp;
623 tp->t_f = a_T_TQUOTE;
624 tp->t_start = PTR2SIZE(++cp - cp1st);
625 for(xp = cp; xp < cpmax; ++xp){
626 if((c.c = *xp) == '"')
627 break;
628 if(c.c == '\\' && xp[1] != '\0')
629 ++xp;
631 tp->t_end = PTR2SIZE(xp - cp1st);
632 cp = &xp[1];
633 if(tp->t_end > tp->t_start){
634 /* No closing quote - strip trailing whitespace */
635 if(*xp != '"'){
636 while(blankchar(*xp--))
637 if(--tp->t_end == tp->t_start)
638 break;
641 tp = NULL;
642 break;
644 default:
645 if(blankchar(c.c)){
646 if(tp != NULL)
647 tp->t_end = PTR2SIZE(cp - cp1st);
648 tp = NULL;
649 ++cp;
650 break;
653 if(tp == NULL){
654 tp = n_lofi_alloc(sizeof *tp);
655 tp->t_next = NULL;
656 if((tp->t_last = tcurr) != NULL)
657 tcurr->t_next = tp;
658 else
659 thead = tp;
660 tcurr = tp;
661 tp->t_f = a_T_TATOM;
662 tp->t_start = PTR2SIZE(cp - cp1st);
664 ++cp;
666 /* Reverse solidus transforms the following into a quoted-pair, and
667 * therefore (must occur in comment or quoted-string only) the
668 * entire atom into a quoted string */
669 if(c.c == '\\'){
670 tp->t_f |= a_T_SPECIAL;
671 if(cp < cpmax)
672 ++cp;
674 /* Is this plain RFC 5322 "atext", or "specials"? Because we don't
675 * TODO know structured/unstructured, nor anything else, we need to
676 * TODO treat "dot-atom" as being identical to "specials" */
677 else if(!alnumchar(c.c) &&
678 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
679 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
680 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
681 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
682 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
683 tp->t_f |= a_T_SPECIAL;
684 break;
687 if(tp != NULL)
688 tp->t_end = PTR2SIZE(cp - cp1st);
690 if(hadat == FAL0){
691 hadat = TRU1;
692 tp = n_lofi_alloc(sizeof *tp);
693 tp->t_next = NULL;
694 if((tp->t_last = tcurr) != NULL)
695 tcurr->t_next = tp;
696 else
697 thead = tp;
698 tcurr = tp;
699 tp->t_f = a_T_TADDR;
700 tp->t_start = agp->ag_iaddr_start;
701 tp->t_end = agp->ag_iaddr_aend;
702 tp = NULL;
704 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
705 cpmax = &agp->ag_input[agp->ag_ilen];
706 if(cp < cpmax)
707 goto jnode_redo;
710 /* Nothing may follow the address, move it to the end */
711 if(!(tcurr->t_f & a_T_TADDR)){
712 for(tp = thead; tp != NULL; tp = tp->t_next){
713 if(tp->t_f & a_T_TADDR){
714 if(tp->t_last != NULL)
715 tp->t_last->t_next = tp->t_next;
716 else
717 thead = tp->t_next;
718 if(tp->t_next != NULL)
719 tp->t_next->t_last = tp->t_last;
721 tcurr = tp;
722 while(tp->t_next != NULL)
723 tp = tp->t_next;
724 tp->t_next = tcurr;
725 tcurr->t_last = tp;
726 tcurr->t_next = NULL;
727 break;
732 /* Make ranges contiguous: ensure a continuous range of atoms is converted
733 * to a SPECIAL one if at least one of them requires it */
734 for(tp = thead; tp != NULL; tp = tp->t_next){
735 if(tp->t_f & a_T_SPECIAL){
736 tcurr = tp;
737 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
738 tp->t_f |= a_T_SPECIAL;
739 tp = tcurr;
740 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
741 tp->t_f |= a_T_SPECIAL;
745 /* And yes, we want quotes to extend as much as possible */
746 for(tp = thead; tp != NULL; tp = tp->t_next){
747 if(tp->t_f & a_T_TQUOTE){
748 tcurr = tp;
749 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
750 tp->t_f |= a_T_SPECIAL;
751 tp = tcurr;
752 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
753 tp->t_f |= a_T_SPECIAL;
757 /* Then rejoin */
758 ostp = n_string_creat_auto(&ost);
759 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
760 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
762 for(tcurr = thead; tcurr != NULL;){
763 if(tcurr != thead)
764 ostp = n_string_push_c(ostp, ' ');
765 if(tcurr->t_f & a_T_TADDR){
766 ostp = n_string_push_c(ostp, '<');
767 agp->ag_iaddr_start = ostp->s_len;
768 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
769 (tcurr->t_end - tcurr->t_start));
770 agp->ag_iaddr_aend = ostp->s_len;
771 ostp = n_string_push_c(ostp, '>');
772 tcurr = tcurr->t_next;
773 }else if(tcurr->t_f & a_T_TCOMM){
774 ostp = n_string_push_c(ostp, '(');
775 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
776 (tcurr->t_end - tcurr->t_start));
777 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
778 tcurr = tp;
779 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
780 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
781 (tcurr->t_end - tcurr->t_start));
783 ostp = n_string_push_c(ostp, ')');
784 tcurr = tcurr->t_next;
785 }else if(tcurr->t_f & a_T_TQUOTE){
786 jput_quote:
787 ostp = n_string_push_c(ostp, '"');
788 tp = tcurr;
789 do/* while tcurr && TATOM||TQUOTE */{
790 cp = &cp1st[tcurr->t_start];
791 cpmax = &cp1st[tcurr->t_end];
792 if(cp == cpmax)
793 continue;
795 if(tcurr != tp)
796 ostp = n_string_push_c(ostp, ' ');
798 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
799 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
800 else{
801 bool_t esc;
803 for(esc = FAL0; cp < cpmax;){
804 if((c.c = *cp++) == '\\' && !esc){
805 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
806 esc = TRU1;
807 }else{
808 if(esc || c.c == '"'){
809 jput_quote_esc:
810 ostp = n_string_push_c(ostp, '\\');
812 ostp = n_string_push_c(ostp, c.c);
813 esc = FAL0;
816 if(esc){
817 c.c = '\\';
818 goto jput_quote_esc;
821 }while((tcurr = tcurr->t_next) != NULL &&
822 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
823 ostp = n_string_push_c(ostp, '"');
824 }else if(tcurr->t_f & a_T_SPECIAL)
825 goto jput_quote;
826 else{
827 /* Can we use a fast join mode? */
828 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
829 if(!(tcurr->t_f & a_T_TATOM))
830 break;
831 if(tcurr != tp)
832 ostp = n_string_push_c(ostp, ' ');
833 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
834 (tcurr->t_end - tcurr->t_start));
839 n_lofi_snap_unroll(lofi_snap);
841 agp->ag_input = n_string_cp(ostp);
842 agp->ag_ilen = ostp->s_len;
843 ostp = n_string_drop_ownership(ostp);
845 jleave:
846 NYD_LEAVE;
847 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) == 0);
850 static int
851 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
853 char *line2 = NULL, *cp, *cp2;
854 size_t line2size = 0;
855 int c, isenc;
856 NYD2_ENTER;
858 if (*linebuf == NULL)
859 *linebuf = srealloc(*linebuf, *linesize = 1);
860 **linebuf = '\0';
861 for (;;) {
862 if (--rem < 0) {
863 rem = -1;
864 break;
866 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
867 rem = -1;
868 break;
870 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
872 if (cp > *linebuf)
873 while (blankchar(*cp))
874 ++cp;
875 if (*cp != ':' || cp == *linebuf)
876 continue;
878 /* I guess we got a headline. Handle wraparound */
879 *colon = cp;
880 cp = *linebuf + c;
881 for (;;) {
882 isenc = 0;
883 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
885 cp++;
886 if (rem <= 0)
887 break;
888 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
889 isenc |= 1;
890 ungetc(c = getc(f), f);
891 if (!blankchar(c))
892 break;
893 c = readline_restart(f, &line2, &line2size, 0);
894 if (c < 0)
895 break;
896 --rem;
897 for (cp2 = line2; blankchar(*cp2); ++cp2)
899 c -= (int)PTR2SIZE(cp2 - line2);
900 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
901 isenc |= 2;
902 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
903 size_t diff = PTR2SIZE(cp - *linebuf),
904 colondiff = PTR2SIZE(*colon - *linebuf);
905 *linebuf = srealloc(*linebuf, *linesize += c + 2);
906 cp = &(*linebuf)[diff];
907 *colon = &(*linebuf)[colondiff];
909 if (isenc != 3)
910 *cp++ = ' ';
911 memcpy(cp, cp2, c);
912 cp += c;
914 *cp = '\0';
916 if (line2 != NULL)
917 free(line2);
918 break;
920 NYD2_LEAVE;
921 return rem;
924 static int
925 msgidnextc(char const **cp, int *status)
927 int c;
928 NYD2_ENTER;
930 assert(cp != NULL);
931 assert(*cp != NULL);
932 assert(status != NULL);
934 for (;;) {
935 if (*status & 01) {
936 if (**cp == '"') {
937 *status &= ~01;
938 (*cp)++;
939 continue;
941 if (**cp == '\\') {
942 (*cp)++;
943 if (**cp == '\0')
944 goto jeof;
946 goto jdfl;
948 switch (**cp) {
949 case '(':
950 *cp = skip_comment(&(*cp)[1]);
951 continue;
952 case '>':
953 case '\0':
954 jeof:
955 c = '\0';
956 goto jleave;
957 case '"':
958 (*cp)++;
959 *status |= 01;
960 continue;
961 case '@':
962 *status |= 02;
963 /*FALLTHRU*/
964 default:
965 jdfl:
966 c = *(*cp)++ & 0377;
967 c = (*status & 02) ? lowerconv(c) : c;
968 goto jleave;
971 jleave:
972 NYD2_LEAVE;
973 return c;
976 static int
977 charcount(char *str, int c)
979 char *cp;
980 int i;
981 NYD2_ENTER;
983 for (i = 0, cp = str; *cp; ++cp)
984 if (*cp == c)
985 ++i;
986 NYD2_LEAVE;
987 return i;
990 static char const *
991 nexttoken(char const *cp)
993 NYD2_ENTER;
994 for (;;) {
995 if (*cp == '\0') {
996 cp = NULL;
997 break;
1000 if (*cp == '(') {
1001 size_t nesting = 1;
1003 do switch (*++cp) {
1004 case '(':
1005 ++nesting;
1006 break;
1007 case ')':
1008 --nesting;
1009 break;
1010 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
1011 } else if (blankchar(*cp) || *cp == ',')
1012 ++cp;
1013 else
1014 break;
1016 NYD2_LEAVE;
1017 return cp;
1020 static char *
1021 a_head_customhdr__sep(char **iolist){
1022 char *cp, c, *base;
1023 bool_t isesc, anyesc;
1024 NYD2_ENTER;
1026 for(base = *iolist; base != NULL; base = *iolist){
1027 while((c = *base) != '\0' && blankspacechar(c))
1028 ++base;
1030 for(isesc = anyesc = FAL0, cp = base;; ++cp){
1031 if(n_UNLIKELY((c = *cp) == '\0')){
1032 *iolist = NULL;
1033 break;
1034 }else if(!isesc){
1035 if(c == ','){
1036 *iolist = cp + 1;
1037 break;
1039 isesc = (c == '\\');
1040 }else{
1041 isesc = FAL0;
1042 anyesc |= (c == ',');
1046 while(cp > base && blankspacechar(cp[-1]))
1047 --cp;
1048 *cp = '\0';
1050 if(*base != '\0'){
1051 if(anyesc){
1052 char *ins;
1054 for(ins = cp = base;; ++ins)
1055 if((c = *cp) == '\\' && cp[1] == ','){
1056 *ins = ',';
1057 cp += 2;
1058 }else if((*ins = (++cp, c)) == '\0')
1059 break;
1061 break;
1064 NYD2_LEAVE;
1065 return base;
1068 FL char const *
1069 myaddrs(struct header *hp) /* TODO */
1071 struct name *np;
1072 char const *rv, *mta;
1073 NYD_ENTER;
1075 if (hp != NULL && (np = hp->h_from) != NULL) {
1076 if ((rv = np->n_fullname) != NULL)
1077 goto jleave;
1078 if ((rv = np->n_name) != NULL)
1079 goto jleave;
1082 if((rv = ok_vlook(from)) != NULL){
1083 if((np = lextract(rv, GEXTRA | GFULL)) == NULL)
1084 jefrom:
1085 n_err(_("An address given in *from* is invalid: %s\n"), rv);
1086 else for(; np != NULL; np = np->n_flink)
1087 if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1088 goto jefrom;
1089 goto jleave;
1092 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1093 * undeterminable From: address. However, if the user sets *hostname*,
1094 * accept his desire */
1095 if (ok_vlook(hostname) != NULL)
1096 goto jnodename;
1097 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
1098 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1099 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
1100 goto jnodename;
1101 jleave:
1102 NYD_LEAVE;
1103 return rv;
1105 jnodename:{
1106 char *cp;
1107 char const *hn, *ln;
1108 size_t i;
1110 hn = n_nodename(TRU1);
1111 ln = ok_vlook(LOGNAME);
1112 i = strlen(ln) + strlen(hn) + 1 +1;
1113 rv = cp = salloc(i);
1114 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1116 goto jleave;
1119 FL char const *
1120 myorigin(struct header *hp) /* TODO */
1122 char const *rv = NULL, *ccp;
1123 struct name *np;
1124 NYD_ENTER;
1126 if((ccp = myaddrs(hp)) != NULL &&
1127 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1128 if(np->n_flink == NULL)
1129 rv = ccp;
1130 else if((ccp = ok_vlook(sender)) != NULL) {
1131 if((np = lextract(ccp, GEXTRA | GFULL)) == NULL ||
1132 np->n_flink != NULL ||
1133 is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1134 n_err(_("The address given in *sender* is invalid: %s\n"), ccp);
1135 else
1136 rv = ccp;
1139 NYD_LEAVE;
1140 return rv;
1143 FL bool_t
1144 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1146 char date[n_FROM_DATEBUF];
1147 bool_t rv;
1148 NYD2_ENTER;
1150 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1151 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1152 !_is_date(date)))
1153 rv = TRUM1;
1154 NYD2_LEAVE;
1155 return rv;
1158 FL int
1159 extract_date_from_from_(char const *line, size_t linelen,
1160 char datebuf[n_FROM_DATEBUF])
1162 int rv;
1163 char const *cp = line;
1164 NYD_ENTER;
1166 rv = 1;
1168 /* "From " */
1169 cp = _from__skipword(cp);
1170 if (cp == NULL)
1171 goto jerr;
1172 /* "addr-spec " */
1173 cp = _from__skipword(cp);
1174 if (cp == NULL)
1175 goto jerr;
1176 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1177 (cp[2] == 'y' || cp[2] == 'Y')){
1178 cp = _from__skipword(cp);
1179 if (cp == NULL)
1180 goto jerr;
1182 /* It seems there are invalid MBOX archives in the wild, compare
1183 * . http://bugs.debian.org/624111
1184 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1185 * What they do is that they obfuscate the address to "name at host",
1186 * and even "name at host dot dom dot dom.
1187 * The [Aa][Tt] is also RFC 733, so be tolerant */
1188 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1189 cp[2] == ' '){
1190 rv = -1;
1191 cp += 3;
1192 jat_dot:
1193 cp = _from__skipword(cp);
1194 if (cp == NULL)
1195 goto jerr;
1196 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1197 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1198 cp += 4;
1199 goto jat_dot;
1203 linelen -= PTR2SIZE(cp - line);
1204 if (linelen < a_HEAD_DATE_MINLEN)
1205 goto jerr;
1206 if (cp[linelen - 1] == '\n') {
1207 --linelen;
1208 /* (Rather IMAP/POP3 only) */
1209 if (cp[linelen - 1] == '\r')
1210 --linelen;
1211 if (linelen < a_HEAD_DATE_MINLEN)
1212 goto jerr;
1214 if (linelen >= n_FROM_DATEBUF)
1215 goto jerr;
1217 jleave:
1218 memcpy(datebuf, cp, linelen);
1219 datebuf[linelen] = '\0';
1220 NYD_LEAVE;
1221 return rv;
1222 jerr:
1223 cp = _("<Unknown date>");
1224 linelen = strlen(cp);
1225 if (linelen >= n_FROM_DATEBUF)
1226 linelen = n_FROM_DATEBUF;
1227 rv = 0;
1228 goto jleave;
1231 FL void
1232 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1234 /* See the prototype declaration for the hairy relationship of
1235 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1236 struct n_header_field **hftail;
1237 struct header nh, *hq = &nh;
1238 char *linebuf = NULL /* TODO line pool */, *colon;
1239 size_t linesize = 0, seenfields = 0;
1240 int lc, c;
1241 char const *val, *cp;
1242 NYD_ENTER;
1244 memset(hq, 0, sizeof *hq);
1245 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1246 hq->h_to = hp->h_to;
1247 hq->h_cc = hp->h_cc;
1248 hq->h_bcc = hp->h_bcc;
1250 hftail = &hq->h_user_headers;
1252 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1255 /* TODO yippieia, cat(check(lextract)) :-) */
1256 rewind(fp);
1257 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1258 struct name *np;
1260 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1261 * yet expanded when we parse these! */
1262 if ((val = thisfield(linebuf, "to")) != NULL) {
1263 ++seenfields;
1264 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1265 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1266 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1267 ++seenfields;
1268 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1269 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1270 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1271 ++seenfields;
1272 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1273 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1274 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1275 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1276 ++seenfields;
1277 hq->h_from = cat(hq->h_from,
1278 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1279 EACM_STRICT, NULL));
1281 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1282 ++seenfields;
1283 hq->h_replyto = cat(hq->h_replyto,
1284 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1285 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1286 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1287 ++seenfields;
1288 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1289 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1290 EACM_STRICT, NULL));
1291 } else
1292 goto jebadhead;
1293 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1294 (val = thisfield(linebuf, "subj")) != NULL) {
1295 ++seenfields;
1296 for (cp = val; blankchar(*cp); ++cp)
1298 hq->h_subject = (hq->h_subject != NULL)
1299 ? save2str(hq->h_subject, cp) : savestr(cp);
1301 /* The remaining are mostly hacked in and thus TODO -- at least in
1302 * TODO respect to their content checking */
1303 else if((val = thisfield(linebuf, "message-id")) != NULL){
1304 if(n_psonce & n_PSO_t_FLAG){
1305 np = checkaddrs(lextract(val, GREF),
1306 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1307 NULL);
1308 if (np == NULL || np->n_flink != NULL)
1309 goto jebadhead;
1310 ++seenfields;
1311 hq->h_message_id = np;
1312 }else
1313 goto jebadhead;
1314 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1315 if(n_psonce & n_PSO_t_FLAG){
1316 np = checkaddrs(lextract(val, GREF),
1317 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1318 NULL);
1319 ++seenfields;
1320 hq->h_in_reply_to = np;
1321 }else
1322 goto jebadhead;
1323 }else if((val = thisfield(linebuf, "references")) != NULL){
1324 if(n_psonce & n_PSO_t_FLAG){
1325 ++seenfields;
1326 /* TODO Limit number of references TODO better on parser side */
1327 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1328 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1329 NULL));
1330 }else
1331 goto jebadhead;
1333 /* and that is very hairy */
1334 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1335 if(n_psonce & n_PSO_t_FLAG){
1336 ++seenfields;
1337 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1338 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1339 checkaddr_err));
1340 }else
1341 goto jebadhead;
1343 /* A free-form user header; gethfield() did some verification already.. */
1344 else{
1345 struct n_header_field *hfp;
1346 ui32_t nl, bl;
1347 char const *nstart;
1349 for(nstart = cp = linebuf;; ++cp)
1350 if(!fieldnamechar(*cp))
1351 break;
1352 nl = (ui32_t)PTR2SIZE(cp - nstart);
1354 while(blankchar(*cp))
1355 ++cp;
1356 if(*cp++ != ':'){
1357 jebadhead:
1358 n_err(_("Ignoring header field: %s\n"), linebuf);
1359 continue;
1361 while(blankchar(*cp))
1362 ++cp;
1363 bl = (ui32_t)strlen(cp) +1;
1365 ++seenfields;
1366 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1367 ) + nl +1 + bl);
1368 hftail = &hfp->hf_next;
1369 hfp->hf_next = NULL;
1370 hfp->hf_nl = nl;
1371 hfp->hf_bl = bl - 1;
1372 memcpy(hfp->hf_dat, nstart, nl);
1373 hfp->hf_dat[nl++] = '\0';
1374 memcpy(hfp->hf_dat + nl, cp, bl);
1378 /* In case the blank line after the header has been edited out. Otherwise,
1379 * fetch the header separator */
1380 if (linebuf != NULL) {
1381 if (linebuf[0] != '\0') {
1382 for (cp = linebuf; *(++cp) != '\0';)
1384 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1385 } else {
1386 if ((c = getc(fp)) != '\n' && c != EOF)
1387 ungetc(c, fp);
1391 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1392 hp->h_to = hq->h_to;
1393 hp->h_cc = hq->h_cc;
1394 hp->h_bcc = hq->h_bcc;
1395 hp->h_from = hq->h_from;
1396 hp->h_replyto = hq->h_replyto;
1397 hp->h_sender = hq->h_sender;
1398 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1399 !(n_poption & n_PO_t_FLAG))
1400 hp->h_subject = hq->h_subject;
1401 hp->h_user_headers = hq->h_user_headers;
1403 if (n_psonce & n_PSO_t_FLAG) {
1404 hp->h_ref = hq->h_ref;
1405 hp->h_message_id = hq->h_message_id;
1406 hp->h_in_reply_to = hq->h_in_reply_to;
1407 hp->h_mft = hq->h_mft;
1409 /* And perform additional validity checks so that we don't bail later
1410 * on TODO this is good and the place where this should occur,
1411 * TODO unfortunately a lot of other places do again and blabla */
1412 if (hp->h_from == NULL)
1413 hp->h_from = n_poption_arg_r;
1414 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1415 hp->h_sender = lextract(ok_vlook(sender),
1416 GEXTRA | GFULL | GFULLEXTRA);
1418 } else
1419 n_err(_("Restoring deleted header lines\n"));
1421 if (linebuf != NULL)
1422 free(linebuf);
1423 NYD_LEAVE;
1426 FL char *
1427 hfield_mult(char const *field, struct message *mp, int mult)
1429 FILE *ibuf;
1430 int lc;
1431 struct str hfs;
1432 size_t linesize = 0; /* TODO line pool */
1433 char *linebuf = NULL, *colon;
1434 char const *hfield;
1435 NYD_ENTER;
1437 /* There are (spam) messages which have header bytes which are many KB when
1438 * joined, so resize a single heap storage until we are done if we shall
1439 * collect a field that may have multiple bodies; only otherwise use the
1440 * string dope directly */
1441 memset(&hfs, 0, sizeof hfs);
1443 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1444 goto jleave;
1445 if ((lc = mp->m_lines - 1) < 0)
1446 goto jleave;
1448 if ((mp->m_flag & MNOFROM) == 0 &&
1449 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1450 goto jleave;
1451 while (lc > 0) {
1452 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1453 break;
1454 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1455 if (mult)
1456 n_str_add_buf(&hfs, hfield, strlen(hfield));
1457 else {
1458 hfs.s = savestr(hfield);
1459 break;
1464 jleave:
1465 if (linebuf != NULL)
1466 free(linebuf);
1467 if (mult && hfs.s != NULL) {
1468 colon = savestrbuf(hfs.s, hfs.l);
1469 free(hfs.s);
1470 hfs.s = colon;
1472 NYD_LEAVE;
1473 return hfs.s;
1476 FL char const *
1477 thisfield(char const *linebuf, char const *field)
1479 char const *rv = NULL;
1480 NYD2_ENTER;
1482 while (lowerconv(*linebuf) == lowerconv(*field)) {
1483 ++linebuf;
1484 ++field;
1486 if (*field != '\0')
1487 goto jleave;
1489 while (blankchar(*linebuf))
1490 ++linebuf;
1491 if (*linebuf++ != ':')
1492 goto jleave;
1494 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1495 ++linebuf;
1496 rv = linebuf;
1497 jleave:
1498 NYD2_LEAVE;
1499 return rv;
1502 FL char *
1503 nameof(struct message *mp, int reptype)
1505 char *cp, *cp2;
1506 NYD_ENTER;
1508 cp = skin(name1(mp, reptype));
1509 if (reptype != 0 || charcount(cp, '!') < 2)
1510 goto jleave;
1511 cp2 = strrchr(cp, '!');
1512 --cp2;
1513 while (cp2 > cp && *cp2 != '!')
1514 --cp2;
1515 if (*cp2 == '!')
1516 cp = cp2 + 1;
1517 jleave:
1518 NYD_LEAVE;
1519 return cp;
1522 FL char const *
1523 skip_comment(char const *cp)
1525 size_t nesting;
1526 NYD_ENTER;
1528 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1529 switch (*cp) {
1530 case '\\':
1531 if (cp[1])
1532 ++cp;
1533 break;
1534 case '(':
1535 ++nesting;
1536 break;
1537 case ')':
1538 --nesting;
1539 break;
1542 NYD_LEAVE;
1543 return cp;
1546 FL char const *
1547 routeaddr(char const *name)
1549 char const *np, *rp = NULL;
1550 NYD_ENTER;
1552 for (np = name; *np; np++) {
1553 switch (*np) {
1554 case '(':
1555 np = skip_comment(np + 1) - 1;
1556 break;
1557 case '"':
1558 while (*np) {
1559 if (*++np == '"')
1560 break;
1561 if (*np == '\\' && np[1])
1562 np++;
1564 break;
1565 case '<':
1566 rp = np;
1567 break;
1568 case '>':
1569 goto jleave;
1572 rp = NULL;
1573 jleave:
1574 NYD_LEAVE;
1575 return rp;
1578 FL enum expand_addr_flags
1579 expandaddr_to_eaf(void)
1581 struct eafdesc {
1582 char const *eafd_name;
1583 bool_t eafd_is_target;
1584 ui8_t eafd_andoff;
1585 ui8_t eafd_or;
1586 } const eafa[] = {
1587 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1588 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1589 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1590 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1591 {"file", TRU1, EAF_NONE, EAF_FILE},
1592 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1593 {"name", TRU1, EAF_NONE, EAF_NAME},
1594 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1595 }, *eafp;
1597 char *buf;
1598 enum expand_addr_flags rv;
1599 char const *cp;
1600 NYD2_ENTER;
1602 if ((cp = ok_vlook(expandaddr)) == NULL)
1603 rv = EAF_RESTRICT_TARGETS;
1604 else if (*cp == '\0')
1605 rv = EAF_TARGET_MASK;
1606 else {
1607 rv = EAF_TARGET_MASK;
1609 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1610 bool_t minus;
1612 if ((minus = (*cp == '-')) || *cp == '+')
1613 ++cp;
1614 for (eafp = eafa;; ++eafp) {
1615 if (eafp == eafa + n_NELEM(eafa)) {
1616 if (n_poption & n_PO_D_V)
1617 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1618 break;
1619 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1620 if (!minus) {
1621 rv &= ~eafp->eafd_andoff;
1622 rv |= eafp->eafd_or;
1623 } else {
1624 if (eafp->eafd_is_target)
1625 rv &= ~eafp->eafd_or;
1626 else if (n_poption & n_PO_D_V)
1627 n_err(_("minus - prefix invalid for *expandaddr* value: "
1628 "%s\n"), --cp);
1630 break;
1631 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1632 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1633 rv &= ~EAF_NAME;
1634 break;
1639 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1640 (n_poption & n_PO_TILDE_FLAG)))
1641 rv |= EAF_TARGET_MASK;
1642 else if(n_poption & n_PO_D_V){
1643 if(!(rv & EAF_TARGET_MASK))
1644 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1645 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1646 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1649 NYD2_LEAVE;
1650 return rv;
1653 FL si8_t
1654 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1656 char cbuf[sizeof "'\\U12340'"];
1657 char const *cs;
1658 int f;
1659 si8_t rv;
1660 enum expand_addr_flags eaf;
1661 NYD_ENTER;
1663 eaf = expandaddr_to_eaf();
1664 f = np->n_flags;
1666 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1667 if (eaf & EAF_FAILINVADDR)
1668 rv = -rv;
1670 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1672 } else {
1673 ui32_t c;
1674 char const *fmt = "'\\x%02X'";
1675 bool_t ok8bit = TRU1;
1677 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1678 cs = _("Invalid domain name: %s, character %s\n");
1679 fmt = "'\\U%04X'";
1680 ok8bit = FAL0;
1681 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1682 cs = _("%s contains invalid %s sequence\n");
1683 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1684 cs = _("%s is an invalid alias name\n");
1685 } else
1686 cs = _("%s contains invalid byte %s\n");
1688 c = NAME_ADDRSPEC_ERR_GETWC(f);
1689 snprintf(cbuf, sizeof cbuf,
1690 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1691 goto jprint;
1693 goto jleave;
1696 /* *expandaddr* stuff */
1697 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1698 goto jleave;
1700 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1701 if (eaf & EAF_FAIL)
1702 rv = -rv;
1703 cs = _("%s%s: file or pipe addressees not allowed here\n");
1704 if (eacm & EACM_NOLOG)
1705 goto jleave;
1706 else
1707 goto j0print;
1710 eaf |= (eacm & EAF_TARGET_MASK);
1711 if (eacm & EACM_NONAME)
1712 eaf &= ~EAF_NAME;
1714 if (eaf == EAF_NONE) {
1715 rv = FAL0;
1716 goto jleave;
1718 if (eaf & EAF_FAIL)
1719 rv = -rv;
1721 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1722 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1723 if (eacm & EACM_NOLOG)
1724 goto jleave;
1725 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1726 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1727 if (eacm & EACM_NOLOG)
1728 goto jleave;
1729 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1730 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1731 if (eacm & EACM_NOLOG)
1732 goto jleave;
1733 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1734 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1735 if (eacm & EACM_NOLOG)
1736 goto jleave;
1737 } else {
1738 rv = FAL0;
1739 goto jleave;
1742 j0print:
1743 cbuf[0] = '\0';
1744 jprint:
1745 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1746 jleave:
1747 NYD_LEAVE;
1748 return rv;
1751 FL char *
1752 skin(char const *name)
1754 struct n_addrguts ag;
1755 char *rv;
1756 NYD_ENTER;
1758 if(name != NULL){
1759 name = n_addrspec_with_guts(&ag,name, TRU1, FAL0);
1760 rv = ag.ag_skinned;
1761 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1762 rv = savestrbuf(rv, ag.ag_slen);
1763 }else
1764 rv = NULL;
1765 NYD_LEAVE;
1766 return rv;
1769 /* TODO addrspec_with_guts: RFC 5322
1770 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1771 FL char const *
1772 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1773 bool_t issingle_hack){
1774 char const *cp;
1775 char *cp2, *bufend, *nbuf, c;
1776 enum{
1777 a_NONE,
1778 a_GOTLT = 1<<0,
1779 a_GOTADDR = 1<<1,
1780 a_GOTSPACE = 1<<2,
1781 a_LASTSP = 1<<3
1782 } flags;
1783 NYD_ENTER;
1785 memset(agp, 0, sizeof *agp);
1787 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1788 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1789 agp->ag_slen = 0;
1790 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1791 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1792 goto jleave;
1793 }else if(!doskin){
1794 /*agp->ag_iaddr_start = 0;*/
1795 agp->ag_iaddr_aend = agp->ag_ilen;
1796 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1797 agp->ag_slen = agp->ag_ilen;
1798 agp->ag_n_flags = NAME_SKINNED;
1799 goto jcheck;
1802 flags = a_NONE;
1803 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1804 /*agp->ag_iaddr_start = 0;*/
1805 cp2 = bufend = nbuf;
1807 /* TODO This is complete crap and should use a token parser */
1808 for(cp = name++; (c = *cp++) != '\0';){
1809 switch (c) {
1810 case '(':
1811 cp = skip_comment(cp);
1812 flags &= ~a_LASTSP;
1813 break;
1814 case '"':
1815 /* Start of a "quoted-string". Copy it in its entirety */
1816 /* XXX RFC: quotes are "semantically invisible"
1817 * XXX But it was explicitly added (Changelog.Heirloom,
1818 * XXX [9.23] released 11/15/00, "Do not remove quotes
1819 * XXX when skinning names"? No more info.. */
1820 *cp2++ = c;
1821 while ((c = *cp) != '\0') { /* TODO improve */
1822 ++cp;
1823 if (c == '"') {
1824 *cp2++ = c;
1825 break;
1827 if (c != '\\')
1828 *cp2++ = c;
1829 else if ((c = *cp) != '\0') {
1830 *cp2++ = c;
1831 ++cp;
1834 flags &= ~a_LASTSP;
1835 break;
1836 case ' ':
1837 case '\t':
1838 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1839 flags |= a_GOTSPACE;
1840 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1842 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1843 cp += 3, *cp2++ = '@';
1844 else if (cp[0] == '@' && blankchar(cp[1]))
1845 cp += 2, *cp2++ = '@';
1846 else
1847 flags |= a_LASTSP;
1848 break;
1849 case '<':
1850 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1851 cp2 = bufend;
1852 flags &= ~(a_GOTSPACE | a_LASTSP);
1853 flags |= a_GOTLT | a_GOTADDR;
1854 break;
1855 case '>':
1856 if(flags & a_GOTLT){
1857 /* (_addrspec_check() verifies these later!) */
1858 flags &= ~(a_GOTLT | a_LASTSP);
1859 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1861 /* Skip over the entire remaining field */
1862 while((c = *cp) != '\0' && c != ','){
1863 ++cp;
1864 if (c == '(')
1865 cp = skip_comment(cp);
1866 else if (c == '"')
1867 while ((c = *cp) != '\0') {
1868 ++cp;
1869 if (c == '"')
1870 break;
1871 if (c == '\\' && *cp != '\0')
1872 ++cp;
1875 break;
1877 /* FALLTHRU */
1878 default:
1879 if(flags & a_LASTSP){
1880 flags &= ~a_LASTSP;
1881 if(flags & a_GOTADDR)
1882 *cp2++ = ' ';
1884 *cp2++ = c;
1885 /* This character is forbidden here, but it may nonetheless be
1886 * present: ensure we turn this into something valid! (E.g., if the
1887 * next character would be a "..) */
1888 if(c == '\\' && *cp != '\0')
1889 *cp2++ = *cp++;
1890 if(c == ',' && !issingle_hack){
1891 if(!(flags & a_GOTLT)){
1892 *cp2++ = ' ';
1893 for(; blankchar(*cp); ++cp)
1895 flags &= ~a_LASTSP;
1896 bufend = cp2;
1898 }else if(!(flags & a_GOTADDR)){
1899 flags |= a_GOTADDR;
1900 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1904 --name;
1905 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1906 if (agp->ag_iaddr_aend == 0)
1907 agp->ag_iaddr_aend = agp->ag_ilen;
1908 /* Misses > */
1909 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1910 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1911 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1912 agp->ag_iaddr_aend = agp->ag_ilen;
1913 cp2[agp->ag_ilen++] = '>';
1914 cp2[agp->ag_ilen] = '\0';
1916 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1917 n_lofi_free(nbuf);
1918 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1919 jcheck:
1920 if(a_head_addrspec_check(agp, doskin) <= FAL0)
1921 name = NULL;
1922 else
1923 name = agp->ag_input;
1924 jleave:
1925 NYD_LEAVE;
1926 return name;
1929 FL char *
1930 realname(char const *name)
1932 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1933 char *rname, *rp;
1934 struct str in, out;
1935 int quoted, good, nogood;
1936 NYD_ENTER;
1938 if ((cp = n_UNCONST(name)) == NULL)
1939 goto jleave;
1940 for (; *cp != '\0'; ++cp) {
1941 switch (*cp) {
1942 case '(':
1943 if (cstart != NULL) {
1944 /* More than one comment in address, doesn't make sense to display
1945 * it without context. Return the entire field */
1946 cp = mime_fromaddr(name);
1947 goto jleave;
1949 cstart = cp++;
1950 cp = skip_comment(cp);
1951 cend = cp--;
1952 if (cend <= cstart)
1953 cend = cstart = NULL;
1954 break;
1955 case '"':
1956 while (*cp) {
1957 if (*++cp == '"')
1958 break;
1959 if (*cp == '\\' && cp[1])
1960 ++cp;
1962 break;
1963 case '<':
1964 if (cp > name) {
1965 cstart = name;
1966 cend = cp;
1968 break;
1969 case ',':
1970 /* More than one address. Just use the first one */
1971 goto jbrk;
1975 jbrk:
1976 if (cstart == NULL) {
1977 if (*name == '<') {
1978 /* If name contains only a route-addr, the surrounding angle brackets
1979 * don't serve any useful purpose when displaying, so remove */
1980 cp = prstr(skin(name));
1981 } else
1982 cp = mime_fromaddr(name);
1983 goto jleave;
1986 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1987 * not stripped. The idea is to strip only syntactical relevant things (but
1988 * this is not necessarily the most sensible way in practice) */
1989 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1990 quoted = 0;
1991 for (cp = cstart; cp < cend; ++cp) {
1992 if (*cp == '(' && !quoted) {
1993 cq = skip_comment(++cp);
1994 if (PTRCMP(--cq, >, cend))
1995 cq = cend;
1996 while (cp < cq) {
1997 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1998 ++cp;
1999 *rp++ = *cp++;
2001 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
2002 *rp++ = *++cp;
2003 else if (*cp == '"') {
2004 quoted = !quoted;
2005 continue;
2006 } else
2007 *rp++ = *cp;
2009 *rp = '\0';
2010 in.s = rname;
2011 in.l = rp - rname;
2012 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
2013 ac_free(rname);
2014 rname = savestr(out.s);
2015 free(out.s);
2017 while (blankchar(*rname))
2018 ++rname;
2019 for (rp = rname; *rp != '\0'; ++rp)
2021 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
2022 *rp = '\0';
2023 if (rp == rname) {
2024 cp = mime_fromaddr(name);
2025 goto jleave;
2028 /* mime_fromhdr() has converted all nonprintable characters to question
2029 * marks now. These and blanks are considered uninteresting; if the
2030 * displayed part of the real name contains more than 25% of them, it is
2031 * probably better to display the plain email address instead */
2032 good = 0;
2033 nogood = 0;
2034 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
2035 if (*rp == '?' || blankchar(*rp))
2036 ++nogood;
2037 else
2038 ++good;
2039 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
2040 jleave:
2041 NYD_LEAVE;
2042 return n_UNCONST(cp);
2045 FL char *
2046 name1(struct message *mp, int reptype)
2048 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
2049 size_t namesize, linesize = 0;
2050 FILE *ibuf;
2051 int f1st = 1;
2052 NYD_ENTER;
2054 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
2055 goto jleave;
2056 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
2057 goto jleave;
2059 namebuf = smalloc(namesize = 1);
2060 namebuf[0] = 0;
2061 if (mp->m_flag & MNOFROM)
2062 goto jout;
2063 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2064 goto jout;
2065 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2066 goto jout;
2068 jnewname:
2069 if (namesize <= linesize)
2070 namebuf = srealloc(namebuf, namesize = linesize +1);
2071 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
2073 for (; blankchar(*cp); ++cp)
2075 for (cp2 = namebuf + strlen(namebuf);
2076 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
2077 *cp2++ = *cp++;
2078 *cp2 = '\0';
2080 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2081 goto jout;
2082 if ((cp = strchr(linebuf, 'F')) == NULL)
2083 goto jout;
2084 if (strncmp(cp, "From", 4))
2085 goto jout;
2086 if (namesize <= linesize)
2087 namebuf = srealloc(namebuf, namesize = linesize + 1);
2089 while ((cp = strchr(cp, 'r')) != NULL) {
2090 if (!strncmp(cp, "remote", 6)) {
2091 if ((cp = strchr(cp, 'f')) == NULL)
2092 break;
2093 if (strncmp(cp, "from", 4) != 0)
2094 break;
2095 if ((cp = strchr(cp, ' ')) == NULL)
2096 break;
2097 cp++;
2098 if (f1st) {
2099 strncpy(namebuf, cp, namesize);
2100 f1st = 0;
2101 } else {
2102 cp2 = strrchr(namebuf, '!') + 1;
2103 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
2105 namebuf[namesize - 2] = '!';
2106 namebuf[namesize - 1] = '\0';
2107 goto jnewname;
2109 cp++;
2111 jout:
2112 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2113 *cp == '\0')
2114 cp = savestr(namebuf);
2116 if (linebuf != NULL)
2117 free(linebuf);
2118 free(namebuf);
2119 jleave:
2120 NYD_LEAVE;
2121 return cp;
2124 FL char const *
2125 subject_re_trim(char const *s){
2126 struct{
2127 ui8_t len;
2128 char dat[7];
2129 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2130 {3, "re:"},
2131 {3, "aw:"}, {5, "antw:"}, /* de */
2132 {3, "wg:"}, /* Seen too often in the wild */
2133 {0, ""}
2136 bool_t any;
2137 char *re_st, *re_st_x;
2138 char const *orig_s;
2139 size_t re_l;
2140 NYD_ENTER;
2142 any = FAL0;
2143 orig_s = s;
2144 re_st = NULL;
2145 n_UNINIT(re_l, 0);
2147 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2148 (re_l = strlen(re_st_x)) > 0){
2149 re_st = n_lofi_alloc(++re_l * 2);
2150 memcpy(re_st, re_st_x, re_l);
2153 jouter:
2154 while(*s != '\0'){
2155 while(spacechar(*s))
2156 ++s;
2158 for(pp = ignored; pp->len > 0; ++pp)
2159 if(is_asccaseprefix(pp->dat, s)){
2160 s += pp->len;
2161 any = TRU1;
2162 goto jouter;
2165 if(re_st != NULL){
2166 char *cp;
2168 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2169 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2170 if(is_asccaseprefix(cp, s)){
2171 s += strlen(cp);
2172 any = TRU1;
2173 goto jouter;
2176 break;
2179 if(re_st != NULL)
2180 n_lofi_free(re_st);
2181 NYD_LEAVE;
2182 return any ? s : orig_s;
2185 FL int
2186 msgidcmp(char const *s1, char const *s2)
2188 int q1 = 0, q2 = 0, c1, c2;
2189 NYD_ENTER;
2191 while(*s1 == '<')
2192 ++s1;
2193 while(*s2 == '<')
2194 ++s2;
2196 do {
2197 c1 = msgidnextc(&s1, &q1);
2198 c2 = msgidnextc(&s2, &q2);
2199 if (c1 != c2)
2200 break;
2201 } while (c1 && c2);
2202 NYD_LEAVE;
2203 return c1 - c2;
2206 FL char const *
2207 fakefrom(struct message *mp)
2209 char const *name;
2210 NYD_ENTER;
2212 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2213 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2214 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2215 * RFC 4155 however requires a RFC 5322 (2822) conforming
2216 * "addr-spec", but we simply can't provide that */
2217 name = "MAILER-DAEMON";
2218 NYD_LEAVE;
2219 return name;
2222 FL char const *
2223 fakedate(time_t t)
2225 char *cp, *cq;
2226 NYD_ENTER;
2228 cp = ctime(&t);
2229 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
2231 *cq = '\0';
2232 cp = savestr(cp);
2233 NYD_LEAVE;
2234 return cp;
2237 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2238 FL time_t
2239 unixtime(char const *fromline)
2241 char const *fp, *xp;
2242 time_t t;
2243 si32_t i, year, month, day, hour, minute, second, tzdiff;
2244 struct tm *tmptr;
2245 NYD2_ENTER;
2247 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2249 fp -= 24;
2250 if (PTR2SIZE(fp - fromline) < 7)
2251 goto jinvalid;
2252 if (fp[3] != ' ')
2253 goto jinvalid;
2254 for (i = 0;;) {
2255 if (!strncmp(fp + 4, n_month_names[i], 3))
2256 break;
2257 if (n_month_names[++i][0] == '\0')
2258 goto jinvalid;
2260 month = i + 1;
2261 if (fp[7] != ' ')
2262 goto jinvalid;
2263 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2264 if (*xp != ' ' || xp != fp + 10)
2265 goto jinvalid;
2266 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2267 if (*xp != ':' || xp != fp + 13)
2268 goto jinvalid;
2269 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2270 if (*xp != ':' || xp != fp + 16)
2271 goto jinvalid;
2272 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2273 if (*xp != ' ' || xp != fp + 19)
2274 goto jinvalid;
2275 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2276 if (xp != fp + 24)
2277 goto jinvalid;
2278 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2279 goto jinvalid;
2280 tzdiff = t - mktime(gmtime(&t));
2281 tmptr = localtime(&t);
2282 if (tmptr->tm_isdst > 0)
2283 tzdiff += 3600;
2284 t -= tzdiff;
2285 jleave:
2286 NYD2_LEAVE;
2287 return t;
2288 jinvalid:
2289 t = n_time_epoch();
2290 goto jleave;
2292 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2294 FL time_t
2295 rfctime(char const *date) /* TODO n_idec_ return tests */
2297 char const *cp, *x;
2298 time_t t;
2299 si32_t i, year, month, day, hour, minute, second;
2300 NYD2_ENTER;
2302 cp = date;
2304 if ((cp = nexttoken(cp)) == NULL)
2305 goto jinvalid;
2306 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2307 cp[3] == ',') {
2308 if ((cp = nexttoken(&cp[4])) == NULL)
2309 goto jinvalid;
2311 n_idec_si32_cp(&day, cp, 10, &x);
2312 if ((cp = nexttoken(x)) == NULL)
2313 goto jinvalid;
2314 for (i = 0;;) {
2315 if (!strncmp(cp, n_month_names[i], 3))
2316 break;
2317 if (n_month_names[++i][0] == '\0')
2318 goto jinvalid;
2320 month = i + 1;
2321 if ((cp = nexttoken(&cp[3])) == NULL)
2322 goto jinvalid;
2323 /* RFC 5322, 4.3:
2324 * Where a two or three digit year occurs in a date, the year is to be
2325 * interpreted as follows: If a two digit year is encountered whose
2326 * value is between 00 and 49, the year is interpreted by adding 2000,
2327 * ending up with a value between 2000 and 2049. If a two digit year
2328 * is encountered with a value between 50 and 99, or any three digit
2329 * year is encountered, the year is interpreted by adding 1900 */
2330 n_idec_si32_cp(&year, cp, 10, &x);
2331 i = (int)PTR2SIZE(x - cp);
2332 if (i == 2 && year >= 0 && year <= 49)
2333 year += 2000;
2334 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2335 year += 1900;
2336 if ((cp = nexttoken(x)) == NULL)
2337 goto jinvalid;
2338 n_idec_si32_cp(&hour, cp, 10, &x);
2339 if (*x != ':')
2340 goto jinvalid;
2341 cp = &x[1];
2342 n_idec_si32_cp(&minute, cp, 10, &x);
2343 if (*x == ':') {
2344 cp = &x[1];
2345 n_idec_si32_cp(&second, cp, 10, &x);
2346 } else
2347 second = 0;
2349 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2350 goto jinvalid;
2351 if ((cp = nexttoken(x)) != NULL) {
2352 char buf[3];
2353 int sign = 1;
2355 switch (*cp) {
2356 case '+':
2357 sign = -1;
2358 /* FALLTHRU */
2359 case '-':
2360 ++cp;
2361 break;
2363 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2364 digitchar(cp[3])) {
2365 si64_t tadj;
2367 buf[2] = '\0';
2368 buf[0] = cp[0];
2369 buf[1] = cp[1];
2370 n_idec_si32_cp(&i, buf, 10, NULL);
2371 tadj = (si64_t)i * 3600; /* XXX */
2372 buf[0] = cp[2];
2373 buf[1] = cp[3];
2374 n_idec_si32_cp(&i, buf, 10, NULL);
2375 tadj += (si64_t)i * 60; /* XXX */
2376 if (sign < 0)
2377 tadj = -tadj;
2378 t += (time_t)tadj;
2380 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2381 * TODO once again, Christos Zoulas and NetBSD Mail have done
2382 * TODO a really good job already, but using strptime(3), which
2383 * TODO is not portable. Nonetheless, WE must improve, not
2384 * TODO at last because we simply ignore obsolete timezones!!
2385 * TODO See RFC 5322, 4.3! */
2387 jleave:
2388 NYD2_LEAVE;
2389 return t;
2390 jinvalid:
2391 t = 0;
2392 goto jleave;
2395 FL time_t
2396 combinetime(int year, int month, int day, int hour, int minute, int second){
2397 size_t const jdn_epoch = 2440588;
2398 bool_t const y2038p = (sizeof(time_t) == 4);
2400 size_t jdn;
2401 time_t t;
2402 NYD2_ENTER;
2404 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2405 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2406 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2407 day < 1 || day > 31 ||
2408 month < 1 || month > 12 ||
2409 year < 1970)
2410 goto jerr;
2412 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2413 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2414 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2415 * test by stepping second-wise around the flip. Don't care otherwise */
2416 if(!y2038p)
2417 goto jerr;
2418 if(year > 2038 || month > 1 || day > 19 ||
2419 hour > 3 || minute > 14 || second > 7)
2420 goto jerr;
2423 t = second;
2424 t += minute * n_DATE_SECSMIN;
2425 t += hour * n_DATE_SECSHOUR;
2427 jdn = a_head_gregorian_to_jdn(year, month, day);
2428 jdn -= jdn_epoch;
2429 t += (time_t)jdn * n_DATE_SECSDAY;
2430 jleave:
2431 NYD2_LEAVE;
2432 return t;
2433 jerr:
2434 t = (time_t)-1;
2435 goto jleave;
2438 FL void
2439 substdate(struct message *m)
2441 char const *cp;
2442 NYD_ENTER;
2444 /* Determine the date to print in faked 'From ' lines. This is traditionally
2445 * the date the message was written to the mail file. Try to determine this
2446 * using RFC message header fields, or fall back to current time */
2447 m->m_time = 0;
2448 if ((cp = hfield1("received", m)) != NULL) {
2449 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2451 ++cp;
2452 while (alnumchar(*cp));
2454 if (cp && *++cp)
2455 m->m_time = rfctime(cp);
2457 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2458 if ((cp = hfield1("date", m)) != NULL)
2459 m->m_time = rfctime(cp);
2461 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2462 m->m_time = time_current.tc_time;
2463 NYD_LEAVE;
2466 FL void
2467 setup_from_and_sender(struct header *hp)
2469 char const *addr;
2470 struct name *np;
2471 NYD_ENTER;
2473 /* If -t parsed or composed From: then take it. With -t we otherwise
2474 * want -r to be honoured in favour of *from* in order to have
2475 * a behaviour that is compatible with what users would expect from e.g.
2476 * postfix(1) */
2477 if ((np = hp->h_from) != NULL ||
2478 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2480 } else if ((addr = myaddrs(hp)) != NULL)
2481 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2482 hp->h_from = np;
2484 if ((np = hp->h_sender) != NULL) {
2486 } else if ((addr = ok_vlook(sender)) != NULL)
2487 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2488 hp->h_sender = np;
2490 NYD_LEAVE;
2493 FL struct name const *
2494 check_from_and_sender(struct name const *fromfield,
2495 struct name const *senderfield)
2497 struct name const *rv = NULL;
2498 NYD_ENTER;
2500 if (senderfield != NULL) {
2501 if (senderfield->n_flink != NULL) {
2502 n_err(_("The Sender: field may contain only one address\n"));
2503 goto jleave;
2505 rv = senderfield;
2508 if (fromfield != NULL) {
2509 if (fromfield->n_flink != NULL && senderfield == NULL) {
2510 n_err(_("A Sender: is required when there are multiple "
2511 "addresses in From:\n"));
2512 goto jleave;
2514 if (rv == NULL)
2515 rv = fromfield;
2518 if (rv == NULL)
2519 rv = (struct name*)0x1;
2520 jleave:
2521 NYD_LEAVE;
2522 return rv;
2525 #ifdef HAVE_XSSL
2526 FL char *
2527 getsender(struct message *mp)
2529 char *cp;
2530 struct name *np;
2531 NYD_ENTER;
2533 if ((cp = hfield1("from", mp)) == NULL ||
2534 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2535 cp = NULL;
2536 else
2537 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2538 NYD_LEAVE;
2539 return cp;
2541 #endif
2543 FL int
2544 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2545 int subjfirst)
2547 /* TODO grab_headers: again, check counts etc. against RFC;
2548 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2549 int errs;
2550 int volatile comma;
2551 NYD_ENTER;
2553 errs = 0;
2554 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2556 if (gflags & GTO)
2557 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2558 if (subjfirst && (gflags & GSUBJECT))
2559 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2560 if (gflags & GCC)
2561 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2562 if (gflags & GBCC)
2563 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2565 if (gflags & GEXTRA) {
2566 if (hp->h_from == NULL)
2567 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2568 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2569 GEXTRA | GFULL | GFULLEXTRA);
2570 if (hp->h_replyto == NULL)
2571 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
2572 hp->h_replyto = grab_names(gif, "Reply-To: ", hp->h_replyto, comma,
2573 GEXTRA | GFULL);
2574 if (hp->h_sender == NULL)
2575 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2576 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2577 GEXTRA | GFULL);
2580 if (!subjfirst && (gflags & GSUBJECT))
2581 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2583 NYD_LEAVE;
2584 return errs;
2587 FL bool_t
2588 header_match(struct message *mp, struct search_expr const *sep)
2590 struct str in, out;
2591 FILE *ibuf;
2592 int lc;
2593 size_t linesize = 0; /* TODO line pool */
2594 char *linebuf = NULL, *colon;
2595 bool_t rv = FAL0;
2596 NYD_ENTER;
2598 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2599 goto jleave;
2600 if ((lc = mp->m_lines - 1) < 0)
2601 goto jleave;
2603 if ((mp->m_flag & MNOFROM) == 0 &&
2604 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2605 goto jleave;
2606 while (lc > 0) {
2607 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2608 break;
2609 if (blankchar(*++colon))
2610 ++colon;
2611 in.l = strlen(in.s = colon);
2612 mime_fromhdr(&in, &out, TD_ICONV);
2613 #ifdef HAVE_REGEX
2614 if (sep->ss_sexpr == NULL)
2615 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2616 else
2617 #endif
2618 rv = substr(out.s, sep->ss_sexpr);
2619 free(out.s);
2620 if (rv)
2621 break;
2624 jleave:
2625 if (linebuf != NULL)
2626 free(linebuf);
2627 NYD_LEAVE;
2628 return rv;
2631 FL struct n_header_field *
2632 n_customhdr_query(void){
2633 char const *vp;
2634 struct n_header_field *rv, **tail, *hfp;
2635 NYD_ENTER;
2637 rv = NULL;
2639 if((vp = ok_vlook(customhdr)) != NULL){
2640 char *buf;
2642 tail = &rv;
2643 buf = savestr(vp);
2644 jch_outer:
2645 while((vp = a_head_customhdr__sep(&buf)) != NULL){
2646 ui32_t nl, bl;
2647 char const *nstart, *cp;
2649 for(nstart = cp = vp;; ++cp){
2650 if(fieldnamechar(*cp))
2651 continue;
2652 if(*cp == '\0'){
2653 if(cp == nstart){
2654 n_err(_("Invalid nameless *customhdr* entry\n"));
2655 goto jch_outer;
2657 }else if(*cp != ':' && !blankchar(*cp)){
2658 jch_badent:
2659 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2660 goto jch_outer;
2662 break;
2664 nl = (ui32_t)PTR2SIZE(cp - nstart);
2666 while(blankchar(*cp))
2667 ++cp;
2668 if(*cp++ != ':')
2669 goto jch_badent;
2670 while(blankchar(*cp))
2671 ++cp;
2672 bl = (ui32_t)strlen(cp) +1;
2674 *tail =
2675 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2676 nl +1 + bl);
2677 tail = &hfp->hf_next;
2678 hfp->hf_next = NULL;
2679 hfp->hf_nl = nl;
2680 hfp->hf_bl = bl - 1;
2681 memcpy(hfp->hf_dat, nstart, nl);
2682 hfp->hf_dat[nl++] = '\0';
2683 memcpy(hfp->hf_dat + nl, cp, bl);
2686 NYD_LEAVE;
2687 return rv;
2690 /* s-it-mode */