FIX [1c4b8c918] (Address struct name memory usage.., 2015-07-08)..
[s-mailx.git] / head.c
blobc98e119b67736fabe77dcd1c8cdd4f186f9faa8a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
3 *@ TODO Mostly a hackery, we need RFC compliant parsers instead.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE head
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 #ifdef HAVE_IDNA
44 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
45 # include <idna.h>
46 # include <idn-free.h>
47 # include <stringprep.h>
48 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT
49 # include <idn/api.h>
50 # endif
51 #endif
53 struct cmatch_data {
54 size_t tlen; /* Length of .tdata */
55 char const *tdata; /* Template date - see _cmatch_data[] */
58 /* Template characters for cmatch_data.tdata:
59 * 'A' An upper case char
60 * 'a' A lower case char
61 * ' ' A space
62 * '0' A digit
63 * 'O' An optional digit or space
64 * ':' A colon
65 * '+' Either a plus or a minus sign */
66 static struct cmatch_data const _cmatch_data[] = {
67 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
68 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
69 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
70 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
71 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
72 * in the wild (by UW-imap) */
73 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
74 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
75 * zone strings; note that 1. is strictly speaking not correct as some
76 * letters are not used, and 2. is not because only "UT" is defined */
77 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
78 { 28 - 2, __reuse }, { 28 - 1, __reuse }, { 28 - 0, __reuse },
79 { 0, NULL }
81 #define _DATE_MINLEN 21
83 /* Skip over "word" as found in From_ line */
84 static char const * _from__skipword(char const *wp);
86 /* Match the date string against the date template (tp), return if match.
87 * See _cmatch_data[] for template character description */
88 static int _cmatch(size_t len, char const *date,
89 char const *tp);
91 /* Check whether date is a valid 'From_' date.
92 * (Rather ctime(3) generated dates, according to RFC 4155) */
93 static int _is_date(char const *date);
95 /* JulianDayNumber converter(s) */
96 static size_t a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d);
97 #if 0
98 static void a_head_jdn_to_gregorian(size_t jdn,
99 ui32_t *yp, ui32_t *mp, ui32_t *dp);
100 #endif
102 /* Convert the domain part of a skinned address to IDNA.
103 * If an error occurs before Unicode information is available, revert the IDNA
104 * error to a normal CHAR one so that the error message doesn't talk Unicode */
105 #ifdef HAVE_IDNA
106 static struct n_addrguts *a_head_idna_apply(struct n_addrguts *agp);
107 #endif
109 /* Classify and check a (possibly skinned) header body according to RFC
110 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
111 * also a file or a pipe command, so check that first, then.
112 * Otherwise perform content checking and isolate the domain part (for IDNA) */
113 static bool_t a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned);
115 /* Return the next header field found in the given message.
116 * Return >= 0 if something found, < 0 elsewise.
117 * "colon" is set to point to the colon in the header.
118 * Must deal with \ continuations & other such fraud */
119 static int gethfield(FILE *f, char **linebuf, size_t *linesize,
120 int rem, char **colon);
122 static int msgidnextc(char const **cp, int *status);
124 /* Count the occurances of c in str */
125 static int charcount(char *str, int c);
127 static char const * nexttoken(char const *cp);
129 /* TODO v15: change *customhdr* syntax and use shell tokens?! */
130 static char *a_head_customhdr__sep(char **iolist);
132 static char const *
133 _from__skipword(char const *wp)
135 char c = 0;
136 NYD2_ENTER;
138 if (wp != NULL) {
139 while ((c = *wp++) != '\0' && !blankchar(c)) {
140 if (c == '"') {
141 while ((c = *wp++) != '\0' && c != '"')
143 if (c != '"')
144 --wp;
147 for (; blankchar(c); c = *wp++)
150 NYD2_LEAVE;
151 return (c == 0 ? NULL : wp - 1);
154 static int
155 _cmatch(size_t len, char const *date, char const *tp)
157 int ret = 0;
158 NYD2_ENTER;
160 while (len--) {
161 char c = date[len];
162 switch (tp[len]) {
163 case 'a':
164 if (!lowerchar(c))
165 goto jleave;
166 break;
167 case 'A':
168 if (!upperchar(c))
169 goto jleave;
170 break;
171 case ' ':
172 if (c != ' ')
173 goto jleave;
174 break;
175 case '0':
176 if (!digitchar(c))
177 goto jleave;
178 break;
179 case 'O':
180 if (c != ' ' && !digitchar(c))
181 goto jleave;
182 break;
183 case ':':
184 if (c != ':')
185 goto jleave;
186 break;
187 case '+':
188 if (c != '+' && c != '-')
189 goto jleave;
190 break;
193 ret = 1;
194 jleave:
195 NYD2_LEAVE;
196 return ret;
199 static int
200 _is_date(char const *date)
202 struct cmatch_data const *cmdp;
203 size_t dl;
204 int rv = 0;
205 NYD2_ENTER;
207 if ((dl = strlen(date)) >= _DATE_MINLEN)
208 for (cmdp = _cmatch_data; cmdp->tdata != NULL; ++cmdp)
209 if (dl == cmdp->tlen && (rv = _cmatch(dl, date, cmdp->tdata)))
210 break;
211 NYD2_LEAVE;
212 return rv;
215 static size_t
216 a_head_gregorian_to_jdn(ui32_t y, ui32_t m, ui32_t d){
217 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
218 * (via third hand, plus adjustments).
219 * This algorithm is supposed to work for all dates in between 1582-10-15
220 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
221 size_t jdn;
222 NYD2_ENTER;
224 #if 0
225 if(y == 0)
226 y = 1;
227 if(m == 0)
228 m = 1;
229 if(d == 0)
230 d = 1;
231 #endif
233 if(m > 2)
234 m -= 3;
235 else{
236 m += 9;
237 --y;
239 jdn = y;
240 jdn /= 100;
241 y -= 100 * jdn;
242 y *= 1461;
243 y >>= 2;
244 jdn *= 146097;
245 jdn >>= 2;
246 jdn += y;
247 jdn += d;
248 jdn += 1721119;
249 m *= 153;
250 m += 2;
251 m /= 5;
252 jdn += m;
253 NYD2_LEAVE;
254 return jdn;
257 #if 0
258 static void
259 a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){
260 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
261 * (via third hand, plus adjustments) */
262 size_t y, x;
263 NYD2_ENTER;
265 jdn -= 1721119;
266 jdn <<= 2;
267 --jdn;
268 y = jdn / 146097;
269 jdn %= 146097;
270 jdn |= 3;
271 y *= 100;
272 y += jdn / 1461;
273 jdn %= 1461;
274 jdn += 4;
275 jdn >>= 2;
276 x = jdn;
277 jdn <<= 2;
278 jdn += x;
279 jdn -= 3;
280 x = jdn / 153; /* x -> month */
281 jdn %= 153;
282 jdn += 5;
283 jdn /= 5; /* jdn -> day */
284 if(x < 10)
285 x += 3;
286 else{
287 x -= 9;
288 ++y;
291 *yp = (ui32_t)(y & 0xFFFF);
292 *mp = (ui32_t)(x & 0xFF);
293 *dp = (ui32_t)(jdn & 0xFF);
294 NYD2_LEAVE;
296 #endif /* 0 */
298 #ifdef HAVE_IDNA
299 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
300 static struct n_addrguts *
301 a_head_idna_apply(struct n_addrguts *agp)
303 char *idna_utf8, *idna_ascii, *cs;
304 size_t sz, i;
305 NYD_ENTER;
307 sz = agp->ag_slen - agp->ag_sdom_start;
308 assert(sz > 0);
309 idna_utf8 = ac_alloc(sz +1);
310 memcpy(idna_utf8, agp->ag_skinned + agp->ag_sdom_start, sz);
311 idna_utf8[sz] = '\0';
313 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
314 * let it perform the charset conversion, if any should be necessary */
315 if (!(options & OPT_UNICODE)) {
316 char const *tcs = ok_vlook(ttycharset);
317 idna_ascii = idna_utf8;
318 idna_utf8 = stringprep_convert(idna_ascii, "utf-8", tcs);
319 i = (idna_utf8 == NULL && errno == EINVAL);
320 ac_free(idna_ascii);
322 if (idna_utf8 == NULL) {
323 if (i)
324 n_err(_("Cannot convert from %s to %s\n"), tcs, "utf-8");
325 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
326 goto jleave;
330 if (idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) != IDNA_SUCCESS) {
331 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
332 goto jleave1;
335 /* Replace the domain part of .ag_skinned with IDNA version */
336 sz = strlen(idna_ascii);
337 i = agp->ag_sdom_start;
338 cs = salloc(agp->ag_slen - i + sz +1);
339 memcpy(cs, agp->ag_skinned, i);
340 memcpy(cs + i, idna_ascii, sz);
341 i += sz;
342 cs[i] = '\0';
344 agp->ag_skinned = cs;
345 agp->ag_slen = i;
346 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
347 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
349 idn_free(idna_ascii);
350 jleave1:
351 if (options & OPT_UNICODE)
352 ac_free(idna_utf8);
353 else
354 idn_free(idna_utf8);
355 jleave:
356 NYD_LEAVE;
357 return agp;
360 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */
361 static struct n_addrguts *
362 a_head_idna_apply(struct n_addrguts *agp)
364 char *idna_in, *idna_out, *cs;
365 size_t sz, i;
366 idn_result_t r;
367 NYD_ENTER;
369 sz = agp->ag_slen - agp->ag_sdom_start;
370 assert(sz > 0);
371 idna_in = ac_alloc(sz +1);
372 memcpy(idna_in, agp->ag_skinned + agp->ag_sdom_start, sz);
373 idna_in[sz] = '\0';
375 for (idna_out = NULL, sz = HOST_NAME_MAX +1;; sz += HOST_NAME_MAX) {
376 idna_out = ac_alloc(sz);
378 r = idn_encodename(IDN_ENCODE_APP, idna_in, idna_out, sz);
379 switch (r) {
380 case idn_success:
381 case idn_buffer_overflow:
382 break;
383 case idn_invalid_encoding:
384 n_err(_("Cannot convert from %s to %s\n"),
385 ok_vlook(ttycharset), "utf-8");
386 /* FALLTHRU */
387 default:
388 agp->ag_n_flags ^= NAME_ADDRSPEC_ERR_IDNA | NAME_ADDRSPEC_ERR_CHAR;
389 goto jleave;
392 if (r == idn_success)
393 break;
394 ac_free(idna_out);
397 /* Replace the domain part of .ag_skinned with IDNA version */
398 sz = strlen(idna_out);
399 i = agp->ag_sdom_start;
400 cs = salloc(agp->ag_slen - i + sz +1);
401 memcpy(cs, agp->ag_skinned, i);
402 memcpy(cs + i, idna_out, sz);
403 i += sz;
404 cs[i] = '\0';
406 agp->ag_skinned = cs;
407 agp->ag_slen = i;
408 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags,
409 NAME_NAME_SALLOC | NAME_SKINNED | NAME_IDNA, 0);
411 jleave:
412 ac_free(idna_out);
413 ac_free(idna_in);
414 NYD_LEAVE;
415 return agp;
417 # endif /* IDNA==IDNKIT */
418 #endif /* HAVE_IDNA */
420 static bool_t
421 a_head_addrspec_check(struct n_addrguts *agp, bool_t skinned)
423 char *addr, *p;
424 bool_t in_quote;
425 ui8_t in_domain, hadat;
426 union {char c; unsigned char u; ui32_t ui32;} c;
427 #ifdef HAVE_IDNA
428 ui8_t use_idna;
429 #endif
430 NYD_ENTER;
432 #ifdef HAVE_IDNA
433 use_idna = ok_blook(idna_disable) ? 0 : 1;
434 #endif
435 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
436 addr = agp->ag_skinned;
438 if (agp->ag_iaddr_aend - agp->ag_iaddr_start == 0) {
439 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
440 goto jleave;
443 /* If the field is not a recipient, it cannot be a file or a pipe */
444 if (!skinned)
445 goto jaddr_check;
447 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
448 * grep the latter for the complete picture */
449 if (*addr == '|') {
450 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
451 goto jleave;
453 if (addr[0] == '/' || (addr[0] == '.' && addr[1] == '/') ||
454 (addr[0] == '-' && addr[1] == '\0'))
455 goto jisfile;
456 if (memchr(addr, '@', agp->ag_slen) == NULL) {
457 if (*addr == '+')
458 goto jisfile;
459 for (p = addr; (c.c = *p); ++p) {
460 if (c.c == '!' || c.c == '%')
461 break;
462 if (c.c == '/') {
463 jisfile:
464 agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
465 goto jleave;
470 jaddr_check:
471 in_quote = FAL0;
472 in_domain = hadat = 0;
474 /* TODO addrspec_check: we need a real RFC 5322 parser! */
475 for (p = addr; (c.c = *p++) != '\0';) {
476 if (c.c == '"') {
477 in_quote = !in_quote;
478 } else if (c.u < 040 || c.u >= 0177) { /* TODO no magics: !bodychar()? */
479 #ifdef HAVE_IDNA
480 if (in_domain && use_idna > 0) {
481 if (use_idna == 1)
482 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_IDNA,
483 c.u);
484 use_idna = 2;
485 } else
486 #endif
487 break;
488 } else if (in_domain == 2) {
489 if ((c.c == ']' && *p != '\0') || c.c == '\\' || whitechar(c.c))
490 break;
491 } else if (in_quote && in_domain == 0) {
492 /*EMPTY*/;
493 } else if (c.c == '\\' && *p != '\0') {
494 ++p;
495 } else if (c.c == '@') {
496 if (hadat++ > 0) {
497 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_ATSEQ,
498 c.u);
499 goto jleave;
501 agp->ag_sdom_start = PTR2SIZE(p - addr);
502 agp->ag_n_flags |= NAME_ADDRSPEC_ISADDR; /* TODO .. really? */
503 in_domain = (*p == '[') ? 2 : 1;
504 continue;
505 } else if (c.c == '(' || c.c == ')' || c.c == '<' || c.c == '>' ||
506 c.c == '[' || c.c == ']' || c.c == ':' || c.c == ';' ||
507 c.c == '\\' || c.c == ',')
508 break;
509 hadat = 0;
511 if (c.c != '\0') {
512 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_CHAR, c.u);
513 goto jleave;
516 if (!(agp->ag_n_flags & NAME_ADDRSPEC_ISADDR))
517 agp->ag_n_flags |= NAME_ADDRSPEC_ISNAME;
518 else{
519 /* If we seem to know that this is an address. Perform some simple checks
520 * for dot-atom as of RFC 5322 and ensure we fix that "Dr. problem".
521 * TODO We need a real RFC 5322 parser which produces tokens, to store them
522 * TODO in a list including typeattr, as in atom-text, dot-atom-text,
523 * TODO quote, comment address, so then we could simply join as many
524 * TODO consecutive -text tokens and convert them to quote as possible
525 * TODO For now bad and expensive; wrong for some comments in tokens.
526 * TODO In fact i haven't read RFC 5322 so much, esp. before doing this */
527 struct n_string ost, *ostp;
528 char lastc;
529 size_t rangestart, lastpoi;
530 char const *cp, *cpmax, *xp;
532 #ifdef HAVE_IDNA
533 if(use_idna == 2)
534 agp = a_head_idna_apply(agp);
535 #endif
537 ostp = n_string_creat_auto(&ost);
538 if((c.ui32 = agp->ag_ilen) <= UI32_MAX >> 1)
539 ostp = n_string_reserve(ostp, c.ui32 <<= 1);
541 hadat = FAL0;
542 cp = agp->ag_input;
543 if((c.ui32 = agp->ag_iaddr_start) > 0)
544 --c.ui32;
545 cpmax = &cp[c.ui32];
546 rangestart = 0;
548 while(cp < cpmax && blankchar(*cp))
549 ++cp;
551 lastc = '\0';
552 jdotatom_redo:
553 for(lastpoi = UIZ_MAX; cp < cpmax;){
554 switch((c.c = *cp)){
555 case '(':
556 jdotatom_comment:
557 lastc = 0;
558 if((c.ui32 = ostp->s_len) > 0){
559 while(c.ui32 > 0 && blankchar(ostp->s_dat[c.ui32 - 1]))
560 --c.ui32;
561 ostp = n_string_trunc(ostp, c.ui32);
563 if(c.ui32 > 0){
564 --c.ui32;
565 /* Can we join two comments? */
566 if(ostp->s_dat[c.ui32] == ')'){
567 ostp->s_dat[c.ui32] = ' ';
568 lastc = 1;
570 /* Otherwise ensure it is separated! */
571 else if(!blankchar(ostp->s_dat[c.ui32]))
572 ostp = n_string_push_c(ostp, ' ');
575 if(!lastc)
576 ostp = n_string_push_c(ostp, '(');
578 xp = skip_comment(++cp);
579 c.ui32 = (ui32_t)PTR2SIZE(xp - cp);
580 if(c.ui32 > 0){
581 if(xp[-1] == ')')
582 --c.ui32;
583 /* Run out of buffer while searching for comment close, this is
584 * artificial and we strip all blanks at EOS */
585 else while(blankchar(xp[-1])){
586 --xp;
587 if(--c.ui32 == 0)
588 break;
591 ostp = n_string_push_buf(ostp, cp, c.ui32);
593 ostp = n_string_push_c(ostp, ')');
594 lastpoi = ostp->s_len - 1;
595 lastc = '\0';
596 cp = xp;
597 break;
598 case '"':
599 lastc = 0;
600 jdotatom_quote:
601 xp = ++cp;
602 if((c.ui32 = ostp->s_len) > 0){
603 while(c.ui32 > 0 && blankchar(ostp->s_dat[c.ui32 - 1]))
604 --c.ui32;
605 ostp = n_string_trunc(ostp, c.ui32);
607 if(c.ui32 > 0){
608 --c.ui32;
609 /* Can we join two quotes? */
610 if(ostp->s_dat[c.ui32] == '"'){
611 ostp->s_dat[c.ui32] = ' ';
612 lastc = 1;
614 /* Otherwise ensure it is separated! */
615 else if(!blankchar(ostp->s_dat[c.ui32]))
616 ostp = n_string_push_c(ostp, ' ');
619 if(!lastc)
620 ostp = n_string_push_c(ostp, '"');
622 for(; xp < cpmax; ++xp){
623 if((c.c = *xp) == '"')
624 break;
625 if(c.c == '\\' && xp[1] != '\0')
626 ++xp;
628 c.ui32 = (ui32_t)PTR2SIZE(xp - cp);
629 if(c.ui32 > 0){
630 if(xp != cpmax && *xp == '"')
631 ++xp;
632 /* Run out of buffer while searching for quote close, this is
633 * artificial and we strip all blanks at EOS */
634 else while(blankchar(xp[-1])){
635 --xp;
636 if(--c.ui32 == 0)
637 break;
640 ostp = n_string_push_buf(ostp, cp, c.ui32);
642 ostp = n_string_push_c(ostp, '"');
643 lastpoi = ostp->s_len - 1;
644 lastc = '\0';
645 cp = xp;
646 break;
647 case '.':
648 if(lastpoi != UIZ_MAX){
649 if(ostp->s_dat[lastpoi] == '"')
650 /* Simply join backward to a yet existing quote */
651 ostp = n_string_cut(ostp, lastpoi, 1);
652 }else
653 /* Otherwise convert anything before to a quote */
654 ostp = n_string_insert_c(ostp, rangestart, '"');
656 for(xp = cp; cp < cpmax; ++cp){
657 /* If we reach another quote, join forward */
658 if((c.c = *cp) == '"'){
659 lastc = 1;
660 goto jdotatom_quote;
662 else if(c.c == '('){
663 /* End the quote before the whitespace before the comment */
664 for(c.ui32 = 0; blankchar(cp[-1 - c.ui32]); ++c.ui32)
666 if(c.ui32 > 0)
667 ostp = n_string_trunc(ostp, ostp->s_len - c.ui32);
668 ostp = n_string_push_c(ostp, '"');
669 goto jdotatom_comment;
671 ostp = n_string_push_c(ostp, c.c);
674 /* Since we have created this quote it would be false to let it end
675 * in a series of whitespace */
676 for(c.ui32 = ostp->s_len;
677 c.ui32 > 0 && blankchar(ostp->s_dat[c.ui32 - 1]); --c.ui32)
679 ostp = n_string_trunc(ostp, c.ui32);
681 ostp = n_string_push_c(ostp, '"');
682 lastpoi = ostp->s_len - 1;
683 lastc = '\0';
684 break;
685 default:
686 if(lastc == '\0' || !blankchar(c.c) || !blankchar(lastc))
687 ostp = n_string_push_c(ostp, lastc = c.c);
688 ++cp;
689 break;
693 if(hadat == FAL0){
694 hadat = TRU1;
695 if((c.ui32 = ostp->s_len) > 0 && !blankchar(ostp->s_dat[c.ui32 - 1])){
696 ostp = n_string_push_c(ostp, ' ');
697 ++c.ui32;
699 if(c.ui32 != 0)
700 ++c.ui32;
701 agp->ag_iaddr_start = c.ui32;
702 cp = &agp->ag_input[agp->ag_iaddr_aend];
703 if(cp != &agp->ag_input[agp->ag_ilen])
704 ++cp;
705 c.ui32 = (ui32_t)PTR2SIZE(cp - cpmax);
706 ostp = n_string_push_buf(ostp, cpmax, c.ui32);
707 agp->ag_iaddr_aend = ostp->s_len - 1;
708 cpmax = &agp->ag_input[agp->ag_ilen];
710 c.ui32 = (ui32_t)PTR2SIZE(cpmax - cp);
711 if(c.ui32 > 0){
712 ostp = n_string_push_c(ostp, lastc = ' ');
713 rangestart = ostp->s_len;
714 goto jdotatom_redo;
716 }else if((c.ui32 = ostp->s_len) > 0){
717 while(blankchar(ostp->s_dat[c.ui32 - 1]))
718 --c.ui32;
719 ostp = n_string_trunc(ostp, c.ui32);
722 agp->ag_input = n_string_cp(ostp);
723 agp->ag_ilen = ostp->s_len;
724 ostp = n_string_drop_ownership(ostp);
726 jleave:
727 NYD_LEAVE;
728 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
731 static int
732 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
734 char *line2 = NULL, *cp, *cp2;
735 size_t line2size = 0;
736 int c, isenc;
737 NYD2_ENTER;
739 if (*linebuf == NULL)
740 *linebuf = srealloc(*linebuf, *linesize = 1);
741 **linebuf = '\0';
742 for (;;) {
743 if (--rem < 0) {
744 rem = -1;
745 break;
747 if ((c = readline_restart(f, linebuf, linesize, 0)) <= 0) {
748 rem = -1;
749 break;
751 for (cp = *linebuf; fieldnamechar(*cp); ++cp)
753 if (cp > *linebuf)
754 while (blankchar(*cp))
755 ++cp;
756 if (*cp != ':' || cp == *linebuf)
757 continue;
759 /* I guess we got a headline. Handle wraparound */
760 *colon = cp;
761 cp = *linebuf + c;
762 for (;;) {
763 isenc = 0;
764 while (PTRCMP(--cp, >=, *linebuf) && blankchar(*cp))
766 cp++;
767 if (rem <= 0)
768 break;
769 if (PTRCMP(cp - 8, >=, *linebuf) && cp[-1] == '=' && cp[-2] == '?')
770 isenc |= 1;
771 ungetc(c = getc(f), f);
772 if (!blankchar(c))
773 break;
774 c = readline_restart(f, &line2, &line2size, 0);
775 if (c < 0)
776 break;
777 --rem;
778 for (cp2 = line2; blankchar(*cp2); ++cp2)
780 c -= (int)PTR2SIZE(cp2 - line2);
781 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
782 isenc |= 2;
783 if (PTRCMP(cp + c, >=, *linebuf + *linesize - 2)) {
784 size_t diff = PTR2SIZE(cp - *linebuf),
785 colondiff = PTR2SIZE(*colon - *linebuf);
786 *linebuf = srealloc(*linebuf, *linesize += c + 2);
787 cp = &(*linebuf)[diff];
788 *colon = &(*linebuf)[colondiff];
790 if (isenc != 3)
791 *cp++ = ' ';
792 memcpy(cp, cp2, c);
793 cp += c;
795 *cp = '\0';
797 if (line2 != NULL)
798 free(line2);
799 break;
801 NYD2_LEAVE;
802 return rem;
805 static int
806 msgidnextc(char const **cp, int *status)
808 int c;
809 NYD2_ENTER;
811 assert(cp != NULL);
812 assert(*cp != NULL);
813 assert(status != NULL);
815 for (;;) {
816 if (*status & 01) {
817 if (**cp == '"') {
818 *status &= ~01;
819 (*cp)++;
820 continue;
822 if (**cp == '\\') {
823 (*cp)++;
824 if (**cp == '\0')
825 goto jeof;
827 goto jdfl;
829 switch (**cp) {
830 case '(':
831 *cp = skip_comment(&(*cp)[1]);
832 continue;
833 case '>':
834 case '\0':
835 jeof:
836 c = '\0';
837 goto jleave;
838 case '"':
839 (*cp)++;
840 *status |= 01;
841 continue;
842 case '@':
843 *status |= 02;
844 /*FALLTHRU*/
845 default:
846 jdfl:
847 c = *(*cp)++ & 0377;
848 c = (*status & 02) ? lowerconv(c) : c;
849 goto jleave;
852 jleave:
853 NYD2_LEAVE;
854 return c;
857 static int
858 charcount(char *str, int c)
860 char *cp;
861 int i;
862 NYD2_ENTER;
864 for (i = 0, cp = str; *cp; ++cp)
865 if (*cp == c)
866 ++i;
867 NYD2_LEAVE;
868 return i;
871 static char const *
872 nexttoken(char const *cp)
874 NYD2_ENTER;
875 for (;;) {
876 if (*cp == '\0') {
877 cp = NULL;
878 break;
881 if (*cp == '(') {
882 size_t nesting = 1;
884 do switch (*++cp) {
885 case '(':
886 ++nesting;
887 break;
888 case ')':
889 --nesting;
890 break;
891 } while (nesting > 0 && *cp != '\0'); /* XXX error? */
892 } else if (blankchar(*cp) || *cp == ',')
893 ++cp;
894 else
895 break;
897 NYD2_LEAVE;
898 return cp;
901 static char *
902 a_head_customhdr__sep(char **iolist){
903 char *cp, c, *base;
904 bool_t isesc, anyesc;
905 NYD2_ENTER;
907 for(base = *iolist; base != NULL; base = *iolist){
908 while((c = *base) != '\0' && blankspacechar(c))
909 ++base;
911 for(isesc = anyesc = FAL0, cp = base;; ++cp){
912 if(n_UNLIKELY((c = *cp) == '\0')){
913 *iolist = NULL;
914 break;
915 }else if(!isesc){
916 if(c == ','){
917 *iolist = cp + 1;
918 break;
920 isesc = (c == '\\');
921 }else{
922 isesc = FAL0;
923 anyesc |= (c == ',');
927 while(cp > base && blankspacechar(cp[-1]))
928 --cp;
929 *cp = '\0';
931 if(*base != '\0'){
932 if(anyesc){
933 char *ins;
935 for(ins = cp = base;; ++ins)
936 if((c = *cp) == '\\' && cp[1] == ','){
937 *ins = ',';
938 cp += 2;
939 }else if((*ins = (++cp, c)) == '\0')
940 break;
942 break;
945 NYD2_LEAVE;
946 return base;
949 FL char const *
950 myaddrs(struct header *hp)
952 struct name *np;
953 char const *rv, *mta;
954 NYD_ENTER;
956 if (hp != NULL && (np = hp->h_from) != NULL) {
957 if ((rv = np->n_fullname) != NULL)
958 goto jleave;
959 if ((rv = np->n_name) != NULL)
960 goto jleave;
963 if ((rv = ok_vlook(from)) != NULL)
964 goto jleave;
966 /* When invoking *sendmail* directly, it's its task to generate an otherwise
967 * undeterminable From: address. However, if the user sets *hostname*,
968 * accept his desire */
969 if (ok_vlook(hostname) != NULL)
970 goto jnodename;
971 if (ok_vlook(smtp) != NULL || /* TODO obsolete -> mta */
972 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
973 ((mta = ok_vlook(mta)) != NULL &&
974 (mta = n_servbyname(mta, NULL)) != NULL && *mta != '\0'))
975 goto jnodename;
976 jleave:
977 NYD_LEAVE;
978 return rv;
980 jnodename:{
981 char *cp;
982 char const *hn, *ln;
983 size_t i;
985 hn = nodename(1);
986 ln = ok_vlook(LOGNAME);
987 i = strlen(ln) + strlen(hn) + 1 +1;
988 rv = cp = salloc(i);
989 sstpcpy(sstpcpy(sstpcpy(cp, ln), "@"), hn);
991 goto jleave;
994 FL char const *
995 myorigin(struct header *hp)
997 char const *rv = NULL, *ccp;
998 struct name *np;
999 NYD_ENTER;
1001 if ((ccp = myaddrs(hp)) != NULL &&
1002 (np = lextract(ccp, GEXTRA | GFULL)) != NULL)
1003 rv = (np->n_flink != NULL) ? ok_vlook(sender) : ccp;
1004 NYD_LEAVE;
1005 return rv;
1008 FL bool_t
1009 is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155)
1011 char date[FROM_DATEBUF];
1012 bool_t rv;
1013 NYD2_ENTER;
1015 if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 &&
1016 (extract_date_from_from_(linebuf, linelen, date) <= 0 ||
1017 !_is_date(date)))
1018 rv = TRUM1;
1019 NYD2_LEAVE;
1020 return rv;
1023 FL int
1024 extract_date_from_from_(char const *line, size_t linelen,
1025 char datebuf[FROM_DATEBUF])
1027 int rv;
1028 char const *cp = line;
1029 NYD_ENTER;
1031 rv = 1;
1033 /* "From " */
1034 cp = _from__skipword(cp);
1035 if (cp == NULL)
1036 goto jerr;
1037 /* "addr-spec " */
1038 cp = _from__skipword(cp);
1039 if (cp == NULL)
1040 goto jerr;
1041 if (cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
1042 cp = _from__skipword(cp);
1043 if (cp == NULL)
1044 goto jerr;
1046 /* It seems there are invalid MBOX archives in the wild, compare
1047 * . http://bugs.debian.org/624111
1048 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1049 * What they do is that they obfuscate the address to "name at host",
1050 * and even "name at host dot dom dot dom. I think we should handle that */
1051 else if(cp[0] == 'a' && cp[1] == 't' && cp[2] == ' '){
1052 rv = -1;
1053 cp += 3;
1054 jat_dot:
1055 cp = _from__skipword(cp);
1056 if (cp == NULL)
1057 goto jerr;
1058 if(cp[0] == 'd' && cp[1] == 'o' && cp[2] == 't' && cp[3] == ' '){
1059 cp += 4;
1060 goto jat_dot;
1064 linelen -= PTR2SIZE(cp - line);
1065 if (linelen < _DATE_MINLEN)
1066 goto jerr;
1067 if (cp[linelen - 1] == '\n') {
1068 --linelen;
1069 /* (Rather IMAP/POP3 only) */
1070 if (cp[linelen - 1] == '\r')
1071 --linelen;
1072 if (linelen < _DATE_MINLEN)
1073 goto jerr;
1075 if (linelen >= FROM_DATEBUF)
1076 goto jerr;
1078 jleave:
1079 memcpy(datebuf, cp, linelen);
1080 datebuf[linelen] = '\0';
1081 NYD_LEAVE;
1082 return rv;
1083 jerr:
1084 cp = _("<Unknown date>");
1085 linelen = strlen(cp);
1086 if (linelen >= FROM_DATEBUF)
1087 linelen = FROM_DATEBUF;
1088 rv = 0;
1089 goto jleave;
1092 FL void
1093 extract_header(FILE *fp, struct header *hp, si8_t *checkaddr_err)
1095 /* See the prototype declaration for the hairy relationship of
1096 * options&OPT_t_FLAG and/or pstate&PS_t_FLAG in here */
1097 struct n_header_field **hftail;
1098 struct header nh, *hq = &nh;
1099 char *linebuf = NULL /* TODO line pool */, *colon;
1100 size_t linesize = 0, seenfields = 0;
1101 int lc, c;
1102 char const *val, *cp;
1103 NYD_ENTER;
1105 memset(hq, 0, sizeof *hq);
1106 if ((pstate & PS_t_FLAG) && (options & OPT_t_FLAG)) {
1107 hq->h_to = hp->h_to;
1108 hq->h_cc = hp->h_cc;
1109 hq->h_bcc = hp->h_bcc;
1111 hftail = &hq->h_user_headers;
1113 for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
1116 /* TODO yippieia, cat(check(lextract)) :-) */
1117 rewind(fp);
1118 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
1119 struct name *np;
1121 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1122 * yet expanded when we parse these! */
1123 if ((val = thisfield(linebuf, "to")) != NULL) {
1124 ++seenfields;
1125 hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL),
1126 EACM_NORMAL | EAF_NAME, checkaddr_err));
1127 } else if ((val = thisfield(linebuf, "cc")) != NULL) {
1128 ++seenfields;
1129 hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL),
1130 EACM_NORMAL | EAF_NAME, checkaddr_err));
1131 } else if ((val = thisfield(linebuf, "bcc")) != NULL) {
1132 ++seenfields;
1133 hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL),
1134 EACM_NORMAL | EAF_NAME, checkaddr_err));
1135 } else if ((val = thisfield(linebuf, "from")) != NULL) {
1136 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
1137 ++seenfields;
1138 hq->h_from = cat(hq->h_from,
1139 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1140 EACM_STRICT, NULL));
1142 } else if ((val = thisfield(linebuf, "reply-to")) != NULL) {
1143 ++seenfields;
1144 hq->h_replyto = cat(hq->h_replyto,
1145 checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
1146 } else if ((val = thisfield(linebuf, "sender")) != NULL) {
1147 if (!(pstate & PS_t_FLAG) || (options & OPT_t_FLAG)) {
1148 ++seenfields;
1149 hq->h_sender = cat(hq->h_sender, /* TODO cat? check! */
1150 checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
1151 EACM_STRICT, NULL));
1152 } else
1153 goto jebadhead;
1154 } else if ((val = thisfield(linebuf, "subject")) != NULL ||
1155 (val = thisfield(linebuf, "subj")) != NULL) {
1156 ++seenfields;
1157 for (cp = val; blankchar(*cp); ++cp)
1159 hq->h_subject = (hq->h_subject != NULL)
1160 ? save2str(hq->h_subject, cp) : savestr(cp);
1162 /* The remaining are mostly hacked in and thus TODO -- at least in
1163 * TODO respect to their content checking */
1164 else if((val = thisfield(linebuf, "message-id")) != NULL){
1165 if(pstate & PS_t_FLAG){
1166 np = checkaddrs(lextract(val, GREF),
1167 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1168 NULL);
1169 if (np == NULL || np->n_flink != NULL)
1170 goto jebadhead;
1171 ++seenfields;
1172 hq->h_message_id = np;
1173 }else
1174 goto jebadhead;
1175 }else if((val = thisfield(linebuf, "in-reply-to")) != NULL){
1176 if(pstate & PS_t_FLAG){
1177 np = checkaddrs(lextract(val, GREF),
1178 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1179 NULL);
1180 ++seenfields;
1181 hq->h_in_reply_to = np;
1182 }else
1183 goto jebadhead;
1184 }else if((val = thisfield(linebuf, "references")) != NULL){
1185 if(pstate & PS_t_FLAG){
1186 ++seenfields;
1187 /* TODO Limit number of references TODO better on parser side */
1188 hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
1189 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
1190 NULL));
1191 }else
1192 goto jebadhead;
1194 /* and that is very hairy */
1195 else if((val = thisfield(linebuf, "mail-followup-to")) != NULL){
1196 if(pstate & PS_t_FLAG){
1197 ++seenfields;
1198 hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val, GEXTRA | GFULL),
1199 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME,
1200 checkaddr_err));
1201 }else
1202 goto jebadhead;
1204 /* A free-form user header; gethfield() did some verification already.. */
1205 else{
1206 struct n_header_field *hfp;
1207 ui32_t nl, bl;
1208 char const *nstart;
1210 for(nstart = cp = linebuf;; ++cp)
1211 if(!fieldnamechar(*cp))
1212 break;
1213 nl = (ui32_t)PTR2SIZE(cp - nstart);
1215 while(blankchar(*cp))
1216 ++cp;
1217 if(*cp++ != ':'){
1218 jebadhead:
1219 n_err(_("Ignoring header field: %s\n"), linebuf);
1220 continue;
1222 while(blankchar(*cp))
1223 ++cp;
1224 bl = (ui32_t)strlen(cp) +1;
1226 ++seenfields;
1227 *hftail = hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat
1228 ) + nl +1 + bl);
1229 hftail = &hfp->hf_next;
1230 hfp->hf_next = NULL;
1231 hfp->hf_nl = nl;
1232 hfp->hf_bl = bl - 1;
1233 memcpy(hfp->hf_dat, nstart, nl);
1234 hfp->hf_dat[nl++] = '\0';
1235 memcpy(hfp->hf_dat + nl, cp, bl);
1239 /* In case the blank line after the header has been edited out. Otherwise,
1240 * fetch the header separator */
1241 if (linebuf != NULL) {
1242 if (linebuf[0] != '\0') {
1243 for (cp = linebuf; *(++cp) != '\0';)
1245 fseek(fp, (long)-PTR2SIZE(1 + cp - linebuf), SEEK_CUR);
1246 } else {
1247 if ((c = getc(fp)) != '\n' && c != EOF)
1248 ungetc(c, fp);
1252 if (seenfields > 0 && (checkaddr_err == NULL || *checkaddr_err == 0)) {
1253 hp->h_to = hq->h_to;
1254 hp->h_cc = hq->h_cc;
1255 hp->h_bcc = hq->h_bcc;
1256 hp->h_from = hq->h_from;
1257 hp->h_replyto = hq->h_replyto;
1258 hp->h_sender = hq->h_sender;
1259 if (hq->h_subject != NULL || !(pstate & PS_t_FLAG) ||
1260 !(options & OPT_t_FLAG))
1261 hp->h_subject = hq->h_subject;
1262 hp->h_user_headers = hq->h_user_headers;
1264 if (pstate & PS_t_FLAG) {
1265 hp->h_ref = hq->h_ref;
1266 hp->h_message_id = hq->h_message_id;
1267 hp->h_in_reply_to = hq->h_in_reply_to;
1268 hp->h_mft = hq->h_mft;
1270 /* And perform additional validity checks so that we don't bail later
1271 * on TODO this is good and the place where this should occur,
1272 * TODO unfortunately a lot of other places do again and blabla */
1273 if (pstate & PS_t_FLAG) {
1274 if (hp->h_from == NULL)
1275 hp->h_from = option_r_arg;
1276 else if (hp->h_from->n_flink != NULL && hp->h_sender == NULL)
1277 hp->h_sender = lextract(ok_vlook(sender),
1278 GEXTRA | GFULL | GFULLEXTRA);
1281 } else
1282 n_err(_("Restoring deleted header lines\n"));
1284 if (linebuf != NULL)
1285 free(linebuf);
1286 NYD_LEAVE;
1289 FL char *
1290 hfield_mult(char const *field, struct message *mp, int mult)
1292 FILE *ibuf;
1293 int lc;
1294 struct str hfs;
1295 size_t linesize = 0; /* TODO line pool */
1296 char *linebuf = NULL, *colon;
1297 char const *hfield;
1298 NYD_ENTER;
1300 /* There are (spam) messages which have header bytes which are many KB when
1301 * joined, so resize a single heap storage until we are done if we shall
1302 * collect a field that may have multiple bodies; only otherwise use the
1303 * string dope directly */
1304 memset(&hfs, 0, sizeof hfs);
1306 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1307 goto jleave;
1308 if ((lc = mp->m_lines - 1) < 0)
1309 goto jleave;
1311 if ((mp->m_flag & MNOFROM) == 0 &&
1312 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1313 goto jleave;
1314 while (lc > 0) {
1315 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon)) < 0)
1316 break;
1317 if ((hfield = thisfield(linebuf, field)) != NULL && *hfield != '\0') {
1318 if (mult)
1319 n_str_add_buf(&hfs, hfield, strlen(hfield));
1320 else {
1321 hfs.s = savestr(hfield);
1322 break;
1327 jleave:
1328 if (linebuf != NULL)
1329 free(linebuf);
1330 if (mult && hfs.s != NULL) {
1331 colon = savestrbuf(hfs.s, hfs.l);
1332 free(hfs.s);
1333 hfs.s = colon;
1335 NYD_LEAVE;
1336 return hfs.s;
1339 FL char const *
1340 thisfield(char const *linebuf, char const *field)
1342 char const *rv = NULL;
1343 NYD2_ENTER;
1345 while (lowerconv(*linebuf) == lowerconv(*field)) {
1346 ++linebuf;
1347 ++field;
1349 if (*field != '\0')
1350 goto jleave;
1352 while (blankchar(*linebuf))
1353 ++linebuf;
1354 if (*linebuf++ != ':')
1355 goto jleave;
1357 while (blankchar(*linebuf)) /* TODO header parser.. strip trailing WS?!? */
1358 ++linebuf;
1359 rv = linebuf;
1360 jleave:
1361 NYD2_LEAVE;
1362 return rv;
1365 FL char *
1366 nameof(struct message *mp, int reptype)
1368 char *cp, *cp2;
1369 NYD_ENTER;
1371 cp = skin(name1(mp, reptype));
1372 if (reptype != 0 || charcount(cp, '!') < 2)
1373 goto jleave;
1374 cp2 = strrchr(cp, '!');
1375 --cp2;
1376 while (cp2 > cp && *cp2 != '!')
1377 --cp2;
1378 if (*cp2 == '!')
1379 cp = cp2 + 1;
1380 jleave:
1381 NYD_LEAVE;
1382 return cp;
1385 FL char const *
1386 skip_comment(char const *cp)
1388 size_t nesting;
1389 NYD_ENTER;
1391 for (nesting = 1; nesting > 0 && *cp; ++cp) {
1392 switch (*cp) {
1393 case '\\':
1394 if (cp[1])
1395 ++cp;
1396 break;
1397 case '(':
1398 ++nesting;
1399 break;
1400 case ')':
1401 --nesting;
1402 break;
1405 NYD_LEAVE;
1406 return cp;
1409 FL char const *
1410 routeaddr(char const *name)
1412 char const *np, *rp = NULL;
1413 NYD_ENTER;
1415 for (np = name; *np; np++) {
1416 switch (*np) {
1417 case '(':
1418 np = skip_comment(np + 1) - 1;
1419 break;
1420 case '"':
1421 while (*np) {
1422 if (*++np == '"')
1423 break;
1424 if (*np == '\\' && np[1])
1425 np++;
1427 break;
1428 case '<':
1429 rp = np;
1430 break;
1431 case '>':
1432 goto jleave;
1435 rp = NULL;
1436 jleave:
1437 NYD_LEAVE;
1438 return rp;
1441 FL enum expand_addr_flags
1442 expandaddr_to_eaf(void)
1444 struct eafdesc {
1445 char const *eafd_name;
1446 bool_t eafd_is_target;
1447 ui8_t eafd_andoff;
1448 ui8_t eafd_or;
1449 } const eafa[] = {
1450 {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
1451 {"fail", FAL0, EAF_NONE, EAF_FAIL},
1452 {"failinvaddr", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
1453 {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
1454 {"file", TRU1, EAF_NONE, EAF_FILE},
1455 {"pipe", TRU1, EAF_NONE, EAF_PIPE},
1456 {"name", TRU1, EAF_NONE, EAF_NAME},
1457 {"addr", TRU1, EAF_NONE, EAF_ADDR}
1458 }, *eafp;
1460 char *buf;
1461 enum expand_addr_flags rv;
1462 char const *cp;
1463 NYD2_ENTER;
1465 if ((cp = ok_vlook(expandaddr)) == NULL)
1466 rv = EAF_RESTRICT_TARGETS;
1467 else if (*cp == '\0')
1468 rv = EAF_TARGET_MASK;
1469 else {
1470 rv = EAF_TARGET_MASK;
1472 for (buf = savestr(cp); (cp = n_strsep(&buf, ',', TRU1)) != NULL;) {
1473 bool_t minus;
1475 if ((minus = (*cp == '-')) || *cp == '+')
1476 ++cp;
1477 for (eafp = eafa;; ++eafp) {
1478 if (eafp == eafa + n_NELEM(eafa)) {
1479 if (options & OPT_D_V)
1480 n_err(_("Unknown *expandaddr* value: %s\n"), cp);
1481 break;
1482 } else if (!asccasecmp(cp, eafp->eafd_name)) {
1483 if (!minus) {
1484 rv &= ~eafp->eafd_andoff;
1485 rv |= eafp->eafd_or;
1486 } else {
1487 if (eafp->eafd_is_target)
1488 rv &= ~eafp->eafd_or;
1489 else if (options & OPT_D_V)
1490 n_err(_("minus - prefix invalid for *expandaddr* value: "
1491 "%s\n"), --cp);
1493 break;
1494 } else if (!asccasecmp(cp, "noalias")) { /* TODO v15 OBSOLETE */
1495 OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1496 rv &= ~EAF_NAME;
1497 break;
1502 if ((rv & EAF_RESTRICT) && (options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)))
1503 rv |= EAF_TARGET_MASK;
1504 else if (options & OPT_D_V) {
1505 if (!(rv & EAF_TARGET_MASK))
1506 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1507 else if ((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
1508 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1511 NYD2_LEAVE;
1512 return rv;
1515 FL si8_t
1516 is_addr_invalid(struct name *np, enum expand_addr_check_mode eacm)
1518 char cbuf[sizeof "'\\U12340'"];
1519 char const *cs;
1520 int f;
1521 si8_t rv;
1522 enum expand_addr_flags eaf;
1523 NYD_ENTER;
1525 eaf = expandaddr_to_eaf();
1526 f = np->n_flags;
1528 if ((rv = ((f & NAME_ADDRSPEC_INVALID) != 0))) {
1529 if (eaf & EAF_FAILINVADDR)
1530 rv = -rv;
1532 if ((eacm & EACM_NOLOG) || (f & NAME_ADDRSPEC_ERR_EMPTY)) {
1534 } else {
1535 ui32_t c;
1536 char const *fmt = "'\\x%02X'";
1537 bool_t ok8bit = TRU1;
1539 if (f & NAME_ADDRSPEC_ERR_IDNA) {
1540 cs = _("Invalid domain name: %s, character %s\n");
1541 fmt = "'\\U%04X'";
1542 ok8bit = FAL0;
1543 } else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
1544 cs = _("%s contains invalid %s sequence\n");
1545 else
1546 cs = _("%s contains invalid non-ASCII byte %s\n");
1548 c = NAME_ADDRSPEC_ERR_GETWC(f);
1549 snprintf(cbuf, sizeof cbuf,
1550 (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
1551 goto jprint;
1553 goto jleave;
1556 /* *expandaddr* stuff */
1557 if (!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
1558 goto jleave;
1560 if ((eacm & EACM_STRICT) && (f & NAME_ADDRSPEC_ISFILEORPIPE)) {
1561 if (eaf & EAF_FAIL)
1562 rv = -rv;
1563 cs = _("%s%s: file or pipe addressees not allowed here\n");
1564 if (eacm & EACM_NOLOG)
1565 goto jleave;
1566 else
1567 goto j0print;
1570 eaf |= (eacm & EAF_TARGET_MASK);
1571 if (eacm & EACM_NONAME)
1572 eaf &= ~EAF_NAME;
1574 if (eaf == EAF_NONE) {
1575 rv = FAL0;
1576 goto jleave;
1578 if (eaf & EAF_FAIL)
1579 rv = -rv;
1581 if (!(eaf & EAF_FILE) && (f & NAME_ADDRSPEC_ISFILE)) {
1582 cs = _("%s%s: *expandaddr* doesn't allow file target\n");
1583 if (eacm & EACM_NOLOG)
1584 goto jleave;
1585 } else if (!(eaf & EAF_PIPE) && (f & NAME_ADDRSPEC_ISPIPE)) {
1586 cs = _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1587 if (eacm & EACM_NOLOG)
1588 goto jleave;
1589 } else if (!(eaf & EAF_NAME) && (f & NAME_ADDRSPEC_ISNAME)) {
1590 cs = _("%s%s: *expandaddr* doesn't allow user name target\n");
1591 if (eacm & EACM_NOLOG)
1592 goto jleave;
1593 } else if (!(eaf & EAF_ADDR) && (f & NAME_ADDRSPEC_ISADDR)) {
1594 cs = _("%s%s: *expandaddr* doesn't allow mail address target\n");
1595 if (eacm & EACM_NOLOG)
1596 goto jleave;
1597 } else {
1598 rv = FAL0;
1599 goto jleave;
1602 j0print:
1603 cbuf[0] = '\0';
1604 jprint:
1605 n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
1606 jleave:
1607 NYD_LEAVE;
1608 return rv;
1611 FL char *
1612 skin(char const *name)
1614 struct n_addrguts ag;
1615 char *rv;
1616 NYD_ENTER;
1618 if(name != NULL){
1619 name = n_addrspec_with_guts(&ag,name, TRU1);
1620 rv = ag.ag_skinned;
1621 if(!(ag.ag_n_flags & NAME_NAME_SALLOC))
1622 rv = savestrbuf(rv, ag.ag_slen);
1623 }else
1624 rv = NULL;
1625 NYD_LEAVE;
1626 return rv;
1629 /* TODO addrspec_with_guts: RFC 5322
1630 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1631 FL char const *
1632 n_addrspec_with_guts(struct n_addrguts *agp, char const *name, bool_t doskin){
1633 char const *cp;
1634 char *cp2, *bufend, *nbuf, c;
1635 enum{
1636 a_NONE,
1637 a_GOTLT = 1<<0,
1638 a_GOTADDR = 1<<1,
1639 a_GOTSPACE = 1<<2,
1640 a_LASTSP = 1<<3
1641 } flags;
1642 NYD_ENTER;
1644 memset(agp, 0, sizeof *agp);
1646 if((agp->ag_input = name) == NULL || (agp->ag_ilen = strlen(name)) == 0){
1647 agp->ag_skinned = n_UNCONST(n_empty); /* ok: NAME_SALLOC is not set */
1648 agp->ag_slen = 0;
1649 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
1650 NAME_ADDRSPEC_ERR_SET(agp->ag_n_flags, NAME_ADDRSPEC_ERR_EMPTY, 0);
1651 goto jleave;
1652 }else if(!doskin){
1653 /*agp->ag_iaddr_start = 0;*/
1654 agp->ag_iaddr_aend = agp->ag_ilen;
1655 agp->ag_skinned = n_UNCONST(name); /* (NAME_SALLOC not set) */
1656 agp->ag_slen = agp->ag_ilen;
1657 agp->ag_n_flags = NAME_SKINNED;
1658 goto jcheck;
1661 flags = a_NONE;
1662 nbuf = n_lofi_alloc(agp->ag_ilen +1);
1663 /*agp->ag_iaddr_start = 0;*/
1664 cp2 = bufend = nbuf;
1666 for(cp = name++; (c = *cp++) != '\0';){
1667 switch (c) {
1668 case '(':
1669 cp = skip_comment(cp);
1670 flags &= ~a_LASTSP;
1671 break;
1672 case '"':
1673 /* Start of a "quoted-string". Copy it in its entirety */
1674 /* XXX RFC: quotes are "semantically invisible"
1675 * XXX But it was explicitly added (Changelog.Heirloom,
1676 * XXX [9.23] released 11/15/00, "Do not remove quotes
1677 * XXX when skinning names"? No more info.. */
1678 *cp2++ = c;
1679 while ((c = *cp) != '\0') { /* TODO improve */
1680 ++cp;
1681 if (c == '"') {
1682 *cp2++ = c;
1683 break;
1685 if (c != '\\')
1686 *cp2++ = c;
1687 else if ((c = *cp) != '\0') {
1688 *cp2++ = c;
1689 ++cp;
1692 flags &= ~a_LASTSP;
1693 break;
1694 case ' ':
1695 case '\t':
1696 if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
1697 flags |= a_GOTADDR | a_GOTSPACE;
1698 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1700 if (cp[0] == 'a' && cp[1] == 't' && blankchar(cp[2]))
1701 cp += 3, *cp2++ = '@';
1702 else if (cp[0] == '@' && blankchar(cp[1]))
1703 cp += 2, *cp2++ = '@';
1704 else
1705 flags |= a_LASTSP;
1706 break;
1707 case '<':
1708 agp->ag_iaddr_start = PTR2SIZE(cp - (name - 1));
1709 cp2 = bufend;
1710 flags &= ~(a_GOTSPACE | a_LASTSP);
1711 flags |= a_GOTLT | a_GOTADDR;
1712 break;
1713 case '>':
1714 if(flags & a_GOTLT){
1715 /* (_addrspec_check() verifies these later!) */
1716 flags &= ~(a_GOTLT | a_LASTSP);
1717 agp->ag_iaddr_aend = PTR2SIZE(cp - name);
1719 /* Skip over the entire remaining field */
1720 while((c = *cp) != '\0' && c != ','){
1721 ++cp;
1722 if (c == '(')
1723 cp = skip_comment(cp);
1724 else if (c == '"')
1725 while ((c = *cp) != '\0') {
1726 ++cp;
1727 if (c == '"')
1728 break;
1729 if (c == '\\' && *cp != '\0')
1730 ++cp;
1733 break;
1735 /* FALLTHRU */
1736 default:
1737 if(flags & a_LASTSP){
1738 flags &= ~a_LASTSP;
1739 if(flags & a_GOTADDR)
1740 *cp2++ = ' ';
1742 *cp2++ = c;
1743 if(c == ','){
1744 if(!(flags & a_GOTLT)){
1745 *cp2++ = ' ';
1746 for(; blankchar(*cp); ++cp)
1748 flags &= ~a_LASTSP;
1749 bufend = cp2;
1751 }else if(!(flags & a_GOTADDR)){
1752 flags |= a_GOTADDR;
1753 agp->ag_iaddr_start = PTR2SIZE(cp - name);
1757 --name;
1758 agp->ag_slen = PTR2SIZE(cp2 - nbuf);
1759 if (agp->ag_iaddr_aend == 0)
1760 agp->ag_iaddr_aend = agp->ag_ilen;
1761 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
1762 n_lofi_free(nbuf);
1763 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
1764 jcheck:
1765 if(a_head_addrspec_check(agp, doskin) <= 0)
1766 name = NULL;
1767 else
1768 name = agp->ag_input;
1769 jleave:
1770 NYD_LEAVE;
1771 return name;
1774 FL char *
1775 realname(char const *name)
1777 char const *cp, *cq, *cstart = NULL, *cend = NULL;
1778 char *rname, *rp;
1779 struct str in, out;
1780 int quoted, good, nogood;
1781 NYD_ENTER;
1783 if ((cp = n_UNCONST(name)) == NULL)
1784 goto jleave;
1785 for (; *cp != '\0'; ++cp) {
1786 switch (*cp) {
1787 case '(':
1788 if (cstart != NULL) {
1789 /* More than one comment in address, doesn't make sense to display
1790 * it without context. Return the entire field */
1791 cp = mime_fromaddr(name);
1792 goto jleave;
1794 cstart = cp++;
1795 cp = skip_comment(cp);
1796 cend = cp--;
1797 if (cend <= cstart)
1798 cend = cstart = NULL;
1799 break;
1800 case '"':
1801 while (*cp) {
1802 if (*++cp == '"')
1803 break;
1804 if (*cp == '\\' && cp[1])
1805 ++cp;
1807 break;
1808 case '<':
1809 if (cp > name) {
1810 cstart = name;
1811 cend = cp;
1813 break;
1814 case ',':
1815 /* More than one address. Just use the first one */
1816 goto jbrk;
1820 jbrk:
1821 if (cstart == NULL) {
1822 if (*name == '<') {
1823 /* If name contains only a route-addr, the surrounding angle brackets
1824 * don't serve any useful purpose when displaying, so remove */
1825 cp = prstr(skin(name));
1826 } else
1827 cp = mime_fromaddr(name);
1828 goto jleave;
1831 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1832 * not stripped. The idea is to strip only syntactical relevant things (but
1833 * this is not necessarily the most sensible way in practice) */
1834 rp = rname = ac_alloc(PTR2SIZE(cend - cstart +1));
1835 quoted = 0;
1836 for (cp = cstart; cp < cend; ++cp) {
1837 if (*cp == '(' && !quoted) {
1838 cq = skip_comment(++cp);
1839 if (PTRCMP(--cq, >, cend))
1840 cq = cend;
1841 while (cp < cq) {
1842 if (*cp == '\\' && PTRCMP(cp + 1, <, cq))
1843 ++cp;
1844 *rp++ = *cp++;
1846 } else if (*cp == '\\' && PTRCMP(cp + 1, <, cend))
1847 *rp++ = *++cp;
1848 else if (*cp == '"') {
1849 quoted = !quoted;
1850 continue;
1851 } else
1852 *rp++ = *cp;
1854 *rp = '\0';
1855 in.s = rname;
1856 in.l = rp - rname;
1857 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1858 ac_free(rname);
1859 rname = savestr(out.s);
1860 free(out.s);
1862 while (blankchar(*rname))
1863 ++rname;
1864 for (rp = rname; *rp != '\0'; ++rp)
1866 while (PTRCMP(--rp, >=, rname) && blankchar(*rp))
1867 *rp = '\0';
1868 if (rp == rname) {
1869 cp = mime_fromaddr(name);
1870 goto jleave;
1873 /* mime_fromhdr() has converted all nonprintable characters to question
1874 * marks now. These and blanks are considered uninteresting; if the
1875 * displayed part of the real name contains more than 25% of them, it is
1876 * probably better to display the plain email address instead */
1877 good = 0;
1878 nogood = 0;
1879 for (rp = rname; *rp != '\0' && PTRCMP(rp, <, rname + 20); ++rp)
1880 if (*rp == '?' || blankchar(*rp))
1881 ++nogood;
1882 else
1883 ++good;
1884 cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
1885 jleave:
1886 NYD_LEAVE;
1887 return n_UNCONST(cp);
1890 FL char *
1891 name1(struct message *mp, int reptype)
1893 char *namebuf, *cp, *cp2, *linebuf = NULL /* TODO line pool */;
1894 size_t namesize, linesize = 0;
1895 FILE *ibuf;
1896 int f1st = 1;
1897 NYD_ENTER;
1899 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1900 goto jleave;
1901 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL && *cp != '\0')
1902 goto jleave;
1904 namebuf = smalloc(namesize = 1);
1905 namebuf[0] = 0;
1906 if (mp->m_flag & MNOFROM)
1907 goto jout;
1908 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1909 goto jout;
1910 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1911 goto jout;
1913 jnewname:
1914 if (namesize <= linesize)
1915 namebuf = srealloc(namebuf, namesize = linesize +1);
1916 for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
1918 for (; blankchar(*cp); ++cp)
1920 for (cp2 = namebuf + strlen(namebuf);
1921 *cp && !blankchar(*cp) && PTRCMP(cp2, <, namebuf + namesize -1);)
1922 *cp2++ = *cp++;
1923 *cp2 = '\0';
1925 if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
1926 goto jout;
1927 if ((cp = strchr(linebuf, 'F')) == NULL)
1928 goto jout;
1929 if (strncmp(cp, "From", 4))
1930 goto jout;
1931 if (namesize <= linesize)
1932 namebuf = srealloc(namebuf, namesize = linesize + 1);
1934 while ((cp = strchr(cp, 'r')) != NULL) {
1935 if (!strncmp(cp, "remote", 6)) {
1936 if ((cp = strchr(cp, 'f')) == NULL)
1937 break;
1938 if (strncmp(cp, "from", 4) != 0)
1939 break;
1940 if ((cp = strchr(cp, ' ')) == NULL)
1941 break;
1942 cp++;
1943 if (f1st) {
1944 strncpy(namebuf, cp, namesize);
1945 f1st = 0;
1946 } else {
1947 cp2 = strrchr(namebuf, '!') + 1;
1948 strncpy(cp2, cp, PTR2SIZE(namebuf + namesize - cp2));
1950 namebuf[namesize - 2] = '!';
1951 namebuf[namesize - 1] = '\0';
1952 goto jnewname;
1954 cp++;
1956 jout:
1957 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1958 *cp == '\0')
1959 cp = savestr(namebuf);
1961 if (linebuf != NULL)
1962 free(linebuf);
1963 free(namebuf);
1964 jleave:
1965 NYD_LEAVE;
1966 return cp;
1969 FL char *
1970 subject_re_trim(char *s)
1972 struct {
1973 ui8_t len;
1974 char dat[7];
1975 } const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
1976 { 3, "re:" },
1977 { 3, "aw:" }, { 5, "antw:" }, /* de */
1978 { 0, "" }
1981 bool_t any = FAL0;
1982 char *orig_s = s, *re_st = NULL, *re_st_x;
1983 size_t re_l = 0 /* pacify CC */;
1984 NYD_ENTER;
1986 if ((re_st_x = ok_vlook(reply_strings)) != NULL &&
1987 (re_l = strlen(re_st_x)) > 0) {
1988 re_st = ac_alloc(++re_l * 2);
1989 memcpy(re_st, re_st_x, re_l);
1992 jouter:
1993 while (*s != '\0') {
1994 while (spacechar(*s))
1995 ++s;
1997 for (pp = ignored; pp->len > 0; ++pp)
1998 if (is_asccaseprefix(pp->dat, s)) {
1999 s += pp->len;
2000 any = TRU1;
2001 goto jouter;
2004 if (re_st != NULL) {
2005 char *cp;
2007 memcpy(re_st_x = re_st + re_l, re_st, re_l);
2008 while ((cp = n_strsep(&re_st_x, ',', TRU1)) != NULL)
2009 if (is_asccaseprefix(cp, s)) {
2010 s += strlen(cp);
2011 any = TRU1;
2012 goto jouter;
2015 break;
2018 if (re_st != NULL)
2019 ac_free(re_st);
2020 NYD_LEAVE;
2021 return any ? s : orig_s;
2024 FL int
2025 msgidcmp(char const *s1, char const *s2)
2027 int q1 = 0, q2 = 0, c1, c2;
2028 NYD_ENTER;
2030 while(*s1 == '<')
2031 ++s1;
2032 while(*s2 == '<')
2033 ++s2;
2035 do {
2036 c1 = msgidnextc(&s1, &q1);
2037 c2 = msgidnextc(&s2, &q2);
2038 if (c1 != c2)
2039 break;
2040 } while (c1 && c2);
2041 NYD_LEAVE;
2042 return c1 - c2;
2045 FL char const *
2046 fakefrom(struct message *mp)
2048 char const *name;
2049 NYD_ENTER;
2051 if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
2052 ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
2053 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2054 * RFC 4155 however requires a RFC 5322 (2822) conforming
2055 * "addr-spec", but we simply can't provide that */
2056 name = "MAILER-DAEMON";
2057 NYD_LEAVE;
2058 return name;
2061 FL char const *
2062 fakedate(time_t t)
2064 char *cp, *cq;
2065 NYD_ENTER;
2067 cp = ctime(&t);
2068 for (cq = cp; *cq != '\0' && *cq != '\n'; ++cq)
2070 *cq = '\0';
2071 cp = savestr(cp);
2072 NYD_LEAVE;
2073 return cp;
2076 #ifdef HAVE_IMAP_SEARCH
2077 FL time_t
2078 unixtime(char const *fromline)
2080 char const *fp;
2081 char *xp;
2082 time_t t;
2083 int i, year, month, day, hour, minute, second, tzdiff;
2084 struct tm *tmptr;
2085 NYD2_ENTER;
2087 for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
2089 fp -= 24;
2090 if (PTR2SIZE(fp - fromline) < 7)
2091 goto jinvalid;
2092 if (fp[3] != ' ')
2093 goto jinvalid;
2094 for (i = 0;;) {
2095 if (!strncmp(fp + 4, month_names[i], 3))
2096 break;
2097 if (month_names[++i][0] == '\0')
2098 goto jinvalid;
2100 month = i + 1;
2101 if (fp[7] != ' ')
2102 goto jinvalid;
2103 day = strtol(fp + 8, &xp, 10);
2104 if (*xp != ' ' || xp != fp + 10)
2105 goto jinvalid;
2106 hour = strtol(fp + 11, &xp, 10);
2107 if (*xp != ':' || xp != fp + 13)
2108 goto jinvalid;
2109 minute = strtol(fp + 14, &xp, 10);
2110 if (*xp != ':' || xp != fp + 16)
2111 goto jinvalid;
2112 second = strtol(fp + 17, &xp, 10);
2113 if (*xp != ' ' || xp != fp + 19)
2114 goto jinvalid;
2115 year = strtol(fp + 20, &xp, 10);
2116 if (xp != fp + 24)
2117 goto jinvalid;
2118 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2119 goto jinvalid;
2120 tzdiff = t - mktime(gmtime(&t));
2121 tmptr = localtime(&t);
2122 if (tmptr->tm_isdst > 0)
2123 tzdiff += 3600;
2124 t -= tzdiff;
2125 jleave:
2126 NYD2_LEAVE;
2127 return t;
2128 jinvalid:
2129 t = n_time_epoch();
2130 goto jleave;
2132 #endif /* HAVE_IMAP_SEARCH */
2134 FL time_t
2135 rfctime(char const *date)
2137 char const *cp = date;
2138 char *x;
2139 time_t t;
2140 int i, year, month, day, hour, minute, second;
2141 NYD2_ENTER;
2143 if ((cp = nexttoken(cp)) == NULL)
2144 goto jinvalid;
2145 if (alphachar(cp[0]) && alphachar(cp[1]) && alphachar(cp[2]) &&
2146 cp[3] == ',') {
2147 if ((cp = nexttoken(&cp[4])) == NULL)
2148 goto jinvalid;
2150 day = strtol(cp, &x, 10); /* XXX strtol */
2151 if ((cp = nexttoken(x)) == NULL)
2152 goto jinvalid;
2153 for (i = 0;;) {
2154 if (!strncmp(cp, month_names[i], 3))
2155 break;
2156 if (month_names[++i][0] == '\0')
2157 goto jinvalid;
2159 month = i + 1;
2160 if ((cp = nexttoken(&cp[3])) == NULL)
2161 goto jinvalid;
2162 /* RFC 5322, 4.3:
2163 * Where a two or three digit year occurs in a date, the year is to be
2164 * interpreted as follows: If a two digit year is encountered whose
2165 * value is between 00 and 49, the year is interpreted by adding 2000,
2166 * ending up with a value between 2000 and 2049. If a two digit year
2167 * is encountered with a value between 50 and 99, or any three digit
2168 * year is encountered, the year is interpreted by adding 1900 */
2169 year = strtol(cp, &x, 10); /* XXX strtol */
2170 i = (int)PTR2SIZE(x - cp);
2171 if (i == 2 && year >= 0 && year <= 49)
2172 year += 2000;
2173 else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
2174 year += 1900;
2175 if ((cp = nexttoken(x)) == NULL)
2176 goto jinvalid;
2177 hour = strtol(cp, &x, 10); /* XXX strtol */
2178 if (*x != ':')
2179 goto jinvalid;
2180 cp = &x[1];
2181 minute = strtol(cp, &x, 10);
2182 if (*x == ':') {
2183 cp = x + 1;
2184 second = strtol(cp, &x, 10);
2185 } else
2186 second = 0;
2188 if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
2189 goto jinvalid;
2190 if ((cp = nexttoken(x)) != NULL) {
2191 char buf[3];
2192 int sign = 1;
2194 switch (*cp) {
2195 case '+':
2196 sign = -1;
2197 /* FALLTHRU */
2198 case '-':
2199 ++cp;
2200 break;
2202 if (digitchar(cp[0]) && digitchar(cp[1]) && digitchar(cp[2]) &&
2203 digitchar(cp[3])) {
2204 long tadj;
2205 buf[2] = '\0';
2206 buf[0] = cp[0];
2207 buf[1] = cp[1];
2208 tadj = strtol(buf, NULL, 10) * 3600;/*XXX strtrol*/
2209 buf[0] = cp[2];
2210 buf[1] = cp[3];
2211 tadj += strtol(buf, NULL, 10) * 60; /* XXX strtol*/
2212 if (sign < 0)
2213 tadj = -tadj;
2214 t += tadj;
2216 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2217 * TODO once again, Christos Zoulas and NetBSD Mail have done
2218 * TODO a really good job already, but using strptime(3), which
2219 * TODO is not portable. Nonetheless, WE must improve, not
2220 * TODO at last because we simply ignore obsolete timezones!!
2221 * TODO See RFC 5322, 4.3! */
2223 jleave:
2224 NYD2_LEAVE;
2225 return t;
2226 jinvalid:
2227 t = 0;
2228 goto jleave;
2231 FL time_t
2232 combinetime(int year, int month, int day, int hour, int minute, int second){
2233 size_t const jdn_epoch = 2440588;
2234 bool_t const y2038p = (sizeof(time_t) == 4);
2236 size_t jdn;
2237 time_t t;
2238 NYD2_ENTER;
2240 if(UICMP(32, second, >/*XXX leap=*/, DATE_SECSMIN) ||
2241 UICMP(32, minute, >=, DATE_MINSHOUR) ||
2242 UICMP(32, hour, >=, DATE_HOURSDAY) ||
2243 day < 1 || day > 31 ||
2244 month < 1 || month > 12 ||
2245 year < 1970)
2246 goto jerr;
2248 if(year >= 1970 + ((y2038p ? SI32_MAX : SI64_MAX) /
2249 (DATE_SECSDAY * DATE_DAYSYEAR))){
2250 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2251 * test by stepping second-wise around the flip. Don't care otherwise */
2252 if(!y2038p)
2253 goto jerr;
2254 if(year > 2038 || month > 1 || day > 19 ||
2255 hour > 3 || minute > 14 || second > 7)
2256 goto jerr;
2259 t = second;
2260 t += minute * DATE_SECSMIN;
2261 t += hour * DATE_SECSHOUR;
2263 jdn = a_head_gregorian_to_jdn(year, month, day);
2264 jdn -= jdn_epoch;
2265 t += (time_t)jdn * DATE_SECSDAY;
2266 jleave:
2267 NYD2_LEAVE;
2268 return t;
2269 jerr:
2270 t = (time_t)-1;
2271 goto jleave;
2274 FL void
2275 substdate(struct message *m)
2277 char const *cp;
2278 NYD_ENTER;
2280 /* Determine the date to print in faked 'From ' lines. This is traditionally
2281 * the date the message was written to the mail file. Try to determine this
2282 * using RFC message header fields, or fall back to current time */
2283 if ((cp = hfield1("received", m)) != NULL) {
2284 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
2286 ++cp;
2287 while (alnumchar(*cp));
2289 if (cp && *++cp)
2290 m->m_time = rfctime(cp);
2292 if (m->m_time == 0 || m->m_time > time_current.tc_time) {
2293 if ((cp = hfield1("date", m)) != NULL)
2294 m->m_time = rfctime(cp);
2296 if (m->m_time == 0 || m->m_time > time_current.tc_time)
2297 m->m_time = time_current.tc_time;
2298 NYD_LEAVE;
2301 FL void
2302 setup_from_and_sender(struct header *hp)
2304 char const *addr;
2305 struct name *np;
2306 NYD_ENTER;
2308 /* If -t parsed or composed From: then take it. With -t we otherwise
2309 * want -r to be honoured in favour of *from* in order to have
2310 * a behaviour that is compatible with what users would expect from e.g.
2311 * postfix(1) */
2312 if ((np = hp->h_from) != NULL ||
2313 ((pstate & PS_t_FLAG) && (np = option_r_arg) != NULL)) {
2315 } else if ((addr = myaddrs(hp)) != NULL)
2316 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2317 hp->h_from = np;
2319 if ((np = hp->h_sender) != NULL) {
2321 } else if ((addr = ok_vlook(sender)) != NULL)
2322 np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
2323 hp->h_sender = np;
2325 NYD_LEAVE;
2328 FL struct name const *
2329 check_from_and_sender(struct name const *fromfield,
2330 struct name const *senderfield)
2332 struct name const *rv = NULL;
2333 NYD_ENTER;
2335 if (senderfield != NULL) {
2336 if (senderfield->n_flink != NULL) {
2337 n_err(_("The Sender: field may contain only one address\n"));
2338 goto jleave;
2340 rv = senderfield;
2343 if (fromfield != NULL) {
2344 if (fromfield->n_flink != NULL && senderfield == NULL) {
2345 n_err(_("A Sender: is required when there are multiple "
2346 "addresses in From:\n"));
2347 goto jleave;
2349 if (rv == NULL)
2350 rv = fromfield;
2353 if (rv == NULL)
2354 rv = (struct name*)0x1;
2355 jleave:
2356 NYD_LEAVE;
2357 return rv;
2360 #ifdef HAVE_XSSL
2361 FL char *
2362 getsender(struct message *mp)
2364 char *cp;
2365 struct name *np;
2366 NYD_ENTER;
2368 if ((cp = hfield1("from", mp)) == NULL ||
2369 (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
2370 cp = NULL;
2371 else
2372 cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
2373 NYD_LEAVE;
2374 return cp;
2376 #endif
2378 FL int
2379 grab_headers(enum n_lexinput_flags lif, struct header *hp, enum gfield gflags,
2380 int subjfirst)
2382 /* TODO grab_headers: again, check counts etc. against RFC;
2383 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2384 int errs;
2385 int volatile comma;
2386 NYD_ENTER;
2388 errs = 0;
2389 comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;
2391 if (gflags & GTO)
2392 hp->h_to = grab_names(lif, "To: ", hp->h_to, comma, GTO | GFULL);
2393 if (subjfirst && (gflags & GSUBJECT))
2394 hp->h_subject = n_lex_input_cp(lif, "Subject: ", hp->h_subject);
2395 if (gflags & GCC)
2396 hp->h_cc = grab_names(lif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
2397 if (gflags & GBCC)
2398 hp->h_bcc = grab_names(lif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);
2400 if (gflags & GEXTRA) {
2401 if (hp->h_from == NULL)
2402 hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
2403 hp->h_from = grab_names(lif, "From: ", hp->h_from, comma,
2404 GEXTRA | GFULL | GFULLEXTRA);
2405 if (hp->h_replyto == NULL)
2406 hp->h_replyto = lextract(ok_vlook(replyto), GEXTRA | GFULL);
2407 hp->h_replyto = grab_names(lif, "Reply-To: ", hp->h_replyto, comma,
2408 GEXTRA | GFULL);
2409 if (hp->h_sender == NULL)
2410 hp->h_sender = extract(ok_vlook(sender), GEXTRA | GFULL);
2411 hp->h_sender = grab_names(lif, "Sender: ", hp->h_sender, comma,
2412 GEXTRA | GFULL);
2415 if (!subjfirst && (gflags & GSUBJECT))
2416 hp->h_subject = n_lex_input_cp(lif, "Subject: ", hp->h_subject);
2418 NYD_LEAVE;
2419 return errs;
2422 FL bool_t
2423 header_match(struct message *mp, struct search_expr const *sep)
2425 struct str in, out;
2426 FILE *ibuf;
2427 int lc;
2428 size_t linesize = 0; /* TODO line pool */
2429 char *linebuf = NULL, *colon;
2430 bool_t rv = FAL0;
2431 NYD_ENTER;
2433 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
2434 goto jleave;
2435 if ((lc = mp->m_lines - 1) < 0)
2436 goto jleave;
2438 if ((mp->m_flag & MNOFROM) == 0 &&
2439 readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
2440 goto jleave;
2441 while (lc > 0) {
2442 if (gethfield(ibuf, &linebuf, &linesize, lc, &colon) <= 0)
2443 break;
2444 if (blankchar(*++colon))
2445 ++colon;
2446 in.l = strlen(in.s = colon);
2447 mime_fromhdr(&in, &out, TD_ICONV);
2448 #ifdef HAVE_REGEX
2449 if (sep->ss_sexpr == NULL)
2450 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
2451 else
2452 #endif
2453 rv = substr(out.s, sep->ss_sexpr);
2454 free(out.s);
2455 if (rv)
2456 break;
2459 jleave:
2460 if (linebuf != NULL)
2461 free(linebuf);
2462 NYD_LEAVE;
2463 return rv;
2466 FL struct n_header_field *
2467 n_customhdr_query(void){
2468 char const *vp;
2469 struct n_header_field *rv, **tail, *hfp;
2470 NYD_ENTER;
2472 rv = NULL;
2474 if((vp = ok_vlook(customhdr)) != NULL){
2475 char *buf;
2477 tail = &rv;
2478 buf = savestr(vp);
2479 jch_outer:
2480 while((vp = a_head_customhdr__sep(&buf)) != NULL){
2481 ui32_t nl, bl;
2482 char const *nstart, *cp;
2484 for(nstart = cp = vp;; ++cp){
2485 if(fieldnamechar(*cp))
2486 continue;
2487 if(*cp == '\0'){
2488 if(cp == nstart){
2489 n_err(_("Invalid nameless *customhdr* entry\n"));
2490 goto jch_outer;
2492 }else if(*cp != ':' && !blankchar(*cp)){
2493 jch_badent:
2494 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
2495 goto jch_outer;
2497 break;
2499 nl = (ui32_t)PTR2SIZE(cp - nstart);
2501 while(blankchar(*cp))
2502 ++cp;
2503 if(*cp++ != ':')
2504 goto jch_badent;
2505 while(blankchar(*cp))
2506 ++cp;
2507 bl = (ui32_t)strlen(cp) +1;
2509 *tail =
2510 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
2511 nl +1 + bl);
2512 tail = &hfp->hf_next;
2513 hfp->hf_next = NULL;
2514 hfp->hf_nl = nl;
2515 hfp->hf_bl = bl - 1;
2516 memcpy(hfp->hf_dat, nstart, nl);
2517 hfp->hf_dat[nl++] = '\0';
2518 memcpy(hfp->hf_dat + nl, cp, bl);
2521 NYD_LEAVE;
2522 return rv;
2525 /* s-it-mode */