fakedate(): implement ourselfs (Joseph Bisch)..
[s-mailx.git] / head.c
blobbee3d6d5d4261d684dbbac469ff6d6d1836b67ca
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 use_idna = 2;
382 else
383 #endif
384 break;
385 } else if (in_domain == 2) {
386 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
387 break;
388 } else if (in_quote && in_domain == 0) {
389 /*EMPTY*/;
390 } else if (c.c == '\\' && *p != '\0') {
391 ++p;
392 } else if (c.c == '@') {
393 if (hadat++ > 0) {
394 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
395 c.u);
396 goto jleave;
398 agp->ag_sdom_start = PTR2SIZE(p - addr);
399 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
400 in_domain = (*p == '[') ? 2 : 1;
401 continue;
402 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
403 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
404 c.c == '\\' || c.c == ',' || blankchar(c.c))
405 break;
406 hadat = 0;
408 if (c.c != '\0') {
409 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
410 goto jleave;
413 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
414 /* TODO This may be an UUCP address */
415 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
416 if(!n_alias_is_valid_name(agp->ag_input))
417 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
418 }else{
419 /* If we seem to know that this is an address. Ensure this is correct
420 * according to RFC 5322 TODO the entire address parser should be like
421 * TODO that for one, and then we should know whether structured or
422 * TODO unstructured, and just parse correctly overall!
423 * TODO In addition, this can be optimised a lot.
424 * TODO And it is far from perfect: it should not forget whether no
425 * TODO whitespace followed some snippet, and it was written hastily.
426 * TODO It is even wrong sometimes. Not only for strange cases */
427 struct a_token{
428 struct a_token *t_last;
429 struct a_token *t_next;
430 enum{
431 a_T_TATOM = 1u<<0,
432 a_T_TCOMM = 1u<<1,
433 a_T_TQUOTE = 1u<<2,
434 a_T_TADDR = 1u<<3,
435 a_T_TMASK = (1u<<4) - 1,
437 a_T_SPECIAL = 1u<<8 /* An atom actually needs to go TQUOTE */
438 } t_f;
439 ui8_t t__pad[4];
440 size_t t_start;
441 size_t t_end;
442 } *thead, *tcurr, *tp;
444 struct n_string ost, *ostp;
445 char const *cp, *cp1st, *cpmax, *xp;
446 void *lofi_snap;
448 /* Name and domain must be non-empty */
449 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
450 c.c = '@';
451 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
452 goto jleave;
455 cp = agp->ag_input;
457 /* Nothing to do if there is only an address (in angle brackets) */
458 /* TODO This is wrong since we allow invalid constructs in local-part
459 * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
460 * TODO "abcd"@abc. Etc. */
461 if(agp->ag_iaddr_start == 0){
462 if(agp->ag_iaddr_aend == agp->ag_ilen)
463 goto jleave;
464 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
465 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
466 cp[agp->ag_iaddr_aend] == '>')
467 goto jleave;
469 /* It is not, so parse off all tokens, then resort and rejoin */
470 lofi_snap = n_lofi_snap_create();
472 cp1st = cp;
473 if((c.ui32 = agp->ag_iaddr_start) > 0)
474 --c.ui32;
475 cpmax = &cp[c.ui32];
477 thead = tcurr = NULL;
478 hadat = FAL0;
479 jnode_redo:
480 for(tp = NULL; cp < cpmax;){
481 switch((c.c = *cp)){
482 case '(':
483 if(tp != NULL)
484 tp->t_end = PTR2SIZE(cp - cp1st);
485 tp = n_lofi_alloc(sizeof *tp);
486 tp->t_next = NULL;
487 if((tp->t_last = tcurr) != NULL)
488 tcurr->t_next = tp;
489 else
490 thead = tp;
491 tcurr = tp;
492 tp->t_f = a_T_TCOMM;
493 tp->t_start = PTR2SIZE(++cp - cp1st);
494 xp = skip_comment(cp);
495 tp->t_end = PTR2SIZE(xp - cp1st);
496 cp = xp;
497 if(tp->t_end > tp->t_start){
498 if(xp[-1] == ')')
499 --tp->t_end;
500 else{
501 /* No closing comment - strip trailing whitespace */
502 while(blankchar(*--xp))
503 if(--tp->t_end == tp->t_start)
504 break;
507 tp = NULL;
508 break;
510 case '"':
511 if(tp != NULL)
512 tp->t_end = PTR2SIZE(cp - cp1st);
513 tp = n_lofi_alloc(sizeof *tp);
514 tp->t_next = NULL;
515 if((tp->t_last = tcurr) != NULL)
516 tcurr->t_next = tp;
517 else
518 thead = tp;
519 tcurr = tp;
520 tp->t_f = a_T_TQUOTE;
521 tp->t_start = PTR2SIZE(++cp - cp1st);
522 for(xp = cp; xp < cpmax; ++xp){
523 if((c.c = *xp) == '"')
524 break;
525 if(c.c == '\\' && xp[1] != '\0')
526 ++xp;
528 tp->t_end = PTR2SIZE(xp - cp1st);
529 cp = &xp[1];
530 if(tp->t_end > tp->t_start){
531 /* No closing quote - strip trailing whitespace */
532 if(*xp != '"'){
533 while(blankchar(*xp--))
534 if(--tp->t_end == tp->t_start)
535 break;
538 tp = NULL;
539 break;
541 default:
542 if(blankchar(c.c)){
543 if(tp != NULL)
544 tp->t_end = PTR2SIZE(cp - cp1st);
545 tp = NULL;
546 ++cp;
547 break;
550 if(tp == NULL){
551 tp = n_lofi_alloc(sizeof *tp);
552 tp->t_next = NULL;
553 if((tp->t_last = tcurr) != NULL)
554 tcurr->t_next = tp;
555 else
556 thead = tp;
557 tcurr = tp;
558 tp->t_f = a_T_TATOM;
559 tp->t_start = PTR2SIZE(cp - cp1st);
561 ++cp;
563 /* Reverse solidus transforms the following into a quoted-pair, and
564 * therefore (must occur in comment or quoted-string only) the
565 * entire atom into a quoted string */
566 if(c.c == '\\'){
567 tp->t_f |= a_T_SPECIAL;
568 if(cp < cpmax)
569 ++cp;
570 break;
573 /* Is this plain RFC 5322 "atext", or "specials"?
574 * TODO Because we don't know structured/unstructured, nor anything
575 * TODO else, we need to treat "dot-atom" as being identical to
576 * TODO "specials".
577 * However, if the 8th bit is set, this will be RFC 2047 converted
578 * and the entire sequence is skipped */
579 if(!(c.u & 0x80) && !alnumchar(c.c) &&
580 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
581 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
582 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
583 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
584 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
585 tp->t_f |= a_T_SPECIAL;
586 break;
589 if(tp != NULL)
590 tp->t_end = PTR2SIZE(cp - cp1st);
592 if(hadat == FAL0){
593 hadat = TRU1;
594 /* The local-part may be in quotes.. */
595 if((tp = tcurr) != NULL && (tp->t_f & a_T_TQUOTE) &&
596 tp->t_end == agp->ag_iaddr_start - 1){
597 /* ..so backward extend it, including the starting quote */
598 /* TODO This is false and the code below #if 0 away. We would
599 * TODO need to create a properly quoted local-part HERE AND NOW
600 * TODO and REPLACE the original data with that version, but the
601 * TODO current code cannot do that. The node needs the data,
602 * TODO not only offsets for that, for example. If we had all that
603 * TODO the code below could produce a really valid thing */
604 if(tp->t_start > 0)
605 --tp->t_start;
606 if(tp->t_start > 0 &&
607 (tp->t_last == NULL || tp->t_last->t_end < tp->t_start) &&
608 agp->ag_input[tp->t_start - 1] == '\\')
609 --tp->t_start;
610 tp->t_f = a_T_TADDR | a_T_SPECIAL;
611 }else{
612 tp = n_lofi_alloc(sizeof *tp);
613 tp->t_next = NULL;
614 if((tp->t_last = tcurr) != NULL)
615 tcurr->t_next = tp;
616 else
617 thead = tp;
618 tcurr = tp;
619 tp->t_f = a_T_TADDR;
620 tp->t_start = agp->ag_iaddr_start;
621 /* TODO Very special case because of our hacky non-object-based and
622 * TODO non-compliant address parser. Note */
623 if(tp->t_last == NULL && tp->t_start > 0)
624 tp->t_start = 0;
625 if(agp->ag_input[tp->t_start] == '<')
626 ++tp->t_start;
628 /* TODO Very special check for whether we need to massage the
629 * TODO local part. This is wrong, but otherwise even more so */
630 #if 0
631 cp = &agp->ag_input[tp->t_start];
632 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
633 while(cp < cpmax){
634 c.c = *cp++;
635 if(!(c.u & 0x80) && !alnumchar(c.c) &&
636 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
637 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
638 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
639 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
640 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~'){
641 tp->t_f |= a_T_SPECIAL;
642 break;
645 #endif
647 tp->t_end = agp->ag_iaddr_aend;
648 assert(tp->t_start <= tp->t_end);
649 tp = NULL;
651 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
652 cpmax = &agp->ag_input[agp->ag_ilen];
653 if(cp < cpmax)
654 goto jnode_redo;
657 /* Nothing may follow the address, move it to the end */
658 if(!(tcurr->t_f & a_T_TADDR)){
659 for(tp = thead; tp != NULL; tp = tp->t_next){
660 if(tp->t_f & a_T_TADDR){
661 if(tp->t_last != NULL)
662 tp->t_last->t_next = tp->t_next;
663 else
664 thead = tp->t_next;
665 if(tp->t_next != NULL)
666 tp->t_next->t_last = tp->t_last;
668 tcurr = tp;
669 while(tp->t_next != NULL)
670 tp = tp->t_next;
671 tp->t_next = tcurr;
672 tcurr->t_last = tp;
673 tcurr->t_next = NULL;
674 break;
679 /* Make ranges contiguous: ensure a continuous range of atoms is converted
680 * to a SPECIAL one if at least one of them requires it */
681 for(tp = thead; tp != NULL; tp = tp->t_next){
682 if(tp->t_f & a_T_SPECIAL){
683 tcurr = tp;
684 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
685 tp->t_f |= a_T_SPECIAL;
686 tp = tcurr;
687 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
688 tp->t_f |= a_T_SPECIAL;
689 if(tp == NULL)
690 break;
694 /* And yes, we want quotes to extend as much as possible */
695 for(tp = thead; tp != NULL; tp = tp->t_next){
696 if(tp->t_f & a_T_TQUOTE){
697 tcurr = tp;
698 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
699 tp->t_f |= a_T_SPECIAL;
700 tp = tcurr;
701 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
702 tp->t_f |= a_T_SPECIAL;
703 if(tp == NULL)
704 break;
708 /* Then rejoin */
709 ostp = n_string_creat_auto(&ost);
710 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
711 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
713 for(tcurr = thead; tcurr != NULL;){
714 if(tcurr != thead)
715 ostp = n_string_push_c(ostp, ' ');
716 if(tcurr->t_f & a_T_TADDR){
717 if(tcurr->t_last != NULL)
718 ostp = n_string_push_c(ostp, '<');
719 agp->ag_iaddr_start = ostp->s_len;
720 /* Now it is terrible to say, but if that thing contained
721 * quotes, then those may contain quoted-pairs! */
722 #if 0
723 if(!(tcurr->t_f & a_T_SPECIAL)){
724 #endif
725 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
726 (tcurr->t_end - tcurr->t_start));
727 #if 0
728 }else{
729 bool_t quot, esc;
731 ostp = n_string_push_c(ostp, '"');
732 quot = TRU1;
734 cp = &cp1st[tcurr->t_start];
735 cpmax = &cp1st[tcurr->t_end];
736 for(esc = FAL0; cp < cpmax;){
737 if((c.c = *cp++) == '\\' && !esc){
738 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
739 esc = TRU1;
740 }else{
741 if(esc || c.c == '"')
742 ostp = n_string_push_c(ostp, '\\');
743 else if(c.c == '@'){
744 ostp = n_string_push_c(ostp, '"');
745 quot = FAL0;
747 ostp = n_string_push_c(ostp, c.c);
748 esc = FAL0;
752 #endif
753 agp->ag_iaddr_aend = ostp->s_len;
755 if(tcurr->t_last != NULL)
756 ostp = n_string_push_c(ostp, '>');
757 tcurr = tcurr->t_next;
758 }else if(tcurr->t_f & a_T_TCOMM){
759 ostp = n_string_push_c(ostp, '(');
760 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
761 (tcurr->t_end - tcurr->t_start));
762 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
763 tcurr = tp;
764 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
765 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
766 (tcurr->t_end - tcurr->t_start));
768 ostp = n_string_push_c(ostp, ')');
769 tcurr = tcurr->t_next;
770 }else if(tcurr->t_f & a_T_TQUOTE){
771 jput_quote:
772 ostp = n_string_push_c(ostp, '"');
773 tp = tcurr;
774 do/* while tcurr && TATOM||TQUOTE */{
775 cp = &cp1st[tcurr->t_start];
776 cpmax = &cp1st[tcurr->t_end];
777 if(cp == cpmax)
778 continue;
780 if(tcurr != tp)
781 ostp = n_string_push_c(ostp, ' ');
783 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
784 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
785 else{
786 bool_t esc;
788 for(esc = FAL0; cp < cpmax;){
789 if((c.c = *cp++) == '\\' && !esc){
790 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
791 esc = TRU1;
792 }else{
793 if(esc || c.c == '"'){
794 jput_quote_esc:
795 ostp = n_string_push_c(ostp, '\\');
797 ostp = n_string_push_c(ostp, c.c);
798 esc = FAL0;
801 if(esc){
802 c.c = '\\';
803 goto jput_quote_esc;
806 }while((tcurr = tcurr->t_next) != NULL &&
807 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
808 ostp = n_string_push_c(ostp, '"');
809 }else if(tcurr->t_f & a_T_SPECIAL)
810 goto jput_quote;
811 else{
812 /* Can we use a fast join mode? */
813 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
814 if(!(tcurr->t_f & a_T_TATOM))
815 break;
816 if(tcurr != tp)
817 ostp = n_string_push_c(ostp, ' ');
818 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
819 (tcurr->t_end - tcurr->t_start));
824 n_lofi_snap_unroll(lofi_snap);
826 agp->ag_input = n_string_cp(ostp);
827 agp->ag_ilen = ostp->s_len;
828 /*ostp = n_string_drop_ownership(ostp);*/
830 /* Name and domain must be non-empty, the second */
831 cp = &agp->ag_input[agp->ag_iaddr_start];
832 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
833 if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
834 c.c = '@';
835 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
836 goto jleave;
839 agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
841 /* TODO This parser is a mess. We do not know whether this is truly
842 * TODO valid, and all our checks are not truly RFC conforming.
843 * TODO Do check the skinned thing by itself once more, in order
844 * TODO to catch problems from reordering, e.g., this additional
845 * TODO test catches a final address without AT..
846 * TODO This is a plain copy+paste of the weird thing above, no care */
847 agp->ag_n_flags &= ~NAME_ADDRSPEC_ISADDR;
848 in_domain = hadat = 0;
849 for (p = addr; (c.c = *p++) != '\0';) {
850 if (c.c == '"') {
851 in_quote = !in_quote;
852 } else if (c.u < 040 || c.u >= 0177) {
853 #ifdef HAVE_IDNA
854 if(!in_domain)
855 #endif
856 break;
857 } else if (in_domain == 2) {
858 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
859 break;
860 } else if (in_quote && in_domain == 0) {
861 /*EMPTY*/;
862 } else if (c.c == '\\' && *p != '\0') {
863 ++p;
864 } else if (c.c == '@') {
865 if (hadat++ > 0) {
866 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
867 c.u);
868 goto jleave;
870 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
871 in_domain = (*p == '[') ? 2 : 1;
872 continue;
873 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
874 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
875 c.c == '\\' || c.c == ',' || blankchar(c.c))
876 break;
877 hadat = 0;
879 if(c.c != '\0')
880 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
881 else if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
882 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
883 p[-1]);
886 jleave:
887 if(!(agp->ag_n_flags & NAME_ADDRSPEC_INVALID) && use_idna == 2)
888 #ifdef HAVE_IDNA
889 agp = a_head_idna_apply(agp);
890 #endif
891 NYD_LEAVE;
892 return !(agp->ag_n_flags & NAME_ADDRSPEC_INVALID);
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 rv = NULL;
1084 }else for(; np != NULL; np = np->n_flink)
1085 if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1086 goto jefrom;
1087 goto jleave;
1090 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1091 * undeterminable From: address. However, if the user sets *hostname*,
1092 * accept his desire */
1093 if (ok_vlook(hostname) != NULL)
1094 goto jnodename;
1095 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
1096 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1097 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
1098 goto jnodename;
1099 jleave:
1100 NYD_LEAVE;
1101 return rv;
1103 jnodename:{
1104 char *cp;
1105 char const *hn, *ln;
1106 size_t i;
1108 hn = n_nodename(TRU1);
1109 ln = ok_vlook(LOGNAME);
1110 i = strlen(ln) + strlen(hn) + 1 +1;
1111 rv = cp = salloc(i);
1112 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1114 goto jleave;
1117 FL char const *
1118 myorigin(struct header *hp) /* TODO */
1120 char const *rv = NULL, *ccp;
1121 struct name *np;
1122 NYD_ENTER;
1124 if((ccp = myaddrs(hp)) != NULL &&
1125 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1126 if(np->n_flink == NULL)
1127 rv = ccp;
1128 else if((ccp = ok_vlook(sender)) != NULL) {
1129 if((np = lextract(ccp, GEXTRA | GFULL)) == NULL ||
1130 np->n_flink != NULL ||
1131 is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME))
1132 n_err(_("The address given in *sender* is invalid: %s\n"), ccp);
1133 else
1134 rv = ccp;
1137 NYD_LEAVE;
1138 return rv;
1141 FL bool_t
1142 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1144 char date[n_FROM_DATEBUF];
1145 bool_t rv;
1146 NYD2_ENTER;
1148 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1149 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1150 !_is_date(date)))
1151 rv = TRUM1;
1152 NYD2_LEAVE;
1153 return rv;
1156 FL int
1157 extract_date_from_from_(char const *line, size_t linelen,
1158 char datebuf[n_FROM_DATEBUF])
1160 int rv;
1161 char const *cp = line;
1162 NYD_ENTER;
1164 rv = 1;
1166 /* "From " */
1167 cp = _from__skipword(cp);
1168 if (cp == NULL)
1169 goto jerr;
1170 /* "addr-spec " */
1171 cp = _from__skipword(cp);
1172 if (cp == NULL)
1173 goto jerr;
1174 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1175 (cp[2] == 'y' || cp[2] == 'Y')){
1176 cp = _from__skipword(cp);
1177 if (cp == NULL)
1178 goto jerr;
1180 /* It seems there are invalid MBOX archives in the wild, compare
1181 * . http://bugs.debian.org/624111
1182 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1183 * What they do is that they obfuscate the address to "name at host",
1184 * and even "name at host dot dom dot dom.
1185 * The [Aa][Tt] is also RFC 733, so be tolerant */
1186 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1187 cp[2] == ' '){
1188 rv = -1;
1189 cp += 3;
1190 jat_dot:
1191 cp = _from__skipword(cp);
1192 if (cp == NULL)
1193 goto jerr;
1194 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1195 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1196 cp += 4;
1197 goto jat_dot;
1201 linelen -= PTR2SIZE(cp - line);
1202 if (linelen < a_HEAD_DATE_MINLEN)
1203 goto jerr;
1204 if (cp[linelen - 1] == '\n') {
1205 --linelen;
1206 /* (Rather IMAP/POP3 only) */
1207 if (cp[linelen - 1] == '\r')
1208 --linelen;
1209 if (linelen < a_HEAD_DATE_MINLEN)
1210 goto jerr;
1212 if (linelen >= n_FROM_DATEBUF)
1213 goto jerr;
1215 jleave:
1216 memcpy(datebuf, cp, linelen);
1217 datebuf[linelen] = '\0';
1218 NYD_LEAVE;
1219 return rv;
1220 jerr:
1221 cp = _("<Unknown date>");
1222 linelen = strlen(cp);
1223 if (linelen >= n_FROM_DATEBUF)
1224 linelen = n_FROM_DATEBUF;
1225 rv = 0;
1226 goto jleave;
1229 FL void
1230 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1232 /* See the prototype declaration for the hairy relationship of
1233 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1234 struct n_header_field **hftail;
1235 struct header nh, *hq = &nh;
1236 char *linebuf = NULL /* TODO line pool */, *colon;
1237 size_t linesize = 0, seenfields = 0;
1238 int c;
1239 long lc;
1240 char const *val, *cp;
1241 NYD_ENTER;
1243 memset(hq, 0, sizeof *hq);
1244 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1245 hq->h_to = hp->h_to;
1246 hq->h_cc = hp->h_cc;
1247 hq->h_bcc = hp->h_bcc;
1249 hftail = &hq->h_user_headers;
1251 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1254 /* TODO yippieia, cat(check(lextract)) :-) */
1255 rewind(fp);
1256 while ((lc = a_gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1257 struct name *np;
1259 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1260 * yet expanded when we parse these! */
1261 if ((val = thisfield(linebuf, "to")) != NULL) {
1262 ++seenfields;
1263 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1264 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1265 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1266 ++seenfields;
1267 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1268 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1269 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1270 ++seenfields;
1271 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1272 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1273 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1274 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1275 ++seenfields;
1276 hq->h_from = cat(hq->h_from,
1277 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1278 EACM_STRICT, NULL));
1280 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1281 ++seenfields;
1282 hq->h_reply_to = cat(hq->h_reply_to,
1283 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1284 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1285 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1286 ++seenfields;
1287 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1288 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1289 EACM_STRICT, NULL));
1290 } else
1291 goto jebadhead;
1292 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1293 (val = thisfield(linebuf, "subj")) != NULL) {
1294 ++seenfields;
1295 for (cp = val; blankchar(*cp); ++cp)
1297 hq->h_subject = (hq->h_subject != NULL)
1298 ? save2str(hq->h_subject, cp) : savestr(cp);
1300 /* The remaining are mostly hacked in and thus TODO -- at least in
1301 * TODO respect to their content checking */
1302 else if((val = thisfield(linebuf, "message-id")) != NULL){
1303 if(n_psonce & n_PSO_t_FLAG){
1304 np = checkaddrs(lextract(val, GREF),
1305 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1306 NULL);
1307 if (np == NULL || np->n_flink != NULL)
1308 goto jebadhead;
1309 ++seenfields;
1310 hq->h_message_id = np;
1311 }else
1312 goto jebadhead;
1313 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1314 if(n_psonce & n_PSO_t_FLAG){
1315 np = checkaddrs(lextract(val, GREF),
1316 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1317 NULL);
1318 ++seenfields;
1319 hq->h_in_reply_to = np;
1320 }else
1321 goto jebadhead;
1322 }else if((val = thisfield(linebuf, "references")) != NULL){
1323 if(n_psonce & n_PSO_t_FLAG){
1324 ++seenfields;
1325 /* TODO Limit number of references TODO better on parser side */
1326 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1327 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1328 NULL));
1329 }else
1330 goto jebadhead;
1332 /* and that is very hairy */
1333 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1334 if(n_psonce & n_PSO_t_FLAG){
1335 ++seenfields;
1336 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1337 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1338 checkaddr_err));
1339 }else
1340 goto jebadhead;
1342 /* A free-form header; a_gethfield() did some verification already.. */
1343 else{
1344 struct n_header_field *hfp;
1345 ui32_t nl, bl;
1346 char const *nstart;
1348 for(nstart = cp = linebuf;; ++cp)
1349 if(!fieldnamechar(*cp))
1350 break;
1351 nl = (ui32_t)PTR2SIZE(cp - nstart);
1353 while(blankchar(*cp))
1354 ++cp;
1355 if(*cp++ != ':'){
1356 jebadhead:
1357 n_err(_("Ignoring header field: %s\n"), linebuf);
1358 continue;
1360 while(blankchar(*cp))
1361 ++cp;
1362 bl = (ui32_t)strlen(cp) +1;
1364 ++seenfields;
1365 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1366 ) + nl +1 + bl);
1367 hftail = &hfp->hf_next;
1368 hfp->hf_next = NULL;
1369 hfp->hf_nl = nl;
1370 hfp->hf_bl = bl - 1;
1371 memcpy(hfp->hf_dat, nstart, nl);
1372 hfp->hf_dat[nl++] = '\0';
1373 memcpy(hfp->hf_dat + nl, cp, bl);
1377 /* In case the blank line after the header has been edited out. Otherwise,
1378 * fetch the header separator */
1379 if (linebuf != NULL) {
1380 if (linebuf[0] != '\0') {
1381 for (cp = linebuf; *(++cp) != '\0';)
1383 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1384 } else {
1385 if ((c = getc(fp)) != '\n' && c != EOF)
1386 ungetc(c, fp);
1390 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1391 hp->h_to = hq->h_to;
1392 hp->h_cc = hq->h_cc;
1393 hp->h_bcc = hq->h_bcc;
1394 hp->h_from = hq->h_from;
1395 hp->h_reply_to = hq->h_reply_to;
1396 hp->h_sender = hq->h_sender;
1397 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1398 !(n_poption & n_PO_t_FLAG))
1399 hp->h_subject = hq->h_subject;
1400 hp->h_user_headers = hq->h_user_headers;
1402 if (n_psonce & n_PSO_t_FLAG) {
1403 hp->h_ref = hq->h_ref;
1404 hp->h_message_id = hq->h_message_id;
1405 hp->h_in_reply_to = hq->h_in_reply_to;
1406 hp->h_mft = hq->h_mft;
1408 /* And perform additional validity checks so that we don't bail later
1409 * on TODO this is good and the place where this should occur,
1410 * TODO unfortunately a lot of other places do again and blabla */
1411 if (hp->h_from == NULL)
1412 hp->h_from = n_poption_arg_r;
1413 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1414 hp->h_sender = lextract(ok_vlook(sender),
1415 GEXTRA | GFULL | GFULLEXTRA);
1417 } else
1418 n_err(_("Restoring deleted header lines\n"));
1420 if (linebuf != NULL)
1421 free(linebuf);
1422 NYD_LEAVE;
1425 FL char *
1426 hfield_mult(char const *field, struct message *mp, int mult)
1428 FILE *ibuf;
1429 struct str hfs;
1430 long lc;
1431 size_t linesize = 0; /* TODO line pool */
1432 char *linebuf = NULL, *colon;
1433 char const *hfield;
1434 NYD_ENTER;
1436 /* There are (spam) messages which have header bytes which are many KB when
1437 * joined, so resize a single heap storage until we are done if we shall
1438 * collect a field that may have multiple bodies; only otherwise use the
1439 * string dope directly */
1440 memset(&hfs, 0, sizeof hfs);
1442 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1443 goto jleave;
1444 if ((lc = mp->m_lines - 1) < 0)
1445 goto jleave;
1447 if ((mp->m_flag & MNOFROM) == 0 &&
1448 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1449 goto jleave;
1450 while (lc > 0) {
1451 if ((lc = a_gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1452 break;
1453 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1454 if (mult)
1455 n_str_add_buf(&hfs, hfield, strlen(hfield));
1456 else {
1457 hfs.s = savestr(hfield);
1458 break;
1463 jleave:
1464 if (linebuf != NULL)
1465 free(linebuf);
1466 if (mult && hfs.s != NULL) {
1467 colon = savestrbuf(hfs.s, hfs.l);
1468 free(hfs.s);
1469 hfs.s = colon;
1471 NYD_LEAVE;
1472 return hfs.s;
1475 FL char const *
1476 thisfield(char const *linebuf, char const *field)
1478 char const *rv = NULL;
1479 NYD2_ENTER;
1481 while (lowerconv(*linebuf) == lowerconv(*field)) {
1482 ++linebuf;
1483 ++field;
1485 if (*field != '\0')
1486 goto jleave;
1488 while (blankchar(*linebuf))
1489 ++linebuf;
1490 if (*linebuf++ != ':')
1491 goto jleave;
1493 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1494 ++linebuf;
1495 rv = linebuf;
1496 jleave:
1497 NYD2_LEAVE;
1498 return rv;
1501 FL char *
1502 nameof(struct message *mp, int reptype)
1504 char *cp, *cp2;
1505 NYD_ENTER;
1507 cp = skin(name1(mp, reptype));
1508 if (reptype != 0 || charcount(cp, '!') < 2)
1509 goto jleave;
1510 cp2 = strrchr(cp, '!');
1511 --cp2;
1512 while (cp2 > cp && *cp2 != '!')
1513 --cp2;
1514 if (*cp2 == '!')
1515 cp = cp2 + 1;
1516 jleave:
1517 NYD_LEAVE;
1518 return cp;
1521 FL char const *
1522 skip_comment(char const *cp)
1524 size_t nesting;
1525 NYD_ENTER;
1527 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1528 switch (*cp) {
1529 case '\\':
1530 if (cp[1])
1531 ++cp;
1532 break;
1533 case '(':
1534 ++nesting;
1535 break;
1536 case ')':
1537 --nesting;
1538 break;
1541 NYD_LEAVE;
1542 return cp;
1545 FL char const *
1546 routeaddr(char const *name)
1548 char const *np, *rp = NULL;
1549 NYD_ENTER;
1551 for (np = name; *np; np++) {
1552 switch (*np) {
1553 case '(':
1554 np = skip_comment(np + 1) - 1;
1555 break;
1556 case '"':
1557 while (*np) {
1558 if (*++np == '"')
1559 break;
1560 if (*np == '\\' && np[1])
1561 np++;
1563 break;
1564 case '<':
1565 rp = np;
1566 break;
1567 case '>':
1568 goto jleave;
1571 rp = NULL;
1572 jleave:
1573 NYD_LEAVE;
1574 return rp;
1577 FL enum expand_addr_flags
1578 expandaddr_to_eaf(void)
1580 struct eafdesc {
1581 char const *eafd_name;
1582 bool_t eafd_is_target;
1583 ui8_t eafd_andoff;
1584 ui8_t eafd_or;
1585 } const eafa[] = {
1586 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1587 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1588 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1589 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1590 {"file", TRU1, EAF_NONE, EAF_FILE},
1591 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1592 {"name", TRU1, EAF_NONE, EAF_NAME},
1593 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1594 }, *eafp;
1596 char *buf;
1597 enum expand_addr_flags rv;
1598 char const *cp;
1599 NYD2_ENTER;
1601 if ((cp = ok_vlook(expandaddr)) == NULL)
1602 rv = EAF_RESTRICT_TARGETS;
1603 else if (*cp == '\0')
1604 rv = EAF_TARGET_MASK;
1605 else {
1606 rv = EAF_TARGET_MASK;
1608 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1609 bool_t minus;
1611 if ((minus = (*cp == '-')) || *cp == '+')
1612 ++cp;
1613 for (eafp = eafa;; ++eafp) {
1614 if (eafp == eafa + n_NELEM(eafa)) {
1615 if (n_poption & n_PO_D_V)
1616 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1617 break;
1618 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1619 if (!minus) {
1620 rv &= ~eafp->eafd_andoff;
1621 rv |= eafp->eafd_or;
1622 } else {
1623 if (eafp->eafd_is_target)
1624 rv &= ~eafp->eafd_or;
1625 else if (n_poption & n_PO_D_V)
1626 n_err(_("minus - prefix invalid for *expandaddr* value: "
1627 "%s\n"), --cp);
1629 break;
1630 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1631 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1632 rv &= ~EAF_NAME;
1633 break;
1638 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1639 (n_poption & n_PO_TILDE_FLAG)))
1640 rv |= EAF_TARGET_MASK;
1641 else if(n_poption & n_PO_D_V){
1642 if(!(rv & EAF_TARGET_MASK))
1643 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1644 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1645 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1648 NYD2_LEAVE;
1649 return rv;
1652 FL si8_t
1653 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1655 char cbuf[sizeof "'\\U12340'"];
1656 char const *cs;
1657 int f;
1658 si8_t rv;
1659 enum expand_addr_flags eaf;
1660 NYD_ENTER;
1662 eaf = expandaddr_to_eaf();
1663 f = np->n_flags;
1665 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1666 if (eaf & EAF_FAILINVADDR)
1667 rv = -rv;
1669 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1671 } else {
1672 ui32_t c;
1673 char const *fmt = "'\\x%02X'";
1674 bool_t ok8bit = TRU1;
1676 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1677 cs = _("Invalid domain name: %s, character %s\n");
1678 fmt = "'\\U%04X'";
1679 ok8bit = FAL0;
1680 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1681 cs = _("%s contains invalid %s sequence\n");
1682 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1683 cs = _("%s is an invalid alias name\n");
1684 } else
1685 cs = _("%s contains invalid byte %s\n");
1687 c = NAME_ADDRSPEC_ERR_GETWC(f);
1688 snprintf(cbuf, sizeof cbuf,
1689 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1690 goto jprint;
1692 goto jleave;
1695 /* *expandaddr* stuff */
1696 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1697 goto jleave;
1699 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1700 if (eaf & EAF_FAIL)
1701 rv = -rv;
1702 cs = _("%s%s: file or pipe addressees not allowed here\n");
1703 if (eacm & EACM_NOLOG)
1704 goto jleave;
1705 else
1706 goto j0print;
1709 eaf |= (eacm & EAF_TARGET_MASK);
1710 if (eacm & EACM_NONAME)
1711 eaf &= ~EAF_NAME;
1713 if (eaf == EAF_NONE) {
1714 rv = FAL0;
1715 goto jleave;
1717 if (eaf & EAF_FAIL)
1718 rv = -rv;
1720 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1721 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1722 if (eacm & EACM_NOLOG)
1723 goto jleave;
1724 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1725 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1726 if (eacm & EACM_NOLOG)
1727 goto jleave;
1728 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1729 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1730 if (eacm & EACM_NOLOG)
1731 goto jleave;
1732 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1733 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1734 if (eacm & EACM_NOLOG)
1735 goto jleave;
1736 } else {
1737 rv = FAL0;
1738 goto jleave;
1741 j0print:
1742 cbuf[0] = '\0';
1743 jprint:
1744 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1745 jleave:
1746 NYD_LEAVE;
1747 return rv;
1750 FL char *
1751 skin(char const *name)
1753 struct n_addrguts ag;
1754 char *rv;
1755 NYD_ENTER;
1757 if(name != NULL){
1758 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1759 rv = ag.ag_skinned;
1760 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1761 rv = savestrbuf(rv, ag.ag_slen);
1762 }else
1763 rv = NULL;
1764 NYD_LEAVE;
1765 return rv;
1768 /* TODO addrspec_with_guts: RFC 5322
1769 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1770 FL char const *
1771 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1772 bool_t issingle_hack){
1773 char const *cp;
1774 char *cp2, *bufend, *nbuf, c;
1775 enum{
1776 a_NONE,
1777 a_GOTLT = 1<<0,
1778 a_GOTADDR = 1<<1,
1779 a_GOTSPACE = 1<<2,
1780 a_LASTSP = 1<<3
1781 } flags;
1782 NYD_ENTER;
1784 memset(agp, 0, sizeof *agp);
1786 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1787 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1788 agp->ag_slen = 0;
1789 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1790 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1791 goto jleave;
1792 }else if(!doskin){
1793 /*agp->ag_iaddr_start = 0;*/
1794 agp->ag_iaddr_aend = agp->ag_ilen;
1795 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1796 agp->ag_slen = agp->ag_ilen;
1797 agp->ag_n_flags = NAME_SKINNED;
1798 goto jcheck;
1801 flags = a_NONE;
1802 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1803 /*agp->ag_iaddr_start = 0;*/
1804 cp2 = bufend = nbuf;
1806 /* TODO This is complete crap and should use a token parser */
1807 for(cp = name++; (c = *cp++) != '\0';){
1808 switch (c) {
1809 case '(':
1810 cp = skip_comment(cp);
1811 flags &= ~a_LASTSP;
1812 break;
1813 case '"':
1814 /* Start of a "quoted-string". Copy it in its entirety */
1815 /* XXX RFC: quotes are "semantically invisible"
1816 * XXX But it was explicitly added (Changelog.Heirloom,
1817 * XXX [9.23] released 11/15/00, "Do not remove quotes
1818 * XXX when skinning names"? No more info.. */
1819 *cp2++ = c;
1820 while ((c = *cp) != '\0') { /* TODO improve */
1821 ++cp;
1822 if (c == '"') {
1823 *cp2++ = c;
1824 break;
1826 if (c != '\\')
1827 *cp2++ = c;
1828 else if ((c = *cp) != '\0') {
1829 *cp2++ = c;
1830 ++cp;
1833 flags &= ~a_LASTSP;
1834 break;
1835 case ' ':
1836 case '\t':
1837 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1838 flags |= a_GOTSPACE;
1839 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1841 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1842 cp += 3, *cp2++ = '@';
1843 else if (cp[0] == '@' && blankchar(cp[1]))
1844 cp += 2, *cp2++ = '@';
1845 else
1846 flags |= a_LASTSP;
1847 break;
1848 case '<':
1849 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1850 cp2 = bufend;
1851 flags &= ~(a_GOTSPACE | a_LASTSP);
1852 flags |= a_GOTLT | a_GOTADDR;
1853 break;
1854 case '>':
1855 if(flags & a_GOTLT){
1856 /* (_addrspec_check() verifies these later!) */
1857 flags &= ~(a_GOTLT | a_LASTSP);
1858 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1860 /* Skip over the entire remaining field */
1861 while((c = *cp) != '\0' && c != ','){
1862 ++cp;
1863 if (c == '(')
1864 cp = skip_comment(cp);
1865 else if (c == '"')
1866 while ((c = *cp) != '\0') {
1867 ++cp;
1868 if (c == '"')
1869 break;
1870 if (c == '\\' && *cp != '\0')
1871 ++cp;
1874 break;
1876 /* FALLTHRU */
1877 default:
1878 if(flags & a_LASTSP){
1879 flags &= ~a_LASTSP;
1880 if(flags & a_GOTADDR)
1881 *cp2++ = ' ';
1883 *cp2++ = c;
1884 /* This character is forbidden here, but it may nonetheless be
1885 * present: ensure we turn this into something valid! (E.g., if the
1886 * next character would be a "..) */
1887 if(c == '\\' && *cp != '\0')
1888 *cp2++ = *cp++;
1889 if(c == ',' && !issingle_hack){
1890 if(!(flags & a_GOTLT)){
1891 *cp2++ = ' ';
1892 for(; blankchar(*cp); ++cp)
1894 flags &= ~a_LASTSP;
1895 bufend = cp2;
1897 }else if(!(flags & a_GOTADDR)){
1898 flags |= a_GOTADDR;
1899 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1903 --name;
1904 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1905 if (agp->ag_iaddr_aend == 0)
1906 agp->ag_iaddr_aend = agp->ag_ilen;
1907 /* Misses > */
1908 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1909 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1910 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1911 agp->ag_iaddr_aend = agp->ag_ilen;
1912 cp2[agp->ag_ilen++] = '>';
1913 cp2[agp->ag_ilen] = '\0';
1915 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1916 n_lofi_free(nbuf);
1917 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1918 jcheck:
1919 if(a_head_addrspec_check(agp, doskin) <= FAL0)
1920 name = NULL;
1921 else
1922 name = agp->ag_input;
1923 jleave:
1924 NYD_LEAVE;
1925 return name;
1928 FL char *
1929 realname(char const *name)
1931 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1932 char *rname, *rp;
1933 struct str in, out;
1934 int quoted, good, nogood;
1935 NYD_ENTER;
1937 if ((cp = n_UNCONST(name)) == NULL)
1938 goto jleave;
1939 for (; *cp != '\0'; ++cp) {
1940 switch (*cp) {
1941 case '(':
1942 if (cstart != NULL) {
1943 /* More than one comment in address, doesn't make sense to display
1944 * it without context. Return the entire field */
1945 cp = mime_fromaddr(name);
1946 goto jleave;
1948 cstart = cp++;
1949 cp = skip_comment(cp);
1950 cend = cp--;
1951 if (cend <= cstart)
1952 cend = cstart = NULL;
1953 break;
1954 case '"':
1955 while (*cp) {
1956 if (*++cp == '"')
1957 break;
1958 if (*cp == '\\' && cp[1])
1959 ++cp;
1961 break;
1962 case '<':
1963 if (cp > name) {
1964 cstart = name;
1965 cend = cp;
1967 break;
1968 case ',':
1969 /* More than one address. Just use the first one */
1970 goto jbrk;
1974 jbrk:
1975 if (cstart == NULL) {
1976 if (*name == '<') {
1977 /* If name contains only a route-addr, the surrounding angle brackets
1978 * don't serve any useful purpose when displaying, so remove */
1979 cp = prstr(skin(name));
1980 } else
1981 cp = mime_fromaddr(name);
1982 goto jleave;
1985 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1986 * not stripped. The idea is to strip only syntactical relevant things (but
1987 * this is not necessarily the most sensible way in practice) */
1988 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1989 quoted = 0;
1990 for (cp = cstart; cp < cend; ++cp) {
1991 if (*cp == '(' && !quoted) {
1992 cq = skip_comment(++cp);
1993 if (PTRCMP(--cq, >, cend))
1994 cq = cend;
1995 while (cp < cq) {
1996 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1997 ++cp;
1998 *rp++ = *cp++;
2000 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
2001 *rp++ = *++cp;
2002 else if (*cp == '"') {
2003 quoted = !quoted;
2004 continue;
2005 } else
2006 *rp++ = *cp;
2008 *rp = '\0';
2009 in.s = rname;
2010 in.l = rp - rname;
2011 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
2012 ac_free(rname);
2013 rname = savestr(out.s);
2014 free(out.s);
2016 while (blankchar(*rname))
2017 ++rname;
2018 for (rp = rname; *rp != '\0'; ++rp)
2020 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
2021 *rp = '\0';
2022 if (rp == rname) {
2023 cp = mime_fromaddr(name);
2024 goto jleave;
2027 /* mime_fromhdr() has converted all nonprintable characters to question
2028 * marks now. These and blanks are considered uninteresting; if the
2029 * displayed part of the real name contains more than 25% of them, it is
2030 * probably better to display the plain email address instead */
2031 good = 0;
2032 nogood = 0;
2033 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
2034 if (*rp == '?' || blankchar(*rp))
2035 ++nogood;
2036 else
2037 ++good;
2038 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
2039 jleave:
2040 NYD_LEAVE;
2041 return n_UNCONST(cp);
2044 FL char *
2045 name1(struct message *mp, int reptype)
2047 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
2048 size_t namesize, linesize = 0;
2049 FILE *ibuf;
2050 int f1st = 1;
2051 NYD_ENTER;
2053 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
2054 goto jleave;
2055 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
2056 goto jleave;
2058 namebuf = smalloc(namesize = 1);
2059 namebuf[0] = 0;
2060 if (mp->m_flag & MNOFROM)
2061 goto jout;
2062 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2063 goto jout;
2064 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2065 goto jout;
2067 jnewname:
2068 if (namesize <= linesize)
2069 namebuf = srealloc(namebuf, namesize = linesize +1);
2070 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
2072 for (; blankchar(*cp); ++cp)
2074 for (cp2 = namebuf + strlen(namebuf);
2075 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
2076 *cp2++ = *cp++;
2077 *cp2 = '\0';
2079 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2080 goto jout;
2081 if ((cp = strchr(linebuf, 'F')) == NULL)
2082 goto jout;
2083 if (strncmp(cp, "From", 4))
2084 goto jout;
2085 if (namesize <= linesize)
2086 namebuf = srealloc(namebuf, namesize = linesize + 1);
2088 while ((cp = strchr(cp, 'r')) != NULL) {
2089 if (!strncmp(cp, "remote", 6)) {
2090 if ((cp = strchr(cp, 'f')) == NULL)
2091 break;
2092 if (strncmp(cp, "from", 4) != 0)
2093 break;
2094 if ((cp = strchr(cp, ' ')) == NULL)
2095 break;
2096 cp++;
2097 if (f1st) {
2098 strncpy(namebuf, cp, namesize);
2099 f1st = 0;
2100 } else {
2101 cp2 = strrchr(namebuf, '!') + 1;
2102 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
2104 namebuf[namesize - 2] = '!';
2105 namebuf[namesize - 1] = '\0';
2106 goto jnewname;
2108 cp++;
2110 jout:
2111 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2112 *cp == '\0')
2113 cp = savestr(namebuf);
2115 if (linebuf != NULL)
2116 free(linebuf);
2117 free(namebuf);
2118 jleave:
2119 NYD_LEAVE;
2120 return cp;
2123 FL char const *
2124 subject_re_trim(char const *s){
2125 struct{
2126 ui8_t len;
2127 char dat[7];
2128 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2129 {3, "re:"},
2130 {3, "aw:"}, {5, "antw:"}, /* de */
2131 {3, "wg:"}, /* Seen too often in the wild */
2132 {0, ""}
2135 bool_t any;
2136 char *re_st, *re_st_x;
2137 char const *orig_s;
2138 size_t re_l;
2139 NYD_ENTER;
2141 any = FAL0;
2142 orig_s = s;
2143 re_st = NULL;
2144 n_UNINIT(re_l, 0);
2146 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2147 (re_l = strlen(re_st_x)) > 0){
2148 re_st = n_lofi_alloc(++re_l * 2);
2149 memcpy(re_st, re_st_x, re_l);
2152 jouter:
2153 while(*s != '\0'){
2154 while(spacechar(*s))
2155 ++s;
2157 for(pp = ignored; pp->len > 0; ++pp)
2158 if(is_asccaseprefix(pp->dat, s)){
2159 s += pp->len;
2160 any = TRU1;
2161 goto jouter;
2164 if(re_st != NULL){
2165 char *cp;
2167 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2168 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2169 if(is_asccaseprefix(cp, s)){
2170 s += strlen(cp);
2171 any = TRU1;
2172 goto jouter;
2175 break;
2178 if(re_st != NULL)
2179 n_lofi_free(re_st);
2180 NYD_LEAVE;
2181 return any ? s : orig_s;
2184 FL int
2185 msgidcmp(char const *s1, char const *s2)
2187 int q1 = 0, q2 = 0, c1, c2;
2188 NYD_ENTER;
2190 while(*s1 == '<')
2191 ++s1;
2192 while(*s2 == '<')
2193 ++s2;
2195 do {
2196 c1 = msgidnextc(&s1, &q1);
2197 c2 = msgidnextc(&s2, &q2);
2198 if (c1 != c2)
2199 break;
2200 } while (c1 && c2);
2201 NYD_LEAVE;
2202 return c1 - c2;
2205 FL char const *
2206 fakefrom(struct message *mp)
2208 char const *name;
2209 NYD_ENTER;
2211 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2212 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2213 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2214 * RFC 4155 however requires a RFC 5322 (2822) conforming
2215 * "addr-spec", but we simply can't provide that */
2216 name = "MAILER-DAEMON";
2217 NYD_LEAVE;
2218 return name;
2221 FL char const *
2222 fakedate(time_t t)
2224 char *cq, *cp;
2225 int i;
2226 struct tm *tmp;
2227 NYD_ENTER;
2229 /* Problem is that t may be invalid for the representation of ctime(3),
2230 * which indeed is asctime(localtime(t)); musl libc says for asctime(3):
2231 * ISO C requires us to use the above format string,
2232 * even if it will not fit in the buffer. Thus asctime_r
2233 * is _supposed_ to crash if the fields in tm are too large.
2234 * We follow this behavior and crash "gracefully" to warn
2235 * application developers that they may not be so lucky
2236 * on other implementations (e.g. stack smashing..).
2237 * So we need to be a bit careful or the libc kills us */
2238 jredo:
2239 if((tmp = localtime(&t)) == NULL){
2240 /* TODO error log */
2241 t = 0;
2242 goto jredo;
2245 i = 64;
2246 jredo2:
2247 cq = n_lofi_alloc(i);
2248 if(snprintf(cq, i, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
2249 n_weekday_names[tmp->tm_wday], n_month_names[tmp->tm_mon],
2250 tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
2251 tmp->tm_year + 1900) >= i){
2252 n_lofi_free(cq);
2253 i <<= 1;
2254 goto jredo2;
2257 for(cp = cq; *cp != '\0' && *cp != '\n'; ++cp)
2259 *cp = '\0';
2260 cp = savestr(cq);
2262 n_lofi_free(cq);
2263 NYD_LEAVE;
2264 return cp;
2267 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2268 FL time_t
2269 unixtime(char const *fromline)
2271 char const *fp, *xp;
2272 time_t t, t2;
2273 si32_t i, year, month, day, hour, minute, second, tzdiff;
2274 struct tm *tmptr;
2275 NYD2_ENTER;
2277 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2279 fp -= 24;
2280 if (PTR2SIZE(fp - fromline) < 7)
2281 goto jinvalid;
2282 if (fp[3] != ' ')
2283 goto jinvalid;
2284 for (i = 0;;) {
2285 if (!strncmp(fp + 4, n_month_names[i], 3))
2286 break;
2287 if (n_month_names[++i][0] == '\0')
2288 goto jinvalid;
2290 month = i + 1;
2291 if (fp[7] != ' ')
2292 goto jinvalid;
2293 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2294 if (*xp != ' ' || xp != fp + 10)
2295 goto jinvalid;
2296 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2297 if (*xp != ':' || xp != fp + 13)
2298 goto jinvalid;
2299 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2300 if (*xp != ':' || xp != fp + 16)
2301 goto jinvalid;
2302 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2303 if (*xp != ' ' || xp != fp + 19)
2304 goto jinvalid;
2305 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2306 if (xp != fp + 24)
2307 goto jinvalid;
2308 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2309 goto jinvalid;
2310 if((t2 = mktime(gmtime(&t))) == (time_t)-1)
2311 goto jinvalid;
2312 tzdiff = t - t2;
2313 if((tmptr = localtime(&t)) == NULL)
2314 goto jinvalid;
2315 if (tmptr->tm_isdst > 0)
2316 tzdiff += 3600;
2317 t -= tzdiff;
2318 jleave:
2319 NYD2_LEAVE;
2320 return t;
2321 jinvalid:
2322 t = n_time_epoch();
2323 goto jleave;
2325 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2327 FL time_t
2328 rfctime(char const *date) /* TODO n_idec_ return tests */
2330 char const *cp, *x;
2331 time_t t;
2332 si32_t i, year, month, day, hour, minute, second;
2333 NYD2_ENTER;
2335 cp = date;
2337 if ((cp = nexttoken(cp)) == NULL)
2338 goto jinvalid;
2339 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2340 cp[3] == ',') {
2341 if ((cp = nexttoken(&cp[4])) == NULL)
2342 goto jinvalid;
2344 n_idec_si32_cp(&day, cp, 10, &x);
2345 if ((cp = nexttoken(x)) == NULL)
2346 goto jinvalid;
2347 for (i = 0;;) {
2348 if (!strncmp(cp, n_month_names[i], 3))
2349 break;
2350 if (n_month_names[++i][0] == '\0')
2351 goto jinvalid;
2353 month = i + 1;
2354 if ((cp = nexttoken(&cp[3])) == NULL)
2355 goto jinvalid;
2356 /* RFC 5322, 4.3:
2357 * Where a two or three digit year occurs in a date, the year is to be
2358 * interpreted as follows: If a two digit year is encountered whose
2359 * value is between 00 and 49, the year is interpreted by adding 2000,
2360 * ending up with a value between 2000 and 2049. If a two digit year
2361 * is encountered with a value between 50 and 99, or any three digit
2362 * year is encountered, the year is interpreted by adding 1900 */
2363 n_idec_si32_cp(&year, cp, 10, &x);
2364 i = (int)PTR2SIZE(x - cp);
2365 if (i == 2 && year >= 0 && year <= 49)
2366 year += 2000;
2367 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2368 year += 1900;
2369 if ((cp = nexttoken(x)) == NULL)
2370 goto jinvalid;
2371 n_idec_si32_cp(&hour, cp, 10, &x);
2372 if (*x != ':')
2373 goto jinvalid;
2374 cp = &x[1];
2375 n_idec_si32_cp(&minute, cp, 10, &x);
2376 if (*x == ':') {
2377 cp = &x[1];
2378 n_idec_si32_cp(&second, cp, 10, &x);
2379 } else
2380 second = 0;
2382 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2383 goto jinvalid;
2384 if ((cp = nexttoken(x)) != NULL) {
2385 char buf[3];
2386 int sign = 1;
2388 switch (*cp) {
2389 case '+':
2390 sign = -1;
2391 /* FALLTHRU */
2392 case '-':
2393 ++cp;
2394 break;
2396 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2397 digitchar(cp[3])) {
2398 si64_t tadj;
2400 buf[2] = '\0';
2401 buf[0] = cp[0];
2402 buf[1] = cp[1];
2403 n_idec_si32_cp(&i, buf, 10, NULL);
2404 tadj = (si64_t)i * 3600; /* XXX */
2405 buf[0] = cp[2];
2406 buf[1] = cp[3];
2407 n_idec_si32_cp(&i, buf, 10, NULL);
2408 tadj += (si64_t)i * 60; /* XXX */
2409 if (sign < 0)
2410 tadj = -tadj;
2411 t += (time_t)tadj;
2413 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2414 * TODO once again, Christos Zoulas and NetBSD Mail have done
2415 * TODO a really good job already, but using strptime(3), which
2416 * TODO is not portable. Nonetheless, WE must improve, not
2417 * TODO at last because we simply ignore obsolete timezones!!
2418 * TODO See RFC 5322, 4.3! */
2420 jleave:
2421 NYD2_LEAVE;
2422 return t;
2423 jinvalid:
2424 t = 0;
2425 goto jleave;
2428 FL time_t
2429 combinetime(int year, int month, int day, int hour, int minute, int second){
2430 size_t const jdn_epoch = 2440588;
2431 bool_t const y2038p = (sizeof(time_t) == 4);
2433 size_t jdn;
2434 time_t t;
2435 NYD2_ENTER;
2437 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2438 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2439 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2440 day < 1 || day > 31 ||
2441 month < 1 || month > 12 ||
2442 year < 1970)
2443 goto jerr;
2445 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2446 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2447 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2448 * test by stepping second-wise around the flip. Don't care otherwise */
2449 if(!y2038p)
2450 goto jerr;
2451 if(year > 2038 || month > 1 || day > 19 ||
2452 hour > 3 || minute > 14 || second > 7)
2453 goto jerr;
2456 t = second;
2457 t += minute * n_DATE_SECSMIN;
2458 t += hour * n_DATE_SECSHOUR;
2460 jdn = a_head_gregorian_to_jdn(year, month, day);
2461 jdn -= jdn_epoch;
2462 t += (time_t)jdn * n_DATE_SECSDAY;
2463 jleave:
2464 NYD2_LEAVE;
2465 return t;
2466 jerr:
2467 t = (time_t)-1;
2468 goto jleave;
2471 FL void
2472 substdate(struct message *m)
2474 char const *cp;
2475 NYD_ENTER;
2477 /* Determine the date to print in faked 'From ' lines. This is traditionally
2478 * the date the message was written to the mail file. Try to determine this
2479 * using RFC message header fields, or fall back to current time */
2480 m->m_time = 0;
2481 if ((cp = hfield1("received", m)) != NULL) {
2482 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2484 ++cp;
2485 while (alnumchar(*cp));
2487 if (cp && *++cp)
2488 m->m_time = rfctime(cp);
2490 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2491 if ((cp = hfield1("date", m)) != NULL)
2492 m->m_time = rfctime(cp);
2494 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2495 m->m_time = time_current.tc_time;
2496 NYD_LEAVE;
2499 FL void
2500 setup_from_and_sender(struct header *hp)
2502 char const *addr;
2503 struct name *np;
2504 NYD_ENTER;
2506 /* If -t parsed or composed From: then take it. With -t we otherwise
2507 * want -r to be honoured in favour of *from* in order to have
2508 * a behaviour that is compatible with what users would expect from e.g.
2509 * postfix(1) */
2510 if ((np = hp->h_from) != NULL ||
2511 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2513 } else if ((addr = myaddrs(hp)) != NULL)
2514 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2515 hp->h_from = np;
2517 if ((np = hp->h_sender) != NULL) {
2519 } else if ((addr = ok_vlook(sender)) != NULL)
2520 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2521 hp->h_sender = np;
2523 NYD_LEAVE;
2526 FL struct name const *
2527 check_from_and_sender(struct name const *fromfield,
2528 struct name const *senderfield)
2530 struct name const *rv = NULL;
2531 NYD_ENTER;
2533 if (senderfield != NULL) {
2534 if (senderfield->n_flink != NULL) {
2535 n_err(_("The Sender: field may contain only one address\n"));
2536 goto jleave;
2538 rv = senderfield;
2541 if (fromfield != NULL) {
2542 if (fromfield->n_flink != NULL && senderfield == NULL) {
2543 n_err(_("A Sender: is required when there are multiple "
2544 "addresses in From:\n"));
2545 goto jleave;
2547 if (rv == NULL)
2548 rv = fromfield;
2551 if (rv == NULL)
2552 rv = (struct name*)0x1;
2553 jleave:
2554 NYD_LEAVE;
2555 return rv;
2558 #ifdef HAVE_XSSL
2559 FL char *
2560 getsender(struct message *mp)
2562 char *cp;
2563 struct name *np;
2564 NYD_ENTER;
2566 if ((cp = hfield1("from", mp)) == NULL ||
2567 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2568 cp = NULL;
2569 else
2570 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2571 NYD_LEAVE;
2572 return cp;
2574 #endif
2576 FL int
2577 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2578 int subjfirst)
2580 /* TODO grab_headers: again, check counts etc. against RFC;
2581 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2582 int errs;
2583 int volatile comma;
2584 NYD_ENTER;
2586 errs = 0;
2587 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2589 if (gflags & GTO)
2590 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2591 if (subjfirst && (gflags & GSUBJECT))
2592 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2593 if (gflags & GCC)
2594 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2595 if (gflags & GBCC)
2596 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2598 if (gflags & GEXTRA) {
2599 if (hp->h_from == NULL)
2600 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2601 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2602 GEXTRA | GFULL | GFULLEXTRA);
2603 if (hp->h_reply_to == NULL) {
2604 struct name *v15compat;
2606 if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
2607 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2608 hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
2609 if(hp->h_reply_to == NULL) /* v15 */
2610 hp->h_reply_to = v15compat;
2612 hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
2613 GEXTRA | GFULL);
2614 if (hp->h_sender == NULL)
2615 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2616 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2617 GEXTRA | GFULL);
2620 if (!subjfirst && (gflags & GSUBJECT))
2621 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2623 NYD_LEAVE;
2624 return errs;
2627 FL bool_t
2628 n_header_match(struct message *mp, struct search_expr const *sep){
2629 struct str fiter, in, out;
2630 char const *field;
2631 long lc;
2632 FILE *ibuf;
2633 size_t *linesize;
2634 char **linebuf, *colon;
2635 enum {a_NONE, a_ALL, a_ITER, a_RE} match;
2636 bool_t rv;
2637 NYD_ENTER;
2639 rv = FAL0;
2640 match = a_NONE;
2641 linebuf = &termios_state.ts_linebuf; /* XXX line pool */
2642 linesize = &termios_state.ts_linesize; /* XXX line pool */
2643 n_UNINIT(fiter.l, 0);
2644 n_UNINIT(fiter.s, NULL);
2646 if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2647 goto jleave;
2648 if((lc = mp->m_lines - 1) < 0)
2649 goto jleave;
2651 if((mp->m_flag & MNOFROM) == 0 &&
2652 readline_restart(ibuf, linebuf, linesize, 0) < 0)
2653 goto jleave;
2655 /* */
2656 if((field = sep->ss_field) != NULL){
2657 if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
2658 match = a_ALL;
2659 else{
2660 fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
2661 match = a_ITER;
2663 #ifdef HAVE_REGEX
2664 }else if(sep->ss_fieldre != NULL){
2665 match = a_RE;
2666 #endif
2667 }else
2668 match = a_ALL;
2670 /* Iterate over all the headers */
2671 while(lc > 0){
2672 struct name *np;
2674 if((lc = a_gethfield(ibuf, linebuf, linesize, lc, &colon)) <= 0)
2675 break;
2677 /* Is this a header we are interested in? */
2678 if(match == a_ITER){
2679 char *itercp;
2681 memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
2682 while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
2683 /* It may be an abbreviation */
2684 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
2685 size_t i;
2686 char c1;
2688 if(field[0] != '\0' && field[1] == '\0'){
2689 c1 = lowerconv(field[0]);
2690 for(i = 0; i < n_NELEM(x); ++i){
2691 if(c1 == x[i][0]){
2692 field = x[i];
2693 break;
2698 if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
2699 break;
2701 if(field == NULL)
2702 continue;
2703 #ifdef HAVE_REGEX
2704 }else if(match == a_RE){
2705 char *cp;
2706 size_t i;
2708 i = PTR2SIZE(colon - *linebuf);
2709 cp = n_lofi_alloc(i +1);
2710 memcpy(cp, *linebuf, i);
2711 cp[i] = '\0';
2712 i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
2713 n_lofi_free(cp);
2714 if(!i)
2715 continue;
2716 #endif
2719 /* It could be a plain existence test */
2720 if(sep->ss_field_exists){
2721 rv = TRU1;
2722 break;
2725 /* Need to check the body */
2726 while(blankchar(*++colon))
2728 in.s = colon;
2730 /* Shall we split into address list and match as/the addresses only?
2731 * TODO at some later time we should ignore and log efforts to search
2732 * TODO a skinned address list if we know the header has none such */
2733 if(sep->ss_skin){
2734 if((np = lextract(in.s, GSKIN)) == NULL)
2735 continue;
2736 out.s = np->n_name;
2737 }else{
2738 np = NULL;
2739 in.l = strlen(in.s);
2740 mime_fromhdr(&in, &out, TD_ICONV);
2743 jnext_name:
2744 #ifdef HAVE_REGEX
2745 if(sep->ss_bodyre != NULL)
2746 rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
2747 else
2748 #endif
2749 rv = substr(out.s, sep->ss_body);
2751 if(np == NULL)
2752 free(out.s);
2753 if(rv)
2754 break;
2755 if(np != NULL && (np = np->n_flink) != NULL){
2756 out.s = np->n_name;
2757 goto jnext_name;
2761 jleave:
2762 if(match == a_ITER)
2763 n_lofi_free(fiter.s);
2764 NYD_LEAVE;
2765 return rv;
2768 FL struct n_header_field *
2769 n_customhdr_query(void){
2770 char const *vp;
2771 struct n_header_field *rv, **tail, *hfp;
2772 NYD_ENTER;
2774 rv = NULL;
2776 if((vp = ok_vlook(customhdr)) != NULL){
2777 char *buf;
2779 tail = &rv;
2780 buf = savestr(vp);
2781 jch_outer:
2782 while((vp = n_strsep_esc(&buf, ',', TRU1)) != NULL){
2783 ui32_t nl, bl;
2784 char const *nstart, *cp;
2786 for(nstart = cp = vp;; ++cp){
2787 if(fieldnamechar(*cp))
2788 continue;
2789 if(*cp == '\0'){
2790 if(cp == nstart){
2791 n_err(_("Invalid nameless *customhdr* entry\n"));
2792 goto jch_outer;
2794 }else if(*cp != ':' && !blankchar(*cp)){
2795 jch_badent:
2796 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2797 goto jch_outer;
2799 break;
2801 nl = (ui32_t)PTR2SIZE(cp - nstart);
2803 while(blankchar(*cp))
2804 ++cp;
2805 if(*cp++ != ':')
2806 goto jch_badent;
2807 while(blankchar(*cp))
2808 ++cp;
2809 bl = (ui32_t)strlen(cp) +1;
2811 *tail =
2812 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2813 nl +1 + bl);
2814 tail = &hfp->hf_next;
2815 hfp->hf_next = NULL;
2816 hfp->hf_nl = nl;
2817 hfp->hf_bl = bl - 1;
2818 memcpy(hfp->hf_dat, nstart, nl);
2819 hfp->hf_dat[nl++] = '\0';
2820 memcpy(hfp->hf_dat + nl, cp, bl);
2823 NYD_LEAVE;
2824 return rv;
2827 /* s-it-mode */