Uninstall s-nail-uninstall.sh (Paride Legovini)
[s-mailx.git] / header.c
blob48be3a3d4aa7e62c029d76baa1c1a5690f6f2fb8
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 * SPDX-License-Identifier: BSD-3-Clause
8 */
9 /*
10 * Copyright (c) 1980, 1993
11 * The Regents of the University of California. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
37 #undef n_FILE
38 #define n_FILE header
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #include <pwd.h>
46 struct cmatch_data {
47 size_t tlen; /* Length of .tdata */
48 char const *tdata; /* Template date - see _cmatch_data[] */
51 /* Template characters for cmatch_data.tdata:
52 * 'A' An upper case char
53 * 'a' A lower case char
54 * ' ' A space
55 * '0' A digit
56 * 'O' An optional digit or space
57 * ':' A colon
58 * '+' Either a plus or a minus sign */
59 static struct cmatch_data const _cmatch_data[] = {
60 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
61 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
62 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
63 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
64 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
65 * in the wild (by UW-imap) */
66 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
67 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
68 * zone strings; note that 1. is strictly speaking not correct as some
69 * letters are not used, and 2. is not because only "UT" is defined */
70 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
71 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
72 { 0, NULL }
74 #define a_HEADER_DATE_MINLEN 21
76 /* Savage extract date field from From_ line. linelen is convenience as line
77 * must be terminated (but it may end in a newline [sequence]).
78 * Return whether the From_ line was parsed successfully (-1 if the From_ line
79 * wasn't really RFC 4155 compliant) */
80 static int a_header_extract_date_from_from_(char const *line, size_t linelen,
81 char datebuf[n_FROM_DATEBUF]);
83 /* Skip over "word" as found in From_ line */
84 static char const *a_header__from_skipword(char const *wp);
86 /* Match the date string against the date template (tp), return if match.
87 * See _cmatch_data[] for template character description */
88 static int _cmatch(size_t len, char const *date,
89 char const *tp);
91 /* Check whether date is a valid 'From_' date.
92 * (Rather ctime(3) generated dates, according to RFC 4155) */
93 static int _is_date(char const *date);
95 /* JulianDayNumber converter(s) */
96 static size_t a_header_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
97 #if 0
98 static void a_header_jdn_to_gregorian(size_t jdn,
99 ui32_t *yp, ui32_t *mp, ui32_t *dp);
100 #endif
102 /* ... And place the extracted date in `date' */
103 static void a_header_parse_from_(struct message *mp,
104 char date[n_FROM_DATEBUF]);
106 /* Convert the domain part of a skinned address to IDNA.
107 * If an error occurs before Unicode information is available, revert the IDNA
108 * error to a normal CHAR one so that the error message doesn't talk Unicode */
109 #ifdef HAVE_IDNA
110 static struct n_addrguts *a_header_idna_apply(struct n_addrguts *agp);
111 #endif
113 /* Classify and check a (possibly skinned) header body according to RFC
114 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
115 * also a file or a pipe command, so check that first, then.
116 * Otherwise perform content checking and isolate the domain part (for IDNA).
117 * issingle_hack has the same meaning as for n_addrspec_with_guts() */
118 static bool_t a_header_addrspec_check(struct n_addrguts *agp, bool_t skinned,
119 bool_t issingle_hack);
121 /* Return the next header field found in the given message.
122 * Return >= 0 if something found, < 0 elsewise.
123 * "colon" is set to point to the colon in the header.
124 * Must deal with \ continuations & other such fraud */
125 static long a_gethfield(enum n_header_extract_flags hef, FILE *f,
126 char **linebuf, size_t *linesize, long rem, char **colon);
128 static int msgidnextc(char const **cp, int *status);
130 static char const * nexttoken(char const *cp);
132 static int
133 a_header_extract_date_from_from_(char const *line, size_t linelen,
134 char datebuf[n_FROM_DATEBUF])
136 int rv;
137 char const *cp = line;
138 NYD_ENTER;
140 rv = 1;
142 /* "From " */
143 cp = a_header__from_skipword(cp);
144 if (cp == NULL)
145 goto jerr;
146 /* "addr-spec " */
147 cp = a_header__from_skipword(cp);
148 if (cp == NULL)
149 goto jerr;
150 if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') &&
151 (cp[2] == 'y' || cp[2] == 'Y')){
152 cp = a_header__from_skipword(cp);
153 if (cp == NULL)
154 goto jerr;
156 /* It seems there are invalid MBOX archives in the wild, compare
157 * . http://bugs.debian.org/624111
158 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
159 * What they do is that they obfuscate the address to "name at host",
160 * and even "name at host dot dom dot dom.
161 * The [Aa][Tt] is also RFC 733, so be tolerant */
162 else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') &&
163 cp[2] == ' '){
164 rv = -1;
165 cp += 3;
166 jat_dot:
167 cp = a_header__from_skipword(cp);
168 if (cp == NULL)
169 goto jerr;
170 if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') &&
171 (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){
172 cp += 4;
173 goto jat_dot;
177 linelen -= PTR2SIZE(cp - line);
178 if (linelen < a_HEADER_DATE_MINLEN)
179 goto jerr;
180 if (cp[linelen - 1] == '\n') {
181 --linelen;
182 /* (Rather IMAP/POP3 only) */
183 if (cp[linelen - 1] == '\r')
184 --linelen;
185 if (linelen < a_HEADER_DATE_MINLEN)
186 goto jerr;
188 if (linelen >= n_FROM_DATEBUF)
189 goto jerr;
191 jleave:
192 memcpy(datebuf, cp, linelen);
193 datebuf[linelen] = '\0';
194 NYD_LEAVE;
195 return rv;
196 jerr:
197 cp = _("<Unknown date>");
198 linelen = strlen(cp);
199 if (linelen >= n_FROM_DATEBUF)
200 linelen = n_FROM_DATEBUF;
201 rv = 0;
202 goto jleave;
205 static char const *
206 a_header__from_skipword(char const *wp)
208 char c = 0;
209 NYD2_ENTER;
211 if (wp != NULL) {
212 while ((c = *wp++) != '\0' && !blankchar(c)) {
213 if (c == '"') {
214 while ((c = *wp++) != '\0' && c != '"')
216 if (c != '"')
217 --wp;
220 for (; blankchar(c); c = *wp++)
223 NYD2_LEAVE;
224 return (c == 0 ? NULL : wp - 1);
227 static int
228 _cmatch(size_t len, char const *date, char const *tp)
230 int ret = 0;
231 NYD2_ENTER;
233 while (len--) {
234 char c = date[len];
235 switch (tp[len]) {
236 case 'a':
237 if (!lowerchar(c))
238 goto jleave;
239 break;
240 case 'A':
241 if (!upperchar(c))
242 goto jleave;
243 break;
244 case ' ':
245 if (c != ' ')
246 goto jleave;
247 break;
248 case '0':
249 if (!digitchar(c))
250 goto jleave;
251 break;
252 case 'O':
253 if (c != ' ' && !digitchar(c))
254 goto jleave;
255 break;
256 case ':':
257 if (c != ':')
258 goto jleave;
259 break;
260 case '+':
261 if (c != '+' && c != '-')
262 goto jleave;
263 break;
266 ret = 1;
267 jleave:
268 NYD2_LEAVE;
269 return ret;
272 static int
273 _is_date(char const *date)
275 struct cmatch_data const *cmdp;
276 size_t dl;
277 int rv = 0;
278 NYD2_ENTER;
280 if ((dl = strlen(date)) >= a_HEADER_DATE_MINLEN)
281 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
282 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
283 break;
284 NYD2_LEAVE;
285 return rv;
288 static size_t
289 a_header_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
290 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
291 * (via third hand, plus adjustments).
292 * This algorithm is supposed to work for all dates in between 1582-10-15
293 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
294 size_t jdn;
295 NYD2_ENTER;
297 #if 0
298 if(y == 0)
299 y = 1;
300 if(m == 0)
301 m = 1;
302 if(d == 0)
303 d = 1;
304 #endif
306 if(m > 2)
307 m -= 3;
308 else{
309 m += 9;
310 --y;
312 jdn = y;
313 jdn /= 100;
314 y -= 100 * jdn;
315 y *= 1461;
316 y >>= 2;
317 jdn *= 146097;
318 jdn >>= 2;
319 jdn += y;
320 jdn += d;
321 jdn += 1721119;
322 m *= 153;
323 m += 2;
324 m /= 5;
325 jdn += m;
326 NYD2_LEAVE;
327 return jdn;
330 #if 0
331 static void
332 a_header_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
333 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
334 * (via third hand, plus adjustments) */
335 size_t y, x;
336 NYD2_ENTER;
338 jdn -= 1721119;
339 jdn <<= 2;
340 --jdn;
341 y = jdn / 146097;
342 jdn %= 146097;
343 jdn |= 3;
344 y *= 100;
345 y += jdn / 1461;
346 jdn %= 1461;
347 jdn += 4;
348 jdn >>= 2;
349 x = jdn;
350 jdn <<= 2;
351 jdn += x;
352 jdn -= 3;
353 x = jdn / 153; /* x -> month */
354 jdn %= 153;
355 jdn += 5;
356 jdn /= 5; /* jdn -> day */
357 if(x < 10)
358 x += 3;
359 else{
360 x -= 9;
361 ++y;
364 *yp = (ui32_t)(y & 0xFFFF);
365 *mp = (ui32_t)(x & 0xFF);
366 *dp = (ui32_t)(jdn & 0xFF);
367 NYD2_LEAVE;
369 #endif /* 0 */
371 static void
372 a_header_parse_from_(struct message *mp, char date[n_FROM_DATEBUF]){
373 FILE *ibuf;
374 int hlen;
375 char *hline = NULL; /* TODO line pool */
376 size_t hsize = 0;
377 NYD2_ENTER;
379 if((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL &&
380 (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0)
381 a_header_extract_date_from_from_(hline, hlen, date);
382 if(hline != NULL)
383 n_free(hline);
384 NYD2_LEAVE;
387 #ifdef HAVE_IDNA
388 static struct n_addrguts *
389 a_header_idna_apply(struct n_addrguts *agp){
390 struct n_string idna_ascii;
391 NYD_ENTER;
393 n_string_creat_auto(&idna_ascii);
395 if(!n_idna_to_ascii(&idna_ascii, &agp->ag_skinned[agp->ag_sdom_start],
396 agp->ag_slen - agp->ag_sdom_start))
397 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
398 else{
399 /* Replace the domain part of .ag_skinned with IDNA version */
400 n_string_unshift_buf(&idna_ascii, agp->ag_skinned, agp->ag_sdom_start);
402 agp->ag_skinned = n_string_cp(&idna_ascii);
403 agp->ag_slen = idna_ascii.s_len;
404 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
405 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
407 NYD_LEAVE;
408 return agp;
410 #endif /* HAVE_IDNA */
412 static bool_t
413 a_header_addrspec_check(struct n_addrguts *agp, bool_t skinned,
414 bool_t issingle_hack)
416 char *addr, *p;
417 union {bool_t b; char c; unsigned char u; ui32_t ui32; si32_t si32;} c;
418 enum{
419 a_NONE,
420 a_IDNA_ENABLE = 1u<<0,
421 a_IDNA_APPLY = 1u<<1,
422 a_REDO_NODE_AFTER_ADDR = 1u<<2,
423 a_RESET_MASK = a_IDNA_ENABLE | a_IDNA_APPLY | a_REDO_NODE_AFTER_ADDR,
424 a_IN_QUOTE = 1u<<8,
425 a_IN_AT = 1u<<9,
426 a_IN_DOMAIN = 1u<<10,
427 a_DOMAIN_V6 = 1u<<11,
428 a_DOMAIN_MASK = a_IN_DOMAIN | a_DOMAIN_V6
429 } flags;
430 NYD_ENTER;
432 flags = a_NONE;
433 #ifdef HAVE_IDNA
434 if(!ok_blook(idna_disable))
435 flags = a_IDNA_ENABLE;
436 #endif
438 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
439 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
440 goto jleave;
443 addr = agp->ag_skinned;
445 /* If the field is not a recipient, it cannot be a file or a pipe */
446 if (!skinned)
447 goto jaddr_check;
449 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
450 * grep the latter for the complete picture */
451 if (*addr == '|') {
452 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
453 goto jleave;
455 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
456 (addr[0] == '-' && addr[1] == '\0'))
457 goto jisfile;
458 if (memchr(addr, '@', agp->ag_slen) == NULL) {
459 if (*addr == '+')
460 goto jisfile;
461 for (p = addr; (c.c = *p); ++p) {
462 if (c.c == '!' || c.c == '%')
463 break;
464 if (c.c == '/') {
465 jisfile:
466 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
467 goto jleave;
472 jaddr_check:
473 /* TODO This is false. If super correct this should work on wide
474 * TODO characters, just in case (some bytes of) the ASCII set is (are)
475 * TODO shared; it may yet tear apart multibyte sequences, possibly.
476 * TODO All this should interact with mime_enc_mustquote(), too!
477 * TODO That is: once this is an object, we need to do this in a way
478 * TODO that it is valid for the wire format (instead)! */
479 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
480 * TODO Note this correlats with addrspec_with_guts() which is in front
481 * TODO of us and encapsulates (what it thinks is, sigh) the address
482 * TODO boundary. ALL THIS should be one object that knows how to deal */
483 flags &= a_RESET_MASK;
484 for (p = addr; (c.c = *p++) != '\0';) {
485 if (c.c == '"') {
486 flags ^= a_IN_QUOTE;
487 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
488 #ifdef HAVE_IDNA
489 if ((flags & (a_IN_DOMAIN | a_IDNA_ENABLE)) ==
490 (a_IN_DOMAIN | a_IDNA_ENABLE))
491 flags |= a_IDNA_APPLY;
492 else
493 #endif
494 break;
495 } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
496 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
497 break;
498 } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
499 /*EMPTY*/;
500 } else if (c.c == '\\' && *p != '\0') {
501 ++p;
502 } else if (c.c == '@') {
503 if(flags & a_IN_AT){
504 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
505 c.u);
506 goto jleave;
508 agp->ag_sdom_start = PTR2SIZE(p - addr);
509 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
510 flags &= ~a_DOMAIN_MASK;
511 flags |= (*p == '[') ? a_IN_AT | a_IN_DOMAIN | a_DOMAIN_V6
512 : a_IN_AT | a_IN_DOMAIN;
513 continue;
515 /* TODO This interferes with our alias handling, which allows :!
516 * TODO Update manual on support (search the several ALIASCOLON)! */
517 else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
518 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
519 c.c == '\\' || c.c == ',' || blankchar(c.c))
520 break;
521 flags &= ~a_IN_AT;
523 if (c.c != '\0') {
524 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
525 goto jleave;
528 /* If we do not think this is an address we may treat it as an alias name
529 * if and only if the original input is identical to the skinned version */
530 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR) &&
531 !strcmp(agp->ag_skinned, agp->ag_input)){
532 /* TODO This may be an UUCP address */
533 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
534 if(!n_alias_is_valid_name(agp->ag_input))
535 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_NAME, '.');
536 }else{
537 /* If we seem to know that this is an address. Ensure this is correct
538 * according to RFC 5322 TODO the entire address parser should be like
539 * TODO that for one, and then we should know whether structured or
540 * TODO unstructured, and just parse correctly overall!
541 * TODO In addition, this can be optimised a lot.
542 * TODO And it is far from perfect: it should not forget whether no
543 * TODO whitespace followed some snippet, and it was written hastily.
544 * TODO It is even wrong sometimes. Not only for strange cases */
545 struct a_token{
546 struct a_token *t_last;
547 struct a_token *t_next;
548 enum{
549 a_T_TATOM = 1u<<0,
550 a_T_TCOMM = 1u<<1,
551 a_T_TQUOTE = 1u<<2,
552 a_T_TADDR = 1u<<3,
553 a_T_TMASK = (1u<<4) - 1,
555 a_T_SPECIAL = 1u<<8 /* An atom actually needs to go TQUOTE */
556 } t_f;
557 ui8_t t__pad[4];
558 size_t t_start;
559 size_t t_end;
560 } *thead, *tcurr, *tp;
562 struct n_string ost, *ostp;
563 char const *cp, *cp1st, *cpmax, *xp;
564 void *lofi_snap;
566 /* Name and domain must be non-empty */
567 if(*addr == '@' || &addr[2] >= p || p[-2] == '@'){
568 jeat:
569 c.c = '@';
570 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
571 goto jleave;
574 cp = agp->ag_input;
576 /* Nothing to do if there is only an address (in angle brackets) */
577 /* TODO This is wrong since we allow invalid constructs in local-part
578 * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
579 * TODO "abcd"@abc. Etc. */
580 if(agp->ag_iaddr_start == 0){
581 /* No @ seen? */
582 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
583 goto jeat;
584 if(agp->ag_iaddr_aend == agp->ag_ilen)
585 goto jleave;
586 }else if(agp->ag_iaddr_start == 1 && *cp == '<' &&
587 agp->ag_iaddr_aend == agp->ag_ilen - 1 &&
588 cp[agp->ag_iaddr_aend] == '>'){
589 /* No @ seen? Possibly insert n_nodename() */
590 if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
591 cp = &agp->ag_input[agp->ag_iaddr_start];
592 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
593 goto jinsert_domain;
595 goto jleave;
598 /* It is not, so parse off all tokens, then resort and rejoin */
599 lofi_snap = n_lofi_snap_create();
601 cp1st = cp;
602 if((c.ui32 = agp->ag_iaddr_start) > 0)
603 --c.ui32;
604 cpmax = &cp[c.ui32];
606 thead = tcurr = NULL;
607 jnode_redo:
608 for(tp = NULL; cp < cpmax;){
609 switch((c.c = *cp)){
610 case '(':
611 if(tp != NULL)
612 tp->t_end = PTR2SIZE(cp - cp1st);
613 tp = n_lofi_alloc(sizeof *tp);
614 tp->t_next = NULL;
615 if((tp->t_last = tcurr) != NULL)
616 tcurr->t_next = tp;
617 else
618 thead = tp;
619 tcurr = tp;
620 tp->t_f = a_T_TCOMM;
621 tp->t_start = PTR2SIZE(++cp - cp1st);
622 xp = skip_comment(cp);
623 tp->t_end = PTR2SIZE(xp - cp1st);
624 cp = xp;
625 if(tp->t_end > tp->t_start){
626 if(xp[-1] == ')')
627 --tp->t_end;
628 else{
629 /* No closing comment - strip trailing whitespace */
630 while(blankchar(*--xp))
631 if(--tp->t_end == tp->t_start)
632 break;
635 tp = NULL;
636 break;
638 case '"':
639 if(tp != NULL)
640 tp->t_end = PTR2SIZE(cp - cp1st);
641 tp = n_lofi_alloc(sizeof *tp);
642 tp->t_next = NULL;
643 if((tp->t_last = tcurr) != NULL)
644 tcurr->t_next = tp;
645 else
646 thead = tp;
647 tcurr = tp;
648 tp->t_f = a_T_TQUOTE;
649 tp->t_start = PTR2SIZE(++cp - cp1st);
650 for(xp = cp; xp < cpmax; ++xp){
651 if((c.c = *xp) == '"')
652 break;
653 if(c.c == '\\' && xp[1] != '\0')
654 ++xp;
656 tp->t_end = PTR2SIZE(xp - cp1st);
657 cp = &xp[1];
658 if(tp->t_end > tp->t_start){
659 /* No closing quote - strip trailing whitespace */
660 if(*xp != '"'){
661 while(blankchar(*xp--))
662 if(--tp->t_end == tp->t_start)
663 break;
666 tp = NULL;
667 break;
669 default:
670 if(blankchar(c.c)){
671 if(tp != NULL)
672 tp->t_end = PTR2SIZE(cp - cp1st);
673 tp = NULL;
674 ++cp;
675 break;
678 if(tp == NULL){
679 tp = n_lofi_alloc(sizeof *tp);
680 tp->t_next = NULL;
681 if((tp->t_last = tcurr) != NULL)
682 tcurr->t_next = tp;
683 else
684 thead = tp;
685 tcurr = tp;
686 tp->t_f = a_T_TATOM;
687 tp->t_start = PTR2SIZE(cp - cp1st);
689 ++cp;
691 /* Reverse solidus transforms the following into a quoted-pair, and
692 * therefore (must occur in comment or quoted-string only) the
693 * entire atom into a quoted string */
694 if(c.c == '\\'){
695 tp->t_f |= a_T_SPECIAL;
696 if(cp < cpmax)
697 ++cp;
698 break;
701 /* Is this plain RFC 5322 "atext", or "specials"?
702 * TODO Because we don't know structured/unstructured, nor anything
703 * TODO else, we need to treat "dot-atom" as being identical to
704 * TODO "specials".
705 * However, if the 8th bit is set, this will be RFC 2047 converted
706 * and the entire sequence is skipped */
707 if(!(c.u & 0x80) && !alnumchar(c.c) &&
708 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
709 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
710 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
711 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
712 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~')
713 tp->t_f |= a_T_SPECIAL;
714 break;
717 if(tp != NULL)
718 tp->t_end = PTR2SIZE(cp - cp1st);
720 if(!(flags & a_REDO_NODE_AFTER_ADDR)){
721 flags |= a_REDO_NODE_AFTER_ADDR;
723 /* The local-part may be in quotes.. */
724 if((tp = tcurr) != NULL && (tp->t_f & a_T_TQUOTE) &&
725 tp->t_end == agp->ag_iaddr_start - 1){
726 /* ..so backward extend it, including the starting quote */
727 /* TODO This is false and the code below #if 0 away. We would
728 * TODO need to create a properly quoted local-part HERE AND NOW
729 * TODO and REPLACE the original data with that version, but the
730 * TODO current code cannot do that. The node needs the data,
731 * TODO not only offsets for that, for example. If we had all that
732 * TODO the code below could produce a really valid thing */
733 if(tp->t_start > 0)
734 --tp->t_start;
735 if(tp->t_start > 0 &&
736 (tp->t_last == NULL || tp->t_last->t_end < tp->t_start) &&
737 agp->ag_input[tp->t_start - 1] == '\\')
738 --tp->t_start;
739 tp->t_f = a_T_TADDR | a_T_SPECIAL;
740 }else{
741 tp = n_lofi_alloc(sizeof *tp);
742 tp->t_next = NULL;
743 if((tp->t_last = tcurr) != NULL)
744 tcurr->t_next = tp;
745 else
746 thead = tp;
747 tcurr = tp;
748 tp->t_f = a_T_TADDR;
749 tp->t_start = agp->ag_iaddr_start;
750 /* TODO Very special case because of our hacky non-object-based and
751 * TODO non-compliant address parser. Note */
752 if(tp->t_last == NULL && tp->t_start > 0)
753 tp->t_start = 0;
754 if(agp->ag_input[tp->t_start] == '<')
755 ++tp->t_start;
757 /* TODO Very special check for whether we need to massage the
758 * TODO local part. This is wrong, but otherwise even more so */
759 #if 0
760 cp = &agp->ag_input[tp->t_start];
761 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
762 while(cp < cpmax){
763 c.c = *cp++;
764 if(!(c.u & 0x80) && !alnumchar(c.c) &&
765 c.c != '!' && c.c != '#' && c.c != '$' && c.c != '%' &&
766 c.c != '&' && c.c != '\'' && c.c != '*' && c.c != '+' &&
767 c.c != '-' && c.c != '/' && c.c != '=' && c.c != '?' &&
768 c.c != '^' && c.c != '_' && c.c != '`' && c.c != '{' &&
769 c.c != '}' && c.c != '|' && c.c != '}' && c.c != '~'){
770 tp->t_f |= a_T_SPECIAL;
771 break;
774 #endif
776 tp->t_end = agp->ag_iaddr_aend;
777 assert(tp->t_start <= tp->t_end);
778 tp = NULL;
780 cp = &agp->ag_input[agp->ag_iaddr_aend + 1];
781 cpmax = &agp->ag_input[agp->ag_ilen];
782 if(cp < cpmax)
783 goto jnode_redo;
786 /* Nothing may follow the address, move it to the end */
787 assert(tcurr != NULL);
788 if(tcurr != NULL && !(tcurr->t_f & a_T_TADDR)){
789 for(tp = thead; tp != NULL; tp = tp->t_next){
790 if(tp->t_f & a_T_TADDR){
791 if(tp->t_last != NULL)
792 tp->t_last->t_next = tp->t_next;
793 else
794 thead = tp->t_next;
795 if(tp->t_next != NULL)
796 tp->t_next->t_last = tp->t_last;
798 tcurr = tp;
799 while(tp->t_next != NULL)
800 tp = tp->t_next;
801 tp->t_next = tcurr;
802 tcurr->t_last = tp;
803 tcurr->t_next = NULL;
804 break;
809 /* Make ranges contiguous: ensure a continuous range of atoms is converted
810 * to a SPECIAL one if at least one of them requires it */
811 for(tp = thead; tp != NULL; tp = tp->t_next){
812 if(tp->t_f & a_T_SPECIAL){
813 tcurr = tp;
814 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
815 tp->t_f |= a_T_SPECIAL;
816 tp = tcurr;
817 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
818 tp->t_f |= a_T_SPECIAL;
819 if(tp == NULL)
820 break;
824 /* And yes, we want quotes to extend as much as possible */
825 for(tp = thead; tp != NULL; tp = tp->t_next){
826 if(tp->t_f & a_T_TQUOTE){
827 tcurr = tp;
828 while((tp = tp->t_last) != NULL && (tp->t_f & a_T_TATOM))
829 tp->t_f |= a_T_SPECIAL;
830 tp = tcurr;
831 while((tp = tp->t_next) != NULL && (tp->t_f & a_T_TATOM))
832 tp->t_f |= a_T_SPECIAL;
833 if(tp == NULL)
834 break;
838 /* Then rejoin */
839 ostp = n_string_creat_auto(&ost);
840 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
841 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
843 for(tcurr = thead; tcurr != NULL;){
844 if(tcurr != thead)
845 ostp = n_string_push_c(ostp, ' ');
846 if(tcurr->t_f & a_T_TADDR){
847 if(tcurr->t_last != NULL)
848 ostp = n_string_push_c(ostp, '<');
849 agp->ag_iaddr_start = ostp->s_len;
850 /* Now it is terrible to say, but if that thing contained
851 * quotes, then those may contain quoted-pairs! */
852 #if 0
853 if(!(tcurr->t_f & a_T_SPECIAL)){
854 #endif
855 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
856 (tcurr->t_end - tcurr->t_start));
857 #if 0
858 }else{
859 bool_t quot, esc;
861 ostp = n_string_push_c(ostp, '"');
862 quot = TRU1;
864 cp = &cp1st[tcurr->t_start];
865 cpmax = &cp1st[tcurr->t_end];
866 for(esc = FAL0; cp < cpmax;){
867 if((c.c = *cp++) == '\\' && !esc){
868 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
869 esc = TRU1;
870 }else{
871 if(esc || c.c == '"')
872 ostp = n_string_push_c(ostp, '\\');
873 else if(c.c == '@'){
874 ostp = n_string_push_c(ostp, '"');
875 quot = FAL0;
877 ostp = n_string_push_c(ostp, c.c);
878 esc = FAL0;
882 #endif
883 agp->ag_iaddr_aend = ostp->s_len;
885 if(tcurr->t_last != NULL)
886 ostp = n_string_push_c(ostp, '>');
887 tcurr = tcurr->t_next;
888 }else if(tcurr->t_f & a_T_TCOMM){
889 ostp = n_string_push_c(ostp, '(');
890 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
891 (tcurr->t_end - tcurr->t_start));
892 while((tp = tcurr->t_next) != NULL && (tp->t_f & a_T_TCOMM)){
893 tcurr = tp;
894 ostp = n_string_push_c(ostp, ' '); /* XXX may be artificial */
895 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
896 (tcurr->t_end - tcurr->t_start));
898 ostp = n_string_push_c(ostp, ')');
899 tcurr = tcurr->t_next;
900 }else if(tcurr->t_f & a_T_TQUOTE){
901 jput_quote:
902 ostp = n_string_push_c(ostp, '"');
903 tp = tcurr;
904 do/* while tcurr && TATOM||TQUOTE */{
905 cp = &cp1st[tcurr->t_start];
906 cpmax = &cp1st[tcurr->t_end];
907 if(cp == cpmax)
908 continue;
910 if(tcurr != tp)
911 ostp = n_string_push_c(ostp, ' ');
913 if((tcurr->t_f & (a_T_TATOM | a_T_SPECIAL)) == a_T_TATOM)
914 ostp = n_string_push_buf(ostp, cp, PTR2SIZE(cpmax - cp));
915 else{
916 bool_t esc;
918 for(esc = FAL0; cp < cpmax;){
919 if((c.c = *cp++) == '\\' && !esc){
920 if(cp < cpmax && (*cp == '"' || *cp == '\\'))
921 esc = TRU1;
922 }else{
923 if(esc || c.c == '"'){
924 jput_quote_esc:
925 ostp = n_string_push_c(ostp, '\\');
927 ostp = n_string_push_c(ostp, c.c);
928 esc = FAL0;
931 if(esc){
932 c.c = '\\';
933 goto jput_quote_esc;
936 }while((tcurr = tcurr->t_next) != NULL &&
937 (tcurr->t_f & (a_T_TATOM | a_T_TQUOTE)));
938 ostp = n_string_push_c(ostp, '"');
939 }else if(tcurr->t_f & a_T_SPECIAL)
940 goto jput_quote;
941 else{
942 /* Can we use a fast join mode? */
943 for(tp = tcurr; tcurr != NULL; tcurr = tcurr->t_next){
944 if(!(tcurr->t_f & a_T_TATOM))
945 break;
946 if(tcurr != tp)
947 ostp = n_string_push_c(ostp, ' ');
948 ostp = n_string_push_buf(ostp, &cp1st[tcurr->t_start],
949 (tcurr->t_end - tcurr->t_start));
954 n_lofi_snap_unroll(lofi_snap);
956 agp->ag_input = n_string_cp(ostp);
957 agp->ag_ilen = ostp->s_len;
958 /*ostp = n_string_drop_ownership(ostp);*/
960 /* Name and domain must be non-empty, the second */
961 cp = &agp->ag_input[agp->ag_iaddr_start];
962 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
963 if(*cp == '@' || &cp[2] > cpmax || cpmax[-1] == '@'){
964 c.c = '@';
965 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ, c.u);
966 goto jleave;
969 addr = agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
971 /* TODO This parser is a mess. We do not know whether this is truly
972 * TODO valid, and all our checks are not truly RFC conforming.
973 * TODO Do check the skinned thing by itself once more, in order
974 * TODO to catch problems from reordering, e.g., this additional
975 * TODO test catches a final address without AT..
976 * TODO This is a plain copy+paste of the weird thing above, no care */
977 agp->ag_n_flags &= ~NAME_ADDRSPEC_ISADDR;
978 flags &= a_RESET_MASK;
979 for (p = addr; (c.c = *p++) != '\0';) {
980 if(c.c == '"')
981 flags ^= a_IN_QUOTE;
982 else if (c.u < 040 || c.u >= 0177) {
983 #ifdef HAVE_IDNA
984 if(!(flags & a_IN_DOMAIN))
985 #endif
986 break;
987 } else if ((flags & a_DOMAIN_MASK) == a_DOMAIN_MASK) {
988 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
989 break;
990 } else if ((flags & (a_IN_QUOTE | a_DOMAIN_MASK)) == a_IN_QUOTE) {
991 /*EMPTY*/;
992 } else if (c.c == '\\' && *p != '\0') {
993 ++p;
994 } else if (c.c == '@') {
995 if(flags & a_IN_AT){
996 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
997 c.u);
998 goto jleave;
1000 flags |= a_IN_AT;
1001 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
1002 flags &= ~a_DOMAIN_MASK;
1003 flags |= (*p == '[') ? a_IN_DOMAIN | a_DOMAIN_V6 : a_IN_DOMAIN;
1004 continue;
1005 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
1006 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
1007 c.c == '\\' || c.c == ',' || blankchar(c.c))
1008 break;
1009 flags &= ~a_IN_AT;
1011 if(c.c != '\0')
1012 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
1013 else if(!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR)){
1014 /* This is not an address, but if we had seen angle brackets convert
1015 * it to a n_nodename() address if the name is a valid user */
1016 jinsert_domain:
1017 if(cp > &agp->ag_input[0] && cp[-1] == '<' &&
1018 cpmax <= &agp->ag_input[agp->ag_ilen] && cpmax[0] == '>' &&
1019 (!strcmp(addr, ok_vlook(LOGNAME)) || getpwnam(addr) != NULL)){
1020 /* XXX However, if hostname is set to the empty string this
1021 * XXX indicates that the used *mta* will perform the
1022 * XXX auto-expansion instead. Not so with `addrcodec' though */
1023 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR;
1024 if(!issingle_hack &&
1025 (cp = ok_vlook(hostname)) != NULL && *cp == '\0')
1026 agp->ag_n_flags |= NAME_ADDRSPEC_WITHOUT_DOMAIN;
1027 else{
1028 c.ui32 = strlen(cp = n_nodename(TRU1));
1029 /* This is yet IDNA converted.. */
1030 ostp = n_string_creat_auto(&ost);
1031 ostp = n_string_assign_buf(ostp, agp->ag_input, agp->ag_ilen);
1032 ostp = n_string_insert_c(ostp, agp->ag_iaddr_aend++, '@');
1033 ostp = n_string_insert_buf(ostp, agp->ag_iaddr_aend, cp,
1034 c.ui32);
1035 agp->ag_iaddr_aend += c.ui32;
1036 agp->ag_input = n_string_cp(ostp);
1037 agp->ag_ilen = ostp->s_len;
1038 /*ostp = n_string_drop_ownership(ostp);*/
1040 cp = &agp->ag_input[agp->ag_iaddr_start];
1041 cpmax = &agp->ag_input[agp->ag_iaddr_aend];
1042 agp->ag_skinned = savestrbuf(cp, PTR2SIZE(cpmax - cp));
1044 }else
1045 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
1046 '@');
1050 jleave:
1051 #ifdef HAVE_IDNA
1052 if(!(agp->ag_n_flags & NAME_ADDRSPEC_INVALID) && (flags & a_IDNA_APPLY))
1053 agp = a_header_idna_apply(agp);
1054 #endif
1055 NYD_LEAVE;
1056 return !(agp->ag_n_flags & NAME_ADDRSPEC_INVALID);
1059 static long
1060 a_gethfield(enum n_header_extract_flags hef, FILE *f,
1061 char **linebuf, size_t *linesize, long rem, char **colon)
1063 char *line2 = NULL, *cp, *cp2;
1064 size_t line2size = 0;
1065 int c, isenc;
1066 NYD2_ENTER;
1068 if (*linebuf == NULL)
1069 *linebuf = n_realloc(*linebuf, *linesize = 1);
1070 **linebuf = '\0';
1071 for (;;) {
1072 if (--rem < 0) {
1073 rem = -1;
1074 break;
1076 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
1077 rem = -1;
1078 break;
1080 if((hef & n_HEADER_EXTRACT_IGNORE_SHELL_COMMENTS) && **linebuf == '#'){
1081 /* A comment may be last before body, too, ensure empty last line */
1082 **linebuf = '\0';
1083 continue;
1086 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
1088 if (cp > *linebuf)
1089 while (blankchar(*cp))
1090 ++cp;
1091 if (cp == *linebuf)
1092 continue;
1093 /* XXX Not a header line, logging only for -t / compose mode? */
1094 if(*cp != ':'){
1095 if(!(hef & n_HEADER_EXTRACT_IGNORE_FROM_) ||
1096 !is_head(*linebuf, c, FAL0))
1097 n_err(_("Not a header line, skipping: %s\n"),
1098 n_shexp_quote_cp(*linebuf, FAL0));
1099 continue;
1102 /* I guess we got a headline. Handle wraparound */
1103 *colon = cp;
1104 cp = *linebuf + c;
1105 for (;;) {
1106 isenc = 0;
1107 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
1109 cp++;
1110 if (rem <= 0)
1111 break;
1112 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
1113 isenc |= 1;
1114 ungetc(c = getc(f), f);
1115 if (!blankchar(c))
1116 break;
1117 c = readline_restart(f, &line2, &line2size, 0); /* TODO linepool! */
1118 if (c < 0)
1119 break;
1120 --rem;
1121 for (cp2 = line2; blankchar(*cp2); ++cp2)
1123 c -= (int)PTR2SIZE(cp2 - line2);
1124 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
1125 isenc |= 2;
1126 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
1127 size_t diff = PTR2SIZE(cp - *linebuf),
1128 colondiff = PTR2SIZE(*colon - *linebuf);
1129 *linebuf = n_realloc(*linebuf, *linesize += c + 2);
1130 cp = &(*linebuf)[diff];
1131 *colon = &(*linebuf)[colondiff];
1133 if (isenc != 3)
1134 *cp++ = ' ';
1135 memcpy(cp, cp2, c);
1136 cp += c;
1138 *cp = '\0';
1140 if (line2 != NULL)
1141 n_free(line2);
1142 break;
1144 NYD2_LEAVE;
1145 return rem;
1148 static int
1149 msgidnextc(char const **cp, int *status)
1151 int c;
1152 NYD2_ENTER;
1154 assert(cp != NULL);
1155 assert(*cp != NULL);
1156 assert(status != NULL);
1158 for (;;) {
1159 if (*status & 01) {
1160 if (**cp == '"') {
1161 *status &= ~01;
1162 (*cp)++;
1163 continue;
1165 if (**cp == '\\') {
1166 (*cp)++;
1167 if (**cp == '\0')
1168 goto jeof;
1170 goto jdfl;
1172 switch (**cp) {
1173 case '(':
1174 *cp = skip_comment(&(*cp)[1]);
1175 continue;
1176 case '>':
1177 case '\0':
1178 jeof:
1179 c = '\0';
1180 goto jleave;
1181 case '"':
1182 (*cp)++;
1183 *status |= 01;
1184 continue;
1185 case '@':
1186 *status |= 02;
1187 /*FALLTHRU*/
1188 default:
1189 jdfl:
1190 c = *(*cp)++ & 0377;
1191 c = (*status & 02) ? lowerconv(c) : c;
1192 goto jleave;
1195 jleave:
1196 NYD2_LEAVE;
1197 return c;
1200 static char const *
1201 nexttoken(char const *cp)
1203 NYD2_ENTER;
1204 for (;;) {
1205 if (*cp == '\0') {
1206 cp = NULL;
1207 break;
1210 if (*cp == '(') {
1211 size_t nesting = 1;
1213 do switch (*++cp) {
1214 case '(':
1215 ++nesting;
1216 break;
1217 case ')':
1218 --nesting;
1219 break;
1220 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
1221 } else if (blankchar(*cp) || *cp == ',')
1222 ++cp;
1223 else
1224 break;
1226 NYD2_LEAVE;
1227 return cp;
1230 FL char const *
1231 myaddrs(struct header *hp) /* TODO */
1233 struct name *np;
1234 char const *rv, *mta;
1235 NYD_ENTER;
1237 if (hp != NULL && (np = hp->h_from) != NULL) {
1238 if ((rv = np->n_fullname) != NULL)
1239 goto jleave;
1240 if ((rv = np->n_name) != NULL)
1241 goto jleave;
1244 /* Verified once variable had been set */
1245 if((rv = ok_vlook(from)) != NULL)
1246 goto jleave;
1248 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1249 * undeterminable From: address. However, if the user sets *hostname*,
1250 * accept his desire */
1251 if (ok_vlook(hostname) != NULL)
1252 goto jnodename;
1253 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
1254 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1255 ((mta = n_servbyname(ok_vlook(mta), NULL)) != NULL && *mta != '\0'))
1256 goto jnodename;
1257 jleave:
1258 NYD_LEAVE;
1259 return rv;
1261 jnodename:{
1262 char *cp;
1263 char const *hn, *ln;
1264 size_t i;
1266 hn = n_nodename(TRU1);
1267 ln = ok_vlook(LOGNAME);
1268 i = strlen(ln) + strlen(hn) + 1 +1;
1269 rv = cp = n_autorec_alloc(i);
1270 sstpcpy(sstpcpy(sstpcpy(cp, ln), n_at), hn);
1272 goto jleave;
1275 FL char const *
1276 myorigin(struct header *hp) /* TODO */
1278 char const *rv = NULL, *ccp;
1279 struct name *np;
1280 NYD_ENTER;
1282 if((ccp = myaddrs(hp)) != NULL &&
1283 (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
1284 if(np->n_flink == NULL)
1285 rv = ccp;
1286 /* Verified upon variable set time */
1287 else if((ccp = ok_vlook(sender)) != NULL)
1288 rv = ccp;
1289 /* TODO why not else rv = n_poption_arg_r; ?? */
1291 NYD_LEAVE;
1292 return rv;
1295 FL bool_t
1296 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1298 char date[n_FROM_DATEBUF];
1299 bool_t rv;
1300 NYD2_ENTER;
1302 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1303 (a_header_extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1304 !_is_date(date)))
1305 rv = TRUM1;
1306 NYD2_LEAVE;
1307 return rv;
1310 FL bool_t
1311 n_header_put4compose(FILE *fp, struct header *hp){
1312 bool_t rv;
1313 int t;
1314 NYD_ENTER;
1316 t = GTO | GSUBJECT | GCC | GBCC | GBCC_IS_FCC | GREF_IRT | GNL | GCOMMA;
1317 if((hp->h_from != NULL || myaddrs(hp) != NULL) ||
1318 (hp->h_sender != NULL || ok_vlook(sender) != NULL) ||
1319 (hp->h_reply_to != NULL || ok_vlook(reply_to) != NULL) ||
1320 ok_vlook(replyto) != NULL /* v15compat, OBSOLETE */ ||
1321 hp->h_list_post != NULL || (hp->h_flags & HF_LIST_REPLY))
1322 t |= GIDENT;
1324 rv = n_puthead(TRUM1, hp, fp, t, SEND_TODISP, CONV_NONE, NULL, NULL);
1325 NYD_LEAVE;
1326 return rv;
1329 FL void
1330 n_header_extract(enum n_header_extract_flags hef, FILE *fp, struct header *hp,
1331 si8_t *checkaddr_err_or_null)
1333 struct n_header_field **hftail;
1334 struct header nh, *hq = &nh;
1335 char *linebuf = NULL /* TODO line pool */, *colon;
1336 size_t linesize = 0, seenfields = 0;
1337 int c;
1338 long lc;
1339 off_t firstoff;
1340 char const *val, *cp;
1341 NYD_ENTER;
1343 memset(hq, 0, sizeof *hq);
1344 if(hef & n_HEADER_EXTRACT_PREFILL_RECEIVERS){
1345 hq->h_to = hp->h_to;
1346 hq->h_cc = hp->h_cc;
1347 hq->h_bcc = hp->h_bcc;
1349 hftail = &hq->h_user_headers;
1351 if((firstoff = ftell(fp)) == -1)
1352 goto jeseek;
1353 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1355 c = fseek(fp, firstoff, SEEK_SET);
1356 clearerr(fp);
1357 if(c != 0){
1358 jeseek:
1359 if(checkaddr_err_or_null != NULL)
1360 *checkaddr_err_or_null = -1;
1361 n_err("I/O error while parsing headers, operation aborted\n");
1362 goto jleave;
1365 /* TODO yippieia, cat(check(lextract)) :-) */
1366 while ((lc = a_gethfield(hef, fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1367 struct name *np;
1369 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1370 * yet expanded when we parse these! */
1371 if ((val = thisfield(linebuf, "to")) != NULL) {
1372 ++seenfields;
1373 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1374 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
1375 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1376 ++seenfields;
1377 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1378 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
1379 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1380 ++seenfields;
1381 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1382 EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
1383 } else if ((val = thisfield(linebuf, "fcc")) != NULL) {
1384 if(hef & n_HEADER_EXTRACT__MODE_MASK){
1385 ++seenfields;
1386 hq->h_fcc = cat(hq->h_fcc, nalloc_fcc(val));
1387 }else
1388 goto jebadhead;
1389 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1390 if(hef & n_HEADER_EXTRACT_FULL){
1391 ++seenfields;
1392 hq->h_from = cat(hq->h_from,
1393 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1394 EACM_STRICT, NULL));
1396 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1397 ++seenfields;
1398 hq->h_reply_to = cat(hq->h_reply_to,
1399 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1400 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1401 if(hef & n_HEADER_EXTRACT_FULL){
1402 ++seenfields;
1403 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1404 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1405 EACM_STRICT, NULL));
1406 } else
1407 goto jebadhead;
1408 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1409 (val = thisfield(linebuf, "subj")) != NULL) {
1410 ++seenfields;
1411 for (cp = val; blankchar(*cp); ++cp)
1413 hq->h_subject = (hq->h_subject != NULL)
1414 ? save2str(hq->h_subject, cp) : savestr(cp);
1416 /* The remaining are mostly hacked in and thus TODO -- at least in
1417 * TODO respect to their content checking */
1418 else if((val = thisfield(linebuf, "message-id")) != NULL){
1419 if(hef & n_HEADER_EXTRACT__MODE_MASK){
1420 np = checkaddrs(lextract(val, GREF),
1421 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1422 NULL);
1423 if (np == NULL || np->n_flink != NULL)
1424 goto jebadhead;
1425 ++seenfields;
1426 hq->h_message_id = np;
1427 }else
1428 goto jebadhead;
1429 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1430 if(hef & n_HEADER_EXTRACT__MODE_MASK){
1431 np = checkaddrs(lextract(val, GREF),
1432 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1433 NULL);
1434 ++seenfields;
1435 hq->h_in_reply_to = np;
1436 }else
1437 goto jebadhead;
1438 }else if((val = thisfield(linebuf, "references")) != NULL){
1439 if(hef & n_HEADER_EXTRACT__MODE_MASK){
1440 ++seenfields;
1441 /* TODO Limit number of references TODO better on parser side */
1442 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1443 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1444 NULL));
1445 }else
1446 goto jebadhead;
1448 /* and that is very hairy */
1449 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1450 if(hef & n_HEADER_EXTRACT__MODE_MASK){
1451 ++seenfields;
1452 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1453 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1454 checkaddr_err_or_null));
1455 }else
1456 goto jebadhead;
1458 /* A free-form header; a_gethfield() did some verification already.. */
1459 else{
1460 struct n_header_field *hfp;
1461 ui32_t nl, bl;
1462 char const *nstart;
1464 for(nstart = cp = linebuf;; ++cp)
1465 if(!fieldnamechar(*cp))
1466 break;
1467 nl = (ui32_t)PTR2SIZE(cp - nstart);
1469 while(blankchar(*cp))
1470 ++cp;
1471 if(*cp++ != ':'){
1472 jebadhead:
1473 n_err(_("Ignoring header field: %s\n"), linebuf);
1474 continue;
1476 while(blankchar(*cp))
1477 ++cp;
1478 bl = (ui32_t)strlen(cp) +1;
1480 ++seenfields;
1481 *hftail =
1482 hfp = n_autorec_alloc(n_VSTRUCT_SIZEOF(struct n_header_field,
1483 hf_dat) + nl +1 + bl);
1484 hftail = &hfp->hf_next;
1485 hfp->hf_next = NULL;
1486 hfp->hf_nl = nl;
1487 hfp->hf_bl = bl - 1;
1488 memcpy(hfp->hf_dat, nstart, nl);
1489 hfp->hf_dat[nl++] = '\0';
1490 memcpy(hfp->hf_dat + nl, cp, bl);
1494 /* In case the blank line after the header has been edited out. Otherwise,
1495 * fetch the header separator */
1496 if (linebuf != NULL) {
1497 if (linebuf[0] != '\0') {
1498 for (cp = linebuf; *(++cp) != '\0';)
1500 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1501 } else {
1502 if ((c = getc(fp)) != '\n' && c != EOF)
1503 ungetc(c, fp);
1507 if (seenfields > 0 &&
1508 (checkaddr_err_or_null == NULL || *checkaddr_err_or_null == 0)) {
1509 hp->h_to = hq->h_to;
1510 hp->h_cc = hq->h_cc;
1511 hp->h_bcc = hq->h_bcc;
1512 hp->h_from = hq->h_from;
1513 hp->h_reply_to = hq->h_reply_to;
1514 hp->h_sender = hq->h_sender;
1515 if(hq->h_subject != NULL ||
1516 (hef & n_HEADER_EXTRACT__MODE_MASK) != n_HEADER_EXTRACT_FULL)
1517 hp->h_subject = hq->h_subject;
1518 hp->h_user_headers = hq->h_user_headers;
1520 if(hef & n_HEADER_EXTRACT__MODE_MASK){
1521 hp->h_fcc = hq->h_fcc;
1522 if(hef & n_HEADER_EXTRACT_FULL)
1523 hp->h_ref = hq->h_ref;
1524 hp->h_message_id = hq->h_message_id;
1525 hp->h_in_reply_to = hq->h_in_reply_to;
1526 hp->h_mft = hq->h_mft;
1528 /* And perform additional validity checks so that we don't bail later
1529 * on TODO this is good and the place where this should occur,
1530 * TODO unfortunately a lot of other places do again and blabla */
1531 if(hp->h_from == NULL)
1532 hp->h_from = n_poption_arg_r;
1533 else if((hef & n_HEADER_EXTRACT_FULL) &&
1534 hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1535 hp->h_sender = lextract(ok_vlook(sender),
1536 GEXTRA | GFULL | GFULLEXTRA);
1538 } else
1539 n_err(_("Restoring deleted header lines\n"));
1541 jleave:
1542 if (linebuf != NULL)
1543 n_free(linebuf);
1544 NYD_LEAVE;
1547 FL char *
1548 hfield_mult(char const *field, struct message *mp, int mult)
1550 FILE *ibuf;
1551 struct str hfs;
1552 long lc;
1553 size_t linesize = 0; /* TODO line pool */
1554 char *linebuf = NULL, *colon;
1555 char const *hfield;
1556 NYD_ENTER;
1558 /* There are (spam) messages which have header bytes which are many KB when
1559 * joined, so resize a single heap storage until we are done if we shall
1560 * collect a field that may have multiple bodies; only otherwise use the
1561 * string dope directly */
1562 memset(&hfs, 0, sizeof hfs);
1564 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1565 goto jleave;
1566 if ((lc = mp->m_lines - 1) < 0)
1567 goto jleave;
1569 if ((mp->m_flag & MNOFROM) == 0 &&
1570 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1571 goto jleave;
1572 while (lc > 0) {
1573 if ((lc = a_gethfield(n_HEADER_EXTRACT_NONE, ibuf, &linebuf, &linesize,
1574 lc, &colon)) < 0)
1575 break;
1576 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1577 if (mult)
1578 n_str_add_buf(&hfs, hfield, strlen(hfield));
1579 else {
1580 hfs.s = savestr(hfield);
1581 break;
1586 jleave:
1587 if (linebuf != NULL)
1588 n_free(linebuf);
1589 if (mult && hfs.s != NULL) {
1590 colon = savestrbuf(hfs.s, hfs.l);
1591 n_free(hfs.s);
1592 hfs.s = colon;
1594 NYD_LEAVE;
1595 return hfs.s;
1598 FL char const *
1599 thisfield(char const *linebuf, char const *field)
1601 char const *rv = NULL;
1602 NYD2_ENTER;
1604 while (lowerconv(*linebuf) == lowerconv(*field)) {
1605 ++linebuf;
1606 ++field;
1608 if (*field != '\0')
1609 goto jleave;
1611 while (blankchar(*linebuf))
1612 ++linebuf;
1613 if (*linebuf++ != ':')
1614 goto jleave;
1616 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1617 ++linebuf;
1618 rv = linebuf;
1619 jleave:
1620 NYD2_LEAVE;
1621 return rv;
1624 FL char const *
1625 skip_comment(char const *cp)
1627 size_t nesting;
1628 NYD_ENTER;
1630 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1631 switch (*cp) {
1632 case '\\':
1633 if (cp[1])
1634 ++cp;
1635 break;
1636 case '(':
1637 ++nesting;
1638 break;
1639 case ')':
1640 --nesting;
1641 break;
1644 NYD_LEAVE;
1645 return cp;
1648 FL char const *
1649 routeaddr(char const *name)
1651 char const *np, *rp = NULL;
1652 NYD_ENTER;
1654 for (np = name; *np; np++) {
1655 switch (*np) {
1656 case '(':
1657 np = skip_comment(np + 1) - 1;
1658 break;
1659 case '"':
1660 while (*np) {
1661 if (*++np == '"')
1662 break;
1663 if (*np == '\\' && np[1])
1664 np++;
1666 break;
1667 case '<':
1668 rp = np;
1669 break;
1670 case '>':
1671 goto jleave;
1674 rp = NULL;
1675 jleave:
1676 NYD_LEAVE;
1677 return rp;
1680 FL enum expand_addr_flags
1681 expandaddr_to_eaf(void){
1682 struct eafdesc {
1683 char eafd_name[13];
1684 bool_t eafd_is_target;
1685 ui8_t eafd_andoff;
1686 ui8_t eafd_or;
1687 } const eafa[] = {
1688 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1689 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1690 {"failinvaddr\0", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1691 {"shquote", FAL0, EAF_NONE, EAF_SHEXP_PARSE},
1692 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1693 {"file", TRU1, EAF_NONE, EAF_FILE},
1694 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1695 {"name", TRU1, EAF_NONE, EAF_NAME},
1696 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1697 }, *eafp;
1699 char *buf;
1700 enum expand_addr_flags rv;
1701 char const *cp;
1702 NYD2_ENTER;
1704 if((cp = ok_vlook(expandaddr)) == NULL)
1705 rv = EAF_RESTRICT_TARGETS;
1706 else if(*cp == '\0')
1707 rv = EAF_TARGET_MASK;
1708 else{
1709 rv = EAF_TARGET_MASK;
1711 for(buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;){
1712 bool_t minus;
1714 if((minus = (*cp == '-')) || (*cp == '+' ? (minus = TRUM1) : FAL0))
1715 ++cp;
1717 for(eafp = eafa;; ++eafp) {
1718 if(eafp == &eafa[n_NELEM(eafa)]){
1719 if(n_poption & n_PO_D_V)
1720 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1721 break;
1722 }else if(!asccasecmp(cp, eafp->eafd_name)){
1723 if(minus){
1724 if(eafp->eafd_is_target){
1725 if(minus != TRU1)
1726 goto jandor;
1727 else
1728 rv &= ~eafp->eafd_or;
1729 }else if(n_poption & n_PO_D_V)
1730 n_err(_("- or + prefix invalid for *expandaddr* value: "
1731 "%s\n"), --cp);
1732 }else{
1733 jandor:
1734 rv &= ~eafp->eafd_andoff;
1735 rv |= eafp->eafd_or;
1737 break;
1738 }else if(!asccasecmp(cp, "noalias")){ /* TODO v15 OBSOLETE */
1739 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1740 rv &= ~EAF_NAME;
1741 break;
1746 if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
1747 (n_poption & n_PO_TILDE_FLAG)))
1748 rv |= EAF_TARGET_MASK;
1749 else if(n_poption & n_PO_D_V){
1750 if(!(rv & EAF_TARGET_MASK))
1751 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1752 else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1753 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1756 NYD2_LEAVE;
1757 return rv;
1760 FL si8_t
1761 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1763 char cbuf[sizeof "'\\U12340'"];
1764 char const *cs;
1765 int f;
1766 si8_t rv;
1767 enum expand_addr_flags eaf;
1768 NYD_ENTER;
1770 eaf = expandaddr_to_eaf();
1771 f = np->n_flags;
1773 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1774 if (eaf & EAF_FAILINVADDR)
1775 rv = -rv;
1777 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1779 } else {
1780 ui32_t c;
1781 char const *fmt = "'\\x%02X'";
1782 bool_t ok8bit = TRU1;
1784 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1785 cs = _("Invalid domain name: %s, character %s\n");
1786 fmt = "'\\U%04X'";
1787 ok8bit = FAL0;
1788 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1789 cs = _("%s contains invalid %s sequence\n");
1790 else if (f & NAME_ADDRSPEC_ERR_NAME) {
1791 cs = _("%s is an invalid alias name\n");
1792 } else
1793 cs = _("%s contains invalid byte %s\n");
1795 c = NAME_ADDRSPEC_ERR_GETWC(f);
1796 snprintf(cbuf, sizeof cbuf,
1797 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1798 goto jprint;
1800 goto jleave;
1803 /* *expandaddr* stuff */
1804 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1805 goto jleave;
1807 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1808 if (eaf & EAF_FAIL)
1809 rv = -rv;
1810 cs = _("%s%s: file or pipe addressees not allowed here\n");
1811 if (eacm & EACM_NOLOG)
1812 goto jleave;
1813 else
1814 goto j0print;
1817 eaf |= (eacm & EAF_TARGET_MASK);
1818 if (eacm & EACM_NONAME)
1819 eaf &= ~EAF_NAME;
1821 if (eaf == EAF_NONE) {
1822 rv = FAL0;
1823 goto jleave;
1825 if (eaf & EAF_FAIL)
1826 rv = -rv;
1828 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1829 cs = _("%s%s: *expandaddr* does not allow file target\n");
1830 if (eacm & EACM_NOLOG)
1831 goto jleave;
1832 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1833 cs = _("%s%s: *expandaddr* does not allow command pipe target\n");
1834 if (eacm & EACM_NOLOG)
1835 goto jleave;
1836 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1837 cs = _("%s%s: *expandaddr* does not allow user name target\n");
1838 if (eacm & EACM_NOLOG)
1839 goto jleave;
1840 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1841 cs = _("%s%s: *expandaddr* does not allow mail address target\n");
1842 if (eacm & EACM_NOLOG)
1843 goto jleave;
1844 } else {
1845 rv = FAL0;
1846 goto jleave;
1849 j0print:
1850 cbuf[0] = '\0';
1851 jprint:
1852 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1853 jleave:
1854 NYD_LEAVE;
1855 return rv;
1858 FL char *
1859 skin(char const *name)
1861 struct n_addrguts ag;
1862 char *rv;
1863 NYD_ENTER;
1865 if(name != NULL){
1866 /*name =*/ n_addrspec_with_guts(&ag, name, TRU1, FAL0);
1867 rv = ag.ag_skinned;
1868 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1869 rv = savestrbuf(rv, ag.ag_slen);
1870 }else
1871 rv = NULL;
1872 NYD_LEAVE;
1873 return rv;
1876 /* TODO addrspec_with_guts: RFC 5322
1877 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1878 FL char const *
1879 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin,
1880 bool_t issingle_hack){
1881 char const *cp;
1882 char *cp2, *bufend, *nbuf, c;
1883 enum{
1884 a_NONE,
1885 a_GOTLT = 1<<0,
1886 a_GOTADDR = 1<<1,
1887 a_GOTSPACE = 1<<2,
1888 a_LASTSP = 1<<3
1889 } flags;
1890 NYD_ENTER;
1892 memset(agp, 0, sizeof *agp);
1894 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1895 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1896 agp->ag_slen = 0;
1897 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1898 goto jleave;
1899 }else if(!doskin){
1900 /*agp->ag_iaddr_start = 0;*/
1901 agp->ag_iaddr_aend = agp->ag_ilen;
1902 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1903 agp->ag_slen = agp->ag_ilen;
1904 agp->ag_n_flags = NAME_SKINNED;
1905 goto jcheck;
1908 flags = a_NONE;
1909 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1910 /*agp->ag_iaddr_start = 0;*/
1911 cp2 = bufend = nbuf;
1913 /* TODO This is complete crap and should use a token parser */
1914 for(cp = name++; (c = *cp++) != '\0';){
1915 switch (c) {
1916 case '(':
1917 cp = skip_comment(cp);
1918 flags &= ~a_LASTSP;
1919 break;
1920 case '"':
1921 /* Start of a "quoted-string". Copy it in its entirety */
1922 /* XXX RFC: quotes are "semantically invisible"
1923 * XXX But it was explicitly added (Changelog.Heirloom,
1924 * XXX [9.23] released 11/15/00, "Do not remove quotes
1925 * XXX when skinning names"? No more info.. */
1926 *cp2++ = c;
1927 while ((c = *cp) != '\0') { /* TODO improve */
1928 ++cp;
1929 if (c == '"') {
1930 *cp2++ = c;
1931 break;
1933 if (c != '\\')
1934 *cp2++ = c;
1935 else if ((c = *cp) != '\0') {
1936 *cp2++ = c;
1937 ++cp;
1940 flags &= ~a_LASTSP;
1941 break;
1942 case ' ':
1943 case '\t':
1944 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1945 flags |= a_GOTSPACE;
1946 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1948 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1949 cp += 3, *cp2++ = '@';
1950 else if (cp[0] == '@' && blankchar(cp[1]))
1951 cp += 2, *cp2++ = '@';
1952 else
1953 flags |= a_LASTSP;
1954 break;
1955 case '<':
1956 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1957 cp2 = bufend;
1958 flags &= ~(a_GOTSPACE | a_LASTSP);
1959 flags |= a_GOTLT | a_GOTADDR;
1960 break;
1961 case '>':
1962 if(flags & a_GOTLT){
1963 /* (_addrspec_check() verifies these later!) */
1964 flags &= ~(a_GOTLT | a_LASTSP);
1965 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1967 /* Skip over the entire remaining field */
1968 while((c = *cp) != '\0' && c != ','){
1969 ++cp;
1970 if (c == '(')
1971 cp = skip_comment(cp);
1972 else if (c == '"')
1973 while ((c = *cp) != '\0') {
1974 ++cp;
1975 if (c == '"')
1976 break;
1977 if (c == '\\' && *cp != '\0')
1978 ++cp;
1981 break;
1983 /* FALLTHRU */
1984 default:
1985 if(flags & a_LASTSP){
1986 flags &= ~a_LASTSP;
1987 if(flags & a_GOTADDR)
1988 *cp2++ = ' ';
1990 *cp2++ = c;
1991 /* This character is forbidden here, but it may nonetheless be
1992 * present: ensure we turn this into something valid! (E.g., if the
1993 * next character would be a "..) */
1994 if(c == '\\' && *cp != '\0')
1995 *cp2++ = *cp++;
1996 if(c == ',' && !issingle_hack){
1997 if(!(flags & a_GOTLT)){
1998 *cp2++ = ' ';
1999 for(; blankchar(*cp); ++cp)
2001 flags &= ~a_LASTSP;
2002 bufend = cp2;
2004 }else if(!(flags & a_GOTADDR)){
2005 flags |= a_GOTADDR;
2006 agp->ag_iaddr_start = PTR2SIZE(cp - name);
2010 --name;
2011 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
2012 if (agp->ag_iaddr_aend == 0)
2013 agp->ag_iaddr_aend = agp->ag_ilen;
2014 /* Misses > */
2015 else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
2016 cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
2017 memcpy(cp2, agp->ag_input, agp->ag_ilen);
2018 agp->ag_iaddr_aend = agp->ag_ilen;
2019 cp2[agp->ag_ilen++] = '>';
2020 cp2[agp->ag_ilen] = '\0';
2021 agp->ag_input = cp2;
2023 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
2024 n_lofi_free(nbuf);
2025 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
2026 jcheck:
2027 if(a_header_addrspec_check(agp, doskin, issingle_hack) <= FAL0)
2028 name = NULL;
2029 else
2030 name = agp->ag_input;
2031 jleave:
2032 NYD_LEAVE;
2033 return name;
2036 FL char *
2037 realname(char const *name)
2039 char const *cp, *cq, *cstart = NULL, *cend = NULL;
2040 char *rname, *rp;
2041 struct str in, out;
2042 int quoted, good, nogood;
2043 NYD_ENTER;
2045 if ((cp = n_UNCONST(name)) == NULL)
2046 goto jleave;
2047 for (; *cp != '\0'; ++cp) {
2048 switch (*cp) {
2049 case '(':
2050 if (cstart != NULL) {
2051 /* More than one comment in address, doesn't make sense to display
2052 * it without context. Return the entire field */
2053 cp = mime_fromaddr(name);
2054 goto jleave;
2056 cstart = cp++;
2057 cp = skip_comment(cp);
2058 cend = cp--;
2059 if (cend <= cstart)
2060 cend = cstart = NULL;
2061 break;
2062 case '"':
2063 while (*cp) {
2064 if (*++cp == '"')
2065 break;
2066 if (*cp == '\\' && cp[1])
2067 ++cp;
2069 break;
2070 case '<':
2071 if (cp > name) {
2072 cstart = name;
2073 cend = cp;
2075 break;
2076 case ',':
2077 /* More than one address. Just use the first one */
2078 goto jbrk;
2082 jbrk:
2083 if (cstart == NULL) {
2084 if (*name == '<') {
2085 /* If name contains only a route-addr, the surrounding angle brackets
2086 * don't serve any useful purpose when displaying, so remove */
2087 cp = prstr(skin(name));
2088 } else
2089 cp = mime_fromaddr(name);
2090 goto jleave;
2093 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
2094 * not stripped. The idea is to strip only syntactical relevant things (but
2095 * this is not necessarily the most sensible way in practice) */
2096 rp = rname = n_lofi_alloc(PTR2SIZE(cend - cstart +1));
2097 quoted = 0;
2098 for (cp = cstart; cp < cend; ++cp) {
2099 if (*cp == '(' && !quoted) {
2100 cq = skip_comment(++cp);
2101 if (PTRCMP(--cq, >, cend))
2102 cq = cend;
2103 while (cp < cq) {
2104 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
2105 ++cp;
2106 *rp++ = *cp++;
2108 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
2109 *rp++ = *++cp;
2110 else if (*cp == '"') {
2111 quoted = !quoted;
2112 continue;
2113 } else
2114 *rp++ = *cp;
2116 *rp = '\0';
2117 in.s = rname;
2118 in.l = rp - rname;
2119 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
2120 n_lofi_free(rname);
2121 rname = savestr(out.s);
2122 n_free(out.s);
2124 while (blankchar(*rname))
2125 ++rname;
2126 for (rp = rname; *rp != '\0'; ++rp)
2128 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
2129 *rp = '\0';
2130 if (rp == rname) {
2131 cp = mime_fromaddr(name);
2132 goto jleave;
2135 /* mime_fromhdr() has converted all nonprintable characters to question
2136 * marks now. These and blanks are considered uninteresting; if the
2137 * displayed part of the real name contains more than 25% of them, it is
2138 * probably better to display the plain email address instead */
2139 good = 0;
2140 nogood = 0;
2141 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
2142 if (*rp == '?' || blankchar(*rp))
2143 ++nogood;
2144 else
2145 ++good;
2146 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
2147 jleave:
2148 NYD_LEAVE;
2149 return n_UNCONST(cp);
2152 FL char *
2153 n_header_senderfield_of(struct message *mp){
2154 char *cp;
2155 NYD_ENTER;
2157 if((cp = hfield1("from", mp)) != NULL && *cp != '\0')
2159 else if((cp = hfield1("sender", mp)) != NULL && *cp != '\0')
2161 else{
2162 char *namebuf, *cp2, *linebuf = NULL /* TODO line pool */;
2163 size_t namesize, linesize = 0;
2164 FILE *ibuf;
2165 int f1st = 1;
2167 /* And fallback only works for MBOX */
2168 namebuf = n_alloc(namesize = 1);
2169 namebuf[0] = 0;
2170 if (mp->m_flag & MNOFROM)
2171 goto jout;
2172 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2173 goto jout;
2174 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2175 goto jout;
2177 jnewname:
2178 if (namesize <= linesize)
2179 namebuf = n_realloc(namebuf, namesize = linesize +1);
2180 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
2182 for (; blankchar(*cp); ++cp)
2184 for (cp2 = namebuf + strlen(namebuf);
2185 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
2186 *cp2++ = *cp++;
2187 *cp2 = '\0';
2189 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2190 goto jout;
2191 if ((cp = strchr(linebuf, 'F')) == NULL)
2192 goto jout;
2193 if (strncmp(cp, "From", 4))
2194 goto jout;
2195 if (namesize <= linesize)
2196 namebuf = n_realloc(namebuf, namesize = linesize + 1);
2198 /* UUCP from 976 (we do not support anyway!) */
2199 while ((cp = strchr(cp, 'r')) != NULL) {
2200 if (!strncmp(cp, "remote", 6)) {
2201 if ((cp = strchr(cp, 'f')) == NULL)
2202 break;
2203 if (strncmp(cp, "from", 4) != 0)
2204 break;
2205 if ((cp = strchr(cp, ' ')) == NULL)
2206 break;
2207 cp++;
2208 if (f1st) {
2209 strncpy(namebuf, cp, namesize);
2210 f1st = 0;
2211 } else {
2212 cp2 = strrchr(namebuf, '!') + 1;
2213 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
2215 namebuf[namesize - 2] = '!';
2216 namebuf[namesize - 1] = '\0';
2217 goto jnewname;
2219 cp++;
2221 jout:
2222 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
2223 *cp == '\0')
2224 cp = savestr(namebuf);
2226 if (linebuf != NULL)
2227 n_free(linebuf);
2228 n_free(namebuf);
2231 NYD_LEAVE;
2232 return cp;
2235 FL char const *
2236 subject_re_trim(char const *s){
2237 struct{
2238 ui8_t len;
2239 char dat[7];
2240 }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
2241 {3, "re:"},
2242 {3, "aw:"}, {5, "antw:"}, /* de */
2243 {3, "wg:"}, /* Seen too often in the wild */
2244 {0, ""}
2247 bool_t any;
2248 char *re_st, *re_st_x;
2249 char const *orig_s;
2250 size_t re_l;
2251 NYD_ENTER;
2253 any = FAL0;
2254 orig_s = s;
2255 re_st = NULL;
2256 n_UNINIT(re_l, 0);
2258 if((re_st_x = ok_vlook(reply_strings)) != NULL &&
2259 (re_l = strlen(re_st_x)) > 0){
2260 re_st = n_lofi_alloc(++re_l * 2);
2261 memcpy(re_st, re_st_x, re_l);
2264 jouter:
2265 while(*s != '\0'){
2266 while(spacechar(*s))
2267 ++s;
2269 for(pp = ignored; pp->len > 0; ++pp)
2270 if(is_asccaseprefix(pp->dat, s)){
2271 s += pp->len;
2272 any = TRU1;
2273 goto jouter;
2276 if(re_st != NULL){
2277 char *cp;
2279 memcpy(re_st_x = &re_st[re_l], re_st, re_l);
2280 while((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2281 if(is_asccaseprefix(cp, s)){
2282 s += strlen(cp);
2283 any = TRU1;
2284 goto jouter;
2287 break;
2290 if(re_st != NULL)
2291 n_lofi_free(re_st);
2292 NYD_LEAVE;
2293 return any ? s : orig_s;
2296 FL int
2297 msgidcmp(char const *s1, char const *s2)
2299 int q1 = 0, q2 = 0, c1, c2;
2300 NYD_ENTER;
2302 while(*s1 == '<')
2303 ++s1;
2304 while(*s2 == '<')
2305 ++s2;
2307 do {
2308 c1 = msgidnextc(&s1, &q1);
2309 c2 = msgidnextc(&s2, &q2);
2310 if (c1 != c2)
2311 break;
2312 } while (c1 && c2);
2313 NYD_LEAVE;
2314 return c1 - c2;
2317 FL char const *
2318 fakefrom(struct message *mp)
2320 char const *name;
2321 NYD_ENTER;
2323 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2324 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2325 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2326 * RFC 4155 however requires a RFC 5322 (2822) conforming
2327 * "addr-spec", but we simply can't provide that */
2328 name = "MAILER-DAEMON";
2329 NYD_LEAVE;
2330 return name;
2333 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2334 FL time_t
2335 unixtime(char const *fromline)
2337 char const *fp, *xp;
2338 time_t t, t2;
2339 si32_t i, year, month, day, hour, minute, second, tzdiff;
2340 struct tm *tmptr;
2341 NYD2_ENTER;
2343 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2345 fp -= 24;
2346 if (PTR2SIZE(fp - fromline) < 7)
2347 goto jinvalid;
2348 if (fp[3] != ' ')
2349 goto jinvalid;
2350 for (i = 0;;) {
2351 if (!strncmp(fp + 4, n_month_names[i], 3))
2352 break;
2353 if (n_month_names[++i][0] == '\0')
2354 goto jinvalid;
2356 month = i + 1;
2357 if (fp[7] != ' ')
2358 goto jinvalid;
2359 n_idec_si32_cp(&day, &fp[8], 10, &xp);
2360 if (*xp != ' ' || xp != fp + 10)
2361 goto jinvalid;
2362 n_idec_si32_cp(&hour, &fp[11], 10, &xp);
2363 if (*xp != ':' || xp != fp + 13)
2364 goto jinvalid;
2365 n_idec_si32_cp(&minute, &fp[14], 10, &xp);
2366 if (*xp != ':' || xp != fp + 16)
2367 goto jinvalid;
2368 n_idec_si32_cp(&second, &fp[17], 10, &xp);
2369 if (*xp != ' ' || xp != fp + 19)
2370 goto jinvalid;
2371 n_idec_si32_cp(&year, &fp[20], 10, &xp);
2372 if (xp != fp + 24)
2373 goto jinvalid;
2374 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2375 goto jinvalid;
2376 if((t2 = mktime(gmtime(&t))) == (time_t)-1)
2377 goto jinvalid;
2378 tzdiff = t - t2;
2379 if((tmptr = localtime(&t)) == NULL)
2380 goto jinvalid;
2381 if (tmptr->tm_isdst > 0)
2382 tzdiff += 3600; /* TODO simply adding an hour for ISDST is .. buuh */
2383 t -= tzdiff;
2384 jleave:
2385 NYD2_LEAVE;
2386 return t;
2387 jinvalid:
2388 t = n_time_epoch();
2389 goto jleave;
2391 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2393 FL time_t
2394 rfctime(char const *date) /* TODO n_idec_ return tests */
2396 char const *cp, *x;
2397 time_t t;
2398 si32_t i, year, month, day, hour, minute, second;
2399 NYD2_ENTER;
2401 cp = date;
2403 if ((cp = nexttoken(cp)) == NULL)
2404 goto jinvalid;
2405 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2406 cp[3] == ',') {
2407 if ((cp = nexttoken(&cp[4])) == NULL)
2408 goto jinvalid;
2410 n_idec_si32_cp(&day, cp, 10, &x);
2411 if ((cp = nexttoken(x)) == NULL)
2412 goto jinvalid;
2413 for (i = 0;;) {
2414 if (!strncmp(cp, n_month_names[i], 3))
2415 break;
2416 if (n_month_names[++i][0] == '\0')
2417 goto jinvalid;
2419 month = i + 1;
2420 if ((cp = nexttoken(&cp[3])) == NULL)
2421 goto jinvalid;
2422 /* RFC 5322, 4.3:
2423 * Where a two or three digit year occurs in a date, the year is to be
2424 * interpreted as follows: If a two digit year is encountered whose
2425 * value is between 00 and 49, the year is interpreted by adding 2000,
2426 * ending up with a value between 2000 and 2049. If a two digit year
2427 * is encountered with a value between 50 and 99, or any three digit
2428 * year is encountered, the year is interpreted by adding 1900 */
2429 n_idec_si32_cp(&year, cp, 10, &x);
2430 i = (int)PTR2SIZE(x - cp);
2431 if (i == 2 && year >= 0 && year <= 49)
2432 year += 2000;
2433 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2434 year += 1900;
2435 if ((cp = nexttoken(x)) == NULL)
2436 goto jinvalid;
2437 n_idec_si32_cp(&hour, cp, 10, &x);
2438 if (*x != ':')
2439 goto jinvalid;
2440 cp = &x[1];
2441 n_idec_si32_cp(&minute, cp, 10, &x);
2442 if (*x == ':') {
2443 cp = &x[1];
2444 n_idec_si32_cp(&second, cp, 10, &x);
2445 } else
2446 second = 0;
2448 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2449 goto jinvalid;
2450 if ((cp = nexttoken(x)) != NULL) {
2451 char buf[3];
2452 int sign = 1;
2454 switch (*cp) {
2455 case '+':
2456 sign = -1;
2457 /* FALLTHRU */
2458 case '-':
2459 ++cp;
2460 break;
2462 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2463 digitchar(cp[3])) {
2464 si64_t tadj;
2466 buf[2] = '\0';
2467 buf[0] = cp[0];
2468 buf[1] = cp[1];
2469 n_idec_si32_cp(&i, buf, 10, NULL);
2470 tadj = (si64_t)i * 3600; /* XXX */
2471 buf[0] = cp[2];
2472 buf[1] = cp[3];
2473 n_idec_si32_cp(&i, buf, 10, NULL);
2474 tadj += (si64_t)i * 60; /* XXX */
2475 if (sign < 0)
2476 tadj = -tadj;
2477 t += (time_t)tadj;
2479 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2480 * TODO once again, Christos Zoulas and NetBSD Mail have done
2481 * TODO a really good job already, but using strptime(3), which
2482 * TODO is not portable. Nonetheless, WE must improve, not
2483 * TODO at last because we simply ignore obsolete timezones!!
2484 * TODO See RFC 5322, 4.3! */
2486 jleave:
2487 NYD2_LEAVE;
2488 return t;
2489 jinvalid:
2490 t = 0;
2491 goto jleave;
2494 FL time_t
2495 combinetime(int year, int month, int day, int hour, int minute, int second){
2496 size_t const jdn_epoch = 2440588;
2497 bool_t const y2038p = (sizeof(time_t) == 4);
2499 size_t jdn;
2500 time_t t;
2501 NYD2_ENTER;
2503 if(UICMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
2504 UICMP(32, minute, >=, n_DATE_MINSHOUR) ||
2505 UICMP(32, hour, >=, n_DATE_HOURSDAY) ||
2506 day < 1 || day > 31 ||
2507 month < 1 || month > 12 ||
2508 year < 1970)
2509 goto jerr;
2511 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2512 (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
2513 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2514 * test by stepping second-wise around the flip. Don't care otherwise */
2515 if(!y2038p)
2516 goto jerr;
2517 if(year > 2038 || month > 1 || day > 19 ||
2518 hour > 3 || minute > 14 || second > 7)
2519 goto jerr;
2522 t = second;
2523 t += minute * n_DATE_SECSMIN;
2524 t += hour * n_DATE_SECSHOUR;
2526 jdn = a_header_gregorian_to_jdn(year, month, day);
2527 jdn -= jdn_epoch;
2528 t += (time_t)jdn * n_DATE_SECSDAY;
2529 jleave:
2530 NYD2_LEAVE;
2531 return t;
2532 jerr:
2533 t = (time_t)-1;
2534 goto jleave;
2537 FL void
2538 substdate(struct message *m)
2540 /* The Date: of faked From_ lines is traditionally the date the message was
2541 * written to the mail file. Try to determine this using RFC message header
2542 * fields, or fall back to current time */
2543 char const *cp;
2544 NYD_ENTER;
2546 m->m_time = 0;
2547 if ((cp = hfield1("received", m)) != NULL) {
2548 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2550 ++cp;
2551 while (alnumchar(*cp));
2553 if (cp && *++cp)
2554 m->m_time = rfctime(cp);
2556 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2557 if ((cp = hfield1("date", m)) != NULL)
2558 m->m_time = rfctime(cp);
2560 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2561 m->m_time = time_current.tc_time;
2562 NYD_LEAVE;
2565 FL char *
2566 n_header_textual_date_info(struct message *mp, char const **color_tag_or_null){
2567 struct tm tmlocal;
2568 char *rv;
2569 char const *fmt, *cp;
2570 time_t t;
2571 NYD_ENTER;
2572 n_UNUSED(color_tag_or_null);
2574 t = mp->m_time;
2575 fmt = ok_vlook(datefield);
2577 jredo:
2578 if(fmt != NULL){
2579 ui8_t i;
2581 cp = hfield1("date", mp);/* TODO use m_date field! */
2582 if(cp == NULL){
2583 fmt = NULL;
2584 goto jredo;
2587 t = rfctime(cp);
2588 rv = n_time_ctime(t, NULL);
2589 cp = ok_vlook(datefield_markout_older);
2590 i = (*fmt != '\0');
2591 if(cp != NULL)
2592 i |= (*cp != '\0') ? 2 | 4 : 2; /* XXX no magics */
2594 /* May we strftime(3)? */
2595 if(i & (1 | 4)){
2596 /* This localtime(3) should not fail since rfctime(3).. but .. */
2597 struct tm *tmp;
2598 time_t t2;
2600 /* TODO the datetime stuff is horror: mails should be parsed into
2601 * TODO an object tree, and date: etc. have a datetime object, which
2602 * TODO verifies upon parse time; then ALL occurrences of datetime are
2603 * TODO valid all through the program; and: to_wire, to_user! */
2604 t2 = t;
2605 jredo_localtime:
2606 if((tmp = localtime(&t2)) == NULL){
2607 t2 = 0;
2608 goto jredo_localtime;
2610 memcpy(&tmlocal, tmp, sizeof *tmp);
2613 if((i & 2) &&
2614 (UICMP(64, t, >, time_current.tc_time + n_DATE_SECSDAY) ||
2615 #define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY)
2616 UICMP(64, t + _6M, <, time_current.tc_time))){
2617 #undef _6M
2618 if((fmt = (i & 4) ? cp : NULL) == NULL){
2619 char *x;
2620 n_LCTA(n_FROM_DATEBUF >= 4 + 7 + 1 + 4, "buffer too small");
2622 x = n_autorec_alloc(n_FROM_DATEBUF);
2623 memset(x, ' ', 4 + 7 + 1 + 4);
2624 memcpy(&x[4], &rv[4], 7);
2625 x[4 + 7] = ' ';
2626 memcpy(&x[4 + 7 + 1], &rv[20], 4);
2627 x[4 + 7 + 1 + 4] = '\0';
2628 rv = x;
2630 n_COLOUR(
2631 if(color_tag_or_null != NULL)
2632 *color_tag_or_null = n_COLOUR_TAG_SUM_OLDER;
2634 }else if((i & 1) == 0)
2635 fmt = NULL;
2637 if(fmt != NULL){
2638 size_t j;
2640 for(j = n_FROM_DATEBUF;; j <<= 1){
2641 i = strftime(rv = n_autorec_alloc(j), j, fmt, &tmlocal);
2642 if(i != 0)
2643 break;
2644 if(j > 128){
2645 n_err(_("Ignoring this date format: %s\n"),
2646 n_shexp_quote_cp(fmt, FAL0));
2647 n_strscpy(rv, n_time_ctime(t, NULL), j);
2651 }else if(t == (time_t)0 && !(mp->m_flag & MNOFROM)){
2652 /* TODO eliminate this path, query the FROM_ date in setptr(),
2653 * TODO all other codepaths do so by themselves ALREADY ?????
2654 * TODO assert(mp->m_time != 0);, then
2655 * TODO ALSO changes behaviour of datefield_markout_older */
2656 a_header_parse_from_(mp, rv = n_autorec_alloc(n_FROM_DATEBUF));
2657 }else
2658 rv = savestr(n_time_ctime(t, NULL));
2659 NYD_LEAVE;
2660 return rv;
2663 FL struct name *
2664 n_header_textual_sender_info(struct message *mp, char **cumulation_or_null,
2665 char **addr_or_null, char **name_real_or_null, char **name_full_or_null,
2666 bool_t *is_to_or_null){
2667 struct n_string s_b1, s_b2, *sp1, *sp2;
2668 struct name *np, *np2;
2669 bool_t isto, b;
2670 char *cp;
2671 NYD_ENTER;
2673 cp = n_header_senderfield_of(mp);
2674 isto = FAL0;
2676 if((np = lextract(cp, GFULL | GSKIN)) != NULL){
2677 if(is_to_or_null != NULL && ok_blook(showto) &&
2678 np->n_flink == NULL && n_is_myname(np->n_name)){
2679 if((cp = hfield1("to", mp)) != NULL &&
2680 (np2 = lextract(cp, GFULL | GSKIN)) != NULL){
2681 np = np2;
2682 isto = TRU1;
2686 if(((b = ok_blook(showname)) && cumulation_or_null != NULL) ||
2687 name_real_or_null != NULL || name_full_or_null != NULL){
2688 size_t i;
2690 for(i = 0, np2 = np; np2 != NULL; np2 = np2->n_flink)
2691 i += strlen(np2->n_fullname) +3;
2693 sp1 = n_string_book(n_string_creat_auto(&s_b1), i);
2694 sp2 = (name_full_or_null == NULL) ? NULL
2695 : n_string_book(n_string_creat_auto(&s_b2), i);
2697 for(np2 = np; np2 != NULL; np2 = np2->n_flink){
2698 if(sp1->s_len > 0){
2699 sp1 = n_string_push_c(sp1, ',');
2700 sp1 = n_string_push_c(sp1, ' ');
2701 if(sp2 != NULL){
2702 sp2 = n_string_push_c(sp2, ',');
2703 sp2 = n_string_push_c(sp2, ' ');
2707 if((cp = realname(np2->n_fullname)) == NULL)
2708 cp = np2->n_name;
2709 sp1 = n_string_push_cp(sp1, cp);
2710 if(sp2 != NULL)
2711 sp2 = n_string_push_cp(sp2, np2->n_fullname);
2714 n_string_cp(sp1);
2715 if(b && cumulation_or_null != NULL)
2716 *cumulation_or_null = sp1->s_dat;
2717 if(name_real_or_null != NULL)
2718 *name_real_or_null = sp1->s_dat;
2719 if(name_full_or_null != NULL)
2720 *name_full_or_null = n_string_cp(sp2);
2722 /* n_string_gut(n_string_drop_ownership(sp2)); */
2723 /* n_string_gut(n_string_drop_ownership(sp1)); */
2726 if((b = (!b && cumulation_or_null != NULL)) || addr_or_null != NULL){
2727 cp = detract(np, GCOMMA | GNAMEONLY);
2728 if(b)
2729 *cumulation_or_null = cp;
2730 if(addr_or_null != NULL)
2731 *addr_or_null = cp;
2733 }else if(cumulation_or_null != NULL || addr_or_null != NULL ||
2734 name_real_or_null != NULL || name_full_or_null != NULL){
2735 cp = savestr(n_empty);
2737 if(cumulation_or_null != NULL)
2738 *cumulation_or_null = cp;
2739 if(addr_or_null != NULL)
2740 *addr_or_null = cp;
2741 if(name_real_or_null != NULL)
2742 *name_real_or_null = cp;
2743 if(name_full_or_null != NULL)
2744 *name_full_or_null = cp;
2747 if(is_to_or_null != NULL)
2748 *is_to_or_null = isto;
2749 NYD_LEAVE;
2750 return np;
2753 FL void
2754 setup_from_and_sender(struct header *hp)
2756 char const *addr;
2757 struct name *np;
2758 NYD_ENTER;
2760 /* If -t parsed or composed From: then take it. With -t we otherwise
2761 * want -r to be honoured in favour of *from* in order to have
2762 * a behaviour that is compatible with what users would expect from e.g.
2763 * postfix(1) */
2764 if ((np = hp->h_from) != NULL ||
2765 ((n_poption & n_PO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
2767 } else if ((addr = myaddrs(hp)) != NULL)
2768 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2769 hp->h_from = np;
2771 if ((np = hp->h_sender) != NULL) {
2773 } else if ((addr = ok_vlook(sender)) != NULL)
2774 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2775 hp->h_sender = np;
2777 NYD_LEAVE;
2780 FL struct name const *
2781 check_from_and_sender(struct name const *fromfield,
2782 struct name const *senderfield)
2784 struct name const *rv = NULL;
2785 NYD_ENTER;
2787 if (senderfield != NULL) {
2788 if (senderfield->n_flink != NULL) {
2789 n_err(_("The Sender: field may contain only one address\n"));
2790 goto jleave;
2792 rv = senderfield;
2795 if (fromfield != NULL) {
2796 if (fromfield->n_flink != NULL && senderfield == NULL) {
2797 n_err(_("A Sender: is required when there are multiple "
2798 "addresses in From:\n"));
2799 goto jleave;
2801 if (rv == NULL)
2802 rv = fromfield;
2805 if (rv == NULL)
2806 rv = (struct name*)0x1;
2807 jleave:
2808 NYD_LEAVE;
2809 return rv;
2812 #ifdef HAVE_XTLS
2813 FL char *
2814 getsender(struct message *mp)
2816 char *cp;
2817 struct name *np;
2818 NYD_ENTER;
2820 if ((cp = hfield1("from", mp)) == NULL ||
2821 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2822 cp = NULL;
2823 else
2824 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2825 NYD_LEAVE;
2826 return cp;
2828 #endif
2830 FL struct name *
2831 n_header_setup_in_reply_to(struct header *hp){
2832 struct name *np;
2833 NYD_ENTER;
2835 np = NULL;
2837 if(hp != NULL)
2838 if((np = hp->h_in_reply_to) == NULL && (np = hp->h_ref) != NULL)
2839 while(np->n_flink != NULL)
2840 np = np->n_flink;
2841 NYD_LEAVE;
2842 return np;
2845 FL int
2846 grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
2847 int subjfirst)
2849 /* TODO grab_headers: again, check counts etc. against RFC;
2850 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2851 int errs;
2852 int volatile comma;
2853 NYD_ENTER;
2855 errs = 0;
2856 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2858 if (gflags & GTO)
2859 hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
2860 if (subjfirst && (gflags & GSUBJECT))
2861 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2862 if (gflags & GCC)
2863 hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2864 if (gflags & GBCC)
2865 hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2867 if (gflags & GEXTRA) {
2868 if (hp->h_from == NULL)
2869 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2870 hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
2871 GEXTRA | GFULL | GFULLEXTRA);
2872 if (hp->h_reply_to == NULL) {
2873 struct name *v15compat;
2875 if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
2876 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2877 hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
2878 if(hp->h_reply_to == NULL) /* v15 */
2879 hp->h_reply_to = v15compat;
2881 hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
2882 GEXTRA | GFULL);
2883 if (hp->h_sender == NULL)
2884 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2885 hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
2886 GEXTRA | GFULL);
2889 if (!subjfirst && (gflags & GSUBJECT))
2890 hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
2892 NYD_LEAVE;
2893 return errs;
2896 FL bool_t
2897 n_header_match(struct message *mp, struct search_expr const *sep){
2898 struct str fiter, in, out;
2899 char const *field;
2900 long lc;
2901 FILE *ibuf;
2902 size_t *linesize;
2903 char **linebuf, *colon;
2904 enum {a_NONE, a_ALL, a_ITER, a_RE} match;
2905 bool_t rv;
2906 NYD_ENTER;
2908 rv = FAL0;
2909 match = a_NONE;
2910 linebuf = &termios_state.ts_linebuf; /* XXX line pool */
2911 linesize = &termios_state.ts_linesize; /* XXX line pool */
2912 n_UNINIT(fiter.l, 0);
2913 n_UNINIT(fiter.s, NULL);
2915 if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2916 goto jleave;
2917 if((lc = mp->m_lines - 1) < 0)
2918 goto jleave;
2920 if((mp->m_flag & MNOFROM) == 0 &&
2921 readline_restart(ibuf, linebuf, linesize, 0) < 0)
2922 goto jleave;
2924 /* */
2925 if((field = sep->ss_field) != NULL){
2926 if(!asccasecmp(field, "header") || (field[0] == '<' && field[1] == '\0'))
2927 match = a_ALL;
2928 else{
2929 fiter.s = n_lofi_alloc((fiter.l = strlen(field)) +1);
2930 match = a_ITER;
2932 #ifdef HAVE_REGEX
2933 }else if(sep->ss_fieldre != NULL){
2934 match = a_RE;
2935 #endif
2936 }else
2937 match = a_ALL;
2939 /* Iterate over all the headers */
2940 while(lc > 0){
2941 struct name *np;
2943 if((lc = a_gethfield(n_HEADER_EXTRACT_NONE, ibuf, linebuf, linesize,
2944 lc, &colon)) <= 0)
2945 break;
2947 /* Is this a header we are interested in? */
2948 if(match == a_ITER){
2949 char *itercp;
2951 memcpy(itercp = fiter.s, sep->ss_field, fiter.l +1);
2952 while((field = n_strsep(&itercp, ',', TRU1)) != NULL){
2953 /* It may be an abbreviation */
2954 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
2955 size_t i;
2956 char c1;
2958 if(field[0] != '\0' && field[1] == '\0'){
2959 c1 = lowerconv(field[0]);
2960 for(i = 0; i < n_NELEM(x); ++i){
2961 if(c1 == x[i][0]){
2962 field = x[i];
2963 break;
2968 if(!ascncasecmp(field, *linebuf, PTR2SIZE(colon - *linebuf)))
2969 break;
2971 if(field == NULL)
2972 continue;
2973 #ifdef HAVE_REGEX
2974 }else if(match == a_RE){
2975 char *cp;
2976 size_t i;
2978 i = PTR2SIZE(colon - *linebuf);
2979 cp = n_lofi_alloc(i +1);
2980 memcpy(cp, *linebuf, i);
2981 cp[i] = '\0';
2982 i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
2983 n_lofi_free(cp);
2984 if(!i)
2985 continue;
2986 #endif
2989 /* It could be a plain existence test */
2990 if(sep->ss_field_exists){
2991 rv = TRU1;
2992 break;
2995 /* Need to check the body */
2996 while(blankchar(*++colon))
2998 in.s = colon;
3000 /* Shall we split into address list and match as/the addresses only?
3001 * TODO at some later time we should ignore and log efforts to search
3002 * TODO a skinned address list if we know the header has none such */
3003 if(sep->ss_skin){
3004 if((np = lextract(in.s, GSKIN)) == NULL)
3005 continue;
3006 out.s = np->n_name;
3007 }else{
3008 np = NULL;
3009 in.l = strlen(in.s);
3010 mime_fromhdr(&in, &out, TD_ICONV);
3013 jnext_name:
3014 #ifdef HAVE_REGEX
3015 if(sep->ss_bodyre != NULL)
3016 rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
3017 else
3018 #endif
3019 rv = substr(out.s, sep->ss_body);
3021 if(np == NULL)
3022 n_free(out.s);
3023 if(rv)
3024 break;
3025 if(np != NULL && (np = np->n_flink) != NULL){
3026 out.s = np->n_name;
3027 goto jnext_name;
3031 jleave:
3032 if(match == a_ITER)
3033 n_lofi_free(fiter.s);
3034 NYD_LEAVE;
3035 return rv;
3038 FL char const *
3039 n_header_is_known(char const *name, size_t len){
3040 static char const * const names[] = {
3041 "Bcc", "Cc", "From",
3042 "In-Reply-To", "Mail-Followup-To",
3043 "Message-ID", "References", "Reply-To",
3044 "Sender", "Subject", "To",
3045 /* More known, here and there */
3046 "Fcc",
3047 /* Mailx internal temporaries */
3048 "Mailx-Command",
3049 "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From", "Mailx-Orig-To",
3050 "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
3051 NULL
3053 char const * const *rv;
3054 NYD_ENTER;
3056 if(len == UIZ_MAX)
3057 len = strlen(name);
3059 for(rv = names; *rv != NULL; ++rv)
3060 if(!ascncasecmp(*rv, name, len))
3061 break;
3062 NYD_LEAVE;
3063 return *rv;
3066 FL bool_t
3067 n_header_add_custom(struct n_header_field **hflp, char const *dat,
3068 bool_t heap){
3069 size_t i;
3070 ui32_t nl, bl;
3071 char const *cp;
3072 struct n_header_field *hfp;
3073 NYD_ENTER;
3075 hfp = NULL;
3077 /* For (-C) convenience, allow leading WS */
3078 while(blankchar(*dat))
3079 ++dat;
3081 /* Isolate the header field from the body */
3082 for(cp = dat;; ++cp){
3083 if(fieldnamechar(*cp))
3084 continue;
3085 if(*cp == '\0'){
3086 if(cp == dat)
3087 goto jename;
3088 }else if(*cp != ':' && !blankchar(*cp)){
3089 jename:
3090 cp = N_("Invalid custom header (not \"field: body\"): %s\n");
3091 goto jerr;
3093 break;
3095 nl = (ui32_t)PTR2SIZE(cp - dat);
3096 if(nl == 0)
3097 goto jename;
3099 /* Verify the custom header does not use standard/managed field name */
3100 if(n_header_is_known(dat, nl) != NULL){
3101 cp = N_("Custom headers cannot use standard header names: %s\n");
3102 goto jerr;
3105 /* Skip on over to the body */
3106 while(blankchar(*cp))
3107 ++cp;
3108 if(*cp++ != ':')
3109 goto jename;
3110 while(blankchar(*cp))
3111 ++cp;
3112 bl = (ui32_t)strlen(cp);
3113 for(i = bl++; i-- != 0;)
3114 if(cntrlchar(cp[i])){
3115 cp = N_("Invalid custom header: contains control characters: %s\n");
3116 goto jerr;
3119 i = n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + nl +1 + bl;
3120 *hflp = hfp = heap ? n_alloc(i) : n_autorec_alloc(i);
3121 hfp->hf_next = NULL;
3122 hfp->hf_nl = nl;
3123 hfp->hf_bl = bl - 1;
3124 memcpy(hfp->hf_dat, dat, nl);
3125 hfp->hf_dat[nl++] = '\0';
3126 memcpy(hfp->hf_dat + nl, cp, bl);
3127 jleave:
3128 NYD_LEAVE;
3129 return (hfp != NULL);
3131 jerr:
3132 n_err(V_(cp), n_shexp_quote_cp(dat, FAL0));
3133 goto jleave;
3136 /* s-it-mode */