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 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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
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
39 #ifndef HAVE_AMALGAMATION
44 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
46 # include <idn-free.h>
47 # include <stringprep.h>
48 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT
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
63 * 'O' An optional digit or space
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
},
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
,
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
);
98 static void a_head_jdn_to_gregorian(size_t jdn
,
99 ui32_t
*yp
, ui32_t
*mp
, ui32_t
*dp
);
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 */
106 static struct n_addrguts
*a_head_idna_apply(struct n_addrguts
*agp
);
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
);
133 _from__skipword(char const *wp
)
139 while ((c
= *wp
++) != '\0' && !blankchar(c
)) {
141 while ((c
= *wp
++) != '\0' && c
!= '"')
147 for (; blankchar(c
); c
= *wp
++)
151 return (c
== 0 ? NULL
: wp
- 1);
155 _cmatch(size_t len
, char const *date
, char const *tp
)
180 if (c
!= ' ' && !digitchar(c
))
188 if (c
!= '+' && c
!= '-')
200 _is_date(char const *date
)
202 struct cmatch_data
const *cmdp
;
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
)))
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 */
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) */
280 x
= jdn
/ 153; /* x -> month */
283 jdn
/= 5; /* jdn -> day */
291 *yp
= (ui32_t
)(y
& 0xFFFF);
292 *mp
= (ui32_t
)(x
& 0xFF);
293 *dp
= (ui32_t
)(jdn
& 0xFF);
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
;
307 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
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
);
322 if (idna_utf8
== NULL
) {
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
;
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
;
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
);
344 agp
->ag_skinned
= cs
;
346 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
347 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
349 idn_free(idna_ascii
);
351 if (options
& OPT_UNICODE
)
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
;
369 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
371 idna_in
= ac_alloc(sz
+1);
372 memcpy(idna_in
, agp
->ag_skinned
+ agp
->ag_sdom_start
, sz
);
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
);
381 case idn_buffer_overflow
:
383 case idn_invalid_encoding
:
384 n_err(_("Cannot convert from %s to %s\n"),
385 ok_vlook(ttycharset
), "utf-8");
388 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
392 if (r
== idn_success
)
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
);
406 agp
->ag_skinned
= cs
;
408 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
409 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
417 # endif /* IDNA==IDNKIT */
418 #endif /* HAVE_IDNA */
421 a_head_addrspec_check(struct n_addrguts
*agp
, bool_t skinned
)
425 ui8_t in_domain
, hadat
;
426 union {char c
; unsigned char u
; ui32_t ui32
;} c
;
433 use_idna
= ok_blook(idna_disable
) ? 0 : 1;
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);
443 /* If the field is not a recipient, it cannot be a file or a pipe */
447 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
448 * grep the latter for the complete picture */
450 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
453 if (addr
[0] == '/' || (addr
[0] == '.' && addr
[1] == '/') ||
454 (addr
[0] == '-' && addr
[1] == '\0'))
456 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
459 for (p
= addr
; (c
.c
= *p
); ++p
) {
460 if (c
.c
== '!' || c
.c
== '%')
464 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
472 in_domain
= hadat
= 0;
474 /* TODO addrspec_check: we need a real RFC 5322 parser! */
475 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
477 in_quote
= !in_quote
;
478 } else if (c
.u
< 040 || c
.u
>= 0177) { /* TODO no magics: !bodychar()? */
480 if (in_domain
&& use_idna
> 0) {
482 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_IDNA
,
488 } else if (in_domain
== 2) {
489 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
491 } else if (in_quote
&& in_domain
== 0) {
493 } else if (c
.c
== '\\' && *p
!= '\0') {
495 } else if (c
.c
== '@') {
497 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
501 agp
->ag_sdom_start
= PTR2SIZE(p
- addr
);
502 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
; /* TODO .. really? */
503 in_domain
= (*p
== '[') ? 2 : 1;
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
== ',')
512 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
516 if (!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
))
517 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISNAME
;
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
;
529 size_t rangestart
, lastpoi
;
530 char const *cp
, *cpmax
, *xp
;
534 agp
= a_head_idna_apply(agp
);
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);
543 if((c
.ui32
= agp
->ag_iaddr_start
) > 0)
548 while(cp
< cpmax
&& blankchar(*cp
))
553 for(lastpoi
= UIZ_MAX
; cp
< cpmax
;){
558 if((c
.ui32
= ostp
->s_len
) > 0){
559 while(c
.ui32
> 0 && blankchar(ostp
->s_dat
[c
.ui32
- 1]))
561 ostp
= n_string_trunc(ostp
, c
.ui32
);
565 /* Can we join two comments? */
566 if(ostp
->s_dat
[c
.ui32
] == ')'){
567 ostp
->s_dat
[c
.ui32
] = ' ';
570 /* Otherwise ensure it is separated! */
571 else if(!blankchar(ostp
->s_dat
[c
.ui32
]))
572 ostp
= n_string_push_c(ostp
, ' ');
576 ostp
= n_string_push_c(ostp
, '(');
578 xp
= skip_comment(++cp
);
579 c
.ui32
= (ui32_t
)PTR2SIZE(xp
- cp
);
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])){
591 ostp
= n_string_push_buf(ostp
, cp
, c
.ui32
);
593 ostp
= n_string_push_c(ostp
, ')');
594 lastpoi
= ostp
->s_len
- 1;
602 if((c
.ui32
= ostp
->s_len
) > 0){
603 while(c
.ui32
> 0 && blankchar(ostp
->s_dat
[c
.ui32
- 1]))
605 ostp
= n_string_trunc(ostp
, c
.ui32
);
609 /* Can we join two quotes? */
610 if(ostp
->s_dat
[c
.ui32
] == '"'){
611 ostp
->s_dat
[c
.ui32
] = ' ';
614 /* Otherwise ensure it is separated! */
615 else if(!blankchar(ostp
->s_dat
[c
.ui32
]))
616 ostp
= n_string_push_c(ostp
, ' ');
620 ostp
= n_string_push_c(ostp
, '"');
622 for(; xp
< cpmax
; ++xp
){
623 if((c
.c
= *xp
) == '"')
625 if(c
.c
== '\\' && xp
[1] != '\0')
628 c
.ui32
= (ui32_t
)PTR2SIZE(xp
- cp
);
630 if(xp
!= cpmax
&& *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])){
640 ostp
= n_string_push_buf(ostp
, cp
, c
.ui32
);
642 ostp
= n_string_push_c(ostp
, '"');
643 lastpoi
= ostp
->s_len
- 1;
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);
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
) == '"'){
663 /* End the quote before the whitespace before the comment */
664 for(c
.ui32
= 0; blankchar(cp
[-1 - c
.ui32
]); ++c
.ui32
)
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;
686 if(lastc
== '\0' || !blankchar(c
.c
) || !blankchar(lastc
))
687 ostp
= n_string_push_c(ostp
, lastc
= c
.c
);
695 if((c
.ui32
= ostp
->s_len
) > 0 && !blankchar(ostp
->s_dat
[c
.ui32
- 1])){
696 ostp
= n_string_push_c(ostp
, ' ');
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
])
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
);
712 ostp
= n_string_push_c(ostp
, lastc
= ' ');
713 rangestart
= ostp
->s_len
;
716 }else if((c
.ui32
= ostp
->s_len
) > 0){
717 while(blankchar(ostp
->s_dat
[c
.ui32
- 1]))
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
);
728 return ((agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) != 0);
732 gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
, char **colon
)
734 char *line2
= NULL
, *cp
, *cp2
;
735 size_t line2size
= 0;
739 if (*linebuf
== NULL
)
740 *linebuf
= srealloc(*linebuf
, *linesize
= 1);
747 if ((c
= readline_restart(f
, linebuf
, linesize
, 0)) <= 0) {
751 for (cp
= *linebuf
; fieldnamechar(*cp
); ++cp
)
754 while (blankchar(*cp
))
756 if (*cp
!= ':' || cp
== *linebuf
)
759 /* I guess we got a headline. Handle wraparound */
764 while (PTRCMP(--cp
, >=, *linebuf
) && blankchar(*cp
))
769 if (PTRCMP(cp
- 8, >=, *linebuf
) && cp
[-1] == '=' && cp
[-2] == '?')
771 ungetc(c
= getc(f
), f
);
774 c
= readline_restart(f
, &line2
, &line2size
, 0);
778 for (cp2
= line2
; blankchar(*cp2
); ++cp2
)
780 c
-= (int)PTR2SIZE(cp2
- line2
);
781 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
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
];
806 msgidnextc(char const **cp
, int *status
)
813 assert(status
!= NULL
);
831 *cp
= skip_comment(&(*cp
)[1]);
848 c
= (*status
& 02) ? lowerconv(c
) : c
;
858 charcount(char *str
, int c
)
864 for (i
= 0, cp
= str
; *cp
; ++cp
)
872 nexttoken(char const *cp
)
891 } while (nesting
> 0 && *cp
!= '\0'); /* XXX error? */
892 } else if (blankchar(*cp
) || *cp
== ',')
902 a_head_customhdr__sep(char **iolist
){
904 bool_t isesc
, anyesc
;
907 for(base
= *iolist
; base
!= NULL
; base
= *iolist
){
908 while((c
= *base
) != '\0' && blankspacechar(c
))
911 for(isesc
= anyesc
= FAL0
, cp
= base
;; ++cp
){
912 if(n_UNLIKELY((c
= *cp
) == '\0')){
923 anyesc
|= (c
== ',');
927 while(cp
> base
&& blankspacechar(cp
[-1]))
935 for(ins
= cp
= base
;; ++ins
)
936 if((c
= *cp
) == '\\' && cp
[1] == ','){
939 }else if((*ins
= (++cp
, c
)) == '\0')
950 myaddrs(struct header
*hp
)
953 char const *rv
, *mta
;
956 if (hp
!= NULL
&& (np
= hp
->h_from
) != NULL
) {
957 if ((rv
= np
->n_fullname
) != NULL
)
959 if ((rv
= np
->n_name
) != NULL
)
963 if ((rv
= ok_vlook(from
)) != NULL
)
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
)
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'))
985 i
= strlen(myname
) + strlen(hn
) + 1 +1;
987 sstpcpy(sstpcpy(sstpcpy(cp
, myname
), "@"), hn
);
993 myorigin(struct header
*hp
)
995 char const *rv
= NULL
, *ccp
;
999 if ((ccp
= myaddrs(hp
)) != NULL
&&
1000 (np
= lextract(ccp
, GEXTRA
| GFULL
)) != NULL
)
1001 rv
= (np
->n_flink
!= NULL
) ? ok_vlook(sender
) : ccp
;
1007 is_head(char const *linebuf
, size_t linelen
, bool_t check_rfc4155
)
1009 char date
[FROM_DATEBUF
];
1013 if ((rv
= (linelen
>= 5 && !memcmp(linebuf
, "From ", 5))) && check_rfc4155
&&
1014 (extract_date_from_from_(linebuf
, linelen
, date
) <= 0 ||
1022 extract_date_from_from_(char const *line
, size_t linelen
,
1023 char datebuf
[FROM_DATEBUF
])
1026 char const *cp
= line
;
1032 cp
= _from__skipword(cp
);
1036 cp
= _from__skipword(cp
);
1039 if (cp
[0] == 't' && cp
[1] == 't' && cp
[2] == 'y') {
1040 cp
= _from__skipword(cp
);
1044 /* It seems there are invalid MBOX archives in the wild, compare
1045 * . http://bugs.debian.org/624111
1046 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1047 * What they do is that they obfuscate the address to "name at host",
1048 * and even "name at host dot dom dot dom. I think we should handle that */
1049 else if(cp
[0] == 'a' && cp
[1] == 't' && cp
[2] == ' '){
1053 cp
= _from__skipword(cp
);
1056 if(cp
[0] == 'd' && cp
[1] == 'o' && cp
[2] == 't' && cp
[3] == ' '){
1062 linelen
-= PTR2SIZE(cp
- line
);
1063 if (linelen
< _DATE_MINLEN
)
1065 if (cp
[linelen
- 1] == '\n') {
1067 /* (Rather IMAP/POP3 only) */
1068 if (cp
[linelen
- 1] == '\r')
1070 if (linelen
< _DATE_MINLEN
)
1073 if (linelen
>= FROM_DATEBUF
)
1077 memcpy(datebuf
, cp
, linelen
);
1078 datebuf
[linelen
] = '\0';
1082 cp
= _("<Unknown date>");
1083 linelen
= strlen(cp
);
1084 if (linelen
>= FROM_DATEBUF
)
1085 linelen
= FROM_DATEBUF
;
1091 extract_header(FILE *fp
, struct header
*hp
, si8_t
*checkaddr_err
)
1093 /* See the prototype declaration for the hairy relationship of
1094 * options&OPT_t_FLAG and/or pstate&PS_t_FLAG in here */
1095 struct n_header_field
**hftail
;
1096 struct header nh
, *hq
= &nh
;
1097 char *linebuf
= NULL
/* TODO line pool */, *colon
;
1098 size_t linesize
= 0, seenfields
= 0;
1100 char const *val
, *cp
;
1103 memset(hq
, 0, sizeof *hq
);
1104 if ((pstate
& PS_t_FLAG
) && (options
& OPT_t_FLAG
)) {
1105 hq
->h_to
= hp
->h_to
;
1106 hq
->h_cc
= hp
->h_cc
;
1107 hq
->h_bcc
= hp
->h_bcc
;
1109 hftail
= &hq
->h_user_headers
;
1111 for (lc
= 0; readline_restart(fp
, &linebuf
, &linesize
, 0) > 0; ++lc
)
1114 /* TODO yippieia, cat(check(lextract)) :-) */
1116 while ((lc
= gethfield(fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
1119 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1120 * yet expanded when we parse these! */
1121 if ((val
= thisfield(linebuf
, "to")) != NULL
) {
1123 hq
->h_to
= cat(hq
->h_to
, checkaddrs(lextract(val
, GTO
| GFULL
),
1124 EACM_NORMAL
| EAF_NAME
, checkaddr_err
));
1125 } else if ((val
= thisfield(linebuf
, "cc")) != NULL
) {
1127 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(lextract(val
, GCC
| GFULL
),
1128 EACM_NORMAL
| EAF_NAME
, checkaddr_err
));
1129 } else if ((val
= thisfield(linebuf
, "bcc")) != NULL
) {
1131 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(lextract(val
, GBCC
| GFULL
),
1132 EACM_NORMAL
| EAF_NAME
, checkaddr_err
));
1133 } else if ((val
= thisfield(linebuf
, "from")) != NULL
) {
1134 if (!(pstate
& PS_t_FLAG
) || (options
& OPT_t_FLAG
)) {
1136 hq
->h_from
= cat(hq
->h_from
,
1137 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
1138 EACM_STRICT
, NULL
));
1140 } else if ((val
= thisfield(linebuf
, "reply-to")) != NULL
) {
1142 hq
->h_replyto
= cat(hq
->h_replyto
,
1143 checkaddrs(lextract(val
, GEXTRA
| GFULL
), EACM_STRICT
, NULL
));
1144 } else if ((val
= thisfield(linebuf
, "sender")) != NULL
) {
1145 if (!(pstate
& PS_t_FLAG
) || (options
& OPT_t_FLAG
)) {
1147 hq
->h_sender
= cat(hq
->h_sender
, /* TODO cat? check! */
1148 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
1149 EACM_STRICT
, NULL
));
1152 } else if ((val
= thisfield(linebuf
, "subject")) != NULL
||
1153 (val
= thisfield(linebuf
, "subj")) != NULL
) {
1155 for (cp
= val
; blankchar(*cp
); ++cp
)
1157 hq
->h_subject
= (hq
->h_subject
!= NULL
)
1158 ? save2str(hq
->h_subject
, cp
) : savestr(cp
);
1160 /* The remaining are mostly hacked in and thus TODO -- at least in
1161 * TODO respect to their content checking */
1162 else if((val
= thisfield(linebuf
, "message-id")) != NULL
){
1163 if(pstate
& PS_t_FLAG
){
1164 np
= checkaddrs(lextract(val
, GREF
),
1165 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1167 if (np
== NULL
|| np
->n_flink
!= NULL
)
1170 hq
->h_message_id
= np
;
1173 }else if((val
= thisfield(linebuf
, "in-reply-to")) != NULL
){
1174 if(pstate
& PS_t_FLAG
){
1175 np
= checkaddrs(lextract(val
, GREF
),
1176 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1179 hq
->h_in_reply_to
= np
;
1182 }else if((val
= thisfield(linebuf
, "references")) != NULL
){
1183 if(pstate
& PS_t_FLAG
){
1185 /* TODO Limit number of references TODO better on parser side */
1186 hq
->h_ref
= cat(hq
->h_ref
, checkaddrs(extract(val
, GREF
),
1187 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1192 /* and that is very hairy */
1193 else if((val
= thisfield(linebuf
, "mail-followup-to")) != NULL
){
1194 if(pstate
& PS_t_FLAG
){
1196 hq
->h_mft
= cat(hq
->h_mft
, checkaddrs(lextract(val
, GEXTRA
| GFULL
),
1197 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME
,
1202 /* A free-form user header; gethfield() did some verification already.. */
1204 struct n_header_field
*hfp
;
1208 for(nstart
= cp
= linebuf
;; ++cp
)
1209 if(!fieldnamechar(*cp
))
1211 nl
= (ui32_t
)PTR2SIZE(cp
- nstart
);
1213 while(blankchar(*cp
))
1217 n_err(_("Ignoring header field: %s\n"), linebuf
);
1220 while(blankchar(*cp
))
1222 bl
= (ui32_t
)strlen(cp
) +1;
1225 *hftail
= hfp
= salloc(n_VSTRUCT_SIZEOF(struct n_header_field
, hf_dat
1227 hftail
= &hfp
->hf_next
;
1228 hfp
->hf_next
= NULL
;
1230 hfp
->hf_bl
= bl
- 1;
1231 memcpy(hfp
->hf_dat
, nstart
, nl
);
1232 hfp
->hf_dat
[nl
++] = '\0';
1233 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);
1237 /* In case the blank line after the header has been edited out. Otherwise,
1238 * fetch the header separator */
1239 if (linebuf
!= NULL
) {
1240 if (linebuf
[0] != '\0') {
1241 for (cp
= linebuf
; *(++cp
) != '\0';)
1243 fseek(fp
, (long)-PTR2SIZE(1 + cp
- linebuf
), SEEK_CUR
);
1245 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
1250 if (seenfields
> 0 && (checkaddr_err
== NULL
|| *checkaddr_err
== 0)) {
1251 hp
->h_to
= hq
->h_to
;
1252 hp
->h_cc
= hq
->h_cc
;
1253 hp
->h_bcc
= hq
->h_bcc
;
1254 hp
->h_from
= hq
->h_from
;
1255 hp
->h_replyto
= hq
->h_replyto
;
1256 hp
->h_sender
= hq
->h_sender
;
1257 if (hq
->h_subject
!= NULL
|| !(pstate
& PS_t_FLAG
) ||
1258 !(options
& OPT_t_FLAG
))
1259 hp
->h_subject
= hq
->h_subject
;
1260 hp
->h_user_headers
= hq
->h_user_headers
;
1262 if (pstate
& PS_t_FLAG
) {
1263 hp
->h_ref
= hq
->h_ref
;
1264 hp
->h_message_id
= hq
->h_message_id
;
1265 hp
->h_in_reply_to
= hq
->h_in_reply_to
;
1266 hp
->h_mft
= hq
->h_mft
;
1268 /* And perform additional validity checks so that we don't bail later
1269 * on TODO this is good and the place where this should occur,
1270 * TODO unfortunately a lot of other places do again and blabla */
1271 if (pstate
& PS_t_FLAG
) {
1272 if (hp
->h_from
== NULL
)
1273 hp
->h_from
= option_r_arg
;
1274 else if (hp
->h_from
->n_flink
!= NULL
&& hp
->h_sender
== NULL
)
1275 hp
->h_sender
= lextract(ok_vlook(sender
),
1276 GEXTRA
| GFULL
| GFULLEXTRA
);
1280 n_err(_("Restoring deleted header lines\n"));
1282 if (linebuf
!= NULL
)
1288 hfield_mult(char const *field
, struct message
*mp
, int mult
)
1293 size_t linesize
= 0; /* TODO line pool */
1294 char *linebuf
= NULL
, *colon
;
1298 /* There are (spam) messages which have header bytes which are many KB when
1299 * joined, so resize a single heap storage until we are done if we shall
1300 * collect a field that may have multiple bodies; only otherwise use the
1301 * string dope directly */
1302 memset(&hfs
, 0, sizeof hfs
);
1304 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1306 if ((lc
= mp
->m_lines
- 1) < 0)
1309 if ((mp
->m_flag
& MNOFROM
) == 0 &&
1310 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1313 if ((lc
= gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
)) < 0)
1315 if ((hfield
= thisfield(linebuf
, field
)) != NULL
&& *hfield
!= '\0') {
1317 n_str_add_buf(&hfs
, hfield
, strlen(hfield
));
1319 hfs
.s
= savestr(hfield
);
1326 if (linebuf
!= NULL
)
1328 if (mult
&& hfs
.s
!= NULL
) {
1329 colon
= savestrbuf(hfs
.s
, hfs
.l
);
1338 thisfield(char const *linebuf
, char const *field
)
1340 char const *rv
= NULL
;
1343 while (lowerconv(*linebuf
) == lowerconv(*field
)) {
1350 while (blankchar(*linebuf
))
1352 if (*linebuf
++ != ':')
1355 while (blankchar(*linebuf
)) /* TODO header parser.. strip trailing WS?!? */
1364 nameof(struct message
*mp
, int reptype
)
1369 cp
= skin(name1(mp
, reptype
));
1370 if (reptype
!= 0 || charcount(cp
, '!') < 2)
1372 cp2
= strrchr(cp
, '!');
1374 while (cp2
> cp
&& *cp2
!= '!')
1384 skip_comment(char const *cp
)
1389 for (nesting
= 1; nesting
> 0 && *cp
; ++cp
) {
1408 routeaddr(char const *name
)
1410 char const *np
, *rp
= NULL
;
1413 for (np
= name
; *np
; np
++) {
1416 np
= skip_comment(np
+ 1) - 1;
1422 if (*np
== '\\' && np
[1])
1439 FL
enum expand_addr_flags
1440 expandaddr_to_eaf(void)
1443 char const *eafd_name
;
1444 bool_t eafd_is_target
;
1448 {"restrict", FAL0
, EAF_TARGET_MASK
, EAF_RESTRICT
| EAF_RESTRICT_TARGETS
},
1449 {"fail", FAL0
, EAF_NONE
, EAF_FAIL
},
1450 {"failinvaddr", FAL0
, EAF_NONE
, EAF_FAILINVADDR
| EAF_ADDR
},
1451 {"all", TRU1
, EAF_NONE
, EAF_TARGET_MASK
},
1452 {"file", TRU1
, EAF_NONE
, EAF_FILE
},
1453 {"pipe", TRU1
, EAF_NONE
, EAF_PIPE
},
1454 {"name", TRU1
, EAF_NONE
, EAF_NAME
},
1455 {"addr", TRU1
, EAF_NONE
, EAF_ADDR
}
1459 enum expand_addr_flags rv
;
1463 if ((cp
= ok_vlook(expandaddr
)) == NULL
)
1464 rv
= EAF_RESTRICT_TARGETS
;
1465 else if (*cp
== '\0')
1466 rv
= EAF_TARGET_MASK
;
1468 rv
= EAF_TARGET_MASK
;
1470 for (buf
= savestr(cp
); (cp
= n_strsep(&buf
, ',', TRU1
)) != NULL
;) {
1473 if ((minus
= (*cp
== '-')) || *cp
== '+')
1475 for (eafp
= eafa
;; ++eafp
) {
1476 if (eafp
== eafa
+ n_NELEM(eafa
)) {
1477 if (options
& OPT_D_V
)
1478 n_err(_("Unknown *expandaddr* value: %s\n"), cp
);
1480 } else if (!asccasecmp(cp
, eafp
->eafd_name
)) {
1482 rv
&= ~eafp
->eafd_andoff
;
1483 rv
|= eafp
->eafd_or
;
1485 if (eafp
->eafd_is_target
)
1486 rv
&= ~eafp
->eafd_or
;
1487 else if (options
& OPT_D_V
)
1488 n_err(_("minus - prefix invalid for *expandaddr* value: "
1492 } else if (!asccasecmp(cp
, "noalias")) { /* TODO v15 OBSOLETE */
1493 OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1500 if ((rv
& EAF_RESTRICT
) && (options
& (OPT_INTERACTIVE
| OPT_TILDE_FLAG
)))
1501 rv
|= EAF_TARGET_MASK
;
1502 else if (options
& OPT_D_V
) {
1503 if (!(rv
& EAF_TARGET_MASK
))
1504 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1505 else if ((rv
& EAF_FAIL
) && (rv
& EAF_TARGET_MASK
) == EAF_TARGET_MASK
)
1506 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1514 is_addr_invalid(struct name
*np
, enum expand_addr_check_mode eacm
)
1516 char cbuf
[sizeof "'\\U12340'"];
1520 enum expand_addr_flags eaf
;
1523 eaf
= expandaddr_to_eaf();
1526 if ((rv
= ((f
& NAME_ADDRSPEC_INVALID
) != 0))) {
1527 if (eaf
& EAF_FAILINVADDR
)
1530 if ((eacm
& EACM_NOLOG
) || (f
& NAME_ADDRSPEC_ERR_EMPTY
)) {
1534 char const *fmt
= "'\\x%02X'";
1535 bool_t ok8bit
= TRU1
;
1537 if (f
& NAME_ADDRSPEC_ERR_IDNA
) {
1538 cs
= _("Invalid domain name: %s, character %s\n");
1541 } else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
1542 cs
= _("%s contains invalid %s sequence\n");
1544 cs
= _("%s contains invalid non-ASCII byte %s\n");
1546 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
1547 snprintf(cbuf
, sizeof cbuf
,
1548 (ok8bit
&& c
>= 040 && c
<= 0177 ? "'%c'" : fmt
), c
);
1554 /* *expandaddr* stuff */
1555 if (!(rv
= ((eacm
& EACM_MODE_MASK
) != EACM_NONE
)))
1558 if ((eacm
& EACM_STRICT
) && (f
& NAME_ADDRSPEC_ISFILEORPIPE
)) {
1561 cs
= _("%s%s: file or pipe addressees not allowed here\n");
1562 if (eacm
& EACM_NOLOG
)
1568 eaf
|= (eacm
& EAF_TARGET_MASK
);
1569 if (eacm
& EACM_NONAME
)
1572 if (eaf
== EAF_NONE
) {
1579 if (!(eaf
& EAF_FILE
) && (f
& NAME_ADDRSPEC_ISFILE
)) {
1580 cs
= _("%s%s: *expandaddr* doesn't allow file target\n");
1581 if (eacm
& EACM_NOLOG
)
1583 } else if (!(eaf
& EAF_PIPE
) && (f
& NAME_ADDRSPEC_ISPIPE
)) {
1584 cs
= _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1585 if (eacm
& EACM_NOLOG
)
1587 } else if (!(eaf
& EAF_NAME
) && (f
& NAME_ADDRSPEC_ISNAME
)) {
1588 cs
= _("%s%s: *expandaddr* doesn't allow user name target\n");
1589 if (eacm
& EACM_NOLOG
)
1591 } else if (!(eaf
& EAF_ADDR
) && (f
& NAME_ADDRSPEC_ISADDR
)) {
1592 cs
= _("%s%s: *expandaddr* doesn't allow mail address target\n");
1593 if (eacm
& EACM_NOLOG
)
1603 n_err(cs
, n_shexp_quote_cp(np
->n_name
, TRU1
), cbuf
);
1610 skin(char const *name
)
1612 struct n_addrguts ag
;
1617 name
= n_addrspec_with_guts(&ag
,name
, TRU1
);
1619 if(!(ag
.ag_n_flags
& NAME_NAME_SALLOC
))
1620 rv
= savestrbuf(rv
, ag
.ag_slen
);
1627 /* TODO addrspec_with_guts: RFC 5322
1628 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1630 n_addrspec_with_guts(struct n_addrguts
*agp
, char const *name
, bool_t doskin
){
1632 char *cp2
, *bufend
, *nbuf
, c
;
1642 memset(agp
, 0, sizeof *agp
);
1644 if((agp
->ag_input
= name
) == NULL
|| (agp
->ag_ilen
= strlen(name
)) == 0){
1645 agp
->ag_skinned
= n_UNCONST(n_empty
); /* ok: NAME_SALLOC is not set */
1647 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
1648 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
1651 /*agp->ag_iaddr_start = 0;*/
1652 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1653 agp
->ag_skinned
= n_UNCONST(name
); /* (NAME_SALLOC not set) */
1654 agp
->ag_slen
= agp
->ag_ilen
;
1655 agp
->ag_n_flags
= NAME_SKINNED
;
1660 nbuf
= n_lofi_alloc(agp
->ag_ilen
+1);
1661 /*agp->ag_iaddr_start = 0;*/
1662 cp2
= bufend
= nbuf
;
1664 for(cp
= name
++; (c
= *cp
++) != '\0';){
1667 cp
= skip_comment(cp
);
1671 /* Start of a "quoted-string". Copy it in its entirety */
1672 /* XXX RFC: quotes are "semantically invisible"
1673 * XXX But it was explicitly added (Changelog.Heirloom,
1674 * XXX [9.23] released 11/15/00, "Do not remove quotes
1675 * XXX when skinning names"? No more info.. */
1677 while ((c
= *cp
) != '\0') { /* TODO improve */
1685 else if ((c
= *cp
) != '\0') {
1694 if((flags
& (a_GOTADDR
| a_GOTSPACE
)) == a_GOTADDR
){
1695 flags
|= a_GOTADDR
| a_GOTSPACE
;
1696 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1698 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
1699 cp
+= 3, *cp2
++ = '@';
1700 else if (cp
[0] == '@' && blankchar(cp
[1]))
1701 cp
+= 2, *cp2
++ = '@';
1706 agp
->ag_iaddr_start
= PTR2SIZE(cp
- (name
- 1));
1708 flags
&= ~(a_GOTSPACE
| a_LASTSP
);
1709 flags
|= a_GOTLT
| a_GOTADDR
;
1712 if(flags
& a_GOTLT
){
1713 /* (_addrspec_check() verifies these later!) */
1714 flags
&= ~(a_GOTLT
| a_LASTSP
);
1715 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1717 /* Skip over the entire remaining field */
1718 while((c
= *cp
) != '\0' && c
!= ','){
1721 cp
= skip_comment(cp
);
1723 while ((c
= *cp
) != '\0') {
1727 if (c
== '\\' && *cp
!= '\0')
1735 if(flags
& a_LASTSP
){
1737 if(flags
& a_GOTADDR
)
1742 if(!(flags
& a_GOTLT
)){
1744 for(; blankchar(*cp
); ++cp
)
1749 }else if(!(flags
& a_GOTADDR
)){
1751 agp
->ag_iaddr_start
= PTR2SIZE(cp
- name
);
1756 agp
->ag_slen
= PTR2SIZE(cp2
- nbuf
);
1757 if (agp
->ag_iaddr_aend
== 0)
1758 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1759 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
1761 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
1763 if(a_head_addrspec_check(agp
, doskin
) <= 0)
1766 name
= agp
->ag_input
;
1773 realname(char const *name
)
1775 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
1778 int quoted
, good
, nogood
;
1781 if ((cp
= n_UNCONST(name
)) == NULL
)
1783 for (; *cp
!= '\0'; ++cp
) {
1786 if (cstart
!= NULL
) {
1787 /* More than one comment in address, doesn't make sense to display
1788 * it without context. Return the entire field */
1789 cp
= mime_fromaddr(name
);
1793 cp
= skip_comment(cp
);
1796 cend
= cstart
= NULL
;
1802 if (*cp
== '\\' && cp
[1])
1813 /* More than one address. Just use the first one */
1819 if (cstart
== NULL
) {
1821 /* If name contains only a route-addr, the surrounding angle brackets
1822 * don't serve any useful purpose when displaying, so remove */
1823 cp
= prstr(skin(name
));
1825 cp
= mime_fromaddr(name
);
1829 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1830 * not stripped. The idea is to strip only syntactical relevant things (but
1831 * this is not necessarily the most sensible way in practice) */
1832 rp
= rname
= ac_alloc(PTR2SIZE(cend
- cstart
+1));
1834 for (cp
= cstart
; cp
< cend
; ++cp
) {
1835 if (*cp
== '(' && !quoted
) {
1836 cq
= skip_comment(++cp
);
1837 if (PTRCMP(--cq
, >, cend
))
1840 if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cq
))
1844 } else if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cend
))
1846 else if (*cp
== '"') {
1855 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
1857 rname
= savestr(out
.s
);
1860 while (blankchar(*rname
))
1862 for (rp
= rname
; *rp
!= '\0'; ++rp
)
1864 while (PTRCMP(--rp
, >=, rname
) && blankchar(*rp
))
1867 cp
= mime_fromaddr(name
);
1871 /* mime_fromhdr() has converted all nonprintable characters to question
1872 * marks now. These and blanks are considered uninteresting; if the
1873 * displayed part of the real name contains more than 25% of them, it is
1874 * probably better to display the plain email address instead */
1877 for (rp
= rname
; *rp
!= '\0' && PTRCMP(rp
, <, rname
+ 20); ++rp
)
1878 if (*rp
== '?' || blankchar(*rp
))
1882 cp
= (good
* 3 < nogood
) ? prstr(skin(name
)) : rname
;
1885 return n_UNCONST(cp
);
1889 name1(struct message
*mp
, int reptype
)
1891 char *namebuf
, *cp
, *cp2
, *linebuf
= NULL
/* TODO line pool */;
1892 size_t namesize
, linesize
= 0;
1897 if ((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
1899 if (reptype
== 0 && (cp
= hfield1("sender", mp
)) != NULL
&& *cp
!= '\0')
1902 namebuf
= smalloc(namesize
= 1);
1904 if (mp
->m_flag
& MNOFROM
)
1906 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1908 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1912 if (namesize
<= linesize
)
1913 namebuf
= srealloc(namebuf
, namesize
= linesize
+1);
1914 for (cp
= linebuf
; *cp
!= '\0' && *cp
!= ' '; ++cp
)
1916 for (; blankchar(*cp
); ++cp
)
1918 for (cp2
= namebuf
+ strlen(namebuf
);
1919 *cp
&& !blankchar(*cp
) && PTRCMP(cp2
, <, namebuf
+ namesize
-1);)
1923 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1925 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
1927 if (strncmp(cp
, "From", 4))
1929 if (namesize
<= linesize
)
1930 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1932 while ((cp
= strchr(cp
, 'r')) != NULL
) {
1933 if (!strncmp(cp
, "remote", 6)) {
1934 if ((cp
= strchr(cp
, 'f')) == NULL
)
1936 if (strncmp(cp
, "from", 4) != 0)
1938 if ((cp
= strchr(cp
, ' ')) == NULL
)
1942 strncpy(namebuf
, cp
, namesize
);
1945 cp2
= strrchr(namebuf
, '!') + 1;
1946 strncpy(cp2
, cp
, PTR2SIZE(namebuf
+ namesize
- cp2
));
1948 namebuf
[namesize
- 2] = '!';
1949 namebuf
[namesize
- 1] = '\0';
1955 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
1957 cp
= savestr(namebuf
);
1959 if (linebuf
!= NULL
)
1968 subject_re_trim(char *s
)
1973 } const *pp
, ignored
[] = { /* Update *reply-strings* manual upon change! */
1975 { 3, "aw:" }, { 5, "antw:" }, /* de */
1980 char *orig_s
= s
, *re_st
= NULL
, *re_st_x
;
1981 size_t re_l
= 0 /* pacify CC */;
1984 if ((re_st_x
= ok_vlook(reply_strings
)) != NULL
&&
1985 (re_l
= strlen(re_st_x
)) > 0) {
1986 re_st
= ac_alloc(++re_l
* 2);
1987 memcpy(re_st
, re_st_x
, re_l
);
1991 while (*s
!= '\0') {
1992 while (spacechar(*s
))
1995 for (pp
= ignored
; pp
->len
> 0; ++pp
)
1996 if (is_asccaseprefix(s
, pp
->dat
)) {
2002 if (re_st
!= NULL
) {
2005 memcpy(re_st_x
= re_st
+ re_l
, re_st
, re_l
);
2006 while ((cp
= n_strsep(&re_st_x
, ',', TRU1
)) != NULL
)
2007 if (is_asccaseprefix(s
, cp
)) {
2019 return any
? s
: orig_s
;
2023 msgidcmp(char const *s1
, char const *s2
)
2025 int q1
= 0, q2
= 0, c1
, c2
;
2034 c1
= msgidnextc(&s1
, &q1
);
2035 c2
= msgidnextc(&s2
, &q2
);
2044 fakefrom(struct message
*mp
)
2049 if (((name
= skin(hfield1("return-path", mp
))) == NULL
|| *name
== '\0' ) &&
2050 ((name
= skin(hfield1("from", mp
))) == NULL
|| *name
== '\0'))
2051 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2052 * RFC 4155 however requires a RFC 5322 (2822) conforming
2053 * "addr-spec", but we simply can't provide that */
2054 name
= "MAILER-DAEMON";
2066 for (cq
= cp
; *cq
!= '\0' && *cq
!= '\n'; ++cq
)
2074 #ifdef HAVE_IMAP_SEARCH
2076 unixtime(char const *fromline
)
2081 int i
, year
, month
, day
, hour
, minute
, second
, tzdiff
;
2085 for (fp
= fromline
; *fp
!= '\0' && *fp
!= '\n'; ++fp
)
2088 if (PTR2SIZE(fp
- fromline
) < 7)
2093 if (!strncmp(fp
+ 4, month_names
[i
], 3))
2095 if (month_names
[++i
][0] == '\0')
2101 day
= strtol(fp
+ 8, &xp
, 10);
2102 if (*xp
!= ' ' || xp
!= fp
+ 10)
2104 hour
= strtol(fp
+ 11, &xp
, 10);
2105 if (*xp
!= ':' || xp
!= fp
+ 13)
2107 minute
= strtol(fp
+ 14, &xp
, 10);
2108 if (*xp
!= ':' || xp
!= fp
+ 16)
2110 second
= strtol(fp
+ 17, &xp
, 10);
2111 if (*xp
!= ' ' || xp
!= fp
+ 19)
2113 year
= strtol(fp
+ 20, &xp
, 10);
2116 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
2118 tzdiff
= t
- mktime(gmtime(&t
));
2119 tmptr
= localtime(&t
);
2120 if (tmptr
->tm_isdst
> 0)
2130 #endif /* HAVE_IMAP_SEARCH */
2133 rfctime(char const *date
)
2135 char const *cp
= date
;
2138 int i
, year
, month
, day
, hour
, minute
, second
;
2141 if ((cp
= nexttoken(cp
)) == NULL
)
2143 if (alphachar(cp
[0]) && alphachar(cp
[1]) && alphachar(cp
[2]) &&
2145 if ((cp
= nexttoken(&cp
[4])) == NULL
)
2148 day
= strtol(cp
, &x
, 10); /* XXX strtol */
2149 if ((cp
= nexttoken(x
)) == NULL
)
2152 if (!strncmp(cp
, month_names
[i
], 3))
2154 if (month_names
[++i
][0] == '\0')
2158 if ((cp
= nexttoken(&cp
[3])) == NULL
)
2161 * Where a two or three digit year occurs in a date, the year is to be
2162 * interpreted as follows: If a two digit year is encountered whose
2163 * value is between 00 and 49, the year is interpreted by adding 2000,
2164 * ending up with a value between 2000 and 2049. If a two digit year
2165 * is encountered with a value between 50 and 99, or any three digit
2166 * year is encountered, the year is interpreted by adding 1900 */
2167 year
= strtol(cp
, &x
, 10); /* XXX strtol */
2168 i
= (int)PTR2SIZE(x
- cp
);
2169 if (i
== 2 && year
>= 0 && year
<= 49)
2171 else if (i
== 3 || (i
== 2 && year
>= 50 && year
<= 99))
2173 if ((cp
= nexttoken(x
)) == NULL
)
2175 hour
= strtol(cp
, &x
, 10); /* XXX strtol */
2179 minute
= strtol(cp
, &x
, 10);
2182 second
= strtol(cp
, &x
, 10);
2186 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
2188 if ((cp
= nexttoken(x
)) != NULL
) {
2200 if (digitchar(cp
[0]) && digitchar(cp
[1]) && digitchar(cp
[2]) &&
2206 tadj
= strtol(buf
, NULL
, 10) * 3600;/*XXX strtrol*/
2209 tadj
+= strtol(buf
, NULL
, 10) * 60; /* XXX strtol*/
2214 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2215 * TODO once again, Christos Zoulas and NetBSD Mail have done
2216 * TODO a really good job already, but using strptime(3), which
2217 * TODO is not portable. Nonetheless, WE must improve, not
2218 * TODO at last because we simply ignore obsolete timezones!!
2219 * TODO See RFC 5322, 4.3! */
2230 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
){
2231 size_t const jdn_epoch
= 2440588;
2232 bool_t
const y2038p
= (sizeof(time_t) == 4);
2238 if(UICMP(32, second
, >=, DATE_SECSMIN
) || /* XXX (leap- */
2239 UICMP(32, minute
, >=, DATE_MINSHOUR
) ||
2240 UICMP(32, hour
, >=, DATE_HOURSDAY
) ||
2241 day
< 1 || day
> 31 ||
2242 month
< 1 || month
> 12 ||
2246 if(year
>= 1970 + ((y2038p
? SI32_MAX
: SI64_MAX
) /
2247 (DATE_SECSDAY
* DATE_DAYSYEAR
))){
2248 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2249 * test by stepping second-wise around the flip. Don't care otherwise */
2252 if(year
> 2038 || month
> 1 || day
> 19 ||
2253 hour
> 3 || minute
> 14 || second
> 7)
2258 t
+= minute
* DATE_SECSMIN
;
2259 t
+= hour
* DATE_SECSHOUR
;
2261 jdn
= a_head_gregorian_to_jdn(year
, month
, day
);
2263 t
+= (time_t)jdn
* DATE_SECSDAY
;
2273 substdate(struct message
*m
)
2278 /* Determine the date to print in faked 'From ' lines. This is traditionally
2279 * the date the message was written to the mail file. Try to determine this
2280 * using RFC message header fields, or fall back to current time */
2281 if ((cp
= hfield1("received", m
)) != NULL
) {
2282 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
2285 while (alnumchar(*cp
));
2288 m
->m_time
= rfctime(cp
);
2290 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
) {
2291 if ((cp
= hfield1("date", m
)) != NULL
)
2292 m
->m_time
= rfctime(cp
);
2294 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
)
2295 m
->m_time
= time_current
.tc_time
;
2300 setup_from_and_sender(struct header
*hp
)
2306 /* If -t parsed or composed From: then take it. With -t we otherwise
2307 * want -r to be honoured in favour of *from* in order to have
2308 * a behaviour that is compatible with what users would expect from e.g.
2310 if ((np
= hp
->h_from
) != NULL
||
2311 ((pstate
& PS_t_FLAG
) && (np
= option_r_arg
) != NULL
)) {
2313 } else if ((addr
= myaddrs(hp
)) != NULL
)
2314 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
2317 if ((np
= hp
->h_sender
) != NULL
) {
2319 } else if ((addr
= ok_vlook(sender
)) != NULL
)
2320 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
2326 FL
struct name
const *
2327 check_from_and_sender(struct name
const *fromfield
,
2328 struct name
const *senderfield
)
2330 struct name
const *rv
= NULL
;
2333 if (senderfield
!= NULL
) {
2334 if (senderfield
->n_flink
!= NULL
) {
2335 n_err(_("The Sender: field may contain only one address\n"));
2341 if (fromfield
!= NULL
) {
2342 if (fromfield
->n_flink
!= NULL
&& senderfield
== NULL
) {
2343 n_err(_("A Sender: is required when there are multiple "
2344 "addresses in From:\n"));
2352 rv
= (struct name
*)0x1;
2360 getsender(struct message
*mp
)
2366 if ((cp
= hfield1("from", mp
)) == NULL
||
2367 (np
= lextract(cp
, GEXTRA
| GSKIN
)) == NULL
)
2370 cp
= (np
->n_flink
!= NULL
) ? skin(hfield1("sender", mp
)) : np
->n_name
;
2377 grab_headers(enum n_lexinput_flags lif
, struct header
*hp
, enum gfield gflags
,
2380 /* TODO grab_headers: again, check counts etc. against RFC;
2381 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2387 comma
= (ok_blook(bsdcompat
) || ok_blook(bsdmsgs
)) ? 0 : GCOMMA
;
2390 hp
->h_to
= grab_names(lif
, "To: ", hp
->h_to
, comma
, GTO
| GFULL
);
2391 if (subjfirst
&& (gflags
& GSUBJECT
))
2392 hp
->h_subject
= n_lex_input_cp(lif
, "Subject: ", hp
->h_subject
);
2394 hp
->h_cc
= grab_names(lif
, "Cc: ", hp
->h_cc
, comma
, GCC
| GFULL
);
2396 hp
->h_bcc
= grab_names(lif
, "Bcc: ", hp
->h_bcc
, comma
, GBCC
| GFULL
);
2398 if (gflags
& GEXTRA
) {
2399 if (hp
->h_from
== NULL
)
2400 hp
->h_from
= lextract(myaddrs(hp
), GEXTRA
| GFULL
| GFULLEXTRA
);
2401 hp
->h_from
= grab_names(lif
, "From: ", hp
->h_from
, comma
,
2402 GEXTRA
| GFULL
| GFULLEXTRA
);
2403 if (hp
->h_replyto
== NULL
)
2404 hp
->h_replyto
= lextract(ok_vlook(replyto
), GEXTRA
| GFULL
);
2405 hp
->h_replyto
= grab_names(lif
, "Reply-To: ", hp
->h_replyto
, comma
,
2407 if (hp
->h_sender
== NULL
)
2408 hp
->h_sender
= extract(ok_vlook(sender
), GEXTRA
| GFULL
);
2409 hp
->h_sender
= grab_names(lif
, "Sender: ", hp
->h_sender
, comma
,
2413 if (!subjfirst
&& (gflags
& GSUBJECT
))
2414 hp
->h_subject
= n_lex_input_cp(lif
, "Subject: ", hp
->h_subject
);
2421 header_match(struct message
*mp
, struct search_expr
const *sep
)
2426 size_t linesize
= 0; /* TODO line pool */
2427 char *linebuf
= NULL
, *colon
;
2431 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
2433 if ((lc
= mp
->m_lines
- 1) < 0)
2436 if ((mp
->m_flag
& MNOFROM
) == 0 &&
2437 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
2440 if (gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
) <= 0)
2442 if (blankchar(*++colon
))
2444 in
.l
= strlen(in
.s
= colon
);
2445 mime_fromhdr(&in
, &out
, TD_ICONV
);
2447 if (sep
->ss_sexpr
== NULL
)
2448 rv
= (regexec(&sep
->ss_regex
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
2451 rv
= substr(out
.s
, sep
->ss_sexpr
);
2458 if (linebuf
!= NULL
)
2464 FL
struct n_header_field
*
2465 n_customhdr_query(void){
2467 struct n_header_field
*rv
, **tail
, *hfp
;
2472 if((vp
= ok_vlook(customhdr
)) != NULL
){
2478 while((vp
= a_head_customhdr__sep(&buf
)) != NULL
){
2480 char const *nstart
, *cp
;
2482 for(nstart
= cp
= vp
;; ++cp
){
2483 if(fieldnamechar(*cp
))
2487 n_err(_("Invalid nameless *customhdr* entry\n"));
2490 }else if(*cp
!= ':' && !blankchar(*cp
)){
2492 n_err(_("Invalid *customhdr* entry: %s\n"), vp
);
2497 nl
= (ui32_t
)PTR2SIZE(cp
- nstart
);
2499 while(blankchar(*cp
))
2503 while(blankchar(*cp
))
2505 bl
= (ui32_t
)strlen(cp
) +1;
2508 hfp
= salloc(n_VSTRUCT_SIZEOF(struct n_header_field
, hf_dat
) +
2510 tail
= &hfp
->hf_next
;
2511 hfp
->hf_next
= NULL
;
2513 hfp
->hf_bl
= bl
- 1;
2514 memcpy(hfp
->hf_dat
, nstart
, nl
);
2515 hfp
->hf_dat
[nl
++] = '\0';
2516 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);