collect(): ~[FfMmUu]: should default to the "dot" (Andrew Gee)
[s-mailx.git] / head.c
blob42e688674a45b3a2f267ebe0d6183091ddb2c9cd
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 #include <pwd.h>
45 struct cmatch_data {
46 size_t tlen; /* Length of .tdata */
47 char const *tdata; /* Template date - see _cmatch_data[] */
50 /* Template characters for cmatch_data.tdata:
51 * 'A' An upper case char
52 * 'a' A lower case char
53 * ' ' A space
54 * '0' A digit
55 * 'O' An optional digit or space
56 * ':' A colon
57 * '+' Either a plus or a minus sign */
58 static struct cmatch_data const _cmatch_data[] = {
59 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
60 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
61 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
62 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
63 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
64 * in the wild (by UW-imap) */
65 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
66 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
67 * zone strings; note that 1. is strictly speaking not correct as some
68 * letters are not used, and 2. is not because only "UT" is defined */
69 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
70 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
71 { 0, NULL }
73 #define a_HEAD_DATE_MINLEN 21
75 /* Skip over "word" as found in From_ line */
76 static char const * _from__skipword(char const *wp);
78 /* Match the date string against the date template (tp), return if match.
79 * See _cmatch_data[] for template character description */
80 static int _cmatch(size_t len, char const *date,
81 char const *tp);
83 /* Check whether date is a valid 'From_' date.
84 * (Rather ctime(3) generated dates, according to RFC 4155) */
85 static int _is_date(char const *date);
87 /* JulianDayNumber converter(s) */
88 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
89 #if 0
90 static void a_head_jdn_to_gregorian(size_t jdn,
91 ui32_t *yp, ui32_t *mp, ui32_t *dp);
92 #endif
94 /* Convert the domain part of a skinned address to IDNA.
95 * If an error occurs before Unicode information is available, revert the IDNA
96 * error to a normal CHAR one so that the error message doesn't talk Unicode */
97 #ifdef HAVE_IDNA
98 static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp);
99 #endif
101 /* Classify and check a (possibly skinned) header body according to RFC
102 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
103 * also a file or a pipe command, so check that first, then.
104 * Otherwise perform content checking and isolate the domain part (for IDNA).
105 * issingle_hack has the same meaning as for n_addrspec_with_guts() */
106 static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned,
107 bool_t issingle_hack);
109 /* Return the next header field found in the given message.
110 * Return >= 0 if something found, < 0 elsewise.
111 * "colon" is set to point to the colon in the header.
112 * Must deal with \ continuations & other such fraud */
113 static long a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem,
114 char **colon);
116 static int msgidnextc(char const **cp, int *status);
118 /* Count the occurances of c in str */
119 static int charcount(char *str, int c);
121 static char const * nexttoken(char const *cp);
123 static char const *
124 _from__skipword(char const *wp)
126 char c = 0;
127 NYD2_ENTER;
129 if (wp != NULL) {
130 while ((c = *wp++) != '\0' && !blankchar(c)) {
131 if (c == '"') {
132 while ((c = *wp++) != '\0' && c != '"')
134 if (c != '"')
135 --wp;
138 for (; blankchar(c); c = *wp++)
141 NYD2_LEAVE;
142 return (c == 0 ? NULL : wp - 1);
145 static int
146 _cmatch(size_t len, char const *date, char const *tp)
148 int ret = 0;
149 NYD2_ENTER;
151 while (len--) {
152 char c = date[len];
153 switch (tp[len]) {
154 case 'a':
155 if (!lowerchar(c))
156 goto jleave;
157 break;
158 case 'A':
159 if (!upperchar(c))
160 goto jleave;
161 break;
162 case ' ':
163 if (c != ' ')
164 goto jleave;
165 break;
166 case '0':
167 if (!digitchar(c))
168 goto jleave;
169 break;
170 case 'O':
171 if (c != ' ' && !digitchar(c))
172 goto jleave;
173 break;
174 case ':':
175 if (c != ':')
176 goto jleave;
177 break;
178 case '+':
179 if (c != '+' && c != '-')
180 goto jleave;
181 break;
184 ret = 1;
185 jleave:
186 NYD2_LEAVE;
187 return ret;
190 static int
191 _is_date(char const *date)
193 struct cmatch_data const *cmdp;
194 size_t dl;
195 int rv = 0;
196 NYD2_ENTER;
198 if ((dl = strlen(date)) >= a_HEAD_DATE_MINLEN)
199 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
200 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
201 break;
202 NYD2_LEAVE;
203 return rv;
206 static size_t
207 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
208 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
209 * (via third hand, plus adjustments).
210 * This algorithm is supposed to work for all dates in between 1582-10-15
211 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
212 size_t jdn;
213 NYD2_ENTER;
215 #if 0
216 if(y == 0)
217 y = 1;
218 if(m == 0)
219 m = 1;
220 if(d == 0)
221 d = 1;
222 #endif
224 if(m > 2)
225 m -= 3;
226 else{
227 m += 9;
228 --y;
230 jdn = y;
231 jdn /= 100;
232 y -= 100 * jdn;
233 y *= 1461;
234 y >>= 2;
235 jdn *= 146097;
236 jdn >>= 2;
237 jdn += y;
238 jdn += d;
239 jdn += 1721119;
240 m *= 153;
241 m += 2;
242 m /= 5;
243 jdn += m;
244 NYD2_LEAVE;
245 return jdn;
248 #if 0
249 static void
250 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
251 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
252 * (via third hand, plus adjustments) */
253 size_t y, x;
254 NYD2_ENTER;
256 jdn -= 1721119;
257 jdn <<= 2;
258 --jdn;
259 y = jdn / 146097;
260 jdn %= 146097;
261 jdn |= 3;
262 y *= 100;
263 y += jdn / 1461;
264 jdn %= 1461;
265 jdn += 4;
266 jdn >>= 2;
267 x = jdn;
268 jdn <<= 2;
269 jdn += x;
270 jdn -= 3;
271 x = jdn / 153; /* x -> month */
272 jdn %= 153;
273 jdn += 5;
274 jdn /= 5; /* jdn -> day */
275 if(x < 10)
276 x += 3;
277 else{
278 x -= 9;
279 ++y;
282 *yp = (ui32_t)(y & 0xFFFF);
283 *mp = (ui32_t)(x & 0xFF);
284 *dp = (ui32_t)(jdn & 0xFF);
285 NYD2_LEAVE;
287 #endif /* 0 */
289 #ifdef HAVE_IDNA
290 static struct n_addrguts *
291 a_head_idna_apply(struct n_addrguts *agp){
292 struct n_string idna_ascii;
293 NYD_ENTER;
295 n_string_creat_auto(&idna_ascii);
297 if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
298 agp->ag_slen - agp->ag_sdom_start))
299 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
300 else{
301 /* Replace the domain part of .ag_skinned with IDNA version */
302 n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
304 agp->ag_skinned = n_string_cp(&idna_ascii);
305 agp->ag_slen = idna_ascii.s_len;
306 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
307 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
309 NYD_LEAVE;
310 return agp;
312 #endif /* HAVE_IDNA */
314 static bool_t
315 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned,
316 bool_t issingle_hack)
318 char *addr, *p;
319 union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
320 enum{
321 a_NONE,
322 a_IDNA_ENABLE = 1u<<0,
323 a_IDNA_APPLY = 1u<<1,
324 a_REDO_NODE_AFTER_ADDR = 1u<<2,
325 a_RESET_MASK = a_IDNA_ENABLE | a_IDNA_APPLY | a_REDO_NODE_AFTER_ADDR,
326 a_IN_QUOTE = 1u<<8,
327 a_IN_AT = 1u<<9,
328 a_IN_DOMAIN = 1u<<10,
329 a_DOMAIN_V6 = 1u<<11,
330 a_DOMAIN_MASK = a_IN_DOMAIN | a_DOMAIN_V6
331 } flags;
332 NYD_ENTER;
334 flags = a_NONE;
335 #ifdef HAVE_IDNA
336 if(!ok_blook(idna_disable))
337 flags = a_IDNA_ENABLE;
338 #endif
340 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
341 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
342 goto jleave;
345 addr = agp->ag_skinned;
347 /* If the field is not a recipient, it cannot be a file or a pipe */
348 if (!skinned)
349 goto jaddr_check;
351 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
352 * grep the latter for the complete picture */
353 if (*addr == '|') {
354 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
355 goto jleave;
357 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
358 (addr[0] == '-' && addr[1] == '\0'))
359 goto jisfile;
360 if (memchr(addr, '@', agp->ag_slen) == NULL) {
361 if (*addr == '+')
362 goto jisfile;
363 for (p = addr; (c.c = *p); ++p) {
364 if (c.c == '!' || c.c == '%')
365 break;
366 if (c.c == '/') {
367 jisfile:
368 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
369 goto jleave;
374 jaddr_check:
375 /* TODO This is false. If super correct this should work on wide
376 * TODO characters, just in case (some bytes of) the ASCII set is (are)
377 * TODO shared; it may yet tear apart multibyte sequences, possibly.
378 * TODO All this should interact with mime_enc_mustquote(), too!
379 * TODO That is: once this is an object, we need to do this in a way
380 * TODO that it is valid for the wire format (instead)! */
381 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
382 * TODO Note this correlats with addrspec_with_guts() which is in front
383 * TODO of us and encapsulates (what it thinks is, sigh) the address
384 * TODO boundary. ALL THIS should be one object that knows how to deal */
385 flags &= a_RESET_MASK;
386 for (p = addr; (c.c = *p++) != '\0';) {
387 if (c.c == '"') {
388 flags ^= a_IN_QUOTE;
389 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
390 #ifdef HAVE_IDNA
391 if ((flags & (a_IN_DOMAIN | a_IDNA_ENABLE)) ==
392 (a_IN_DOMAIN | a_IDNA_ENABLE))
393 flags |= a_IDNA_APPLY;
394 else
395 #endif
396 break;
397 } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
398 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
399 break;
400 } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
401 /*EMPTY*/;
402 } else if (c.c == '\\' && *p != '\0') {
403 ++p;
404 } else if (c.c == '@') {
405 if(flags & a_IN_AT){
406 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
407 c.u);
408 goto jleave;
410 agp->ag_sdom_start = PTR2SIZE(p - addr);
411 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
412 flags &= ~a_DOMAIN_MASK;
413 flags |= (*p == '[') ? a_IN_AT | a_IN_DOMAIN | a_DOMAIN_V6
414 : a_IN_AT | a_IN_DOMAIN;
415 continue;
417 /* TODO This interferes with our alias handling, which allows :!
418 * TODO Update manual on support (search the several ALIASCOLON)! */
419 else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
420 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
421 c.c == '\\' || c.c == ',' || blankchar(c.c))
422 break;
423 flags &= ~a_IN_AT;
425 if (c.c != '\0') {
426 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
427 goto jleave;
430 /* If we do not think this is an address we may treat it as an alias name
431 * if and only if the original input is identical to the skinned version */
432 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR) &&
433 !strcmp(agp->ag_skinned, agp->ag_input)){
434 /* TODO This may be an UUCP address */
435 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
436 if(!n_alias_is_valid_name(agp->ag_input))
437 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
438 }else{
439 /* If we seem to know that this is an address. Ensure this is correct
440 * according to RFC 5322 TODO the entire address parser should be like
441 * TODO that for one, and then we should know whether structured or
442 * TODO unstructured, and just parse correctly overall!
443 * TODO In addition, this can be optimised a lot.
444 * TODO And it is far from perfect: it should not forget whether no
445 * TODO whitespace followed some snippet, and it was written hastily.
446 * TODO It is even wrong sometimes. Not only for strange cases */
447 struct a_token{
448 struct a_token *t_last;
449 struct a_token *t_next;
450 enum{
451 a_T_TATOM = 1u<<0,
452 a_T_TCOMM = 1u<<1,
453 a_T_TQUOTE = 1u<<2,
454 a_T_TADDR = 1u<<3,
455 a_T_TMASK = (1u<<4) - 1,
457 a_T_SPECIAL = 1u<<8 /* An atom actually needs to go TQUOTE */
458 } t_f;
459 ui8_t t__pad[4];
460 size_t t_start;
461 size_t t_end;
462 } *thead, *tcurr, *tp;
464 struct n_string ost, *ostp;
465 char const *cp, *cp1st, *cpmax, *xp;
466 void *lofi_snap;
468 /* Name and domain must be non-empty */
469 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
470 jeat:
471 c.c = '@';
472 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
473 goto jleave;
476 cp = agp->ag_input;
478 /* Nothing to do if there is only an address (in angle brackets) */
479 /* TODO This is wrong since we allow invalid constructs in local-part
480 * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
481 * TODO "abcd"@abc. Etc. */
482 if(agp->ag_iaddr_start == 0){
483 /* No @ seen? */
484 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
485 goto jeat;
486 if(agp->ag_iaddr_aend == agp->ag_ilen)
487 goto jleave;
488 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
489 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
490 cp[agp->ag_iaddr_aend] == '>'){
491 /* No @ seen? Possibly insert n_nodename() */
492 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
493 cp = &agp->ag_input[agp->ag_iaddr_start];
494 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
495 goto jinsert_domain;
497 goto jleave;
500 /* It is not, so parse off all tokens, then resort and rejoin */
501 lofi_snap = n_lofi_snap_create();
503 cp1st = cp;
504 if((c.ui32 = agp->ag_iaddr_start) > 0)
505 --c.ui32;
506 cpmax = &cp[c.ui32];
508 thead = tcurr = NULL;
509 jnode_redo:
510 for(tp = NULL; cp < cpmax;){
511 switch((c.c = *cp)){
512 case '(':
513 if(tp != NULL)
514 tp->t_end = PTR2SIZE(cp - cp1st);
515 tp = n_lofi_alloc(sizeof *tp);
516 tp->t_next = NULL;
517 if((tp->t_last = tcurr) != NULL)
518 tcurr->t_next = tp;
519 else
520 thead = tp;
521 tcurr = tp;
522 tp->t_f = a_T_TCOMM;
523 tp->t_start = PTR2SIZE(++cp - cp1st);
524 xp = skip_comment(cp);
525 tp->t_end = PTR2SIZE(xp - cp1st);
526 cp = xp;
527 if(tp->t_end > tp->t_start){
528 if(xp[-1] == ')')
529 --tp->t_end;
530 else{
531 /* No closing comment - strip trailing whitespace */
532 while(blankchar(*--xp))
533 if(--tp->t_end == tp->t_start)
534 break;
537 tp = NULL;
538 break;
540 case '"':
541 if(tp != NULL)
542 tp->t_end = PTR2SIZE(cp - cp1st);
543 tp = n_lofi_alloc(sizeof *tp);
544 tp->t_next = NULL;
545 if((tp->t_last = tcurr) != NULL)
546 tcurr->t_next = tp;
547 else
548 thead = tp;
549 tcurr = tp;
550 tp->t_f = a_T_TQUOTE;
551 tp->t_start = PTR2SIZE(++cp - cp1st);
552 for(xp = cp; xp < cpmax; ++xp){
553 if((c.c = *xp) == '"')
554 break;
555 if(c.c == '\\' && xp[1] != '\0')
556 ++xp;
558 tp->t_end = PTR2SIZE(xp - cp1st);
559 cp = &xp[1];
560 if(tp->t_end > tp->t_start){
561 /* No closing quote - strip trailing whitespace */
562 if(*xp != '"'){
563 while(blankchar(*xp--))
564 if(--tp->t_end == tp->t_start)
565 break;
568 tp = NULL;
569 break;
571 default:
572 if(blankchar(c.c)){
573 if(tp != NULL)
574 tp->t_end = PTR2SIZE(cp - cp1st);
575 tp = NULL;
576 ++cp;
577 break;
580 if(tp == NULL){
581 tp = n_lofi_alloc(sizeof *tp);
582 tp->t_next = NULL;
583 if((tp->t_last = tcurr) != NULL)
584 tcurr->t_next = tp;
585 else
586 thead = tp;
587 tcurr = tp;
588 tp->t_f = a_T_TATOM;
589 tp->t_start = PTR2SIZE(cp - cp1st);
591 ++cp;
593 /* Reverse solidus transforms the following into a quoted-pair, and
594 * therefore (must occur in comment or quoted-string only) the
595 * entire atom into a quoted string */
596 if(c.c == '\\'){
597 tp->t_f |= a_T_SPECIAL;
598 if(cp < cpmax)
599 ++cp;
600 break;
603 /* Is this plain RFC 5322 "atext", or "specials"?
604 * TODO Because we don't know structured/unstructured, nor anything
605 * TODO else, we need to treat "dot-atom" as being identical to
606 * TODO "specials".
607 * However, if the 8th bit is set, this will be RFC 2047 converted
608 * and the entire sequence is skipped */
609 if(!(c.u & 0x80) && !alnumchar(c.c) &&
610 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
611 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
612 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
613 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
614 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
615 tp->t_f |= a_T_SPECIAL;
616 break;
619 if(tp != NULL)
620 tp->t_end = PTR2SIZE(cp - cp1st);
622 if(!(flags & a_REDO_NODE_AFTER_ADDR)){
623 flags |= a_REDO_NODE_AFTER_ADDR;
625 /* The local-part may be in quotes.. */
626 if((tp = tcurr) != NULL && (tp->t_f & a_T_TQUOTE) &&
627 tp->t_end == agp->ag_iaddr_start - 1){
628 /* ..so backward extend it, including the starting quote */
629 /* TODO This is false and the code below #if 0 away. We would
630 * TODO need to create a properly quoted local-part HERE AND NOW
631 * TODO and REPLACE the original data with that version, but the
632 * TODO current code cannot do that. The node needs the data,
633 * TODO not only offsets for that, for example. If we had all that
634 * TODO the code below could produce a really valid thing */
635 if(tp->t_start > 0)
636 --tp->t_start;
637 if(tp->t_start > 0 &&
638 (tp->t_last == NULL || tp->t_last->t_end < tp->t_start) &&
639 agp->ag_input[tp->t_start - 1] == '\\')
640 --tp->t_start;
641 tp->t_f = a_T_TADDR | a_T_SPECIAL;
642 }else{
643 tp = n_lofi_alloc(sizeof *tp);
644 tp->t_next = NULL;
645 if((tp->t_last = tcurr) != NULL)
646 tcurr->t_next = tp;
647 else
648 thead = tp;
649 tcurr = tp;
650 tp->t_f = a_T_TADDR;
651 tp->t_start = agp->ag_iaddr_start;
652 /* TODO Very special case because of our hacky non-object-based and
653 * TODO non-compliant address parser. Note */
654 if(tp->t_last == NULL && tp->t_start > 0)
655 tp->t_start = 0;
656 if(agp->ag_input[tp->t_start] == '<')
657 ++tp->t_start;
659 /* TODO Very special check for whether we need to massage the
660 * TODO local part. This is wrong, but otherwise even more so */
661 #if 0
662 cp = &agp->ag_input[tp->t_start];
663 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
664 while(cp < cpmax){
665 c.c = *cp++;
666 if(!(c.u & 0x80) && !alnumchar(c.c) &&
667 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
668 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
669 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
670 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
671 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~'){
672 tp->t_f |= a_T_SPECIAL;
673 break;
676 #endif
678 tp->t_end = agp->ag_iaddr_aend;
679 assert(tp->t_start <= tp->t_end);
680 tp = NULL;
682 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
683 cpmax = &agp->ag_input[agp->ag_ilen];
684 if(cp < cpmax)
685 goto jnode_redo;
688 /* Nothing may follow the address, move it to the end */
689 assert(tcurr != NULL);
690 if(tcurr != NULL && !(tcurr->t_f & a_T_TADDR)){
691 for(tp = thead; tp != NULL; tp = tp->t_next){
692 if(tp->t_f & a_T_TADDR){
693 if(tp->t_last != NULL)
694 tp->t_last->t_next = tp->t_next;
695 else
696 thead = tp->t_next;
697 if(tp->t_next != NULL)
698 tp->t_next->t_last = tp->t_last;
700 tcurr = tp;
701 while(tp->t_next != NULL)
702 tp = tp->t_next;
703 tp->t_next = tcurr;
704 tcurr->t_last = tp;
705 tcurr->t_next = NULL;
706 break;
711 /* Make ranges contiguous: ensure a continuous range of atoms is converted
712 * to a SPECIAL one if at least one of them requires it */
713 for(tp = thead; tp != NULL; tp = tp->t_next){
714 if(tp->t_f & a_T_SPECIAL){
715 tcurr = tp;
716 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
717 tp->t_f |= a_T_SPECIAL;
718 tp = tcurr;
719 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
720 tp->t_f |= a_T_SPECIAL;
721 if(tp == NULL)
722 break;
726 /* And yes, we want quotes to extend as much as possible */
727 for(tp = thead; tp != NULL; tp = tp->t_next){
728 if(tp->t_f & a_T_TQUOTE){
729 tcurr = tp;
730 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
731 tp->t_f |= a_T_SPECIAL;
732 tp = tcurr;
733 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
734 tp->t_f |= a_T_SPECIAL;
735 if(tp == NULL)
736 break;
740 /* Then rejoin */
741 ostp = n_string_creat_auto(&ost);
742 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
743 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
745 for(tcurr = thead; tcurr != NULL;){
746 if(tcurr != thead)
747 ostp = n_string_push_c(ostp, ' ');
748 if(tcurr->t_f & a_T_TADDR){
749 if(tcurr->t_last != NULL)
750 ostp = n_string_push_c(ostp, '<');
751 agp->ag_iaddr_start = ostp->s_len;
752 /* Now it is terrible to say, but if that thing contained
753 * quotes, then those may contain quoted-pairs! */
754 #if 0
755 if(!(tcurr->t_f & a_T_SPECIAL)){
756 #endif
757 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
758 (tcurr->t_end - tcurr->t_start));
759 #if 0
760 }else{
761 bool_t quot, esc;
763 ostp = n_string_push_c(ostp, '"');
764 quot = TRU1;
766 cp = &cp1st[tcurr->t_start];
767 cpmax = &cp1st[tcurr->t_end];
768 for(esc = FAL0; cp < cpmax;){
769 if((c.c = *cp++) == '\\' && !esc){
770 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
771 esc = TRU1;
772 }else{
773 if(esc || c.c == '"')
774 ostp = n_string_push_c(ostp, '\\');
775 else if(c.c == '@'){
776 ostp = n_string_push_c(ostp, '"');
777 quot = FAL0;
779 ostp = n_string_push_c(ostp, c.c);
780 esc = FAL0;
784 #endif
785 agp->ag_iaddr_aend = ostp->s_len;
787 if(tcurr->t_last != NULL)
788 ostp = n_string_push_c(ostp, '>');
789 tcurr = tcurr->t_next;
790 }else if(tcurr->t_f & a_T_TCOMM){
791 ostp = n_string_push_c(ostp, '(');
792 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
793 (tcurr->t_end - tcurr->t_start));
794 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
795 tcurr = tp;
796 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
797 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
798 (tcurr->t_end - tcurr->t_start));
800 ostp = n_string_push_c(ostp, ')');
801 tcurr = tcurr->t_next;
802 }else if(tcurr->t_f & a_T_TQUOTE){
803 jput_quote:
804 ostp = n_string_push_c(ostp, '"');
805 tp = tcurr;
806 do/* while tcurr && TATOM||TQUOTE */{
807 cp = &cp1st[tcurr->t_start];
808 cpmax = &cp1st[tcurr->t_end];
809 if(cp == cpmax)
810 continue;
812 if(tcurr != tp)
813 ostp = n_string_push_c(ostp, ' ');
815 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
816 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
817 else{
818 bool_t esc;
820 for(esc = FAL0; cp < cpmax;){
821 if((c.c = *cp++) == '\\' && !esc){
822 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
823 esc = TRU1;
824 }else{
825 if(esc || c.c == '"'){
826 jput_quote_esc:
827 ostp = n_string_push_c(ostp, '\\');
829 ostp = n_string_push_c(ostp, c.c);
830 esc = FAL0;
833 if(esc){
834 c.c = '\\';
835 goto jput_quote_esc;
838 }while((tcurr = tcurr->t_next) != NULL &&
839 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
840 ostp = n_string_push_c(ostp, '"');
841 }else if(tcurr->t_f & a_T_SPECIAL)
842 goto jput_quote;
843 else{
844 /* Can we use a fast join mode? */
845 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
846 if(!(tcurr->t_f & a_T_TATOM))
847 break;
848 if(tcurr != tp)
849 ostp = n_string_push_c(ostp, ' ');
850 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
851 (tcurr->t_end - tcurr->t_start));
856 n_lofi_snap_unroll(lofi_snap);
858 agp->ag_input = n_string_cp(ostp);
859 agp->ag_ilen = ostp->s_len;
860 /*ostp = n_string_drop_ownership(ostp);*/
862 /* Name and domain must be non-empty, the second */
863 cp = &agp->ag_input[agp->ag_iaddr_start];
864 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
865 if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
866 c.c = '@';
867 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
868 goto jleave;
871 addr = agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
873 /* TODO This parser is a mess. We do not know whether this is truly
874 * TODO valid, and all our checks are not truly RFC conforming.
875 * TODO Do check the skinned thing by itself once more, in order
876 * TODO to catch problems from reordering, e.g., this additional
877 * TODO test catches a final address without AT..
878 * TODO This is a plain copy+paste of the weird thing above, no care */
879 agp->ag_n_flags &= ~NAME_ADDRSPEC_ISADDR;
880 flags &= a_RESET_MASK;
881 for (p = addr; (c.c = *p++) != '\0';) {
882 if(c.c == '"')
883 flags ^= a_IN_QUOTE;
884 else if (c.u < 040 || c.u >= 0177) {
885 #ifdef HAVE_IDNA
886 if(!(flags & a_IN_DOMAIN))
887 #endif
888 break;
889 } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
890 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
891 break;
892 } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
893 /*EMPTY*/;
894 } else if (c.c == '\\' && *p != '\0') {
895 ++p;
896 } else if (c.c == '@') {
897 if(flags & a_IN_AT){
898 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
899 c.u);
900 goto jleave;
902 flags |= a_IN_AT;
903 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
904 flags &= ~a_DOMAIN_MASK;
905 flags |= (*p == '[') ? a_IN_DOMAIN | a_DOMAIN_V6 : a_IN_DOMAIN;
906 continue;
907 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
908 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
909 c.c == '\\' || c.c == ',' || blankchar(c.c))
910 break;
911 flags &= ~a_IN_AT;
913 if(c.c != '\0')
914 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
915 else if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
916 /* This is not an address, but if we had seen angle brackets convert
917 * it to a n_nodename() address if the name is a valid user */
918 jinsert_domain:
919 if(cp > &agp->ag_input[0] && cp[-1] == '<' &&
920 cpmax <= &agp->ag_input[agp->ag_ilen] && cpmax[0] == '>' &&
921 (!strcmp(addr, ok_vlook(LOGNAME)) || getpwnam(addr) != NULL)){
922 /* XXX However, if hostname is set to the empty string this
923 * XXX indicates that the used *mta* will perform the
924 * XXX auto-expansion instead. Not so with `addrcodec' though */
925 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR;
926 if(!issingle_hack &&
927 (cp = ok_vlook(hostname)) != NULL && *cp == '\0')
928 agp->ag_n_flags |= NAME_ADDRSPEC_WITHOUT_DOMAIN;
929 else{
930 c.ui32 = strlen(cp = n_nodename(TRU1));
931 /* This is yet IDNA converted.. */
932 ostp = n_string_creat_auto(&ost);
933 ostp = n_string_assign_buf(ostp, agp->ag_input, agp->ag_ilen);
934 ostp = n_string_insert_c(ostp, agp->ag_iaddr_aend++, '@');
935 ostp = n_string_insert_buf(ostp, agp->ag_iaddr_aend, cp,
936 c.ui32);
937 agp->ag_iaddr_aend += c.ui32;
938 agp->ag_input = n_string_cp(ostp);
939 agp->ag_ilen = ostp->s_len;
940 /*ostp = n_string_drop_ownership(ostp);*/
942 cp = &agp->ag_input[agp->ag_iaddr_start];
943 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
944 agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
946 }else
947 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
948 '@');
952 jleave:
953 #ifdef HAVE_IDNA
954 if(!(agp->ag_n_flags & NAME_ADDRSPEC_INVALID) && (flags & a_IDNA_APPLY))
955 agp = a_head_idna_apply(agp);
956 #endif
957 NYD_LEAVE;
958 return !(agp->ag_n_flags & NAME_ADDRSPEC_INVALID);
961 static long
962 a_gethfield(FILE *f, char **linebuf, size_t *linesize, long rem, char **colon)
964 char *line2 = NULL, *cp, *cp2;
965 size_t line2size = 0;
966 int c, isenc;
967 NYD2_ENTER;
969 if (*linebuf == NULL)
970 *linebuf = srealloc(*linebuf, *linesize = 1);
971 **linebuf = '\0';
972 for (;;) {
973 if (--rem < 0) {
974 rem = -1;
975 break;
977 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
978 rem = -1;
979 break;
981 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
983 if (cp > *linebuf)
984 while (blankchar(*cp))
985 ++cp;
986 if (*cp != ':' || cp == *linebuf)
987 continue;
989 /* I guess we got a headline. Handle wraparound */
990 *colon = cp;
991 cp = *linebuf + c;
992 for (;;) {
993 isenc = 0;
994 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
996 cp++;
997 if (rem <= 0)
998 break;
999 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
1000 isenc |= 1;
1001 ungetc(c = getc(f), f);
1002 if (!blankchar(c))
1003 break;
1004 c = readline_restart(f, &line2, &line2size, 0); /* TODO linepool! */
1005 if (c < 0)
1006 break;
1007 --rem;
1008 for (cp2 = line2; blankchar(*cp2); ++cp2)
1010 c -= (int)PTR2SIZE(cp2 - line2);
1011 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
1012 isenc |= 2;
1013 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
1014 size_t diff = PTR2SIZE(cp - *linebuf),
1015 colondiff = PTR2SIZE(*colon - *linebuf);
1016 *linebuf = srealloc(*linebuf, *linesize += c + 2);
1017 cp = &(*linebuf)[diff];
1018 *colon = &(*linebuf)[colondiff];
1020 if (isenc != 3)
1021 *cp++ = ' ';
1022 memcpy(cp, cp2, c);
1023 cp += c;
1025 *cp = '\0';
1027 if (line2 != NULL)
1028 free(line2);
1029 break;
1031 NYD2_LEAVE;
1032 return rem;
1035 static int
1036 msgidnextc(char const **cp, int *status)
1038 int c;
1039 NYD2_ENTER;
1041 assert(cp != NULL);
1042 assert(*cp != NULL);
1043 assert(status != NULL);
1045 for (;;) {
1046 if (*status & 01) {
1047 if (**cp == '"') {
1048 *status &= ~01;
1049 (*cp)++;
1050 continue;
1052 if (**cp == '\\') {
1053 (*cp)++;
1054 if (**cp == '\0')
1055 goto jeof;
1057 goto jdfl;
1059 switch (**cp) {
1060 case '(':
1061 *cp = skip_comment(&(*cp)[1]);
1062 continue;
1063 case '>':
1064 case '\0':
1065 jeof:
1066 c = '\0';
1067 goto jleave;
1068 case '"':
1069 (*cp)++;
1070 *status |= 01;
1071 continue;
1072 case '@':
1073 *status |= 02;
1074 /*FALLTHRU*/
1075 default:
1076 jdfl:
1077 c = *(*cp)++ & 0377;
1078 c = (*status & 02) ? lowerconv(c) : c;
1079 goto jleave;
1082 jleave:
1083 NYD2_LEAVE;
1084 return c;
1087 static int
1088 charcount(char *str, int c)
1090 char *cp;
1091 int i;
1092 NYD2_ENTER;
1094 for (i = 0, cp = str; *cp; ++cp)
1095 if (*cp == c)
1096 ++i;
1097 NYD2_LEAVE;
1098 return i;
1101 static char const *
1102 nexttoken(char const *cp)
1104 NYD2_ENTER;
1105 for (;;) {
1106 if (*cp == '\0') {
1107 cp = NULL;
1108 break;
1111 if (*cp == '(') {
1112 size_t nesting = 1;
1114 do switch (*++cp) {
1115 case '(':
1116 ++nesting;
1117 break;
1118 case ')':
1119 --nesting;
1120 break;
1121 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
1122 } else if (blankchar(*cp) || *cp == ',')
1123 ++cp;
1124 else
1125 break;
1127 NYD2_LEAVE;
1128 return cp;
1131 FL char const *
1132 myaddrs(struct header *hp) /* TODO */
1134 struct name *np;
1135 char const *rv, *mta;
1136 NYD_ENTER;
1138 if (hp != NULL && (np = hp->h_from) != NULL) {
1139 if ((rv = np->n_fullname) != NULL)
1140 goto jleave;
1141 if ((rv = np->n_name) != NULL)
1142 goto jleave;
1145 /* Verified once variable had been set */
1146 if((rv = ok_vlook(from)) != NULL)
1147 goto jleave;
1149 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1150 * undeterminable From: address. However, if the user sets *hostname*,
1151 * accept his desire */
1152 if (ok_vlook(hostname) != NULL)
1153 goto jnodename;
1154 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
1155 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1156 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
1157 goto jnodename;
1158 jleave:
1159 NYD_LEAVE;
1160 return rv;
1162 jnodename:{
1163 char *cp;
1164 char const *hn, *ln;
1165 size_t i;
1167 hn = n_nodename(TRU1);
1168 ln = ok_vlook(LOGNAME);
1169 i = strlen(ln) + strlen(hn) + 1 +1;
1170 rv = cp = salloc(i);
1171 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1173 goto jleave;
1176 FL char const *
1177 myorigin(struct header *hp) /* TODO */
1179 char const *rv = NULL, *ccp;
1180 struct name *np;
1181 NYD_ENTER;
1183 if((ccp = myaddrs(hp)) != NULL &&
1184 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1185 if(np->n_flink == NULL)
1186 rv = ccp;
1187 /* Verified upon variable set time */
1188 else if((ccp = ok_vlook(sender)) != NULL)
1189 rv = ccp;
1190 /* TODO why not else rv = n_poption_arg_r; ?? */
1192 NYD_LEAVE;
1193 return rv;
1196 FL bool_t
1197 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1199 char date[n_FROM_DATEBUF];
1200 bool_t rv;
1201 NYD2_ENTER;
1203 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1204 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1205 !_is_date(date)))
1206 rv = TRUM1;
1207 NYD2_LEAVE;
1208 return rv;
1211 FL int
1212 extract_date_from_from_(char const *line, size_t linelen,
1213 char datebuf[n_FROM_DATEBUF])
1215 int rv;
1216 char const *cp = line;
1217 NYD_ENTER;
1219 rv = 1;
1221 /* "From " */
1222 cp = _from__skipword(cp);
1223 if (cp == NULL)
1224 goto jerr;
1225 /* "addr-spec " */
1226 cp = _from__skipword(cp);
1227 if (cp == NULL)
1228 goto jerr;
1229 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
1230 (cp[2] == 'y' || cp[2] == 'Y')){
1231 cp = _from__skipword(cp);
1232 if (cp == NULL)
1233 goto jerr;
1235 /* It seems there are invalid MBOX archives in the wild, compare
1236 * . http://bugs.debian.org/624111
1237 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1238 * What they do is that they obfuscate the address to "name at host",
1239 * and even "name at host dot dom dot dom.
1240 * The [Aa][Tt] is also RFC 733, so be tolerant */
1241 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
1242 cp[2] == ' '){
1243 rv = -1;
1244 cp += 3;
1245 jat_dot:
1246 cp = _from__skipword(cp);
1247 if (cp == NULL)
1248 goto jerr;
1249 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
1250 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
1251 cp += 4;
1252 goto jat_dot;
1256 linelen -= PTR2SIZE(cp - line);
1257 if (linelen < a_HEAD_DATE_MINLEN)
1258 goto jerr;
1259 if (cp[linelen - 1] == '\n') {
1260 --linelen;
1261 /* (Rather IMAP/POP3 only) */
1262 if (cp[linelen - 1] == '\r')
1263 --linelen;
1264 if (linelen < a_HEAD_DATE_MINLEN)
1265 goto jerr;
1267 if (linelen >= n_FROM_DATEBUF)
1268 goto jerr;
1270 jleave:
1271 memcpy(datebuf, cp, linelen);
1272 datebuf[linelen] = '\0';
1273 NYD_LEAVE;
1274 return rv;
1275 jerr:
1276 cp = _("<Unknown date>");
1277 linelen = strlen(cp);
1278 if (linelen >= n_FROM_DATEBUF)
1279 linelen = n_FROM_DATEBUF;
1280 rv = 0;
1281 goto jleave;
1284 FL void
1285 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1287 /* See the prototype declaration for the hairy relationship of
1288 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1289 struct n_header_field **hftail;
1290 struct header nh, *hq = &nh;
1291 char *linebuf = NULL /* TODO line pool */, *colon;
1292 size_t linesize = 0, seenfields = 0;
1293 int c;
1294 long lc;
1295 char const *val, *cp;
1296 NYD_ENTER;
1298 memset(hq, 0, sizeof *hq);
1299 if ((n_psonce & n_PSO_t_FLAG) && (n_poption & n_PO_t_FLAG)) {
1300 hq->h_to = hp->h_to;
1301 hq->h_cc = hp->h_cc;
1302 hq->h_bcc = hp->h_bcc;
1304 hftail = &hq->h_user_headers;
1306 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1309 /* TODO yippieia, cat(check(lextract)) :-) */
1310 rewind(fp);
1311 while ((lc = a_gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1312 struct name *np;
1314 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1315 * yet expanded when we parse these! */
1316 if ((val = thisfield(linebuf, "to")) != NULL) {
1317 ++seenfields;
1318 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1319 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1320 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1321 ++seenfields;
1322 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1323 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1324 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1325 ++seenfields;
1326 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1327 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err));
1328 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1329 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1330 ++seenfields;
1331 hq->h_from = cat(hq->h_from,
1332 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1333 EACM_STRICT, NULL));
1335 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1336 ++seenfields;
1337 hq->h_reply_to = cat(hq->h_reply_to,
1338 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1339 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1340 if (!(n_psonce & n_PSO_t_FLAG) || (n_poption & n_PO_t_FLAG)) {
1341 ++seenfields;
1342 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1343 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1344 EACM_STRICT, NULL));
1345 } else
1346 goto jebadhead;
1347 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1348 (val = thisfield(linebuf, "subj")) != NULL) {
1349 ++seenfields;
1350 for (cp = val; blankchar(*cp); ++cp)
1352 hq->h_subject = (hq->h_subject != NULL)
1353 ? save2str(hq->h_subject, cp) : savestr(cp);
1355 /* The remaining are mostly hacked in and thus TODO -- at least in
1356 * TODO respect to their content checking */
1357 else if((val = thisfield(linebuf, "message-id")) != NULL){
1358 if(n_psonce & n_PSO_t_FLAG){
1359 np = checkaddrs(lextract(val, GREF),
1360 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1361 NULL);
1362 if (np == NULL || np->n_flink != NULL)
1363 goto jebadhead;
1364 ++seenfields;
1365 hq->h_message_id = np;
1366 }else
1367 goto jebadhead;
1368 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1369 if(n_psonce & n_PSO_t_FLAG){
1370 np = checkaddrs(lextract(val, GREF),
1371 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1372 NULL);
1373 ++seenfields;
1374 hq->h_in_reply_to = np;
1375 }else
1376 goto jebadhead;
1377 }else if((val = thisfield(linebuf, "references")) != NULL){
1378 if(n_psonce & n_PSO_t_FLAG){
1379 ++seenfields;
1380 /* TODO Limit number of references TODO better on parser side */
1381 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1382 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1383 NULL));
1384 }else
1385 goto jebadhead;
1387 /* and that is very hairy */
1388 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1389 if(n_psonce & n_PSO_t_FLAG){
1390 ++seenfields;
1391 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1392 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1393 checkaddr_err));
1394 }else
1395 goto jebadhead;
1397 /* A free-form header; a_gethfield() did some verification already.. */
1398 else{
1399 struct n_header_field *hfp;
1400 ui32_t nl, bl;
1401 char const *nstart;
1403 for(nstart = cp = linebuf;; ++cp)
1404 if(!fieldnamechar(*cp))
1405 break;
1406 nl = (ui32_t)PTR2SIZE(cp - nstart);
1408 while(blankchar(*cp))
1409 ++cp;
1410 if(*cp++ != ':'){
1411 jebadhead:
1412 n_err(_("Ignoring header field: %s\n"), linebuf);
1413 continue;
1415 while(blankchar(*cp))
1416 ++cp;
1417 bl = (ui32_t)strlen(cp) +1;
1419 ++seenfields;
1420 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1421 ) + nl +1 + bl);
1422 hftail = &hfp->hf_next;
1423 hfp->hf_next = NULL;
1424 hfp->hf_nl = nl;
1425 hfp->hf_bl = bl - 1;
1426 memcpy(hfp->hf_dat, nstart, nl);
1427 hfp->hf_dat[nl++] = '\0';
1428 memcpy(hfp->hf_dat + nl, cp, bl);
1432 /* In case the blank line after the header has been edited out. Otherwise,
1433 * fetch the header separator */
1434 if (linebuf != NULL) {
1435 if (linebuf[0] != '\0') {
1436 for (cp = linebuf; *(++cp) != '\0';)
1438 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1439 } else {
1440 if ((c = getc(fp)) != '\n' && c != EOF)
1441 ungetc(c, fp);
1445 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1446 hp->h_to = hq->h_to;
1447 hp->h_cc = hq->h_cc;
1448 hp->h_bcc = hq->h_bcc;
1449 hp->h_from = hq->h_from;
1450 hp->h_reply_to = hq->h_reply_to;
1451 hp->h_sender = hq->h_sender;
1452 if (hq->h_subject != NULL || !(n_psonce & n_PSO_t_FLAG) ||
1453 !(n_poption & n_PO_t_FLAG))
1454 hp->h_subject = hq->h_subject;
1455 hp->h_user_headers = hq->h_user_headers;
1457 if (n_psonce & n_PSO_t_FLAG) {
1458 hp->h_ref = hq->h_ref;
1459 hp->h_message_id = hq->h_message_id;
1460 hp->h_in_reply_to = hq->h_in_reply_to;
1461 hp->h_mft = hq->h_mft;
1463 /* And perform additional validity checks so that we don't bail later
1464 * on TODO this is good and the place where this should occur,
1465 * TODO unfortunately a lot of other places do again and blabla */
1466 if (hp->h_from == NULL)
1467 hp->h_from = n_poption_arg_r;
1468 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1469 hp->h_sender = lextract(ok_vlook(sender),
1470 GEXTRA | GFULL | GFULLEXTRA);
1472 } else
1473 n_err(_("Restoring deleted header lines\n"));
1475 if (linebuf != NULL)
1476 free(linebuf);
1477 NYD_LEAVE;
1480 FL char *
1481 hfield_mult(char const *field, struct message *mp, int mult)
1483 FILE *ibuf;
1484 struct str hfs;
1485 long lc;
1486 size_t linesize = 0; /* TODO line pool */
1487 char *linebuf = NULL, *colon;
1488 char const *hfield;
1489 NYD_ENTER;
1491 /* There are (spam) messages which have header bytes which are many KB when
1492 * joined, so resize a single heap storage until we are done if we shall
1493 * collect a field that may have multiple bodies; only otherwise use the
1494 * string dope directly */
1495 memset(&hfs, 0, sizeof hfs);
1497 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1498 goto jleave;
1499 if ((lc = mp->m_lines - 1) < 0)
1500 goto jleave;
1502 if ((mp->m_flag & MNOFROM) == 0 &&
1503 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1504 goto jleave;
1505 while (lc > 0) {
1506 if ((lc = a_gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1507 break;
1508 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1509 if (mult)
1510 n_str_add_buf(&hfs, hfield, strlen(hfield));
1511 else {
1512 hfs.s = savestr(hfield);
1513 break;
1518 jleave:
1519 if (linebuf != NULL)
1520 free(linebuf);
1521 if (mult && hfs.s != NULL) {
1522 colon = savestrbuf(hfs.s, hfs.l);
1523 free(hfs.s);
1524 hfs.s = colon;
1526 NYD_LEAVE;
1527 return hfs.s;
1530 FL char const *
1531 thisfield(char const *linebuf, char const *field)
1533 char const *rv = NULL;
1534 NYD2_ENTER;
1536 while (lowerconv(*linebuf) == lowerconv(*field)) {
1537 ++linebuf;
1538 ++field;
1540 if (*field != '\0')
1541 goto jleave;
1543 while (blankchar(*linebuf))
1544 ++linebuf;
1545 if (*linebuf++ != ':')
1546 goto jleave;
1548 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1549 ++linebuf;
1550 rv = linebuf;
1551 jleave:
1552 NYD2_LEAVE;
1553 return rv;
1556 FL char *
1557 nameof(struct message *mp, int reptype)
1559 char *cp, *cp2;
1560 NYD_ENTER;
1562 cp = skin(name1(mp, reptype));
1563 if (reptype != 0 || charcount(cp, '!') < 2)
1564 goto jleave;
1565 cp2 = strrchr(cp, '!');
1566 --cp2;
1567 while (cp2 > cp && *cp2 != '!')
1568 --cp2;
1569 if (*cp2 == '!')
1570 cp = cp2 + 1;
1571 jleave:
1572 NYD_LEAVE;
1573 return cp;
1576 FL char const *
1577 skip_comment(char const *cp)
1579 size_t nesting;
1580 NYD_ENTER;
1582 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1583 switch (*cp) {
1584 case '\\':
1585 if (cp[1])
1586 ++cp;
1587 break;
1588 case '(':
1589 ++nesting;
1590 break;
1591 case ')':
1592 --nesting;
1593 break;
1596 NYD_LEAVE;
1597 return cp;
1600 FL char const *
1601 routeaddr(char const *name)
1603 char const *np, *rp = NULL;
1604 NYD_ENTER;
1606 for (np = name; *np; np++) {
1607 switch (*np) {
1608 case '(':
1609 np = skip_comment(np + 1) - 1;
1610 break;
1611 case '"':
1612 while (*np) {
1613 if (*++np == '"')
1614 break;
1615 if (*np == '\\' && np[1])
1616 np++;
1618 break;
1619 case '<':
1620 rp = np;
1621 break;
1622 case '>':
1623 goto jleave;
1626 rp = NULL;
1627 jleave:
1628 NYD_LEAVE;
1629 return rp;
1632 FL enum expand_addr_flags
1633 expandaddr_to_eaf(void)
1635 struct eafdesc {
1636 char const *eafd_name;
1637 bool_t eafd_is_target;
1638 ui8_t eafd_andoff;
1639 ui8_t eafd_or;
1640 } const eafa[] = {
1641 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1642 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1643 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1644 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1645 {"file", TRU1, EAF_NONE, EAF_FILE},
1646 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1647 {"name", TRU1, EAF_NONE, EAF_NAME},
1648 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1649 }, *eafp;
1651 char *buf;
1652 enum expand_addr_flags rv;
1653 char const *cp;
1654 NYD2_ENTER;
1656 if ((cp = ok_vlook(expandaddr)) == NULL)
1657 rv = EAF_RESTRICT_TARGETS;
1658 else if (*cp == '\0')
1659 rv = EAF_TARGET_MASK;
1660 else {
1661 rv = EAF_TARGET_MASK;
1663 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1664 bool_t minus;
1666 if ((minus = (*cp == '-')) || *cp == '+')
1667 ++cp;
1668 for (eafp = eafa;; ++eafp) {
1669 if (eafp == eafa + n_NELEM(eafa)) {
1670 if (n_poption & n_PO_D_V)
1671 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1672 break;
1673 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1674 if (!minus) {
1675 rv &= ~eafp->eafd_andoff;
1676 rv |= eafp->eafd_or;
1677 } else {
1678 if (eafp->eafd_is_target)
1679 rv &= ~eafp->eafd_or;
1680 else if (n_poption & n_PO_D_V)
1681 n_err(_("minus - prefix invalid for *expandaddr* value: "
1682 "%s\n"), --cp);
1684 break;
1685 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1686 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1687 rv &= ~EAF_NAME;
1688 break;
1693 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1694 (n_poption & n_PO_TILDE_FLAG)))
1695 rv |= EAF_TARGET_MASK;
1696 else if(n_poption & n_PO_D_V){
1697 if(!(rv & EAF_TARGET_MASK))
1698 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1699 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1700 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1703 NYD2_LEAVE;
1704 return rv;
1707 FL si8_t
1708 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1710 char cbuf[sizeof "'\\U12340'"];
1711 char const *cs;
1712 int f;
1713 si8_t rv;
1714 enum expand_addr_flags eaf;
1715 NYD_ENTER;
1717 eaf = expandaddr_to_eaf();
1718 f = np->n_flags;
1720 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1721 if (eaf & EAF_FAILINVADDR)
1722 rv = -rv;
1724 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1726 } else {
1727 ui32_t c;
1728 char const *fmt = "'\\x%02X'";
1729 bool_t ok8bit = TRU1;
1731 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1732 cs = _("Invalid domain name: %s, character %s\n");
1733 fmt = "'\\U%04X'";
1734 ok8bit = FAL0;
1735 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1736 cs = _("%s contains invalid %s sequence\n");
1737 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1738 cs = _("%s is an invalid alias name\n");
1739 } else
1740 cs = _("%s contains invalid byte %s\n");
1742 c = NAME_ADDRSPEC_ERR_GETWC(f);
1743 snprintf(cbuf, sizeof cbuf,
1744 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1745 goto jprint;
1747 goto jleave;
1750 /* *expandaddr* stuff */
1751 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1752 goto jleave;
1754 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1755 if (eaf & EAF_FAIL)
1756 rv = -rv;
1757 cs = _("%s%s: file or pipe addressees not allowed here\n");
1758 if (eacm & EACM_NOLOG)
1759 goto jleave;
1760 else
1761 goto j0print;
1764 eaf |= (eacm & EAF_TARGET_MASK);
1765 if (eacm & EACM_NONAME)
1766 eaf &= ~EAF_NAME;
1768 if (eaf == EAF_NONE) {
1769 rv = FAL0;
1770 goto jleave;
1772 if (eaf & EAF_FAIL)
1773 rv = -rv;
1775 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1776 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1777 if (eacm & EACM_NOLOG)
1778 goto jleave;
1779 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1780 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1781 if (eacm & EACM_NOLOG)
1782 goto jleave;
1783 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1784 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1785 if (eacm & EACM_NOLOG)
1786 goto jleave;
1787 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1788 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1789 if (eacm & EACM_NOLOG)
1790 goto jleave;
1791 } else {
1792 rv = FAL0;
1793 goto jleave;
1796 j0print:
1797 cbuf[0] = '\0';
1798 jprint:
1799 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1800 jleave:
1801 NYD_LEAVE;
1802 return rv;
1805 FL char *
1806 skin(char const *name)
1808 struct n_addrguts ag;
1809 char *rv;
1810 NYD_ENTER;
1812 if(name != NULL){
1813 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1814 rv = ag.ag_skinned;
1815 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1816 rv = savestrbuf(rv, ag.ag_slen);
1817 }else
1818 rv = NULL;
1819 NYD_LEAVE;
1820 return rv;
1823 /* TODO addrspec_with_guts: RFC 5322
1824 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1825 FL char const *
1826 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1827 bool_t issingle_hack){
1828 char const *cp;
1829 char *cp2, *bufend, *nbuf, c;
1830 enum{
1831 a_NONE,
1832 a_GOTLT = 1<<0,
1833 a_GOTADDR = 1<<1,
1834 a_GOTSPACE = 1<<2,
1835 a_LASTSP = 1<<3
1836 } flags;
1837 NYD_ENTER;
1839 memset(agp, 0, sizeof *agp);
1841 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1842 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1843 agp->ag_slen = 0;
1844 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1845 goto jleave;
1846 }else if(!doskin){
1847 /*agp->ag_iaddr_start = 0;*/
1848 agp->ag_iaddr_aend = agp->ag_ilen;
1849 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1850 agp->ag_slen = agp->ag_ilen;
1851 agp->ag_n_flags = NAME_SKINNED;
1852 goto jcheck;
1855 flags = a_NONE;
1856 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1857 /*agp->ag_iaddr_start = 0;*/
1858 cp2 = bufend = nbuf;
1860 /* TODO This is complete crap and should use a token parser */
1861 for(cp = name++; (c = *cp++) != '\0';){
1862 switch (c) {
1863 case '(':
1864 cp = skip_comment(cp);
1865 flags &= ~a_LASTSP;
1866 break;
1867 case '"':
1868 /* Start of a "quoted-string". Copy it in its entirety */
1869 /* XXX RFC: quotes are "semantically invisible"
1870 * XXX But it was explicitly added (Changelog.Heirloom,
1871 * XXX [9.23] released 11/15/00, "Do not remove quotes
1872 * XXX when skinning names"? No more info.. */
1873 *cp2++ = c;
1874 while ((c = *cp) != '\0') { /* TODO improve */
1875 ++cp;
1876 if (c == '"') {
1877 *cp2++ = c;
1878 break;
1880 if (c != '\\')
1881 *cp2++ = c;
1882 else if ((c = *cp) != '\0') {
1883 *cp2++ = c;
1884 ++cp;
1887 flags &= ~a_LASTSP;
1888 break;
1889 case ' ':
1890 case '\t':
1891 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1892 flags |= a_GOTSPACE;
1893 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1895 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1896 cp += 3, *cp2++ = '@';
1897 else if (cp[0] == '@' && blankchar(cp[1]))
1898 cp += 2, *cp2++ = '@';
1899 else
1900 flags |= a_LASTSP;
1901 break;
1902 case '<':
1903 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1904 cp2 = bufend;
1905 flags &= ~(a_GOTSPACE | a_LASTSP);
1906 flags |= a_GOTLT | a_GOTADDR;
1907 break;
1908 case '>':
1909 if(flags & a_GOTLT){
1910 /* (_addrspec_check() verifies these later!) */
1911 flags &= ~(a_GOTLT | a_LASTSP);
1912 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1914 /* Skip over the entire remaining field */
1915 while((c = *cp) != '\0' && c != ','){
1916 ++cp;
1917 if (c == '(')
1918 cp = skip_comment(cp);
1919 else if (c == '"')
1920 while ((c = *cp) != '\0') {
1921 ++cp;
1922 if (c == '"')
1923 break;
1924 if (c == '\\' && *cp != '\0')
1925 ++cp;
1928 break;
1930 /* FALLTHRU */
1931 default:
1932 if(flags & a_LASTSP){
1933 flags &= ~a_LASTSP;
1934 if(flags & a_GOTADDR)
1935 *cp2++ = ' ';
1937 *cp2++ = c;
1938 /* This character is forbidden here, but it may nonetheless be
1939 * present: ensure we turn this into something valid! (E.g., if the
1940 * next character would be a "..) */
1941 if(c == '\\' && *cp != '\0')
1942 *cp2++ = *cp++;
1943 if(c == ',' && !issingle_hack){
1944 if(!(flags & a_GOTLT)){
1945 *cp2++ = ' ';
1946 for(; blankchar(*cp); ++cp)
1948 flags &= ~a_LASTSP;
1949 bufend = cp2;
1951 }else if(!(flags & a_GOTADDR)){
1952 flags |= a_GOTADDR;
1953 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1957 --name;
1958 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1959 if (agp->ag_iaddr_aend == 0)
1960 agp->ag_iaddr_aend = agp->ag_ilen;
1961 /* Misses > */
1962 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
1963 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
1964 memcpy(cp2, agp->ag_input, agp->ag_ilen);
1965 agp->ag_iaddr_aend = agp->ag_ilen;
1966 cp2[agp->ag_ilen++] = '>';
1967 cp2[agp->ag_ilen] = '\0';
1969 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1970 n_lofi_free(nbuf);
1971 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1972 jcheck:
1973 if(a_head_addrspec_check(agp, doskin, issingle_hack) <= FAL0)
1974 name = NULL;
1975 else
1976 name = agp->ag_input;
1977 jleave:
1978 NYD_LEAVE;
1979 return name;
1982 FL char *
1983 realname(char const *name)
1985 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1986 char *rname, *rp;
1987 struct str in, out;
1988 int quoted, good, nogood;
1989 NYD_ENTER;
1991 if ((cp = n_UNCONST(name)) == NULL)
1992 goto jleave;
1993 for (; *cp != '\0'; ++cp) {
1994 switch (*cp) {
1995 case '(':
1996 if (cstart != NULL) {
1997 /* More than one comment in address, doesn't make sense to display
1998 * it without context. Return the entire field */
1999 cp = mime_fromaddr(name);
2000 goto jleave;
2002 cstart = cp++;
2003 cp = skip_comment(cp);
2004 cend = cp--;
2005 if (cend <= cstart)
2006 cend = cstart = NULL;
2007 break;
2008 case '"':
2009 while (*cp) {
2010 if (*++cp == '"')
2011 break;
2012 if (*cp == '\\' && cp[1])
2013 ++cp;
2015 break;
2016 case '<':
2017 if (cp > name) {
2018 cstart = name;
2019 cend = cp;
2021 break;
2022 case ',':
2023 /* More than one address. Just use the first one */
2024 goto jbrk;
2028 jbrk:
2029 if (cstart == NULL) {
2030 if (*name == '<') {
2031 /* If name contains only a route-addr, the surrounding angle brackets
2032 * don't serve any useful purpose when displaying, so remove */
2033 cp = prstr(skin(name));
2034 } else
2035 cp = mime_fromaddr(name);
2036 goto jleave;
2039 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
2040 * not stripped. The idea is to strip only syntactical relevant things (but
2041 * this is not necessarily the most sensible way in practice) */
2042 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
2043 quoted = 0;
2044 for (cp = cstart; cp < cend; ++cp) {
2045 if (*cp == '(' && !quoted) {
2046 cq = skip_comment(++cp);
2047 if (PTRCMP(--cq, >, cend))
2048 cq = cend;
2049 while (cp < cq) {
2050 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
2051 ++cp;
2052 *rp++ = *cp++;
2054 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
2055 *rp++ = *++cp;
2056 else if (*cp == '"') {
2057 quoted = !quoted;
2058 continue;
2059 } else
2060 *rp++ = *cp;
2062 *rp = '\0';
2063 in.s = rname;
2064 in.l = rp - rname;
2065 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
2066 ac_free(rname);
2067 rname = savestr(out.s);
2068 free(out.s);
2070 while (blankchar(*rname))
2071 ++rname;
2072 for (rp = rname; *rp != '\0'; ++rp)
2074 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
2075 *rp = '\0';
2076 if (rp == rname) {
2077 cp = mime_fromaddr(name);
2078 goto jleave;
2081 /* mime_fromhdr() has converted all nonprintable characters to question
2082 * marks now. These and blanks are considered uninteresting; if the
2083 * displayed part of the real name contains more than 25% of them, it is
2084 * probably better to display the plain email address instead */
2085 good = 0;
2086 nogood = 0;
2087 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
2088 if (*rp == '?' || blankchar(*rp))
2089 ++nogood;
2090 else
2091 ++good;
2092 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
2093 jleave:
2094 NYD_LEAVE;
2095 return n_UNCONST(cp);
2098 FL char *
2099 name1(struct message *mp, int reptype)
2101 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
2102 size_t namesize, linesize = 0;
2103 FILE *ibuf;
2104 int f1st = 1;
2105 NYD_ENTER;
2107 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
2108 goto jleave;
2109 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
2110 goto jleave;
2112 namebuf = smalloc(namesize = 1);
2113 namebuf[0] = 0;
2114 if (mp->m_flag & MNOFROM)
2115 goto jout;
2116 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2117 goto jout;
2118 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2119 goto jout;
2121 jnewname:
2122 if (namesize <= linesize)
2123 namebuf = srealloc(namebuf, namesize = linesize +1);
2124 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
2126 for (; blankchar(*cp); ++cp)
2128 for (cp2 = namebuf + strlen(namebuf);
2129 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
2130 *cp2++ = *cp++;
2131 *cp2 = '\0';
2133 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2134 goto jout;
2135 if ((cp = strchr(linebuf, 'F')) == NULL)
2136 goto jout;
2137 if (strncmp(cp, "From", 4))
2138 goto jout;
2139 if (namesize <= linesize)
2140 namebuf = srealloc(namebuf, namesize = linesize + 1);
2142 while ((cp = strchr(cp, 'r')) != NULL) {
2143 if (!strncmp(cp, "remote", 6)) {
2144 if ((cp = strchr(cp, 'f')) == NULL)
2145 break;
2146 if (strncmp(cp, "from", 4) != 0)
2147 break;
2148 if ((cp = strchr(cp, ' ')) == NULL)
2149 break;
2150 cp++;
2151 if (f1st) {
2152 strncpy(namebuf, cp, namesize);
2153 f1st = 0;
2154 } else {
2155 cp2 = strrchr(namebuf, '!') + 1;
2156 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
2158 namebuf[namesize - 2] = '!';
2159 namebuf[namesize - 1] = '\0';
2160 goto jnewname;
2162 cp++;
2164 jout:
2165 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2166 *cp == '\0')
2167 cp = savestr(namebuf);
2169 if (linebuf != NULL)
2170 free(linebuf);
2171 free(namebuf);
2172 jleave:
2173 NYD_LEAVE;
2174 return cp;
2177 FL char const *
2178 subject_re_trim(char const *s){
2179 struct{
2180 ui8_t len;
2181 char dat[7];
2182 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2183 {3, "re:"},
2184 {3, "aw:"}, {5, "antw:"}, /* de */
2185 {3, "wg:"}, /* Seen too often in the wild */
2186 {0, ""}
2189 bool_t any;
2190 char *re_st, *re_st_x;
2191 char const *orig_s;
2192 size_t re_l;
2193 NYD_ENTER;
2195 any = FAL0;
2196 orig_s = s;
2197 re_st = NULL;
2198 n_UNINIT(re_l, 0);
2200 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2201 (re_l = strlen(re_st_x)) > 0){
2202 re_st = n_lofi_alloc(++re_l * 2);
2203 memcpy(re_st, re_st_x, re_l);
2206 jouter:
2207 while(*s != '\0'){
2208 while(spacechar(*s))
2209 ++s;
2211 for(pp = ignored; pp->len > 0; ++pp)
2212 if(is_asccaseprefix(pp->dat, s)){
2213 s += pp->len;
2214 any = TRU1;
2215 goto jouter;
2218 if(re_st != NULL){
2219 char *cp;
2221 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2222 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2223 if(is_asccaseprefix(cp, s)){
2224 s += strlen(cp);
2225 any = TRU1;
2226 goto jouter;
2229 break;
2232 if(re_st != NULL)
2233 n_lofi_free(re_st);
2234 NYD_LEAVE;
2235 return any ? s : orig_s;
2238 FL int
2239 msgidcmp(char const *s1, char const *s2)
2241 int q1 = 0, q2 = 0, c1, c2;
2242 NYD_ENTER;
2244 while(*s1 == '<')
2245 ++s1;
2246 while(*s2 == '<')
2247 ++s2;
2249 do {
2250 c1 = msgidnextc(&s1, &q1);
2251 c2 = msgidnextc(&s2, &q2);
2252 if (c1 != c2)
2253 break;
2254 } while (c1 && c2);
2255 NYD_LEAVE;
2256 return c1 - c2;
2259 FL char const *
2260 fakefrom(struct message *mp)
2262 char const *name;
2263 NYD_ENTER;
2265 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2266 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2267 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2268 * RFC 4155 however requires a RFC 5322 (2822) conforming
2269 * "addr-spec", but we simply can't provide that */
2270 name = "MAILER-DAEMON";
2271 NYD_LEAVE;
2272 return name;
2275 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2276 FL time_t
2277 unixtime(char const *fromline)
2279 char const *fp, *xp;
2280 time_t t, t2;
2281 si32_t i, year, month, day, hour, minute, second, tzdiff;
2282 struct tm *tmptr;
2283 NYD2_ENTER;
2285 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2287 fp -= 24;
2288 if (PTR2SIZE(fp - fromline) < 7)
2289 goto jinvalid;
2290 if (fp[3] != ' ')
2291 goto jinvalid;
2292 for (i = 0;;) {
2293 if (!strncmp(fp + 4, n_month_names[i], 3))
2294 break;
2295 if (n_month_names[++i][0] == '\0')
2296 goto jinvalid;
2298 month = i + 1;
2299 if (fp[7] != ' ')
2300 goto jinvalid;
2301 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2302 if (*xp != ' ' || xp != fp + 10)
2303 goto jinvalid;
2304 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2305 if (*xp != ':' || xp != fp + 13)
2306 goto jinvalid;
2307 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2308 if (*xp != ':' || xp != fp + 16)
2309 goto jinvalid;
2310 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2311 if (*xp != ' ' || xp != fp + 19)
2312 goto jinvalid;
2313 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2314 if (xp != fp + 24)
2315 goto jinvalid;
2316 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2317 goto jinvalid;
2318 if((t2 = mktime(gmtime(&t))) == (time_t)-1)
2319 goto jinvalid;
2320 tzdiff = t - t2;
2321 if((tmptr = localtime(&t)) == NULL)
2322 goto jinvalid;
2323 if (tmptr->tm_isdst > 0)
2324 tzdiff += 3600; /* TODO simply adding an hour for ISDST is .. buuh */
2325 t -= tzdiff;
2326 jleave:
2327 NYD2_LEAVE;
2328 return t;
2329 jinvalid:
2330 t = n_time_epoch();
2331 goto jleave;
2333 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2335 FL time_t
2336 rfctime(char const *date) /* TODO n_idec_ return tests */
2338 char const *cp, *x;
2339 time_t t;
2340 si32_t i, year, month, day, hour, minute, second;
2341 NYD2_ENTER;
2343 cp = date;
2345 if ((cp = nexttoken(cp)) == NULL)
2346 goto jinvalid;
2347 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2348 cp[3] == ',') {
2349 if ((cp = nexttoken(&cp[4])) == NULL)
2350 goto jinvalid;
2352 n_idec_si32_cp(&day, cp, 10, &x);
2353 if ((cp = nexttoken(x)) == NULL)
2354 goto jinvalid;
2355 for (i = 0;;) {
2356 if (!strncmp(cp, n_month_names[i], 3))
2357 break;
2358 if (n_month_names[++i][0] == '\0')
2359 goto jinvalid;
2361 month = i + 1;
2362 if ((cp = nexttoken(&cp[3])) == NULL)
2363 goto jinvalid;
2364 /* RFC 5322, 4.3:
2365 * Where a two or three digit year occurs in a date, the year is to be
2366 * interpreted as follows: If a two digit year is encountered whose
2367 * value is between 00 and 49, the year is interpreted by adding 2000,
2368 * ending up with a value between 2000 and 2049. If a two digit year
2369 * is encountered with a value between 50 and 99, or any three digit
2370 * year is encountered, the year is interpreted by adding 1900 */
2371 n_idec_si32_cp(&year, cp, 10, &x);
2372 i = (int)PTR2SIZE(x - cp);
2373 if (i == 2 && year >= 0 && year <= 49)
2374 year += 2000;
2375 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2376 year += 1900;
2377 if ((cp = nexttoken(x)) == NULL)
2378 goto jinvalid;
2379 n_idec_si32_cp(&hour, cp, 10, &x);
2380 if (*x != ':')
2381 goto jinvalid;
2382 cp = &x[1];
2383 n_idec_si32_cp(&minute, cp, 10, &x);
2384 if (*x == ':') {
2385 cp = &x[1];
2386 n_idec_si32_cp(&second, cp, 10, &x);
2387 } else
2388 second = 0;
2390 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2391 goto jinvalid;
2392 if ((cp = nexttoken(x)) != NULL) {
2393 char buf[3];
2394 int sign = 1;
2396 switch (*cp) {
2397 case '+':
2398 sign = -1;
2399 /* FALLTHRU */
2400 case '-':
2401 ++cp;
2402 break;
2404 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2405 digitchar(cp[3])) {
2406 si64_t tadj;
2408 buf[2] = '\0';
2409 buf[0] = cp[0];
2410 buf[1] = cp[1];
2411 n_idec_si32_cp(&i, buf, 10, NULL);
2412 tadj = (si64_t)i * 3600; /* XXX */
2413 buf[0] = cp[2];
2414 buf[1] = cp[3];
2415 n_idec_si32_cp(&i, buf, 10, NULL);
2416 tadj += (si64_t)i * 60; /* XXX */
2417 if (sign < 0)
2418 tadj = -tadj;
2419 t += (time_t)tadj;
2421 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2422 * TODO once again, Christos Zoulas and NetBSD Mail have done
2423 * TODO a really good job already, but using strptime(3), which
2424 * TODO is not portable. Nonetheless, WE must improve, not
2425 * TODO at last because we simply ignore obsolete timezones!!
2426 * TODO See RFC 5322, 4.3! */
2428 jleave:
2429 NYD2_LEAVE;
2430 return t;
2431 jinvalid:
2432 t = 0;
2433 goto jleave;
2436 FL time_t
2437 combinetime(int year, int month, int day, int hour, int minute, int second){
2438 size_t const jdn_epoch = 2440588;
2439 bool_t const y2038p = (sizeof(time_t) == 4);
2441 size_t jdn;
2442 time_t t;
2443 NYD2_ENTER;
2445 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2446 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2447 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2448 day < 1 || day > 31 ||
2449 month < 1 || month > 12 ||
2450 year < 1970)
2451 goto jerr;
2453 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2454 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2455 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2456 * test by stepping second-wise around the flip. Don't care otherwise */
2457 if(!y2038p)
2458 goto jerr;
2459 if(year > 2038 || month > 1 || day > 19 ||
2460 hour > 3 || minute > 14 || second > 7)
2461 goto jerr;
2464 t = second;
2465 t += minute * n_DATE_SECSMIN;
2466 t += hour * n_DATE_SECSHOUR;
2468 jdn = a_head_gregorian_to_jdn(year, month, day);
2469 jdn -= jdn_epoch;
2470 t += (time_t)jdn * n_DATE_SECSDAY;
2471 jleave:
2472 NYD2_LEAVE;
2473 return t;
2474 jerr:
2475 t = (time_t)-1;
2476 goto jleave;
2479 FL void
2480 substdate(struct message *m)
2482 char const *cp;
2483 NYD_ENTER;
2485 /* Determine the date to print in faked 'From ' lines. This is traditionally
2486 * the date the message was written to the mail file. Try to determine this
2487 * using RFC message header fields, or fall back to current time */
2488 m->m_time = 0;
2489 if ((cp = hfield1("received", m)) != NULL) {
2490 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2492 ++cp;
2493 while (alnumchar(*cp));
2495 if (cp && *++cp)
2496 m->m_time = rfctime(cp);
2498 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2499 if ((cp = hfield1("date", m)) != NULL)
2500 m->m_time = rfctime(cp);
2502 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2503 m->m_time = time_current.tc_time;
2504 NYD_LEAVE;
2507 FL void
2508 setup_from_and_sender(struct header *hp)
2510 char const *addr;
2511 struct name *np;
2512 NYD_ENTER;
2514 /* If -t parsed or composed From: then take it. With -t we otherwise
2515 * want -r to be honoured in favour of *from* in order to have
2516 * a behaviour that is compatible with what users would expect from e.g.
2517 * postfix(1) */
2518 if ((np = hp->h_from) != NULL ||
2519 ((n_psonce & n_PSO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2521 } else if ((addr = myaddrs(hp)) != NULL)
2522 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2523 hp->h_from = np;
2525 if ((np = hp->h_sender) != NULL) {
2527 } else if ((addr = ok_vlook(sender)) != NULL)
2528 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2529 hp->h_sender = np;
2531 NYD_LEAVE;
2534 FL struct name const *
2535 check_from_and_sender(struct name const *fromfield,
2536 struct name const *senderfield)
2538 struct name const *rv = NULL;
2539 NYD_ENTER;
2541 if (senderfield != NULL) {
2542 if (senderfield->n_flink != NULL) {
2543 n_err(_("The Sender: field may contain only one address\n"));
2544 goto jleave;
2546 rv = senderfield;
2549 if (fromfield != NULL) {
2550 if (fromfield->n_flink != NULL && senderfield == NULL) {
2551 n_err(_("A Sender: is required when there are multiple "
2552 "addresses in From:\n"));
2553 goto jleave;
2555 if (rv == NULL)
2556 rv = fromfield;
2559 if (rv == NULL)
2560 rv = (struct name*)0x1;
2561 jleave:
2562 NYD_LEAVE;
2563 return rv;
2566 #ifdef HAVE_XSSL
2567 FL char *
2568 getsender(struct message *mp)
2570 char *cp;
2571 struct name *np;
2572 NYD_ENTER;
2574 if ((cp = hfield1("from", mp)) == NULL ||
2575 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2576 cp = NULL;
2577 else
2578 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2579 NYD_LEAVE;
2580 return cp;
2582 #endif
2584 FL int
2585 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2586 int subjfirst)
2588 /* TODO grab_headers: again, check counts etc. against RFC;
2589 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2590 int errs;
2591 int volatile comma;
2592 NYD_ENTER;
2594 errs = 0;
2595 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2597 if (gflags & GTO)
2598 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2599 if (subjfirst && (gflags & GSUBJECT))
2600 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2601 if (gflags & GCC)
2602 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2603 if (gflags & GBCC)
2604 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2606 if (gflags & GEXTRA) {
2607 if (hp->h_from == NULL)
2608 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2609 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2610 GEXTRA | GFULL | GFULLEXTRA);
2611 if (hp->h_reply_to == NULL) {
2612 struct name *v15compat;
2614 if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
2615 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2616 hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
2617 if(hp->h_reply_to == NULL) /* v15 */
2618 hp->h_reply_to = v15compat;
2620 hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
2621 GEXTRA | GFULL);
2622 if (hp->h_sender == NULL)
2623 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2624 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2625 GEXTRA | GFULL);
2628 if (!subjfirst && (gflags & GSUBJECT))
2629 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2631 NYD_LEAVE;
2632 return errs;
2635 FL bool_t
2636 n_header_match(struct message *mp, struct search_expr const *sep){
2637 struct str fiter, in, out;
2638 char const *field;
2639 long lc;
2640 FILE *ibuf;
2641 size_t *linesize;
2642 char **linebuf, *colon;
2643 enum {a_NONE, a_ALL, a_ITER, a_RE} match;
2644 bool_t rv;
2645 NYD_ENTER;
2647 rv = FAL0;
2648 match = a_NONE;
2649 linebuf = &termios_state.ts_linebuf; /* XXX line pool */
2650 linesize = &termios_state.ts_linesize; /* XXX line pool */
2651 n_UNINIT(fiter.l, 0);
2652 n_UNINIT(fiter.s, NULL);
2654 if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2655 goto jleave;
2656 if((lc = mp->m_lines - 1) < 0)
2657 goto jleave;
2659 if((mp->m_flag & MNOFROM) == 0 &&
2660 readline_restart(ibuf, linebuf, linesize, 0) < 0)
2661 goto jleave;
2663 /* */
2664 if((field = sep->ss_field) != NULL){
2665 if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
2666 match = a_ALL;
2667 else{
2668 fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
2669 match = a_ITER;
2671 #ifdef HAVE_REGEX
2672 }else if(sep->ss_fieldre != NULL){
2673 match = a_RE;
2674 #endif
2675 }else
2676 match = a_ALL;
2678 /* Iterate over all the headers */
2679 while(lc > 0){
2680 struct name *np;
2682 if((lc = a_gethfield(ibuf, linebuf, linesize, lc, &colon)) <= 0)
2683 break;
2685 /* Is this a header we are interested in? */
2686 if(match == a_ITER){
2687 char *itercp;
2689 memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
2690 while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
2691 /* It may be an abbreviation */
2692 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
2693 size_t i;
2694 char c1;
2696 if(field[0] != '\0' && field[1] == '\0'){
2697 c1 = lowerconv(field[0]);
2698 for(i = 0; i < n_NELEM(x); ++i){
2699 if(c1 == x[i][0]){
2700 field = x[i];
2701 break;
2706 if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
2707 break;
2709 if(field == NULL)
2710 continue;
2711 #ifdef HAVE_REGEX
2712 }else if(match == a_RE){
2713 char *cp;
2714 size_t i;
2716 i = PTR2SIZE(colon - *linebuf);
2717 cp = n_lofi_alloc(i +1);
2718 memcpy(cp, *linebuf, i);
2719 cp[i] = '\0';
2720 i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
2721 n_lofi_free(cp);
2722 if(!i)
2723 continue;
2724 #endif
2727 /* It could be a plain existence test */
2728 if(sep->ss_field_exists){
2729 rv = TRU1;
2730 break;
2733 /* Need to check the body */
2734 while(blankchar(*++colon))
2736 in.s = colon;
2738 /* Shall we split into address list and match as/the addresses only?
2739 * TODO at some later time we should ignore and log efforts to search
2740 * TODO a skinned address list if we know the header has none such */
2741 if(sep->ss_skin){
2742 if((np = lextract(in.s, GSKIN)) == NULL)
2743 continue;
2744 out.s = np->n_name;
2745 }else{
2746 np = NULL;
2747 in.l = strlen(in.s);
2748 mime_fromhdr(&in, &out, TD_ICONV);
2751 jnext_name:
2752 #ifdef HAVE_REGEX
2753 if(sep->ss_bodyre != NULL)
2754 rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
2755 else
2756 #endif
2757 rv = substr(out.s, sep->ss_body);
2759 if(np == NULL)
2760 free(out.s);
2761 if(rv)
2762 break;
2763 if(np != NULL && (np = np->n_flink) != NULL){
2764 out.s = np->n_name;
2765 goto jnext_name;
2769 jleave:
2770 if(match == a_ITER)
2771 n_lofi_free(fiter.s);
2772 NYD_LEAVE;
2773 return rv;
2776 FL char const *
2777 n_header_is_standard(char const *name, size_t len){
2778 static char const * const names[] = {
2779 "Bcc", "Cc", "From",
2780 "In-Reply-To", "Mail-Followup-To",
2781 "Message-ID", "References", "Reply-To",
2782 "Sender", "Subject", "To",
2783 "Mailx-Command",
2784 "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From", "Mailx-Orig-To",
2785 "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
2786 NULL
2788 char const * const *rv;
2789 NYD_ENTER;
2791 if(len == UIZ_MAX)
2792 len = strlen(name);
2794 for(rv = names; *rv != NULL; ++rv)
2795 if(!ascncasecmp(*rv, name, len))
2796 break;
2797 NYD_LEAVE;
2798 return *rv;
2801 FL bool_t
2802 n_header_add_custom(struct n_header_field **hflp, char const *dat,
2803 bool_t heap){
2804 size_t i;
2805 ui32_t nl, bl;
2806 char const *cp;
2807 struct n_header_field *hfp;
2808 NYD_ENTER;
2810 hfp = NULL;
2812 /* For (-C) convenience, allow leading WS */
2813 while(blankchar(*dat))
2814 ++dat;
2816 /* Isolate the header field from the body */
2817 for(cp = dat;; ++cp){
2818 if(fieldnamechar(*cp))
2819 continue;
2820 if(*cp == '\0'){
2821 if(cp == dat)
2822 goto jename;
2823 }else if(*cp != ':' && !blankchar(*cp)){
2824 jename:
2825 cp = N_("Invalid custom header (not \"field: body\"): %s\n");
2826 goto jerr;
2828 break;
2830 nl = (ui32_t)PTR2SIZE(cp - dat);
2831 if(nl == 0)
2832 goto jename;
2834 /* Verify the custom header does not use standard/managed field name */
2835 if(n_header_is_standard(dat, nl) != NULL){
2836 cp = N_("Custom headers cannot use standard header names: %s\n");
2837 goto jerr;
2840 /* Skip on over to the body */
2841 while(blankchar(*cp))
2842 ++cp;
2843 if(*cp++ != ':')
2844 goto jename;
2845 while(blankchar(*cp))
2846 ++cp;
2847 bl = (ui32_t)strlen(cp);
2848 for(i = bl++; i-- != 0;)
2849 if(cntrlchar(cp[i])){
2850 cp = N_("Invalid custom header: contains control characters: %s\n");
2851 goto jerr;
2854 i = n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + nl +1 + bl;
2855 *hflp = hfp = heap ? n_alloc(i) : n_autorec_alloc(i);
2856 hfp->hf_next = NULL;
2857 hfp->hf_nl = nl;
2858 hfp->hf_bl = bl - 1;
2859 memcpy(hfp->hf_dat, dat, nl);
2860 hfp->hf_dat[nl++] = '\0';
2861 memcpy(hfp->hf_dat + nl, cp, bl);
2862 jleave:
2863 NYD_LEAVE;
2864 return (hfp != NULL);
2866 jerr:
2867 n_err(V_(cp), n_shexp_quote_cp(dat, FAL0));
2868 goto jleave;
2871 /* s-it-mode */