1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #ifndef HAVE_AMALGAMATION
43 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
45 # include <idn-free.h>
46 # include <stringprep.h>
47 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT
53 size_t tlen
; /* Length of .tdata */
54 char const *tdata
; /* Template date - see _cmatch_data[] */
57 /* Template characters for cmatch_data.tdata:
58 * 'A' An upper case char
59 * 'a' A lower case char
62 * 'O' An optional digit or space
64 * '+' Either a plus or a minus sign */
65 static struct cmatch_data
const _cmatch_data
[] = {
66 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
67 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
68 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
69 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
70 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
71 * in the wild (by UW-imap) */
72 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
73 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
74 * zone strings; note that 1. is strictly speaking not correct as some
75 * letters are not used, and 2. is not because only "UT" is defined */
76 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
77 { 28 - 2, __reuse
}, { 28 - 1, __reuse
}, { 28 - 0, __reuse
},
80 #define _DATE_MINLEN 21
82 /* Skip over "word" as found in From_ line */
83 static char const * _from__skipword(char const *wp
);
85 /* Match the date string against the date template (tp), return if match.
86 * See _cmatch_data[] for template character description */
87 static int _cmatch(size_t len
, char const *date
,
90 /* Check wether date is a valid 'From_' date.
91 * (Rather ctime(3) generated dates, according to RFC 4155) */
92 static int _is_date(char const *date
);
94 /* Convert the domain part of a skinned address to IDNA.
95 * If an error occurs before Unicode information is available, revert the IDNA
96 * error to a normal CHAR one so that the error message doesn't talk Unicode */
98 static struct addrguts
* _idna_apply(struct addrguts
*agp
);
101 /* Classify and check a (possibly skinned) header body according to RFC
102 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
103 * also a file or a pipe command, so check that first, then.
104 * Otherwise perform content checking and isolate the domain part (for IDNA) */
105 static int _addrspec_check(int doskin
, struct addrguts
*agp
);
107 /* Return the next header field found in the given message.
108 * Return >= 0 if something found, < 0 elsewise.
109 * "colon" is set to point to the colon in the header.
110 * Must deal with \ continuations & other such fraud */
111 static int gethfield(FILE *f
, char **linebuf
, size_t *linesize
,
112 int rem
, char **colon
);
114 static int msgidnextc(char const **cp
, int *status
);
116 /* Count the occurances of c in str */
117 static int charcount(char *str
, int c
);
119 static char const * nexttoken(char const *cp
);
122 _from__skipword(char const *wp
)
128 while ((c
= *wp
++) != '\0' && !blankchar(c
)) {
130 while ((c
= *wp
++) != '\0' && c
!= '"')
136 for (; blankchar(c
); c
= *wp
++)
140 return (c
== 0 ? NULL
: wp
- 1);
144 _cmatch(size_t len
, char const *date
, char const *tp
)
169 if (c
!= ' ' && !digitchar(c
))
177 if (c
!= '+' && c
!= '-')
189 _is_date(char const *date
)
191 struct cmatch_data
const *cmdp
;
196 if ((dl
= strlen(date
)) >= _DATE_MINLEN
)
197 for (cmdp
= _cmatch_data
; cmdp
->tdata
!= NULL
; ++cmdp
)
198 if (dl
== cmdp
->tlen
&& (rv
= _cmatch(dl
, date
, cmdp
->tdata
)))
205 # if HAVE_IDNA == HAVE_IDNA_LIBIDNA
206 static struct addrguts
*
207 _idna_apply(struct addrguts
*agp
)
209 char *idna_utf8
, *idna_ascii
, *cs
;
213 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
215 idna_utf8
= ac_alloc(sz
+1);
216 memcpy(idna_utf8
, agp
->ag_skinned
+ agp
->ag_sdom_start
, sz
);
217 idna_utf8
[sz
] = '\0';
219 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
220 * let it perform the charset conversion, if any should be necessary */
221 if (!(options
& OPT_UNICODE
)) {
222 char const *tcs
= charset_get_lc();
223 idna_ascii
= idna_utf8
;
224 idna_utf8
= stringprep_convert(idna_ascii
, "UTF-8", tcs
);
225 i
= (idna_utf8
== NULL
&& errno
== EINVAL
);
228 if (idna_utf8
== NULL
) {
230 n_err(_("Cannot convert from %s to %s\n"), tcs
, "UTF-8");
231 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
236 if (idna_to_ascii_8z(idna_utf8
, &idna_ascii
, 0) != IDNA_SUCCESS
) {
237 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
241 /* Replace the domain part of .ag_skinned with IDNA version */
242 sz
= strlen(idna_ascii
);
243 i
= agp
->ag_sdom_start
;
244 cs
= salloc(agp
->ag_slen
- i
+ sz
+1);
245 memcpy(cs
, agp
->ag_skinned
, i
);
246 memcpy(cs
+ i
, idna_ascii
, sz
);
250 agp
->ag_skinned
= cs
;
252 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
253 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
255 idn_free(idna_ascii
);
257 if (options
& OPT_UNICODE
)
266 # elif HAVE_IDNA == HAVE_IDNA_IDNKIT /* IDNA==LIBIDNA */
267 static struct addrguts
*
268 _idna_apply(struct addrguts
*agp
)
270 char *idna_in
, *idna_out
, *cs
;
275 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
277 idna_in
= ac_alloc(sz
+1);
278 memcpy(idna_in
, agp
->ag_skinned
+ agp
->ag_sdom_start
, sz
);
281 for (idna_out
= NULL
, sz
= HOST_NAME_MAX
+1;; sz
+= HOST_NAME_MAX
) {
282 idna_out
= ac_alloc(sz
);
284 r
= idn_encodename(IDN_ENCODE_APP
, idna_in
, idna_out
, sz
);
287 case idn_buffer_overflow
:
289 case idn_invalid_encoding
:
290 n_err(_("Cannot convert from %s to %s\n"), charset_get_lc(), "UTF-8");
293 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
297 if (r
== idn_success
)
302 /* Replace the domain part of .ag_skinned with IDNA version */
303 sz
= strlen(idna_out
);
304 i
= agp
->ag_sdom_start
;
305 cs
= salloc(agp
->ag_slen
- i
+ sz
+1);
306 memcpy(cs
, agp
->ag_skinned
, i
);
307 memcpy(cs
+ i
, idna_out
, sz
);
311 agp
->ag_skinned
= cs
;
313 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
314 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
322 # endif /* IDNA==IDNKIT */
323 #endif /* HAVE_IDNA */
326 _addrspec_check(int skinned
, struct addrguts
*agp
)
330 ui8_t in_domain
, hadat
;
331 union {char c
; unsigned char u
;} c
;
338 use_idna
= ok_blook(idna_disable
) ? 0 : 1;
340 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
341 addr
= agp
->ag_skinned
;
343 if (agp
->ag_iaddr_aend
- agp
->ag_iaddr_start
== 0) {
344 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
348 /* If the field is not a recipient, it cannot be a file or a pipe */
352 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
353 * grep the latter for the complete picture */
355 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
358 if (addr
[0] == '/' || (addr
[0] == '.' && addr
[1] == '/'))
360 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
363 for (p
= addr
; (c
.c
= *p
); ++p
) {
364 if (c
.c
== '!' || c
.c
== '%')
368 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
376 in_domain
= hadat
= 0;
378 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
380 in_quote
= !in_quote
;
381 } else if (c
.u
< 040 || c
.u
>= 0177) { /* TODO no magics: !bodychar()? */
383 if (in_domain
&& use_idna
> 0) {
385 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_IDNA
,
391 } else if (in_domain
== 2) {
392 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
394 } else if (in_quote
&& in_domain
== 0) {
396 } else if (c
.c
== '\\' && *p
!= '\0') {
398 } else if (c
.c
== '@') {
400 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
404 agp
->ag_sdom_start
= PTR2SIZE(p
- addr
);
405 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
; /* TODO .. really? */
406 in_domain
= (*p
== '[') ? 2 : 1;
408 } else if (c
.c
== '(' || c
.c
== ')' || c
.c
== '<' || c
.c
== '>' ||
409 c
.c
== ',' || c
.c
== ';' || c
.c
== ':' || c
.c
== '\\' ||
410 c
.c
== '[' || c
.c
== ']')
415 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
419 if (!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
))
420 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISNAME
;
424 agp
= _idna_apply(agp
);
428 return ((agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) != 0);
432 gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
, char **colon
)
434 char *line2
= NULL
, *cp
, *cp2
;
435 size_t line2size
= 0;
439 if (*linebuf
== NULL
)
440 *linebuf
= srealloc(*linebuf
, *linesize
= 1);
447 if ((c
= readline_restart(f
, linebuf
, linesize
, 0)) <= 0) {
451 for (cp
= *linebuf
; fieldnamechar(*cp
); ++cp
)
454 while (blankchar(*cp
))
456 if (*cp
!= ':' || cp
== *linebuf
)
459 /* I guess we got a headline. Handle wraparound */
464 while (PTRCMP(--cp
, >=, *linebuf
) && blankchar(*cp
))
469 if (PTRCMP(cp
- 8, >=, *linebuf
) && cp
[-1] == '=' && cp
[-2] == '?')
471 ungetc(c
= getc(f
), f
);
474 c
= readline_restart(f
, &line2
, &line2size
, 0);
478 for (cp2
= line2
; blankchar(*cp2
); ++cp2
)
480 c
-= (int)PTR2SIZE(cp2
- line2
);
481 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
483 if (PTRCMP(cp
+ c
, >=, *linebuf
+ *linesize
- 2)) {
484 size_t diff
= PTR2SIZE(cp
- *linebuf
),
485 colondiff
= PTR2SIZE(*colon
- *linebuf
);
486 *linebuf
= srealloc(*linebuf
, *linesize
+= c
+ 2);
487 cp
= &(*linebuf
)[diff
];
488 *colon
= &(*linebuf
)[colondiff
];
506 msgidnextc(char const **cp
, int *status
)
513 assert(status
!= NULL
);
531 *cp
= skip_comment(&(*cp
)[1]);
548 c
= (*status
& 02) ? lowerconv(c
) : c
;
558 charcount(char *str
, int c
)
564 for (i
= 0, cp
= str
; *cp
; ++cp
)
572 nexttoken(char const *cp
)
591 } while (nesting
> 0 && *cp
!= '\0'); /* XXX error? */
592 } else if (blankchar(*cp
) || *cp
== ',')
602 myaddrs(struct header
*hp
)
608 if (hp
!= NULL
&& (np
= hp
->h_from
) != NULL
) {
609 if ((rv
= np
->n_fullname
) != NULL
)
611 if ((rv
= np
->n_name
) != NULL
)
615 if ((rv
= ok_vlook(from
)) != NULL
)
618 /* When invoking *sendmail* directly, it's its task to generate an otherwise
619 * undeterminable From: address. However, if the user sets *hostname*,
620 * accept his desire */
621 if (ok_vlook(smtp
) != NULL
|| ok_vlook(hostname
) != NULL
) {
622 char *hn
= nodename(1);
623 size_t sz
= strlen(myname
) + strlen(hn
) + 1 +1;
625 sstpcpy(sstpcpy(sstpcpy(rv
, myname
), "@"), hn
);
633 myorigin(struct header
*hp
)
635 char const *rv
= NULL
, *ccp
;
639 if ((ccp
= myaddrs(hp
)) != NULL
&&
640 (np
= lextract(ccp
, GEXTRA
| GFULL
)) != NULL
)
641 rv
= (np
->n_flink
!= NULL
) ? ok_vlook(sender
) : ccp
;
647 is_head(char const *linebuf
, size_t linelen
, bool_t compat
)
649 char date
[FROM_DATEBUF
];
653 if ((rv
= (linelen
>= 5 && !strncmp(linebuf
, "From ", 5))) &&
654 (!compat
|| ok_blook(mbox_rfc4155
)))
655 rv
= (extract_date_from_from_(linebuf
, linelen
, date
) && _is_date(date
));
661 extract_date_from_from_(char const *line
, size_t linelen
,
662 char datebuf
[FROM_DATEBUF
])
665 char const *cp
= line
;
669 cp
= _from__skipword(cp
);
673 cp
= _from__skipword(cp
);
676 if (cp
[0] == 't' && cp
[1] == 't' && cp
[2] == 'y') {
677 cp
= _from__skipword(cp
);
681 /* It seems there are invalid MBOX archives in the wild, compare
682 * . http://bugs.debian.org/624111
683 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
684 * What they do is that they obfuscate the address to "name at host".
685 * I think we should handle that */
686 else if(cp
[0] == 'a' && cp
[1] == 't' && cp
[2] == ' '){
687 cp
= _from__skipword(cp
+= 3);
692 linelen
-= PTR2SIZE(cp
- line
);
693 if (linelen
< _DATE_MINLEN
)
695 if (cp
[linelen
- 1] == '\n') {
697 /* (Rather IMAP/POP3 only) */
698 if (cp
[linelen
- 1] == '\r')
700 if (linelen
< _DATE_MINLEN
)
703 if (linelen
>= FROM_DATEBUF
)
708 memcpy(datebuf
, cp
, linelen
);
709 datebuf
[linelen
] = '\0';
713 cp
= _("<Unknown date>");
714 linelen
= strlen(cp
);
715 if (linelen
>= FROM_DATEBUF
)
716 linelen
= FROM_DATEBUF
;
721 extract_header(FILE *fp
, struct header
*hp
, si8_t
*checkaddr_err
)
723 /* See the prototype declaration for the hairy relationship of
724 * options&OPT_t_FLAG and/or pstate&PS_t_FLAG in here */
725 struct n_header_field
**hftail
;
726 struct header nh
, *hq
= &nh
;
727 char *linebuf
= NULL
/* TODO line pool */, *colon
;
728 size_t linesize
= 0, seenfields
= 0;
730 char const *val
, *cp
;
733 memset(hq
, 0, sizeof *hq
);
734 if ((pstate
& PS_t_FLAG
) && (options
& OPT_t_FLAG
)) {
737 hq
->h_bcc
= hp
->h_bcc
;
739 hftail
= &hq
->h_user_headers
;
741 for (lc
= 0; readline_restart(fp
, &linebuf
, &linesize
, 0) > 0; ++lc
)
744 /* TODO yippieia, cat(check(lextract)) :-) */
746 while ((lc
= gethfield(fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
749 /* We explicitly allow EAF_NAME for some addressees since aliases are not
750 * yet expanded when we parse these! */
751 if ((val
= thisfield(linebuf
, "to")) != NULL
) {
753 hq
->h_to
= cat(hq
->h_to
, checkaddrs(lextract(val
, GTO
| GFULL
),
754 EACM_NORMAL
| EAF_NAME
, checkaddr_err
));
755 } else if ((val
= thisfield(linebuf
, "cc")) != NULL
) {
757 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(lextract(val
, GCC
| GFULL
),
758 EACM_NORMAL
| EAF_NAME
, checkaddr_err
));
759 } else if ((val
= thisfield(linebuf
, "bcc")) != NULL
) {
761 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(lextract(val
, GBCC
| GFULL
),
762 EACM_NORMAL
| EAF_NAME
, checkaddr_err
));
763 } else if ((val
= thisfield(linebuf
, "from")) != NULL
) {
764 if (!(pstate
& PS_t_FLAG
) || (options
& OPT_t_FLAG
)) {
766 hq
->h_from
= cat(hq
->h_from
,
767 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
770 } else if ((val
= thisfield(linebuf
, "reply-to")) != NULL
) {
772 hq
->h_replyto
= cat(hq
->h_replyto
,
773 checkaddrs(lextract(val
, GEXTRA
| GFULL
), EACM_STRICT
, NULL
));
774 } else if ((val
= thisfield(linebuf
, "sender")) != NULL
) {
775 if (!(pstate
& PS_t_FLAG
) || (options
& OPT_t_FLAG
)) {
777 hq
->h_sender
= cat(hq
->h_sender
, /* TODO cat? check! */
778 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
782 } else if ((val
= thisfield(linebuf
, "subject")) != NULL
||
783 (val
= thisfield(linebuf
, "subj")) != NULL
) {
785 for (cp
= val
; blankchar(*cp
); ++cp
)
787 hq
->h_subject
= (hq
->h_subject
!= NULL
)
788 ? save2str(hq
->h_subject
, cp
) : savestr(cp
);
790 /* The remaining are mostly hacked in and thus TODO -- at least in
791 * TODO respect to their content checking */
792 else if((val
= thisfield(linebuf
, "message-id")) != NULL
){
793 if(pstate
& PS_t_FLAG
){
794 np
= checkaddrs(lextract(val
, GREF
),
795 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
797 if (np
== NULL
|| np
->n_flink
!= NULL
)
800 hq
->h_message_id
= np
;
803 }else if((val
= thisfield(linebuf
, "in-reply-to")) != NULL
){
804 if(pstate
& PS_t_FLAG
){
805 np
= checkaddrs(lextract(val
, GREF
),
806 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
808 if (np
== NULL
|| np
->n_flink
!= NULL
)
811 hq
->h_in_reply_to
= np
;
814 }else if((val
= thisfield(linebuf
, "references")) != NULL
){
815 if(pstate
& PS_t_FLAG
){
817 /* TODO Limit number of references TODO better on parser side */
818 hq
->h_ref
= cat(hq
->h_ref
, checkaddrs(extract(val
, GREF
),
819 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
824 /* and that is very hairy */
825 else if((val
= thisfield(linebuf
, "mail-followup-to")) != NULL
){
826 if(pstate
& PS_t_FLAG
){
828 hq
->h_mft
= cat(hq
->h_mft
, checkaddrs(lextract(val
, GEXTRA
| GFULL
),
829 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME
,
834 /* A free-form user header; gethfield() did some verification already.. */
836 struct n_header_field
*hfp
;
840 for(nstart
= cp
= linebuf
;; ++cp
)
841 if(!fieldnamechar(*cp
))
843 nl
= (ui32_t
)PTR2SIZE(cp
- nstart
);
845 while(blankchar(*cp
))
849 n_err(_("Ignoring header field \"%s\"\n"), linebuf
);
852 while(blankchar(*cp
))
854 bl
= (ui32_t
)strlen(cp
) +1;
857 *hftail
= hfp
= salloc(VSTRUCT_SIZEOF(struct n_header_field
, hf_dat
) +
859 hftail
= &hfp
->hf_next
;
863 memcpy(hfp
->hf_dat
, nstart
, nl
);
864 hfp
->hf_dat
[nl
++] = '\0';
865 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);
869 /* In case the blank line after the header has been edited out. Otherwise,
870 * fetch the header separator */
871 if (linebuf
!= NULL
) {
872 if (linebuf
[0] != '\0') {
873 for (cp
= linebuf
; *(++cp
) != '\0';)
875 fseek(fp
, (long)-PTR2SIZE(1 + cp
- linebuf
), SEEK_CUR
);
877 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
882 if (seenfields
> 0) {
885 hp
->h_bcc
= hq
->h_bcc
;
886 hp
->h_from
= hq
->h_from
;
887 hp
->h_replyto
= hq
->h_replyto
;
888 hp
->h_sender
= hq
->h_sender
;
889 if (hq
->h_subject
!= NULL
|| !(pstate
& PS_t_FLAG
) ||
890 !(options
& OPT_t_FLAG
))
891 hp
->h_subject
= hq
->h_subject
;
892 hp
->h_user_headers
= hq
->h_user_headers
;
894 if (pstate
& PS_t_FLAG
) {
895 hp
->h_ref
= hq
->h_ref
;
896 hp
->h_message_id
= hq
->h_message_id
;
897 hp
->h_in_reply_to
= hq
->h_in_reply_to
;
898 hp
->h_mft
= hq
->h_mft
;
900 /* And perform additional validity checks so that we don't bail later
901 * on TODO this is good and the place where this should occur,
902 * TODO unfortunately a lot of other places do again and blabla */
903 if (pstate
& PS_t_FLAG
) {
904 if (hp
->h_from
== NULL
)
905 hp
->h_from
= option_r_arg
;
906 else if (hp
->h_from
->n_flink
!= NULL
&& hp
->h_sender
== NULL
)
907 hp
->h_sender
= lextract(ok_vlook(sender
),
908 GEXTRA
| GFULL
| GFULLEXTRA
);
912 n_err(_("Restoring deleted header lines\n"));
920 hfield_mult(char const *field
, struct message
*mp
, int mult
)
925 size_t linesize
= 0; /* TODO line pool */
926 char *linebuf
= NULL
, *colon
;
930 /* There are (spam) messages which have header bytes which are many KB when
931 * joined, so resize a single heap storage until we are done if we shall
932 * collect a field that may have multiple bodies; only otherwise use the
933 * string dope directly */
934 memset(&hfs
, 0, sizeof hfs
);
936 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
938 if ((lc
= mp
->m_lines
- 1) < 0)
941 if ((mp
->m_flag
& MNOFROM
) == 0 &&
942 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
945 if ((lc
= gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
)) < 0)
947 if ((hfield
= thisfield(linebuf
, field
)) != NULL
&& *hfield
!= '\0') {
949 n_str_add_buf(&hfs
, hfield
, strlen(hfield
));
951 hfs
.s
= savestr(hfield
);
960 if (mult
&& hfs
.s
!= NULL
) {
961 colon
= savestrbuf(hfs
.s
, hfs
.l
);
970 thisfield(char const *linebuf
, char const *field
)
972 char const *rv
= NULL
;
975 while (lowerconv(*linebuf
) == lowerconv(*field
)) {
982 while (blankchar(*linebuf
))
984 if (*linebuf
++ != ':')
987 while (blankchar(*linebuf
)) /* TODO header parser.. strip trailing WS?!? */
996 nameof(struct message
*mp
, int reptype
)
1001 cp
= skin(name1(mp
, reptype
));
1002 if (reptype
!= 0 || charcount(cp
, '!') < 2)
1004 cp2
= strrchr(cp
, '!');
1006 while (cp2
> cp
&& *cp2
!= '!')
1016 skip_comment(char const *cp
)
1021 for (nesting
= 1; nesting
> 0 && *cp
; ++cp
) {
1040 routeaddr(char const *name
)
1042 char const *np
, *rp
= NULL
;
1045 for (np
= name
; *np
; np
++) {
1048 np
= skip_comment(np
+ 1) - 1;
1054 if (*np
== '\\' && np
[1])
1071 FL
enum expand_addr_flags
1072 expandaddr_to_eaf(void)
1075 char const *eafd_name
;
1076 bool_t eafd_is_target
;
1080 {"restrict", FAL0
, EAF_TARGET_MASK
, EAF_RESTRICT
| EAF_RESTRICT_TARGETS
},
1081 {"fail", FAL0
, EAF_NONE
, EAF_FAIL
},
1082 {"all", TRU1
, EAF_NONE
, EAF_TARGET_MASK
},
1083 {"file", TRU1
, EAF_NONE
, EAF_FILE
},
1084 {"pipe", TRU1
, EAF_NONE
, EAF_PIPE
},
1085 {"name", TRU1
, EAF_NONE
, EAF_NAME
},
1086 {"addr", TRU1
, EAF_NONE
, EAF_ADDR
}
1090 enum expand_addr_flags rv
;
1094 if ((cp
= ok_vlook(expandaddr
)) == NULL
)
1095 rv
= EAF_RESTRICT_TARGETS
;
1096 else if (*cp
== '\0')
1097 rv
= EAF_TARGET_MASK
;
1099 rv
= EAF_TARGET_MASK
;
1101 for (buf
= savestr(cp
); (cp
= n_strsep(&buf
, ',', TRU1
)) != NULL
;) {
1104 if ((minus
= (*cp
== '-')) || *cp
== '+')
1106 for (eafp
= eafa
;; ++eafp
) {
1107 if (eafp
== eafa
+ NELEM(eafa
)) {
1108 if (options
& OPT_D_V
)
1109 n_err(_("Unknown *expandaddr* value: \"%s\"\n"), cp
);
1111 } else if (!asccasecmp(cp
, eafp
->eafd_name
)) {
1113 rv
&= ~eafp
->eafd_andoff
;
1114 rv
|= eafp
->eafd_or
;
1116 if (eafp
->eafd_is_target
)
1117 rv
&= ~eafp
->eafd_or
;
1118 else if (options
& OPT_D_V
)
1119 n_err(_("\"-\" prefix invalid for *expandaddr* value: "
1123 } else if (!asccasecmp(cp
, "noalias")) { /* TODO v15 OBSOLETE */
1124 OBSOLETE(_("*expandaddr*: \"noalias\" is henceforth \"-name\""));
1131 if ((rv
& EAF_RESTRICT
) && (options
& (OPT_INTERACTIVE
| OPT_TILDE_FLAG
)))
1132 rv
|= EAF_TARGET_MASK
;
1133 else if (options
& OPT_D_V
) {
1134 if (!(rv
& EAF_TARGET_MASK
))
1135 n_err(_("*expandaddr* doesn't allow any addresses\n"));
1136 else if ((rv
& EAF_FAIL
) && (rv
& EAF_TARGET_MASK
) == EAF_TARGET_MASK
)
1137 n_err(_("*expandaddr* with \"fail\" but no restrictions\n"));
1145 is_addr_invalid(struct name
*np
, enum expand_addr_check_mode eacm
)
1147 char cbuf
[sizeof "'\\U12340'"];
1148 enum expand_addr_flags eaf
;
1156 if ((rv
= ((f
& NAME_ADDRSPEC_INVALID
) != 0))) {
1157 if ((eacm
& EACM_NOLOG
) || (f
& NAME_ADDRSPEC_ERR_EMPTY
)) {
1161 char const *fmt
= "'\\x%02X'";
1162 bool_t ok8bit
= TRU1
;
1164 if (f
& NAME_ADDRSPEC_ERR_IDNA
) {
1165 cs
= _("Invalid domain name: \"%s\", character %s\n");
1168 } else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
1169 cs
= _("\"%s\" contains invalid %s sequence\n");
1171 cs
= _("\"%s\" contains invalid character %s\n");
1173 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
1174 snprintf(cbuf
, sizeof cbuf
,
1175 (ok8bit
&& c
>= 040 && c
<= 0177 ? "'%c'" : fmt
), c
);
1181 /* *expandaddr* stuff */
1182 if (!(rv
= ((eacm
& EACM_MODE_MASK
) != EACM_NONE
)))
1185 eaf
= expandaddr_to_eaf();
1187 if ((eacm
& EACM_STRICT
) && (f
& NAME_ADDRSPEC_ISFILEORPIPE
)) {
1190 cs
= _("\"%s\"%s: file or pipe addressees not allowed here\n");
1191 if (eacm
& EACM_NOLOG
)
1197 eaf
|= (eacm
& EAF_TARGET_MASK
);
1198 if (eacm
& EACM_NONAME
)
1201 if (eaf
== EAF_NONE
) {
1208 if (!(eaf
& EAF_FILE
) && (f
& NAME_ADDRSPEC_ISFILE
)) {
1209 cs
= _("\"%s\"%s: *expandaddr* doesn't allow file target\n");
1210 if (eacm
& EACM_NOLOG
)
1212 } else if (!(eaf
& EAF_PIPE
) && (f
& NAME_ADDRSPEC_ISPIPE
)) {
1213 cs
= _("\"%s\"%s: *expandaddr* doesn't allow command pipe target\n");
1214 if (eacm
& EACM_NOLOG
)
1216 } else if (!(eaf
& EAF_NAME
) && (f
& NAME_ADDRSPEC_ISNAME
)) {
1217 cs
= _("\"%s\"%s: *expandaddr* doesn't allow user name target\n");
1218 if (eacm
& EACM_NOLOG
)
1220 } else if (!(eaf
& EAF_ADDR
) && (f
& NAME_ADDRSPEC_ISADDR
)) {
1221 cs
= _("\"%s\"%s: *expandaddr* doesn't allow mail address target\n");
1222 if (eacm
& EACM_NOLOG
)
1232 n_err(cs
, np
->n_name
, cbuf
);
1239 skin(char const *name
)
1246 addrspec_with_guts(1, name
, &ag
);
1247 ret
= ag
.ag_skinned
;
1248 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
))
1249 ret
= savestrbuf(ret
, ag
.ag_slen
);
1255 /* TODO addrspec_with_guts: RFC 5322
1256 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1258 addrspec_with_guts(int doskin
, char const *name
, struct addrguts
*agp
)
1261 char *cp2
, *bufend
, *nbuf
, c
, gotlt
, gotaddr
, lastsp
;
1265 memset(agp
, 0, sizeof *agp
);
1267 if ((agp
->ag_input
= name
) == NULL
|| (agp
->ag_ilen
= strlen(name
)) == 0) {
1268 agp
->ag_skinned
= UNCONST(""); /* ok: NAME_SALLOC is not set */
1270 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
1271 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
1275 if (!doskin
|| !anyof(name
, "(< ")) {
1276 /*agp->ag_iaddr_start = 0;*/
1277 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1278 agp
->ag_skinned
= UNCONST(name
); /* (NAME_SALLOC not set) */
1279 agp
->ag_slen
= agp
->ag_ilen
;
1280 agp
->ag_n_flags
= NAME_SKINNED
;
1284 /* Something makes us think we have to perform the skin operation */
1285 nbuf
= ac_alloc(agp
->ag_ilen
+ 1);
1286 /*agp->ag_iaddr_start = 0;*/
1287 cp2
= bufend
= nbuf
;
1288 gotlt
= gotaddr
= lastsp
= 0;
1290 for (cp
= name
++; (c
= *cp
++) != '\0'; ) {
1293 cp
= skip_comment(cp
);
1297 /* Start of a "quoted-string".
1298 * Copy it in its entirety */
1299 /* XXX RFC: quotes are "semantically invisible"
1300 * XXX But it was explicitly added (Changelog.Heirloom,
1301 * XXX [9.23] released 11/15/00, "Do not remove quotes
1302 * XXX when skinning names"? No more info.. */
1304 while ((c
= *cp
) != '\0') { /* TODO improve */
1312 else if ((c
= *cp
) != '\0') {
1323 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1325 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
1326 cp
+= 3, *cp2
++ = '@';
1327 else if (cp
[0] == '@' && blankchar(cp
[1]))
1328 cp
+= 2, *cp2
++ = '@';
1333 agp
->ag_iaddr_start
= PTR2SIZE(cp
- (name
- 1));
1335 gotlt
= gotaddr
= 1;
1340 /* (_addrspec_check() verifies these later!) */
1341 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1343 while ((c
= *cp
) != '\0' && c
!= ',') {
1346 cp
= skip_comment(cp
);
1348 while ((c
= *cp
) != '\0') {
1352 if (c
== '\\' && *cp
!= '\0')
1370 for (; blankchar(*cp
); ++cp
)
1375 } else if (!gotaddr
) {
1377 agp
->ag_iaddr_start
= PTR2SIZE(cp
- name
);
1381 agp
->ag_slen
= PTR2SIZE(cp2
- nbuf
);
1382 if (agp
->ag_iaddr_aend
== 0)
1383 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1385 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
1387 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
1389 rv
= _addrspec_check(doskin
, agp
);
1396 realname(char const *name
)
1398 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
1401 int quoted
, good
, nogood
;
1404 if ((cp
= UNCONST(name
)) == NULL
)
1406 for (; *cp
!= '\0'; ++cp
) {
1409 if (cstart
!= NULL
) {
1410 /* More than one comment in address, doesn't make sense to display
1411 * it without context. Return the entire field */
1412 cp
= mime_fromaddr(name
);
1416 cp
= skip_comment(cp
);
1419 cend
= cstart
= NULL
;
1425 if (*cp
== '\\' && cp
[1])
1436 /* More than one address. Just use the first one */
1442 if (cstart
== NULL
) {
1444 /* If name contains only a route-addr, the surrounding angle brackets
1445 * don't serve any useful purpose when displaying, so remove */
1446 cp
= prstr(skin(name
));
1448 cp
= mime_fromaddr(name
);
1452 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1453 * not stripped. The idea is to strip only syntactical relevant things (but
1454 * this is not necessarily the most sensible way in practice) */
1455 rp
= rname
= ac_alloc(PTR2SIZE(cend
- cstart
+1));
1457 for (cp
= cstart
; cp
< cend
; ++cp
) {
1458 if (*cp
== '(' && !quoted
) {
1459 cq
= skip_comment(++cp
);
1460 if (PTRCMP(--cq
, >, cend
))
1463 if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cq
))
1467 } else if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cend
))
1469 else if (*cp
== '"') {
1478 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
1480 rname
= savestr(out
.s
);
1483 while (blankchar(*rname
))
1485 for (rp
= rname
; *rp
!= '\0'; ++rp
)
1487 while (PTRCMP(--rp
, >=, rname
) && blankchar(*rp
))
1490 cp
= mime_fromaddr(name
);
1494 /* mime_fromhdr() has converted all nonprintable characters to question
1495 * marks now. These and blanks are considered uninteresting; if the
1496 * displayed part of the real name contains more than 25% of them, it is
1497 * probably better to display the plain email address instead */
1500 for (rp
= rname
; *rp
!= '\0' && PTRCMP(rp
, <, rname
+ 20); ++rp
)
1501 if (*rp
== '?' || blankchar(*rp
))
1505 cp
= (good
* 3 < nogood
) ? prstr(skin(name
)) : rname
;
1512 name1(struct message
*mp
, int reptype
)
1514 char *namebuf
, *cp
, *cp2
, *linebuf
= NULL
/* TODO line pool */;
1515 size_t namesize
, linesize
= 0;
1520 if ((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
1522 if (reptype
== 0 && (cp
= hfield1("sender", mp
)) != NULL
&& *cp
!= '\0')
1525 namebuf
= smalloc(namesize
= 1);
1527 if (mp
->m_flag
& MNOFROM
)
1529 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1531 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1535 if (namesize
<= linesize
)
1536 namebuf
= srealloc(namebuf
, namesize
= linesize
+1);
1537 for (cp
= linebuf
; *cp
!= '\0' && *cp
!= ' '; ++cp
)
1539 for (; blankchar(*cp
); ++cp
)
1541 for (cp2
= namebuf
+ strlen(namebuf
);
1542 *cp
&& !blankchar(*cp
) && PTRCMP(cp2
, <, namebuf
+ namesize
-1);)
1546 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1548 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
1550 if (strncmp(cp
, "From", 4)) /* XXX is_head? */
1552 if (namesize
<= linesize
)
1553 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1555 while ((cp
= strchr(cp
, 'r')) != NULL
) {
1556 if (!strncmp(cp
, "remote", 6)) {
1557 if ((cp
= strchr(cp
, 'f')) == NULL
)
1559 if (strncmp(cp
, "from", 4) != 0)
1561 if ((cp
= strchr(cp
, ' ')) == NULL
)
1565 strncpy(namebuf
, cp
, namesize
);
1568 cp2
= strrchr(namebuf
, '!') + 1;
1569 strncpy(cp2
, cp
, PTR2SIZE(namebuf
+ namesize
- cp2
));
1571 namebuf
[namesize
- 2] = '!';
1572 namebuf
[namesize
- 1] = '\0';
1578 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
1580 cp
= savestr(namebuf
);
1582 if (linebuf
!= NULL
)
1591 subject_re_trim(char *s
)
1596 } const *pp
, ignored
[] = { /* Update *reply-strings* manual upon change! */
1598 { 3, "aw:" }, { 5, "antw:" }, /* de */
1603 char *orig_s
= s
, *re_st
= NULL
, *re_st_x
;
1604 size_t re_l
= 0 /* pacify CC */;
1607 if ((re_st_x
= ok_vlook(reply_strings
)) != NULL
&&
1608 (re_l
= strlen(re_st_x
)) > 0) {
1609 re_st
= ac_alloc(++re_l
* 2);
1610 memcpy(re_st
, re_st_x
, re_l
);
1614 while (*s
!= '\0') {
1615 while (spacechar(*s
))
1618 for (pp
= ignored
; pp
->len
> 0; ++pp
)
1619 if (is_asccaseprefix(s
, pp
->dat
)) {
1625 if (re_st
!= NULL
) {
1628 memcpy(re_st_x
= re_st
+ re_l
, re_st
, re_l
);
1629 while ((cp
= n_strsep(&re_st_x
, ',', TRU1
)) != NULL
)
1630 if (is_asccaseprefix(s
, cp
)) {
1642 return any
? s
: orig_s
;
1646 msgidcmp(char const *s1
, char const *s2
)
1648 int q1
= 0, q2
= 0, c1
, c2
;
1652 c1
= msgidnextc(&s1
, &q1
);
1653 c2
= msgidnextc(&s2
, &q2
);
1662 is_ign(char const *field
, size_t fieldlen
, struct ignoretab igta
[2])
1672 if (igta
== allignore
)
1675 /* Lowercase it so that "Status" and "status" will hash to the same place */
1676 realfld
= ac_alloc(fieldlen
+1);
1677 i_strcpy(realfld
, field
, fieldlen
+1);
1678 if (igta
[1].i_count
> 0)
1679 rv
= !member(realfld
, igta
+ 1);
1681 rv
= member(realfld
, igta
);
1689 member(char const *realfield
, struct ignoretab
*table
)
1691 struct ignored
*igp
;
1695 for (igp
= table
->i_head
[hash(realfield
)]; igp
!= 0; igp
= igp
->i_link
)
1696 if (*igp
->i_field
== *realfield
&& !strcmp(igp
->i_field
, realfield
)) {
1705 fakefrom(struct message
*mp
)
1710 if (((name
= skin(hfield1("return-path", mp
))) == NULL
|| *name
== '\0' ) &&
1711 ((name
= skin(hfield1("from", mp
))) == NULL
|| *name
== '\0'))
1712 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1713 * RFC 4155 however requires a RFC 5322 (2822) conforming
1714 * "addr-spec", but we simply can't provide that */
1715 name
= "MAILER-DAEMON";
1727 for (cq
= cp
; *cq
!= '\0' && *cq
!= '\n'; ++cq
)
1735 #ifdef HAVE_IMAP_SEARCH
1737 unixtime(char const *fromline
)
1742 int i
, year
, month
, day
, hour
, minute
, second
, tzdiff
;
1746 for (fp
= fromline
; *fp
!= '\0' && *fp
!= '\n'; ++fp
)
1749 if (PTR2SIZE(fp
- fromline
) < 7)
1754 if (!strncmp(fp
+ 4, month_names
[i
], 3))
1756 if (month_names
[++i
][0] == '\0')
1762 day
= strtol(fp
+ 8, &xp
, 10);
1763 if (*xp
!= ' ' || xp
!= fp
+ 10)
1765 hour
= strtol(fp
+ 11, &xp
, 10);
1766 if (*xp
!= ':' || xp
!= fp
+ 13)
1768 minute
= strtol(fp
+ 14, &xp
, 10);
1769 if (*xp
!= ':' || xp
!= fp
+ 16)
1771 second
= strtol(fp
+ 17, &xp
, 10);
1772 if (*xp
!= ' ' || xp
!= fp
+ 19)
1774 year
= strtol(fp
+ 20, &xp
, 10);
1777 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
1779 tzdiff
= t
- mktime(gmtime(&t
));
1780 tmptr
= localtime(&t
);
1781 if (tmptr
->tm_isdst
> 0)
1791 #endif /* HAVE_IMAP_SEARCH */
1794 rfctime(char const *date
)
1796 char const *cp
= date
;
1799 int i
, year
, month
, day
, hour
, minute
, second
;
1802 if ((cp
= nexttoken(cp
)) == NULL
)
1804 if (alphachar(cp
[0]) && alphachar(cp
[1]) && alphachar(cp
[2]) &&
1806 if ((cp
= nexttoken(&cp
[4])) == NULL
)
1809 day
= strtol(cp
, &x
, 10); /* XXX strtol */
1810 if ((cp
= nexttoken(x
)) == NULL
)
1813 if (!strncmp(cp
, month_names
[i
], 3))
1815 if (month_names
[++i
][0] == '\0')
1819 if ((cp
= nexttoken(&cp
[3])) == NULL
)
1822 * Where a two or three digit year occurs in a date, the year is to be
1823 * interpreted as follows: If a two digit year is encountered whose
1824 * value is between 00 and 49, the year is interpreted by adding 2000,
1825 * ending up with a value between 2000 and 2049. If a two digit year
1826 * is encountered with a value between 50 and 99, or any three digit
1827 * year is encountered, the year is interpreted by adding 1900 */
1828 year
= strtol(cp
, &x
, 10); /* XXX strtol */
1829 i
= (int)PTR2SIZE(x
- cp
);
1830 if (i
== 2 && year
>= 0 && year
<= 49)
1832 else if (i
== 3 || (i
== 2 && year
>= 50 && year
<= 99))
1834 if ((cp
= nexttoken(x
)) == NULL
)
1836 hour
= strtol(cp
, &x
, 10); /* XXX strtol */
1840 minute
= strtol(cp
, &x
, 10);
1843 second
= strtol(cp
, &x
, 10);
1847 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
1849 if ((cp
= nexttoken(x
)) != NULL
) {
1861 if (digitchar(cp
[0]) && digitchar(cp
[1]) && digitchar(cp
[2]) &&
1867 tadj
= strtol(buf
, NULL
, 10) * 3600;/*XXX strtrol*/
1870 tadj
+= strtol(buf
, NULL
, 10) * 60; /* XXX strtol*/
1875 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1876 * TODO once again, Christos Zoulas and NetBSD Mail have done
1877 * TODO a really good job already, but using strptime(3), which
1878 * TODO is not portable. Nonetheless, WE must improve, not
1879 * TODO at last because we simply ignore obsolete timezones!!
1880 * TODO See RFC 5322, 4.3! */
1890 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1893 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
)
1898 if (second
< 0 || minute
< 0 || hour
< 0 || day
< 1) {
1903 t
= second
+ minute
* 60 + hour
* 3600 + (day
- 1) * 86400;
1907 t
+= 86400 * (is_leapyear(year
) ? 29 : 28);
1927 t
+= (year
- 70) * 31536000 + ((year
- 69) / 4) * 86400 -
1928 ((year
- 1) / 100) * 86400 + ((year
+ 299) / 400) * 86400;
1935 substdate(struct message
*m
)
1940 /* Determine the date to print in faked 'From ' lines. This is traditionally
1941 * the date the message was written to the mail file. Try to determine this
1942 * using RFC message header fields, or fall back to current time */
1943 if ((cp
= hfield1("received", m
)) != NULL
) {
1944 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
1947 while (alnumchar(*cp
));
1950 m
->m_time
= rfctime(cp
);
1952 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
) {
1953 if ((cp
= hfield1("date", m
)) != NULL
)
1954 m
->m_time
= rfctime(cp
);
1956 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
)
1957 m
->m_time
= time_current
.tc_time
;
1962 setup_from_and_sender(struct header
*hp
)
1968 /* If -t parsed or composed From: then take it. With -t we otherwise
1969 * want -r to be honoured in favour of *from* in order to have
1970 * a behaviour that is compatible with what users would expect from e.g.
1972 if ((np
= hp
->h_from
) != NULL
||
1973 ((pstate
& PS_t_FLAG
) && (np
= option_r_arg
) != NULL
)) {
1975 } else if ((addr
= myaddrs(hp
)) != NULL
)
1976 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
1979 if ((np
= hp
->h_sender
) != NULL
) {
1981 } else if ((addr
= ok_vlook(sender
)) != NULL
)
1982 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
1988 FL
struct name
const *
1989 check_from_and_sender(struct name
const *fromfield
,
1990 struct name
const *senderfield
)
1992 struct name
const *rv
= NULL
;
1995 if (senderfield
!= NULL
) {
1996 if (senderfield
->n_flink
!= NULL
) {
1997 n_err(_("The \"Sender:\" field may contain only one address\n"));
2003 if (fromfield
!= NULL
) {
2004 if (fromfield
->n_flink
!= NULL
&& senderfield
== NULL
) {
2005 n_err(_("A \"Sender:\" field is required with multiple "
2006 "addresses in \"From:\" field\n"));
2014 rv
= (struct name
*)0x1;
2022 getsender(struct message
*mp
)
2028 if ((cp
= hfield1("from", mp
)) == NULL
||
2029 (np
= lextract(cp
, GEXTRA
| GSKIN
)) == NULL
)
2032 cp
= (np
->n_flink
!= NULL
) ? skin(hfield1("sender", mp
)) : np
->n_name
;
2039 grab_headers(struct header
*hp
, enum gfield gflags
, int subjfirst
)
2041 /* TODO grab_headers: again, check counts etc. against RFC;
2042 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2048 comma
= (ok_blook(bsdcompat
) || ok_blook(bsdmsgs
)) ? 0 : GCOMMA
;
2051 hp
->h_to
= grab_names("To: ", hp
->h_to
, comma
, GTO
| GFULL
);
2052 if (subjfirst
&& (gflags
& GSUBJECT
))
2053 hp
->h_subject
= n_input_cp_addhist("Subject: ", hp
->h_subject
, TRU1
);
2055 hp
->h_cc
= grab_names("Cc: ", hp
->h_cc
, comma
, GCC
| GFULL
);
2057 hp
->h_bcc
= grab_names("Bcc: ", hp
->h_bcc
, comma
, GBCC
| GFULL
);
2059 if (gflags
& GEXTRA
) {
2060 if (hp
->h_from
== NULL
)
2061 hp
->h_from
= lextract(myaddrs(hp
), GEXTRA
| GFULL
| GFULLEXTRA
);
2062 hp
->h_from
= grab_names("From: ", hp
->h_from
, comma
,
2063 GEXTRA
| GFULL
| GFULLEXTRA
);
2064 if (hp
->h_replyto
== NULL
)
2065 hp
->h_replyto
= lextract(ok_vlook(replyto
), GEXTRA
| GFULL
);
2066 hp
->h_replyto
= grab_names("Reply-To: ", hp
->h_replyto
, comma
,
2068 if (hp
->h_sender
== NULL
)
2069 hp
->h_sender
= extract(ok_vlook(sender
), GEXTRA
| GFULL
);
2070 hp
->h_sender
= grab_names("Sender: ", hp
->h_sender
, comma
,
2074 if (!subjfirst
&& (gflags
& GSUBJECT
))
2075 hp
->h_subject
= n_input_cp_addhist("Subject: ", hp
->h_subject
, TRU1
);
2082 header_match(struct message
*mp
, struct search_expr
const *sep
)
2087 size_t linesize
= 0; /* TODO line pool */
2088 char *linebuf
= NULL
, *colon
;
2092 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
2094 if ((lc
= mp
->m_lines
- 1) < 0)
2097 if ((mp
->m_flag
& MNOFROM
) == 0 &&
2098 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
2101 if (gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
) <= 0)
2103 if (blankchar(*++colon
))
2105 in
.l
= strlen(in
.s
= colon
);
2106 mime_fromhdr(&in
, &out
, TD_ICONV
);
2108 if (sep
->ss_sexpr
== NULL
)
2109 rv
= (regexec(&sep
->ss_regex
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
2112 rv
= substr(out
.s
, sep
->ss_sexpr
);
2119 if (linebuf
!= NULL
)