cc-test.sh: t_behave_iconv_mainbody() should compile test instead, sigh!
[s-mailx.git] / head.c
blob0db90d32d594081ba83df530a5918dbcd7c3d998
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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE head
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 struct cmatch_data {
44 size_t tlen; /* Length of .tdata */
45 char const *tdata; /* Template date - see _cmatch_data[] */
48 /* Template characters for cmatch_data.tdata:
49 * 'A' An upper case char
50 * 'a' A lower case char
51 * ' ' A space
52 * '0' A digit
53 * 'O' An optional digit or space
54 * ':' A colon
55 * '+' Either a plus or a minus sign */
56 static struct cmatch_data const _cmatch_data[] = {
57 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
58 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
59 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
60 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
61 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
62 * in the wild (by UW-imap) */
63 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
64 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
65 * zone strings; note that 1. is strictly speaking not correct as some
66 * letters are not used, and 2. is not because only "UT" is defined */
67 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
68 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
69 { 0, NULL }
71 #define a_HEAD_DATE_MINLEN 21
73 /* Skip over "word" as found in From_ line */
74 static char const * _from__skipword(char const *wp);
76 /* Match the date string against the date template (tp), return if match.
77 * See _cmatch_data[] for template character description */
78 static int _cmatch(size_t len, char const *date,
79 char const *tp);
81 /* Check whether date is a valid 'From_' date.
82 * (Rather ctime(3) generated dates, according to RFC 4155) */
83 static int _is_date(char const *date);
85 /* JulianDayNumber converter(s) */
86 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
87 #if 0
88 static void a_head_jdn_to_gregorian(size_t jdn,
89 ui32_t *yp, ui32_t *mp, ui32_t *dp);
90 #endif
92 /* Convert the domain part of a skinned address to IDNA.
93 * If an error occurs before Unicode information is available, revert the IDNA
94 * error to a normal CHAR one so that the error message doesn't talk Unicode */
95 #ifdef HAVE_IDNA
96 static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp);
97 #endif
99 /* Classify and check a (possibly skinned) header body according to RFC
100 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
101 * also a file or a pipe command, so check that first, then.
102 * Otherwise perform content checking and isolate the domain part (for IDNA) */
103 static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned);
105 /* Return the next header field found in the given message.
106 * Return >= 0 if something found, < 0 elsewise.
107 * "colon" is set to point to the colon in the header.
108 * Must deal with \ continuations & other such fraud */
109 static long a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem,
110 char **colon);
112 static int msgidnextc(char const **cp, int *status);
114 /* Count the occurances of c in str */
115 static int charcount(char *str, int c);
117 static char const * nexttoken(char const *cp);
119 static char const *
120 _from__skipword(char const *wp)
122 char c = 0;
123 NYD2_ENTER;
125 if (wp != NULL) {
126 while ((c = *wp++) != '\0' && !blankchar(c)) {
127 if (c == '"') {
128 while ((c = *wp++) != '\0' && c != '"')
130 if (c != '"')
131 --wp;
134 for (; blankchar(c); c = *wp++)
137 NYD2_LEAVE;
138 return (c == 0 ? NULL : wp - 1);
141 static int
142 _cmatch(size_t len, char const *date, char const *tp)
144 int ret = 0;
145 NYD2_ENTER;
147 while (len--) {
148 char c = date[len];
149 switch (tp[len]) {
150 case 'a':
151 if (!lowerchar(c))
152 goto jleave;
153 break;
154 case 'A':
155 if (!upperchar(c))
156 goto jleave;
157 break;
158 case ' ':
159 if (c != ' ')
160 goto jleave;
161 break;
162 case '0':
163 if (!digitchar(c))
164 goto jleave;
165 break;
166 case 'O':
167 if (c != ' ' && !digitchar(c))
168 goto jleave;
169 break;
170 case ':':
171 if (c != ':')
172 goto jleave;
173 break;
174 case '+':
175 if (c != '+' && c != '-')
176 goto jleave;
177 break;
180 ret = 1;
181 jleave:
182 NYD2_LEAVE;
183 return ret;
186 static int
187 _is_date(char const *date)
189 struct cmatch_data const *cmdp;
190 size_t dl;
191 int rv = 0;
192 NYD2_ENTER;
194 if ((dl = strlen(date)) >= a_HEAD_DATE_MINLEN)
195 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
196 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
197 break;
198 NYD2_LEAVE;
199 return rv;
202 static size_t
203 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
204 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
205 * (via third hand, plus adjustments).
206 * This algorithm is supposed to work for all dates in between 1582-10-15
207 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
208 size_t jdn;
209 NYD2_ENTER;
211 #if 0
212 if(y == 0)
213 y = 1;
214 if(m == 0)
215 m = 1;
216 if(d == 0)
217 d = 1;
218 #endif
220 if(m > 2)
221 m -= 3;
222 else{
223 m += 9;
224 --y;
226 jdn = y;
227 jdn /= 100;
228 y -= 100 * jdn;
229 y *= 1461;
230 y >>= 2;
231 jdn *= 146097;
232 jdn >>= 2;
233 jdn += y;
234 jdn += d;
235 jdn += 1721119;
236 m *= 153;
237 m += 2;
238 m /= 5;
239 jdn += m;
240 NYD2_LEAVE;
241 return jdn;
244 #if 0
245 static void
246 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
247 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
248 * (via third hand, plus adjustments) */
249 size_t y, x;
250 NYD2_ENTER;
252 jdn -= 1721119;
253 jdn <<= 2;
254 --jdn;
255 y = jdn / 146097;
256 jdn %= 146097;
257 jdn |= 3;
258 y *= 100;
259 y += jdn / 1461;
260 jdn %= 1461;
261 jdn += 4;
262 jdn >>= 2;
263 x = jdn;
264 jdn <<= 2;
265 jdn += x;
266 jdn -= 3;
267 x = jdn / 153; /* x -> month */
268 jdn %= 153;
269 jdn += 5;
270 jdn /= 5; /* jdn -> day */
271 if(x < 10)
272 x += 3;
273 else{
274 x -= 9;
275 ++y;
278 *yp = (ui32_t)(y & 0xFFFF);
279 *mp = (ui32_t)(x & 0xFF);
280 *dp = (ui32_t)(jdn & 0xFF);
281 NYD2_LEAVE;
283 #endif /* 0 */
285 #ifdef HAVE_IDNA
286 static struct n_addrguts *
287 a_head_idna_apply(struct n_addrguts *agp){
288 struct n_string idna_ascii;
289 NYD_ENTER;
291 n_string_creat_auto(&idna_ascii);
293 if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
294 agp->ag_slen - agp->ag_sdom_start))
295 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
296 else{
297 /* Replace the domain part of .ag_skinned with IDNA version */
298 n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
300 agp->ag_skinned = n_string_cp(&idna_ascii);
301 agp->ag_slen = idna_ascii.s_len;
302 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
303 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
305 NYD_LEAVE;
306 return agp;
308 #endif /* HAVE_IDNA */
310 static bool_t
311 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned)
313 char *addr, *p;
314 bool_t in_quote;
315 ui8_t in_domain, hadat;
316 union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
317 #ifdef HAVE_IDNA
318 ui8_t use_idna;
319 #endif
320 NYD_ENTER;
322 #ifdef HAVE_IDNA
323 use_idna = ok_blook(idna_disable) ? 0 : 1;
324 #endif
325 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
327 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
328 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
329 goto jleave;
332 addr = agp->ag_skinned;
334 /* If the field is not a recipient, it cannot be a file or a pipe */
335 if (!skinned)
336 goto jaddr_check;
338 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
339 * grep the latter for the complete picture */
340 if (*addr == '|') {
341 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
342 goto jleave;
344 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
345 (addr[0] == '-' && addr[1] == '\0'))
346 goto jisfile;
347 if (memchr(addr, '@', agp->ag_slen) == NULL) {
348 if (*addr == '+')
349 goto jisfile;
350 for (p = addr; (c.c = *p); ++p) {
351 if (c.c == '!' || c.c == '%')
352 break;
353 if (c.c == '/') {
354 jisfile:
355 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
356 goto jleave;
361 jaddr_check:
362 /* TODO This is false. If super correct this should work on wide
363 * TODO characters, just in case (some bytes of) the ASCII set is (are)
364 * TODO shared; it may yet tear apart multibyte sequences, possibly.
365 * TODO All this should interact with mime_enc_mustquote(), too!
366 * TODO That is: once this is an object, we need to do this in a way
367 * TODO that it is valid for the wire format (instead)! */
368 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
369 * TODO Note this correlats with addrspec_with_guts() which is in front
370 * TODO of us and encapsulates (what it thinks is, sigh) the address
371 * TODO boundary. ALL THIS should be one object that knows how to deal */
372 in_quote = FAL0;
373 in_domain = hadat = 0;
375 for (p = addr; (c.c = *p++) != '\0';) {
376 if (c.c == '"') {
377 in_quote = !in_quote;
378 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
379 #ifdef HAVE_IDNA
380 if (in_domain && use_idna > 0) {
381 if (use_idna == 1)
382 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
383 c.u);
384 use_idna = 2;
385 } else
386 #endif
387 break;
388 } else if (in_domain == 2) {
389 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
390 break;
391 } else if (in_quote && in_domain == 0) {
392 /*EMPTY*/;
393 } else if (c.c == '\\' && *p != '\0') {
394 ++p;
395 } else if (c.c == '@') {
396 if (hadat++ > 0) {
397 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
398 c.u);
399 goto jleave;
401 agp->ag_sdom_start = PTR2SIZE(p - addr);
402 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
403 in_domain = (*p == '[') ? 2 : 1;
404 continue;
405 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
406 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
407 c.c == '\\' || c.c == ',' || blankchar(c.c))
408 break;
409 hadat = 0;
411 if (c.c != '\0') {
412 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
413 goto jleave;
416 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
417 /* TODO This may be an UUCP address */
418 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
419 if(!n_alias_is_valid_name(agp->ag_input))
420 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
421 }else{
422 /* If we seem to know that this is an address. Ensure this is correct
423 * according to RFC 5322 TODO the entire address parser should be like
424 * TODO that for one, and then we should know whether structured or
425 * TODO unstructured, and just parse correctly overall!
426 * TODO In addition, this can be optimised a lot.
427 * TODO And it is far from perfect: it should not forget whether no
428 * TODO whitespace followed some snippet, and it was written hastily.
429 * TODO It is even wrong sometimes. Not only for strange cases */
430 struct a_token{
431 struct a_token *t_last;
432 struct a_token *t_next;
433 enum{
434 a_T_TATOM = 1u<<0,
435 a_T_TCOMM = 1u<<1,
436 a_T_TQUOTE = 1u<<2,
437 a_T_TADDR = 1u<<3,
438 a_T_TMASK = (1u<<4) - 1,
440 a_T_SPECIAL = 1u<<8 /* An atom actually needs to go TQUOTE */
441 } t_f;
442 ui8_t t__pad[4];
443 size_t t_start;
444 size_t t_end;
445 } *thead, *tcurr, *tp;
447 struct n_string ost, *ostp;
448 char const *cp, *cp1st, *cpmax, *xp;
449 void *lofi_snap;
451 /* Name and domain must be non-empty */
452 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
453 c.c = '@';
454 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
455 goto jleave;
458 #ifdef HAVE_IDNA
459 if(use_idna == 2)
460 agp = a_head_idna_apply(agp);
461 #endif
463 cp = agp->ag_input;
465 /* Nothing to do if there is only an address (in angle brackets) */
466 /* TODO This is wrong since we allow invalid constructs in local-part
467 * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
468 * TODO "abcd"@abc. Etc. */
469 if(agp->ag_iaddr_start == 0){
470 if(agp->ag_iaddr_aend == agp->ag_ilen)
471 goto jleave;
472 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
473 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
474 cp[agp->ag_iaddr_aend] == '>')
475 goto jleave;
477 /* It is not, so parse off all tokens, then resort and rejoin */
478 lofi_snap = n_lofi_snap_create();
480 cp1st = cp;
481 if((c.ui32 = agp->ag_iaddr_start) > 0)
482 --c.ui32;
483 cpmax = &cp[c.ui32];
485 thead = tcurr = NULL;
486 hadat = FAL0;
487 jnode_redo:
488 for(tp = NULL; cp < cpmax;){
489 switch((c.c = *cp)){
490 case '(':
491 if(tp != NULL)
492 tp->t_end = PTR2SIZE(cp - cp1st);
493 tp = n_lofi_alloc(sizeof *tp);
494 tp->t_next = NULL;
495 if((tp->t_last = tcurr) != NULL)
496 tcurr->t_next = tp;
497 else
498 thead = tp;
499 tcurr = tp;
500 tp->t_f = a_T_TCOMM;
501 tp->t_start = PTR2SIZE(++cp - cp1st);
502 xp = skip_comment(cp);
503 tp->t_end = PTR2SIZE(xp - cp1st);
504 cp = xp;
505 if(tp->t_end > tp->t_start){
506 if(xp[-1] == ')')
507 --tp->t_end;
508 else{
509 /* No closing comment - strip trailing whitespace */
510 while(blankchar(*--xp))
511 if(--tp->t_end == tp->t_start)
512 break;
515 tp = NULL;
516 break;
518 case '"':
519 if(tp != NULL)
520 tp->t_end = PTR2SIZE(cp - cp1st);
521 tp = n_lofi_alloc(sizeof *tp);
522 tp->t_next = NULL;
523 if((tp->t_last = tcurr) != NULL)
524 tcurr->t_next = tp;
525 else
526 thead = tp;
527 tcurr = tp;
528 tp->t_f = a_T_TQUOTE;
529 tp->t_start = PTR2SIZE(++cp - cp1st);
530 for(xp = cp; xp < cpmax; ++xp){
531 if((c.c = *xp) == '"')
532 break;
533 if(c.c == '\\' && xp[1] != '\0')
534 ++xp;
536 tp->t_end = PTR2SIZE(xp - cp1st);
537 cp = &xp[1];
538 if(tp->t_end > tp->t_start){
539 /* No closing quote - strip trailing whitespace */
540 if(*xp != '"'){
541 while(blankchar(*xp--))
542 if(--tp->t_end == tp->t_start)
543 break;
546 tp = NULL;
547 break;
549 default:
550 if(blankchar(c.c)){
551 if(tp != NULL)
552 tp->t_end = PTR2SIZE(cp - cp1st);
553 tp = NULL;
554 ++cp;
555 break;
558 if(tp == NULL){
559 tp = n_lofi_alloc(sizeof *tp);
560 tp->t_next = NULL;
561 if((tp->t_last = tcurr) != NULL)
562 tcurr->t_next = tp;
563 else
564 thead = tp;
565 tcurr = tp;
566 tp->t_f = a_T_TATOM;
567 tp->t_start = PTR2SIZE(cp - cp1st);
569 ++cp;
571 /* Reverse solidus transforms the following into a quoted-pair, and
572 * therefore (must occur in comment or quoted-string only) the
573 * entire atom into a quoted string */
574 if(c.c == '\\'){
575 tp->t_f |= a_T_SPECIAL;
576 if(cp < cpmax)
577 ++cp;
578 break;
581 /* Is this plain RFC 5322 "atext", or "specials"?
582 * TODO Because we don't know structured/unstructured, nor anything
583 * TODO else, we need to treat "dot-atom" as being identical to
584 * TODO "specials".
585 * However, if the 8th bit is set, this will be RFC 2047 converted
586 * and the entire sequence is skipped */
587 if(!(c.u & 0x80) && !alnumchar(c.c) &&
588 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
589 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
590 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
591 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
592 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
593 tp->t_f |= a_T_SPECIAL;
594 break;
597 if(tp != NULL)
598 tp->t_end = PTR2SIZE(cp - cp1st);
600 if(hadat == FAL0){
601 hadat = TRU1;
602 /* The local-part may be in quotes.. */
603 if((tp = tcurr) != NULL && (tp->t_f & a_T_TQUOTE) &&
604 tp->t_end == agp->ag_iaddr_start - 1){
605 /* ..so backward extend it, including the starting quote */
606 /* TODO This is false and the code below #if 0 away. We would
607 * TODO need to create a properly quoted local-part HERE AND NOW
608 * TODO and REPLACE the original data with that version, but the
609 * TODO current code cannot do that. The node needs the data,
610 * TODO not only offsets for that, for example. If we had all that
611 * TODO the code below could produce a really valid thing */
612 if(tp->t_start > 0)
613 --tp->t_start;
614 if(tp->t_start > 0 &&
615 (tp->t_last == NULL || tp->t_last->t_end < tp->t_start) &&
616 agp->ag_input[tp->t_start - 1] == '\\')
617 --tp->t_start;
618 tp->t_f = a_T_TADDR | a_T_SPECIAL;
619 }else{
620 tp = n_lofi_alloc(sizeof *tp);
621 tp->t_next = NULL;
622 if((tp->t_last = tcurr) != NULL)
623 tcurr->t_next = tp;
624 else
625 thead = tp;
626 tcurr = tp;
627 tp->t_f = a_T_TADDR;
628 tp->t_start = agp->ag_iaddr_start;
629 /* TODO Very special case because of our hacky non-object-based and
630 * TODO non-compliant address parser. Note */
631 if(tp->t_last == NULL && tp->t_start > 0)
632 tp->t_start = 0;
633 if(agp->ag_input[tp->t_start] == '<')
634 ++tp->t_start;
636 /* TODO Very special check for whether we need to massage the
637 * TODO local part. This is wrong, but otherwise even more so */
638 #if 0
639 cp = &agp->ag_input[tp->t_start];
640 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
641 while(cp < cpmax){
642 c.c = *cp++;
643 if(!(c.u & 0x80) && !alnumchar(c.c) &&
644 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
645 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
646 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
647 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
648 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~'){
649 tp->t_f |= a_T_SPECIAL;
650 break;
653 #endif
655 tp->t_end = agp->ag_iaddr_aend;
656 assert(tp->t_start <= tp->t_end);
657 tp = NULL;
659 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
660 cpmax = &agp->ag_input[agp->ag_ilen];
661 if(cp < cpmax)
662 goto jnode_redo;
665 /* Nothing may follow the address, move it to the end */
666 if(!(tcurr->t_f & a_T_TADDR)){
667 for(tp = thead; tp != NULL; tp = tp->t_next){
668 if(tp->t_f & a_T_TADDR){
669 if(tp->t_last != NULL)
670 tp->t_last->t_next = tp->t_next;
671 else
672 thead = tp->t_next;
673 if(tp->t_next != NULL)
674 tp->t_next->t_last = tp->t_last;
676 tcurr = tp;
677 while(tp->t_next != NULL)
678 tp = tp->t_next;
679 tp->t_next = tcurr;
680 tcurr->t_last = tp;
681 tcurr->t_next = NULL;
682 break;
687 /* Make ranges contiguous: ensure a continuous range of atoms is converted
688 * to a SPECIAL one if at least one of them requires it */
689 for(tp = thead; tp != NULL; tp = tp->t_next){
690 if(tp->t_f & a_T_SPECIAL){
691 tcurr = tp;
692 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
693 tp->t_f |= a_T_SPECIAL;
694 tp = tcurr;
695 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
696 tp->t_f |= a_T_SPECIAL;
697 if(tp == NULL)
698 break;
702 /* And yes, we want quotes to extend as much as possible */
703 for(tp = thead; tp != NULL; tp = tp->t_next){
704 if(tp->t_f & a_T_TQUOTE){
705 tcurr = tp;
706 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
707 tp->t_f |= a_T_SPECIAL;
708 tp = tcurr;
709 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
710 tp->t_f |= a_T_SPECIAL;
711 if(tp == NULL)
712 break;
716 /* Then rejoin */
717 ostp = n_string_creat_auto(&ost);
718 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
719 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
721 for(tcurr = thead; tcurr != NULL;){
722 if(tcurr != thead)
723 ostp = n_string_push_c(ostp, ' ');
724 if(tcurr->t_f & a_T_TADDR){
725 if(tcurr->t_last != NULL)
726 ostp = n_string_push_c(ostp, '<');
727 agp->ag_iaddr_start = ostp->s_len;
728 /* Now it is terrible to say, but if that thing contained
729 * quotes, then those may contain quoted-pairs! */
730 #if 0
731 if(!(tcurr->t_f & a_T_SPECIAL)){
732 #endif
733 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
734 (tcurr->t_end - tcurr->t_start));
735 #if 0
736 }else{
737 bool_t quot, esc;
739 ostp = n_string_push_c(ostp, '"');
740 quot = TRU1;
742 cp = &cp1st[tcurr->t_start];
743 cpmax = &cp1st[tcurr->t_end];
744 for(esc = FAL0; cp < cpmax;){
745 if((c.c = *cp++) == '\\' && !esc){
746 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
747 esc = TRU1;
748 }else{
749 if(esc || c.c == '"')
750 ostp = n_string_push_c(ostp, '\\');
751 else if(c.c == '@'){
752 ostp = n_string_push_c(ostp, '"');
753 quot = FAL0;
755 ostp = n_string_push_c(ostp, c.c);
756 esc = FAL0;
760 #endif
761 agp->ag_iaddr_aend = ostp->s_len;
763 if(tcurr->t_last != NULL)
764 ostp = n_string_push_c(ostp, '>');
765 tcurr = tcurr->t_next;
766 }else if(tcurr->t_f & a_T_TCOMM){
767 ostp = n_string_push_c(ostp, '(');
768 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
769 (tcurr->t_end - tcurr->t_start));
770 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
771 tcurr = tp;
772 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
773 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
774 (tcurr->t_end - tcurr->t_start));
776 ostp = n_string_push_c(ostp, ')');
777 tcurr = tcurr->t_next;
778 }else if(tcurr->t_f & a_T_TQUOTE){
779 jput_quote:
780 ostp = n_string_push_c(ostp, '"');
781 tp = tcurr;
782 do/* while tcurr && TATOM||TQUOTE */{
783 cp = &cp1st[tcurr->t_start];
784 cpmax = &cp1st[tcurr->t_end];
785 if(cp == cpmax)
786 continue;
788 if(tcurr != tp)
789 ostp = n_string_push_c(ostp, ' ');
791 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
792 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
793 else{
794 bool_t esc;
796 for(esc = FAL0; cp < cpmax;){
797 if((c.c = *cp++) == '\\' && !esc){
798 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
799 esc = TRU1;
800 }else{
801 if(esc || c.c == '"'){
802 jput_quote_esc:
803 ostp = n_string_push_c(ostp, '\\');
805 ostp = n_string_push_c(ostp, c.c);
806 esc = FAL0;
809 if(esc){
810 c.c = '\\';
811 goto jput_quote_esc;
814 }while((tcurr = tcurr->t_next) != NULL &&
815 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
816 ostp = n_string_push_c(ostp, '"');
817 }else if(tcurr->t_f & a_T_SPECIAL)
818 goto jput_quote;
819 else{
820 /* Can we use a fast join mode? */
821 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
822 if(!(tcurr->t_f & a_T_TATOM))
823 break;
824 if(tcurr != tp)
825 ostp = n_string_push_c(ostp, ' ');
826 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
827 (tcurr->t_end - tcurr->t_start));
832 n_lofi_snap_unroll(lofi_snap);
834 agp->ag_input = n_string_cp(ostp);
835 agp->ag_ilen = ostp->s_len;
836 /*ostp = n_string_drop_ownership(ostp);*/
838 /* Name and domain must be non-empty, the second */
839 cp = &agp->ag_input[agp->ag_iaddr_start];
840 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
841 if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
842 c.c = '@';
843 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
844 goto jleave;
847 agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
849 /* TODO This parser is a mess. We do not know whether this is truly
850 * TODO valid, and all our checks are not truly RFC conforming.
851 * TODO Do check the skinned thing by itself once more, in order
852 * TODO to catch problems from reordering, e.g., this additional
853 * TODO test catches a final address without AT..
854 * TODO This is a plain copy+paste of the weird thing above, no care */
855 agp->ag_n_flags &= ~NAME_ADDRSPEC_ISADDR;
856 in_domain = hadat = 0;
857 for (p = addr; (c.c = *p++) != '\0';) {
858 if (c.c == '"') {
859 in_quote = !in_quote;
860 } else if (c.u < 040 || c.u >= 0177) {
861 break;
862 } else if (in_domain == 2) {
863 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
864 break;
865 } else if (in_quote && in_domain == 0) {
866 /*EMPTY*/;
867 } else if (c.c == '\\' && *p != '\0') {
868 ++p;
869 } else if (c.c == '@') {
870 if (hadat++ > 0) {
871 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
872 c.u);
873 goto jleave;
875 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
876 in_domain = (*p == '[') ? 2 : 1;
877 continue;
878 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
879 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
880 c.c == '\\' || c.c == ',' || blankchar(c.c))
881 break;
882 hadat = 0;
884 if(c.c != '\0')
885 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
886 else if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
887 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
888 p[-1]);
890 jleave:
891 NYD_LEAVE;
892 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) == 0);
895 static long
896 a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem, char **colon)
898 char *line2 = NULL, *cp, *cp2;
899 size_t line2size = 0;
900 int c, isenc;
901 NYD2_ENTER;
903 if (*linebuf == NULL)
904 *linebuf = srealloc(*linebuf, *linesize = 1);
905 **linebuf = '\0';
906 for (;;) {
907 if (--rem < 0) {
908 rem = -1;
909 break;
911 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
912 rem = -1;
913 break;
915 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
917 if (cp > *linebuf)
918 while (blankchar(*cp))
919 ++cp;
920 if (*cp != ':' || cp == *linebuf)
921 continue;
923 /* I guess we got a headline. Handle wraparound */
924 *colon = cp;
925 cp = *linebuf + c;
926 for (;;) {
927 isenc = 0;
928 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
930 cp++;
931 if (rem <= 0)
932 break;
933 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
934 isenc |= 1;
935 ungetc(c = getc(f), f);
936 if (!blankchar(c))
937 break;
938 c = readline_restart(f, &line2, &line2size, 0); /* TODO linepool! */
939 if (c < 0)
940 break;
941 --rem;
942 for (cp2 = line2; blankchar(*cp2); ++cp2)
944 c -= (int)PTR2SIZE(cp2 - line2);
945 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
946 isenc |= 2;
947 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
948 size_t diff = PTR2SIZE(cp - *linebuf),
949 colondiff = PTR2SIZE(*colon - *linebuf);
950 *linebuf = srealloc(*linebuf, *linesize += c + 2);
951 cp = &(*linebuf)[diff];
952 *colon = &(*linebuf)[colondiff];
954 if (isenc != 3)
955 *cp++ = ' ';
956 memcpy(cp, cp2, c);
957 cp += c;
959 *cp = '\0';
961 if (line2 != NULL)
962 free(line2);
963 break;
965 NYD2_LEAVE;
966 return rem;
969 static int
970 msgidnextc(char const **cp, int *status)
972 int c;
973 NYD2_ENTER;
975 assert(cp != NULL);
976 assert(*cp != NULL);
977 assert(status != NULL);
979 for (;;) {
980 if (*status & 01) {
981 if (**cp == '"') {
982 *status &= ~01;
983 (*cp)++;
984 continue;
986 if (**cp == '\\') {
987 (*cp)++;
988 if (**cp == '\0')
989 goto jeof;
991 goto jdfl;
993 switch (**cp) {
994 case '(':
995 *cp = skip_comment(&(*cp)[1]);
996 continue;
997 case '>':
998 case '\0':
999 jeof:
1000 c = '\0';
1001 goto jleave;
1002 case '"':
1003 (*cp)++;
1004 *status |= 01;
1005 continue;
1006 case '@':
1007 *status |= 02;
1008 /*FALLTHRU*/
1009 default:
1010 jdfl:
1011 c = *(*cp)++ & 0377;
1012 c = (*status & 02) ? lowerconv(c) : c;
1013 goto jleave;
1016 jleave:
1017 NYD2_LEAVE;
1018 return c;
1021 static int
1022 charcount(char *str, int c)
1024 char *cp;
1025 int i;
1026 NYD2_ENTER;
1028 for (i = 0, cp = str; *cp; ++cp)
1029 if (*cp == c)
1030 ++i;
1031 NYD2_LEAVE;
1032 return i;
1035 static char const *
1036 nexttoken(char const *cp)
1038 NYD2_ENTER;
1039 for (;;) {
1040 if (*cp == '\0') {
1041 cp = NULL;
1042 break;
1045 if (*cp == '(') {
1046 size_t nesting = 1;
1048 do switch (*++cp) {
1049 case '(':
1050 ++nesting;
1051 break;
1052 case ')':
1053 --nesting;
1054 break;
1055 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
1056 } else if (blankchar(*cp) || *cp == ',')
1057 ++cp;
1058 else
1059 break;
1061 NYD2_LEAVE;
1062 return cp;
1065 FL char const *
1066 myaddrs(struct header *hp) /* TODO */
1068 struct name *np;
1069 char const *rv, *mta;
1070 NYD_ENTER;
1072 if (hp != NULL && (np = hp->h_from) != NULL) {
1073 if ((rv = np->n_fullname) != NULL)
1074 goto jleave;
1075 if ((rv = np->n_name) != NULL)
1076 goto jleave;
1079 if((rv = ok_vlook(from)) != NULL){
1080 if((np = lextract(rv, GEXTRA | GFULL)) == NULL)
1081 jefrom:
1082 n_err(_("An address given in *from* is invalid: %s\n"), rv);
1083 else for(; np != NULL; np = np->n_flink)
1084 if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1085 goto jefrom;
1086 goto jleave;
1089 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1090 * undeterminable From: address. However, if the user sets *hostname*,
1091 * accept his desire */
1092 if (ok_vlook(hostname) != NULL)
1093 goto jnodename;
1094 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
1095 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1096 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
1097 goto jnodename;
1098 jleave:
1099 NYD_LEAVE;
1100 return rv;
1102 jnodename:{
1103 char *cp;
1104 char const *hn, *ln;
1105 size_t i;
1107 hn = n_nodename(TRU1);
1108 ln = ok_vlook(LOGNAME);
1109 i = strlen(ln) + strlen(hn) + 1 +1;
1110 rv = cp = salloc(i);
1111 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1113 goto jleave;
1116 FL char const *
1117 myorigin(struct header *hp) /* TODO */
1119 char const *rv = NULL, *ccp;
1120 struct name *np;
1121 NYD_ENTER;
1123 if((ccp = myaddrs(hp)) != NULL &&
1124 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1125 if(np->n_flink == NULL)
1126 rv = ccp;
1127 else if((ccp = ok_vlook(sender)) != NULL) {
1128 if((np = lextract(ccp, GEXTRA | GFULL)) == NULL ||
1129 np->n_flink != NULL ||
1130 is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1131 n_err(_("The address given in *sender* is invalid: %s\n"), ccp);
1132 else
1133 rv = ccp;
1136 NYD_LEAVE;
1137 return rv;
1140 FL bool_t
1141 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1143 char date[n_FROM_DATEBUF];
1144 bool_t rv;
1145 NYD2_ENTER;
1147 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1148 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1149 !_is_date(date)))
1150 rv = TRUM1;
1151 NYD2_LEAVE;
1152 return rv;
1155 FL int
1156 extract_date_from_from_(char const *line, size_t linelen,
1157 char datebuf[n_FROM_DATEBUF])
1159 int rv;
1160 char const *cp = line;
1161 NYD_ENTER;
1163 rv = 1;
1165 /* "From " */
1166 cp = _from__skipword(cp);
1167 if (cp == NULL)
1168 goto jerr;
1169 /* "addr-spec " */
1170 cp = _from__skipword(cp);
1171 if (cp == NULL)
1172 goto jerr;
1173 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1174 (cp[2] == 'y' || cp[2] == 'Y')){
1175 cp = _from__skipword(cp);
1176 if (cp == NULL)
1177 goto jerr;
1179 /* It seems there are invalid MBOX archives in the wild, compare
1180 * . http://bugs.debian.org/624111
1181 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1182 * What they do is that they obfuscate the address to "name at host",
1183 * and even "name at host dot dom dot dom.
1184 * The [Aa][Tt] is also RFC 733, so be tolerant */
1185 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1186 cp[2] == ' '){
1187 rv = -1;
1188 cp += 3;
1189 jat_dot:
1190 cp = _from__skipword(cp);
1191 if (cp == NULL)
1192 goto jerr;
1193 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1194 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1195 cp += 4;
1196 goto jat_dot;
1200 linelen -= PTR2SIZE(cp - line);
1201 if (linelen < a_HEAD_DATE_MINLEN)
1202 goto jerr;
1203 if (cp[linelen - 1] == '\n') {
1204 --linelen;
1205 /* (Rather IMAP/POP3 only) */
1206 if (cp[linelen - 1] == '\r')
1207 --linelen;
1208 if (linelen < a_HEAD_DATE_MINLEN)
1209 goto jerr;
1211 if (linelen >= n_FROM_DATEBUF)
1212 goto jerr;
1214 jleave:
1215 memcpy(datebuf, cp, linelen);
1216 datebuf[linelen] = '\0';
1217 NYD_LEAVE;
1218 return rv;
1219 jerr:
1220 cp = _("<Unknown date>");
1221 linelen = strlen(cp);
1222 if (linelen >= n_FROM_DATEBUF)
1223 linelen = n_FROM_DATEBUF;
1224 rv = 0;
1225 goto jleave;
1228 FL void
1229 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1231 /* See the prototype declaration for the hairy relationship of
1232 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1233 struct n_header_field **hftail;
1234 struct header nh, *hq = &nh;
1235 char *linebuf = NULL /* TODO line pool */, *colon;
1236 size_t linesize = 0, seenfields = 0;
1237 int c;
1238 long lc;
1239 char const *val, *cp;
1240 NYD_ENTER;
1242 memset(hq, 0, sizeof *hq);
1243 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1244 hq->h_to = hp->h_to;
1245 hq->h_cc = hp->h_cc;
1246 hq->h_bcc = hp->h_bcc;
1248 hftail = &hq->h_user_headers;
1250 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1253 /* TODO yippieia, cat(check(lextract)) :-) */
1254 rewind(fp);
1255 while ((lc = a_gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1256 struct name *np;
1258 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1259 * yet expanded when we parse these! */
1260 if ((val = thisfield(linebuf, "to")) != NULL) {
1261 ++seenfields;
1262 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1263 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1264 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1265 ++seenfields;
1266 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1267 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1268 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1269 ++seenfields;
1270 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1271 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1272 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1273 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1274 ++seenfields;
1275 hq->h_from = cat(hq->h_from,
1276 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1277 EACM_STRICT, NULL));
1279 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1280 ++seenfields;
1281 hq->h_reply_to = cat(hq->h_reply_to,
1282 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1283 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1284 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1285 ++seenfields;
1286 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1287 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1288 EACM_STRICT, NULL));
1289 } else
1290 goto jebadhead;
1291 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1292 (val = thisfield(linebuf, "subj")) != NULL) {
1293 ++seenfields;
1294 for (cp = val; blankchar(*cp); ++cp)
1296 hq->h_subject = (hq->h_subject != NULL)
1297 ? save2str(hq->h_subject, cp) : savestr(cp);
1299 /* The remaining are mostly hacked in and thus TODO -- at least in
1300 * TODO respect to their content checking */
1301 else if((val = thisfield(linebuf, "message-id")) != NULL){
1302 if(n_psonce & n_PSO_t_FLAG){
1303 np = checkaddrs(lextract(val, GREF),
1304 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1305 NULL);
1306 if (np == NULL || np->n_flink != NULL)
1307 goto jebadhead;
1308 ++seenfields;
1309 hq->h_message_id = np;
1310 }else
1311 goto jebadhead;
1312 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1313 if(n_psonce & n_PSO_t_FLAG){
1314 np = checkaddrs(lextract(val, GREF),
1315 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1316 NULL);
1317 ++seenfields;
1318 hq->h_in_reply_to = np;
1319 }else
1320 goto jebadhead;
1321 }else if((val = thisfield(linebuf, "references")) != NULL){
1322 if(n_psonce & n_PSO_t_FLAG){
1323 ++seenfields;
1324 /* TODO Limit number of references TODO better on parser side */
1325 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1326 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1327 NULL));
1328 }else
1329 goto jebadhead;
1331 /* and that is very hairy */
1332 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1333 if(n_psonce & n_PSO_t_FLAG){
1334 ++seenfields;
1335 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1336 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1337 checkaddr_err));
1338 }else
1339 goto jebadhead;
1341 /* A free-form header; a_gethfield() did some verification already.. */
1342 else{
1343 struct n_header_field *hfp;
1344 ui32_t nl, bl;
1345 char const *nstart;
1347 for(nstart = cp = linebuf;; ++cp)
1348 if(!fieldnamechar(*cp))
1349 break;
1350 nl = (ui32_t)PTR2SIZE(cp - nstart);
1352 while(blankchar(*cp))
1353 ++cp;
1354 if(*cp++ != ':'){
1355 jebadhead:
1356 n_err(_("Ignoring header field: %s\n"), linebuf);
1357 continue;
1359 while(blankchar(*cp))
1360 ++cp;
1361 bl = (ui32_t)strlen(cp) +1;
1363 ++seenfields;
1364 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1365 ) + nl +1 + bl);
1366 hftail = &hfp->hf_next;
1367 hfp->hf_next = NULL;
1368 hfp->hf_nl = nl;
1369 hfp->hf_bl = bl - 1;
1370 memcpy(hfp->hf_dat, nstart, nl);
1371 hfp->hf_dat[nl++] = '\0';
1372 memcpy(hfp->hf_dat + nl, cp, bl);
1376 /* In case the blank line after the header has been edited out. Otherwise,
1377 * fetch the header separator */
1378 if (linebuf != NULL) {
1379 if (linebuf[0] != '\0') {
1380 for (cp = linebuf; *(++cp) != '\0';)
1382 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1383 } else {
1384 if ((c = getc(fp)) != '\n' && c != EOF)
1385 ungetc(c, fp);
1389 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1390 hp->h_to = hq->h_to;
1391 hp->h_cc = hq->h_cc;
1392 hp->h_bcc = hq->h_bcc;
1393 hp->h_from = hq->h_from;
1394 hp->h_reply_to = hq->h_reply_to;
1395 hp->h_sender = hq->h_sender;
1396 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1397 !(n_poption & n_PO_t_FLAG))
1398 hp->h_subject = hq->h_subject;
1399 hp->h_user_headers = hq->h_user_headers;
1401 if (n_psonce & n_PSO_t_FLAG) {
1402 hp->h_ref = hq->h_ref;
1403 hp->h_message_id = hq->h_message_id;
1404 hp->h_in_reply_to = hq->h_in_reply_to;
1405 hp->h_mft = hq->h_mft;
1407 /* And perform additional validity checks so that we don't bail later
1408 * on TODO this is good and the place where this should occur,
1409 * TODO unfortunately a lot of other places do again and blabla */
1410 if (hp->h_from == NULL)
1411 hp->h_from = n_poption_arg_r;
1412 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1413 hp->h_sender = lextract(ok_vlook(sender),
1414 GEXTRA | GFULL | GFULLEXTRA);
1416 } else
1417 n_err(_("Restoring deleted header lines\n"));
1419 if (linebuf != NULL)
1420 free(linebuf);
1421 NYD_LEAVE;
1424 FL char *
1425 hfield_mult(char const *field, struct message *mp, int mult)
1427 FILE *ibuf;
1428 struct str hfs;
1429 long lc;
1430 size_t linesize = 0; /* TODO line pool */
1431 char *linebuf = NULL, *colon;
1432 char const *hfield;
1433 NYD_ENTER;
1435 /* There are (spam) messages which have header bytes which are many KB when
1436 * joined, so resize a single heap storage until we are done if we shall
1437 * collect a field that may have multiple bodies; only otherwise use the
1438 * string dope directly */
1439 memset(&hfs, 0, sizeof hfs);
1441 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1442 goto jleave;
1443 if ((lc = mp->m_lines - 1) < 0)
1444 goto jleave;
1446 if ((mp->m_flag & MNOFROM) == 0 &&
1447 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1448 goto jleave;
1449 while (lc > 0) {
1450 if ((lc = a_gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1451 break;
1452 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1453 if (mult)
1454 n_str_add_buf(&hfs, hfield, strlen(hfield));
1455 else {
1456 hfs.s = savestr(hfield);
1457 break;
1462 jleave:
1463 if (linebuf != NULL)
1464 free(linebuf);
1465 if (mult && hfs.s != NULL) {
1466 colon = savestrbuf(hfs.s, hfs.l);
1467 free(hfs.s);
1468 hfs.s = colon;
1470 NYD_LEAVE;
1471 return hfs.s;
1474 FL char const *
1475 thisfield(char const *linebuf, char const *field)
1477 char const *rv = NULL;
1478 NYD2_ENTER;
1480 while (lowerconv(*linebuf) == lowerconv(*field)) {
1481 ++linebuf;
1482 ++field;
1484 if (*field != '\0')
1485 goto jleave;
1487 while (blankchar(*linebuf))
1488 ++linebuf;
1489 if (*linebuf++ != ':')
1490 goto jleave;
1492 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1493 ++linebuf;
1494 rv = linebuf;
1495 jleave:
1496 NYD2_LEAVE;
1497 return rv;
1500 FL char *
1501 nameof(struct message *mp, int reptype)
1503 char *cp, *cp2;
1504 NYD_ENTER;
1506 cp = skin(name1(mp, reptype));
1507 if (reptype != 0 || charcount(cp, '!') < 2)
1508 goto jleave;
1509 cp2 = strrchr(cp, '!');
1510 --cp2;
1511 while (cp2 > cp && *cp2 != '!')
1512 --cp2;
1513 if (*cp2 == '!')
1514 cp = cp2 + 1;
1515 jleave:
1516 NYD_LEAVE;
1517 return cp;
1520 FL char const *
1521 skip_comment(char const *cp)
1523 size_t nesting;
1524 NYD_ENTER;
1526 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1527 switch (*cp) {
1528 case '\\':
1529 if (cp[1])
1530 ++cp;
1531 break;
1532 case '(':
1533 ++nesting;
1534 break;
1535 case ')':
1536 --nesting;
1537 break;
1540 NYD_LEAVE;
1541 return cp;
1544 FL char const *
1545 routeaddr(char const *name)
1547 char const *np, *rp = NULL;
1548 NYD_ENTER;
1550 for (np = name; *np; np++) {
1551 switch (*np) {
1552 case '(':
1553 np = skip_comment(np + 1) - 1;
1554 break;
1555 case '"':
1556 while (*np) {
1557 if (*++np == '"')
1558 break;
1559 if (*np == '\\' && np[1])
1560 np++;
1562 break;
1563 case '<':
1564 rp = np;
1565 break;
1566 case '>':
1567 goto jleave;
1570 rp = NULL;
1571 jleave:
1572 NYD_LEAVE;
1573 return rp;
1576 FL enum expand_addr_flags
1577 expandaddr_to_eaf(void)
1579 struct eafdesc {
1580 char const *eafd_name;
1581 bool_t eafd_is_target;
1582 ui8_t eafd_andoff;
1583 ui8_t eafd_or;
1584 } const eafa[] = {
1585 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1586 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1587 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1588 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1589 {"file", TRU1, EAF_NONE, EAF_FILE},
1590 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1591 {"name", TRU1, EAF_NONE, EAF_NAME},
1592 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1593 }, *eafp;
1595 char *buf;
1596 enum expand_addr_flags rv;
1597 char const *cp;
1598 NYD2_ENTER;
1600 if ((cp = ok_vlook(expandaddr)) == NULL)
1601 rv = EAF_RESTRICT_TARGETS;
1602 else if (*cp == '\0')
1603 rv = EAF_TARGET_MASK;
1604 else {
1605 rv = EAF_TARGET_MASK;
1607 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1608 bool_t minus;
1610 if ((minus = (*cp == '-')) || *cp == '+')
1611 ++cp;
1612 for (eafp = eafa;; ++eafp) {
1613 if (eafp == eafa + n_NELEM(eafa)) {
1614 if (n_poption & n_PO_D_V)
1615 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1616 break;
1617 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1618 if (!minus) {
1619 rv &= ~eafp->eafd_andoff;
1620 rv |= eafp->eafd_or;
1621 } else {
1622 if (eafp->eafd_is_target)
1623 rv &= ~eafp->eafd_or;
1624 else if (n_poption & n_PO_D_V)
1625 n_err(_("minus - prefix invalid for *expandaddr* value: "
1626 "%s\n"), --cp);
1628 break;
1629 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1630 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1631 rv &= ~EAF_NAME;
1632 break;
1637 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1638 (n_poption & n_PO_TILDE_FLAG)))
1639 rv |= EAF_TARGET_MASK;
1640 else if(n_poption & n_PO_D_V){
1641 if(!(rv & EAF_TARGET_MASK))
1642 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1643 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1644 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1647 NYD2_LEAVE;
1648 return rv;
1651 FL si8_t
1652 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1654 char cbuf[sizeof "'\\U12340'"];
1655 char const *cs;
1656 int f;
1657 si8_t rv;
1658 enum expand_addr_flags eaf;
1659 NYD_ENTER;
1661 eaf = expandaddr_to_eaf();
1662 f = np->n_flags;
1664 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1665 if (eaf & EAF_FAILINVADDR)
1666 rv = -rv;
1668 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1670 } else {
1671 ui32_t c;
1672 char const *fmt = "'\\x%02X'";
1673 bool_t ok8bit = TRU1;
1675 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1676 cs = _("Invalid domain name: %s, character %s\n");
1677 fmt = "'\\U%04X'";
1678 ok8bit = FAL0;
1679 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1680 cs = _("%s contains invalid %s sequence\n");
1681 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1682 cs = _("%s is an invalid alias name\n");
1683 } else
1684 cs = _("%s contains invalid byte %s\n");
1686 c = NAME_ADDRSPEC_ERR_GETWC(f);
1687 snprintf(cbuf, sizeof cbuf,
1688 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1689 goto jprint;
1691 goto jleave;
1694 /* *expandaddr* stuff */
1695 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1696 goto jleave;
1698 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1699 if (eaf & EAF_FAIL)
1700 rv = -rv;
1701 cs = _("%s%s: file or pipe addressees not allowed here\n");
1702 if (eacm & EACM_NOLOG)
1703 goto jleave;
1704 else
1705 goto j0print;
1708 eaf |= (eacm & EAF_TARGET_MASK);
1709 if (eacm & EACM_NONAME)
1710 eaf &= ~EAF_NAME;
1712 if (eaf == EAF_NONE) {
1713 rv = FAL0;
1714 goto jleave;
1716 if (eaf & EAF_FAIL)
1717 rv = -rv;
1719 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1720 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1721 if (eacm & EACM_NOLOG)
1722 goto jleave;
1723 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1724 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1725 if (eacm & EACM_NOLOG)
1726 goto jleave;
1727 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1728 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1729 if (eacm & EACM_NOLOG)
1730 goto jleave;
1731 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1732 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1733 if (eacm & EACM_NOLOG)
1734 goto jleave;
1735 } else {
1736 rv = FAL0;
1737 goto jleave;
1740 j0print:
1741 cbuf[0] = '\0';
1742 jprint:
1743 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1744 jleave:
1745 NYD_LEAVE;
1746 return rv;
1749 FL char *
1750 skin(char const *name)
1752 struct n_addrguts ag;
1753 char *rv;
1754 NYD_ENTER;
1756 if(name != NULL){
1757 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1758 rv = ag.ag_skinned;
1759 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1760 rv = savestrbuf(rv, ag.ag_slen);
1761 }else
1762 rv = NULL;
1763 NYD_LEAVE;
1764 return rv;
1767 /* TODO addrspec_with_guts: RFC 5322
1768 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1769 FL char const *
1770 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1771 bool_t issingle_hack){
1772 char const *cp;
1773 char *cp2, *bufend, *nbuf, c;
1774 enum{
1775 a_NONE,
1776 a_GOTLT = 1<<0,
1777 a_GOTADDR = 1<<1,
1778 a_GOTSPACE = 1<<2,
1779 a_LASTSP = 1<<3
1780 } flags;
1781 NYD_ENTER;
1783 memset(agp, 0, sizeof *agp);
1785 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1786 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1787 agp->ag_slen = 0;
1788 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1789 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1790 goto jleave;
1791 }else if(!doskin){
1792 /*agp->ag_iaddr_start = 0;*/
1793 agp->ag_iaddr_aend = agp->ag_ilen;
1794 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1795 agp->ag_slen = agp->ag_ilen;
1796 agp->ag_n_flags = NAME_SKINNED;
1797 goto jcheck;
1800 flags = a_NONE;
1801 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1802 /*agp->ag_iaddr_start = 0;*/
1803 cp2 = bufend = nbuf;
1805 /* TODO This is complete crap and should use a token parser */
1806 for(cp = name++; (c = *cp++) != '\0';){
1807 switch (c) {
1808 case '(':
1809 cp = skip_comment(cp);
1810 flags &= ~a_LASTSP;
1811 break;
1812 case '"':
1813 /* Start of a "quoted-string". Copy it in its entirety */
1814 /* XXX RFC: quotes are "semantically invisible"
1815 * XXX But it was explicitly added (Changelog.Heirloom,
1816 * XXX [9.23] released 11/15/00, "Do not remove quotes
1817 * XXX when skinning names"? No more info.. */
1818 *cp2++ = c;
1819 while ((c = *cp) != '\0') { /* TODO improve */
1820 ++cp;
1821 if (c == '"') {
1822 *cp2++ = c;
1823 break;
1825 if (c != '\\')
1826 *cp2++ = c;
1827 else if ((c = *cp) != '\0') {
1828 *cp2++ = c;
1829 ++cp;
1832 flags &= ~a_LASTSP;
1833 break;
1834 case ' ':
1835 case '\t':
1836 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1837 flags |= a_GOTSPACE;
1838 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1840 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1841 cp += 3, *cp2++ = '@';
1842 else if (cp[0] == '@' && blankchar(cp[1]))
1843 cp += 2, *cp2++ = '@';
1844 else
1845 flags |= a_LASTSP;
1846 break;
1847 case '<':
1848 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1849 cp2 = bufend;
1850 flags &= ~(a_GOTSPACE | a_LASTSP);
1851 flags |= a_GOTLT | a_GOTADDR;
1852 break;
1853 case '>':
1854 if(flags & a_GOTLT){
1855 /* (_addrspec_check() verifies these later!) */
1856 flags &= ~(a_GOTLT | a_LASTSP);
1857 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1859 /* Skip over the entire remaining field */
1860 while((c = *cp) != '\0' && c != ','){
1861 ++cp;
1862 if (c == '(')
1863 cp = skip_comment(cp);
1864 else if (c == '"')
1865 while ((c = *cp) != '\0') {
1866 ++cp;
1867 if (c == '"')
1868 break;
1869 if (c == '\\' && *cp != '\0')
1870 ++cp;
1873 break;
1875 /* FALLTHRU */
1876 default:
1877 if(flags & a_LASTSP){
1878 flags &= ~a_LASTSP;
1879 if(flags & a_GOTADDR)
1880 *cp2++ = ' ';
1882 *cp2++ = c;
1883 /* This character is forbidden here, but it may nonetheless be
1884 * present: ensure we turn this into something valid! (E.g., if the
1885 * next character would be a "..) */
1886 if(c == '\\' && *cp != '\0')
1887 *cp2++ = *cp++;
1888 if(c == ',' && !issingle_hack){
1889 if(!(flags & a_GOTLT)){
1890 *cp2++ = ' ';
1891 for(; blankchar(*cp); ++cp)
1893 flags &= ~a_LASTSP;
1894 bufend = cp2;
1896 }else if(!(flags & a_GOTADDR)){
1897 flags |= a_GOTADDR;
1898 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1902 --name;
1903 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1904 if (agp->ag_iaddr_aend == 0)
1905 agp->ag_iaddr_aend = agp->ag_ilen;
1906 /* Misses > */
1907 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1908 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1909 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1910 agp->ag_iaddr_aend = agp->ag_ilen;
1911 cp2[agp->ag_ilen++] = '>';
1912 cp2[agp->ag_ilen] = '\0';
1914 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1915 n_lofi_free(nbuf);
1916 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1917 jcheck:
1918 if(a_head_addrspec_check(agp, doskin) <= FAL0)
1919 name = NULL;
1920 else
1921 name = agp->ag_input;
1922 jleave:
1923 NYD_LEAVE;
1924 return name;
1927 FL char *
1928 realname(char const *name)
1930 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1931 char *rname, *rp;
1932 struct str in, out;
1933 int quoted, good, nogood;
1934 NYD_ENTER;
1936 if ((cp = n_UNCONST(name)) == NULL)
1937 goto jleave;
1938 for (; *cp != '\0'; ++cp) {
1939 switch (*cp) {
1940 case '(':
1941 if (cstart != NULL) {
1942 /* More than one comment in address, doesn't make sense to display
1943 * it without context. Return the entire field */
1944 cp = mime_fromaddr(name);
1945 goto jleave;
1947 cstart = cp++;
1948 cp = skip_comment(cp);
1949 cend = cp--;
1950 if (cend <= cstart)
1951 cend = cstart = NULL;
1952 break;
1953 case '"':
1954 while (*cp) {
1955 if (*++cp == '"')
1956 break;
1957 if (*cp == '\\' && cp[1])
1958 ++cp;
1960 break;
1961 case '<':
1962 if (cp > name) {
1963 cstart = name;
1964 cend = cp;
1966 break;
1967 case ',':
1968 /* More than one address. Just use the first one */
1969 goto jbrk;
1973 jbrk:
1974 if (cstart == NULL) {
1975 if (*name == '<') {
1976 /* If name contains only a route-addr, the surrounding angle brackets
1977 * don't serve any useful purpose when displaying, so remove */
1978 cp = prstr(skin(name));
1979 } else
1980 cp = mime_fromaddr(name);
1981 goto jleave;
1984 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1985 * not stripped. The idea is to strip only syntactical relevant things (but
1986 * this is not necessarily the most sensible way in practice) */
1987 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1988 quoted = 0;
1989 for (cp = cstart; cp < cend; ++cp) {
1990 if (*cp == '(' && !quoted) {
1991 cq = skip_comment(++cp);
1992 if (PTRCMP(--cq, >, cend))
1993 cq = cend;
1994 while (cp < cq) {
1995 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1996 ++cp;
1997 *rp++ = *cp++;
1999 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
2000 *rp++ = *++cp;
2001 else if (*cp == '"') {
2002 quoted = !quoted;
2003 continue;
2004 } else
2005 *rp++ = *cp;
2007 *rp = '\0';
2008 in.s = rname;
2009 in.l = rp - rname;
2010 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
2011 ac_free(rname);
2012 rname = savestr(out.s);
2013 free(out.s);
2015 while (blankchar(*rname))
2016 ++rname;
2017 for (rp = rname; *rp != '\0'; ++rp)
2019 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
2020 *rp = '\0';
2021 if (rp == rname) {
2022 cp = mime_fromaddr(name);
2023 goto jleave;
2026 /* mime_fromhdr() has converted all nonprintable characters to question
2027 * marks now. These and blanks are considered uninteresting; if the
2028 * displayed part of the real name contains more than 25% of them, it is
2029 * probably better to display the plain email address instead */
2030 good = 0;
2031 nogood = 0;
2032 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
2033 if (*rp == '?' || blankchar(*rp))
2034 ++nogood;
2035 else
2036 ++good;
2037 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
2038 jleave:
2039 NYD_LEAVE;
2040 return n_UNCONST(cp);
2043 FL char *
2044 name1(struct message *mp, int reptype)
2046 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
2047 size_t namesize, linesize = 0;
2048 FILE *ibuf;
2049 int f1st = 1;
2050 NYD_ENTER;
2052 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
2053 goto jleave;
2054 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
2055 goto jleave;
2057 namebuf = smalloc(namesize = 1);
2058 namebuf[0] = 0;
2059 if (mp->m_flag & MNOFROM)
2060 goto jout;
2061 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2062 goto jout;
2063 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2064 goto jout;
2066 jnewname:
2067 if (namesize <= linesize)
2068 namebuf = srealloc(namebuf, namesize = linesize +1);
2069 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
2071 for (; blankchar(*cp); ++cp)
2073 for (cp2 = namebuf + strlen(namebuf);
2074 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
2075 *cp2++ = *cp++;
2076 *cp2 = '\0';
2078 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2079 goto jout;
2080 if ((cp = strchr(linebuf, 'F')) == NULL)
2081 goto jout;
2082 if (strncmp(cp, "From", 4))
2083 goto jout;
2084 if (namesize <= linesize)
2085 namebuf = srealloc(namebuf, namesize = linesize + 1);
2087 while ((cp = strchr(cp, 'r')) != NULL) {
2088 if (!strncmp(cp, "remote", 6)) {
2089 if ((cp = strchr(cp, 'f')) == NULL)
2090 break;
2091 if (strncmp(cp, "from", 4) != 0)
2092 break;
2093 if ((cp = strchr(cp, ' ')) == NULL)
2094 break;
2095 cp++;
2096 if (f1st) {
2097 strncpy(namebuf, cp, namesize);
2098 f1st = 0;
2099 } else {
2100 cp2 = strrchr(namebuf, '!') + 1;
2101 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
2103 namebuf[namesize - 2] = '!';
2104 namebuf[namesize - 1] = '\0';
2105 goto jnewname;
2107 cp++;
2109 jout:
2110 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2111 *cp == '\0')
2112 cp = savestr(namebuf);
2114 if (linebuf != NULL)
2115 free(linebuf);
2116 free(namebuf);
2117 jleave:
2118 NYD_LEAVE;
2119 return cp;
2122 FL char const *
2123 subject_re_trim(char const *s){
2124 struct{
2125 ui8_t len;
2126 char dat[7];
2127 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2128 {3, "re:"},
2129 {3, "aw:"}, {5, "antw:"}, /* de */
2130 {3, "wg:"}, /* Seen too often in the wild */
2131 {0, ""}
2134 bool_t any;
2135 char *re_st, *re_st_x;
2136 char const *orig_s;
2137 size_t re_l;
2138 NYD_ENTER;
2140 any = FAL0;
2141 orig_s = s;
2142 re_st = NULL;
2143 n_UNINIT(re_l, 0);
2145 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2146 (re_l = strlen(re_st_x)) > 0){
2147 re_st = n_lofi_alloc(++re_l * 2);
2148 memcpy(re_st, re_st_x, re_l);
2151 jouter:
2152 while(*s != '\0'){
2153 while(spacechar(*s))
2154 ++s;
2156 for(pp = ignored; pp->len > 0; ++pp)
2157 if(is_asccaseprefix(pp->dat, s)){
2158 s += pp->len;
2159 any = TRU1;
2160 goto jouter;
2163 if(re_st != NULL){
2164 char *cp;
2166 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2167 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2168 if(is_asccaseprefix(cp, s)){
2169 s += strlen(cp);
2170 any = TRU1;
2171 goto jouter;
2174 break;
2177 if(re_st != NULL)
2178 n_lofi_free(re_st);
2179 NYD_LEAVE;
2180 return any ? s : orig_s;
2183 FL int
2184 msgidcmp(char const *s1, char const *s2)
2186 int q1 = 0, q2 = 0, c1, c2;
2187 NYD_ENTER;
2189 while(*s1 == '<')
2190 ++s1;
2191 while(*s2 == '<')
2192 ++s2;
2194 do {
2195 c1 = msgidnextc(&s1, &q1);
2196 c2 = msgidnextc(&s2, &q2);
2197 if (c1 != c2)
2198 break;
2199 } while (c1 && c2);
2200 NYD_LEAVE;
2201 return c1 - c2;
2204 FL char const *
2205 fakefrom(struct message *mp)
2207 char const *name;
2208 NYD_ENTER;
2210 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2211 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2212 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2213 * RFC 4155 however requires a RFC 5322 (2822) conforming
2214 * "addr-spec", but we simply can't provide that */
2215 name = "MAILER-DAEMON";
2216 NYD_LEAVE;
2217 return name;
2220 FL char const *
2221 fakedate(time_t t)
2223 char *cp, *cq;
2224 NYD_ENTER;
2226 cp = ctime(&t);
2227 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
2229 *cq = '\0';
2230 cp = savestr(cp);
2231 NYD_LEAVE;
2232 return cp;
2235 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2236 FL time_t
2237 unixtime(char const *fromline)
2239 char const *fp, *xp;
2240 time_t t;
2241 si32_t i, year, month, day, hour, minute, second, tzdiff;
2242 struct tm *tmptr;
2243 NYD2_ENTER;
2245 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2247 fp -= 24;
2248 if (PTR2SIZE(fp - fromline) < 7)
2249 goto jinvalid;
2250 if (fp[3] != ' ')
2251 goto jinvalid;
2252 for (i = 0;;) {
2253 if (!strncmp(fp + 4, n_month_names[i], 3))
2254 break;
2255 if (n_month_names[++i][0] == '\0')
2256 goto jinvalid;
2258 month = i + 1;
2259 if (fp[7] != ' ')
2260 goto jinvalid;
2261 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2262 if (*xp != ' ' || xp != fp + 10)
2263 goto jinvalid;
2264 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2265 if (*xp != ':' || xp != fp + 13)
2266 goto jinvalid;
2267 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2268 if (*xp != ':' || xp != fp + 16)
2269 goto jinvalid;
2270 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2271 if (*xp != ' ' || xp != fp + 19)
2272 goto jinvalid;
2273 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2274 if (xp != fp + 24)
2275 goto jinvalid;
2276 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2277 goto jinvalid;
2278 tzdiff = t - mktime(gmtime(&t));
2279 tmptr = localtime(&t);
2280 if (tmptr->tm_isdst > 0)
2281 tzdiff += 3600;
2282 t -= tzdiff;
2283 jleave:
2284 NYD2_LEAVE;
2285 return t;
2286 jinvalid:
2287 t = n_time_epoch();
2288 goto jleave;
2290 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2292 FL time_t
2293 rfctime(char const *date) /* TODO n_idec_ return tests */
2295 char const *cp, *x;
2296 time_t t;
2297 si32_t i, year, month, day, hour, minute, second;
2298 NYD2_ENTER;
2300 cp = date;
2302 if ((cp = nexttoken(cp)) == NULL)
2303 goto jinvalid;
2304 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2305 cp[3] == ',') {
2306 if ((cp = nexttoken(&cp[4])) == NULL)
2307 goto jinvalid;
2309 n_idec_si32_cp(&day, cp, 10, &x);
2310 if ((cp = nexttoken(x)) == NULL)
2311 goto jinvalid;
2312 for (i = 0;;) {
2313 if (!strncmp(cp, n_month_names[i], 3))
2314 break;
2315 if (n_month_names[++i][0] == '\0')
2316 goto jinvalid;
2318 month = i + 1;
2319 if ((cp = nexttoken(&cp[3])) == NULL)
2320 goto jinvalid;
2321 /* RFC 5322, 4.3:
2322 * Where a two or three digit year occurs in a date, the year is to be
2323 * interpreted as follows: If a two digit year is encountered whose
2324 * value is between 00 and 49, the year is interpreted by adding 2000,
2325 * ending up with a value between 2000 and 2049. If a two digit year
2326 * is encountered with a value between 50 and 99, or any three digit
2327 * year is encountered, the year is interpreted by adding 1900 */
2328 n_idec_si32_cp(&year, cp, 10, &x);
2329 i = (int)PTR2SIZE(x - cp);
2330 if (i == 2 && year >= 0 && year <= 49)
2331 year += 2000;
2332 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2333 year += 1900;
2334 if ((cp = nexttoken(x)) == NULL)
2335 goto jinvalid;
2336 n_idec_si32_cp(&hour, cp, 10, &x);
2337 if (*x != ':')
2338 goto jinvalid;
2339 cp = &x[1];
2340 n_idec_si32_cp(&minute, cp, 10, &x);
2341 if (*x == ':') {
2342 cp = &x[1];
2343 n_idec_si32_cp(&second, cp, 10, &x);
2344 } else
2345 second = 0;
2347 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2348 goto jinvalid;
2349 if ((cp = nexttoken(x)) != NULL) {
2350 char buf[3];
2351 int sign = 1;
2353 switch (*cp) {
2354 case '+':
2355 sign = -1;
2356 /* FALLTHRU */
2357 case '-':
2358 ++cp;
2359 break;
2361 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2362 digitchar(cp[3])) {
2363 si64_t tadj;
2365 buf[2] = '\0';
2366 buf[0] = cp[0];
2367 buf[1] = cp[1];
2368 n_idec_si32_cp(&i, buf, 10, NULL);
2369 tadj = (si64_t)i * 3600; /* XXX */
2370 buf[0] = cp[2];
2371 buf[1] = cp[3];
2372 n_idec_si32_cp(&i, buf, 10, NULL);
2373 tadj += (si64_t)i * 60; /* XXX */
2374 if (sign < 0)
2375 tadj = -tadj;
2376 t += (time_t)tadj;
2378 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2379 * TODO once again, Christos Zoulas and NetBSD Mail have done
2380 * TODO a really good job already, but using strptime(3), which
2381 * TODO is not portable. Nonetheless, WE must improve, not
2382 * TODO at last because we simply ignore obsolete timezones!!
2383 * TODO See RFC 5322, 4.3! */
2385 jleave:
2386 NYD2_LEAVE;
2387 return t;
2388 jinvalid:
2389 t = 0;
2390 goto jleave;
2393 FL time_t
2394 combinetime(int year, int month, int day, int hour, int minute, int second){
2395 size_t const jdn_epoch = 2440588;
2396 bool_t const y2038p = (sizeof(time_t) == 4);
2398 size_t jdn;
2399 time_t t;
2400 NYD2_ENTER;
2402 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2403 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2404 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2405 day < 1 || day > 31 ||
2406 month < 1 || month > 12 ||
2407 year < 1970)
2408 goto jerr;
2410 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2411 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2412 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2413 * test by stepping second-wise around the flip. Don't care otherwise */
2414 if(!y2038p)
2415 goto jerr;
2416 if(year > 2038 || month > 1 || day > 19 ||
2417 hour > 3 || minute > 14 || second > 7)
2418 goto jerr;
2421 t = second;
2422 t += minute * n_DATE_SECSMIN;
2423 t += hour * n_DATE_SECSHOUR;
2425 jdn = a_head_gregorian_to_jdn(year, month, day);
2426 jdn -= jdn_epoch;
2427 t += (time_t)jdn * n_DATE_SECSDAY;
2428 jleave:
2429 NYD2_LEAVE;
2430 return t;
2431 jerr:
2432 t = (time_t)-1;
2433 goto jleave;
2436 FL void
2437 substdate(struct message *m)
2439 char const *cp;
2440 NYD_ENTER;
2442 /* Determine the date to print in faked 'From ' lines. This is traditionally
2443 * the date the message was written to the mail file. Try to determine this
2444 * using RFC message header fields, or fall back to current time */
2445 m->m_time = 0;
2446 if ((cp = hfield1("received", m)) != NULL) {
2447 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2449 ++cp;
2450 while (alnumchar(*cp));
2452 if (cp && *++cp)
2453 m->m_time = rfctime(cp);
2455 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2456 if ((cp = hfield1("date", m)) != NULL)
2457 m->m_time = rfctime(cp);
2459 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2460 m->m_time = time_current.tc_time;
2461 NYD_LEAVE;
2464 FL void
2465 setup_from_and_sender(struct header *hp)
2467 char const *addr;
2468 struct name *np;
2469 NYD_ENTER;
2471 /* If -t parsed or composed From: then take it. With -t we otherwise
2472 * want -r to be honoured in favour of *from* in order to have
2473 * a behaviour that is compatible with what users would expect from e.g.
2474 * postfix(1) */
2475 if ((np = hp->h_from) != NULL ||
2476 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2478 } else if ((addr = myaddrs(hp)) != NULL)
2479 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2480 hp->h_from = np;
2482 if ((np = hp->h_sender) != NULL) {
2484 } else if ((addr = ok_vlook(sender)) != NULL)
2485 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2486 hp->h_sender = np;
2488 NYD_LEAVE;
2491 FL struct name const *
2492 check_from_and_sender(struct name const *fromfield,
2493 struct name const *senderfield)
2495 struct name const *rv = NULL;
2496 NYD_ENTER;
2498 if (senderfield != NULL) {
2499 if (senderfield->n_flink != NULL) {
2500 n_err(_("The Sender: field may contain only one address\n"));
2501 goto jleave;
2503 rv = senderfield;
2506 if (fromfield != NULL) {
2507 if (fromfield->n_flink != NULL && senderfield == NULL) {
2508 n_err(_("A Sender: is required when there are multiple "
2509 "addresses in From:\n"));
2510 goto jleave;
2512 if (rv == NULL)
2513 rv = fromfield;
2516 if (rv == NULL)
2517 rv = (struct name*)0x1;
2518 jleave:
2519 NYD_LEAVE;
2520 return rv;
2523 #ifdef HAVE_XSSL
2524 FL char *
2525 getsender(struct message *mp)
2527 char *cp;
2528 struct name *np;
2529 NYD_ENTER;
2531 if ((cp = hfield1("from", mp)) == NULL ||
2532 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2533 cp = NULL;
2534 else
2535 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2536 NYD_LEAVE;
2537 return cp;
2539 #endif
2541 FL int
2542 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2543 int subjfirst)
2545 /* TODO grab_headers: again, check counts etc. against RFC;
2546 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2547 int errs;
2548 int volatile comma;
2549 NYD_ENTER;
2551 errs = 0;
2552 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2554 if (gflags & GTO)
2555 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2556 if (subjfirst && (gflags & GSUBJECT))
2557 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2558 if (gflags & GCC)
2559 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2560 if (gflags & GBCC)
2561 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2563 if (gflags & GEXTRA) {
2564 if (hp->h_from == NULL)
2565 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2566 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2567 GEXTRA | GFULL | GFULLEXTRA);
2568 if (hp->h_reply_to == NULL) {
2569 struct name *v15compat;
2571 if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
2572 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2573 hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
2574 if(hp->h_reply_to == NULL) /* v15 */
2575 hp->h_reply_to = v15compat;
2577 hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
2578 GEXTRA | GFULL);
2579 if (hp->h_sender == NULL)
2580 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2581 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2582 GEXTRA | GFULL);
2585 if (!subjfirst && (gflags & GSUBJECT))
2586 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2588 NYD_LEAVE;
2589 return errs;
2592 FL bool_t
2593 n_header_match(struct message *mp, struct search_expr const *sep){
2594 struct str fiter, in, out;
2595 char const *field;
2596 long lc;
2597 FILE *ibuf;
2598 size_t *linesize;
2599 char **linebuf, *colon;
2600 enum {a_NONE, a_ALL, a_ITER, a_RE} match;
2601 bool_t rv;
2602 NYD_ENTER;
2604 rv = FAL0;
2605 match = a_NONE;
2606 linebuf = &termios_state.ts_linebuf; /* XXX line pool */
2607 linesize = &termios_state.ts_linesize; /* XXX line pool */
2608 n_UNINIT(fiter.l, 0);
2609 n_UNINIT(fiter.s, NULL);
2611 if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2612 goto jleave;
2613 if((lc = mp->m_lines - 1) < 0)
2614 goto jleave;
2616 if((mp->m_flag & MNOFROM) == 0 &&
2617 readline_restart(ibuf, linebuf, linesize, 0) < 0)
2618 goto jleave;
2620 /* */
2621 if((field = sep->ss_field) != NULL){
2622 if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
2623 match = a_ALL;
2624 else{
2625 fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
2626 match = a_ITER;
2628 #ifdef HAVE_REGEX
2629 }else if(sep->ss_fieldre != NULL){
2630 match = a_RE;
2631 #endif
2632 }else
2633 match = a_ALL;
2635 /* Iterate over all the headers */
2636 while(lc > 0){
2637 struct name *np;
2639 if((lc = a_gethfield(ibuf, linebuf, linesize, lc, &colon)) <= 0)
2640 break;
2642 /* Is this a header we are interested in? */
2643 if(match == a_ITER){
2644 char *itercp;
2646 memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
2647 while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
2648 /* It may be an abbreviation */
2649 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
2650 size_t i;
2651 char c1;
2653 if(field[0] != '\0' && field[1] == '\0'){
2654 c1 = lowerconv(field[0]);
2655 for(i = 0; i < n_NELEM(x); ++i){
2656 if(c1 == x[i][0]){
2657 field = x[i];
2658 break;
2663 if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
2664 break;
2666 if(field == NULL)
2667 continue;
2668 #ifdef HAVE_REGEX
2669 }else if(match == a_RE){
2670 char *cp;
2671 size_t i;
2673 i = PTR2SIZE(colon - *linebuf);
2674 cp = n_lofi_alloc(i +1);
2675 memcpy(cp, *linebuf, i);
2676 cp[i] = '\0';
2677 i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
2678 n_lofi_free(cp);
2679 if(!i)
2680 continue;
2681 #endif
2684 /* It could be a plain existence test */
2685 if(sep->ss_field_exists){
2686 rv = TRU1;
2687 break;
2690 /* Need to check the body */
2691 while(blankchar(*++colon))
2693 in.s = colon;
2695 /* Shall we split into address list and match as/the addresses only?
2696 * TODO at some later time we should ignore and log efforts to search
2697 * TODO a skinned address list if we know the header has none such */
2698 if(sep->ss_skin){
2699 if((np = lextract(in.s, GSKIN)) == NULL)
2700 continue;
2701 out.s = np->n_name;
2702 }else{
2703 np = NULL;
2704 in.l = strlen(in.s);
2705 mime_fromhdr(&in, &out, TD_ICONV);
2708 jnext_name:
2709 #ifdef HAVE_REGEX
2710 if(sep->ss_bodyre != NULL)
2711 rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
2712 else
2713 #endif
2714 rv = substr(out.s, sep->ss_body);
2716 if(np == NULL)
2717 free(out.s);
2718 if(rv)
2719 break;
2720 if(np != NULL && (np = np->n_flink) != NULL){
2721 out.s = np->n_name;
2722 goto jnext_name;
2726 jleave:
2727 if(match == a_ITER)
2728 n_lofi_free(fiter.s);
2729 NYD_LEAVE;
2730 return rv;
2733 FL struct n_header_field *
2734 n_customhdr_query(void){
2735 char const *vp;
2736 struct n_header_field *rv, **tail, *hfp;
2737 NYD_ENTER;
2739 rv = NULL;
2741 if((vp = ok_vlook(customhdr)) != NULL){
2742 char *buf;
2744 tail = &rv;
2745 buf = savestr(vp);
2746 jch_outer:
2747 while((vp = n_strsep_esc(&buf, ',', TRU1)) != NULL){
2748 ui32_t nl, bl;
2749 char const *nstart, *cp;
2751 for(nstart = cp = vp;; ++cp){
2752 if(fieldnamechar(*cp))
2753 continue;
2754 if(*cp == '\0'){
2755 if(cp == nstart){
2756 n_err(_("Invalid nameless *customhdr* entry\n"));
2757 goto jch_outer;
2759 }else if(*cp != ':' && !blankchar(*cp)){
2760 jch_badent:
2761 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2762 goto jch_outer;
2764 break;
2766 nl = (ui32_t)PTR2SIZE(cp - nstart);
2768 while(blankchar(*cp))
2769 ++cp;
2770 if(*cp++ != ':')
2771 goto jch_badent;
2772 while(blankchar(*cp))
2773 ++cp;
2774 bl = (ui32_t)strlen(cp) +1;
2776 *tail =
2777 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2778 nl +1 + bl);
2779 tail = &hfp->hf_next;
2780 hfp->hf_next = NULL;
2781 hfp->hf_nl = nl;
2782 hfp->hf_bl = bl - 1;
2783 memcpy(hfp->hf_dat, nstart, nl);
2784 hfp->hf_dat[nl++] = '\0';
2785 memcpy(hfp->hf_dat + nl, cp, bl);
2788 NYD_LEAVE;
2789 return rv;
2792 /* s-it-mode */