1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
3 *@ TODO Mostly a hackery, we need RFC compliant parsers instead.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 * SPDX-License-Identifier: BSD-3-Clause
10 * Copyright (c) 1980, 1993
11 * The Regents of the University of California. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
47 size_t tlen
; /* Length of .tdata */
48 char const *tdata
; /* Template date - see _cmatch_data[] */
51 /* Template characters for cmatch_data.tdata:
52 * 'A' An upper case char
53 * 'a' A lower case char
56 * 'O' An optional digit or space
58 * '+' Either a plus or a minus sign */
59 static struct cmatch_data
const _cmatch_data
[] = {
60 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
61 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
62 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
63 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
64 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
65 * in the wild (by UW-imap) */
66 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
67 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
68 * zone strings; note that 1. is strictly speaking not correct as some
69 * letters are not used, and 2. is not because only "UT" is defined */
70 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
71 { 28 - 2, __reuse
}, { 28 - 1, __reuse
}, { 28 - 0, __reuse
},
74 #define a_HEADER_DATE_MINLEN 21
76 /* Savage extract date field from From_ line. linelen is convenience as line
77 * must be terminated (but it may end in a newline [sequence]).
78 * Return whether the From_ line was parsed successfully (-1 if the From_ line
79 * wasn't really RFC 4155 compliant) */
80 static int a_header_extract_date_from_from_(char const *line
, size_t linelen
,
81 char datebuf
[n_FROM_DATEBUF
]);
83 /* Skip over "word" as found in From_ line */
84 static char const *a_header__from_skipword(char const *wp
);
86 /* Match the date string against the date template (tp), return if match.
87 * See _cmatch_data[] for template character description */
88 static int _cmatch(size_t len
, char const *date
,
91 /* Check whether date is a valid 'From_' date.
92 * (Rather ctime(3) generated dates, according to RFC 4155) */
93 static int _is_date(char const *date
);
95 /* JulianDayNumber converter(s) */
96 static size_t a_header_gregorian_to_jdn(ui32_t y
, ui32_t m
, ui32_t d
);
98 static void a_header_jdn_to_gregorian(size_t jdn
,
99 ui32_t
*yp
, ui32_t
*mp
, ui32_t
*dp
);
102 /* ... And place the extracted date in `date' */
103 static void a_header_parse_from_(struct message
*mp
,
104 char date
[n_FROM_DATEBUF
]);
106 /* Convert the domain part of a skinned address to IDNA.
107 * If an error occurs before Unicode information is available, revert the IDNA
108 * error to a normal CHAR one so that the error message doesn't talk Unicode */
110 static struct n_addrguts
*a_header_idna_apply(struct n_addrguts
*agp
);
113 /* Classify and check a (possibly skinned) header body according to RFC
114 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
115 * also a file or a pipe command, so check that first, then.
116 * Otherwise perform content checking and isolate the domain part (for IDNA).
117 * issingle_hack has the same meaning as for n_addrspec_with_guts() */
118 static bool_t
a_header_addrspec_check(struct n_addrguts
*agp
, bool_t skinned
,
119 bool_t issingle_hack
);
121 /* Return the next header field found in the given message.
122 * Return >= 0 if something found, < 0 elsewise.
123 * "colon" is set to point to the colon in the header.
124 * Must deal with \ continuations & other such fraud */
125 static long a_gethfield(enum n_header_extract_flags hef
, FILE *f
,
126 char **linebuf
, size_t *linesize
, long rem
, char **colon
);
128 static int msgidnextc(char const **cp
, int *status
);
130 static char const * nexttoken(char const *cp
);
133 a_header_extract_date_from_from_(char const *line
, size_t linelen
,
134 char datebuf
[n_FROM_DATEBUF
])
137 char const *cp
= line
;
143 cp
= a_header__from_skipword(cp
);
147 cp
= a_header__from_skipword(cp
);
150 if((cp
[0] == 't' || cp
[0] == 'T') && (cp
[1] == 't' || cp
[1] == 'T') &&
151 (cp
[2] == 'y' || cp
[2] == 'Y')){
152 cp
= a_header__from_skipword(cp
);
156 /* It seems there are invalid MBOX archives in the wild, compare
157 * . http://bugs.debian.org/624111
158 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
159 * What they do is that they obfuscate the address to "name at host",
160 * and even "name at host dot dom dot dom.
161 * The [Aa][Tt] is also RFC 733, so be tolerant */
162 else if((cp
[0] == 'a' || cp
[0] == 'A') && (cp
[1] == 't' || cp
[1] == 'T') &&
167 cp
= a_header__from_skipword(cp
);
170 if((cp
[0] == 'd' || cp
[0] == 'D') && (cp
[1] == 'o' || cp
[1] == 'O') &&
171 (cp
[2] == 't' || cp
[2] == 'T') && cp
[3] == ' '){
177 linelen
-= PTR2SIZE(cp
- line
);
178 if (linelen
< a_HEADER_DATE_MINLEN
)
180 if (cp
[linelen
- 1] == '\n') {
182 /* (Rather IMAP/POP3 only) */
183 if (cp
[linelen
- 1] == '\r')
185 if (linelen
< a_HEADER_DATE_MINLEN
)
188 if (linelen
>= n_FROM_DATEBUF
)
192 memcpy(datebuf
, cp
, linelen
);
193 datebuf
[linelen
] = '\0';
197 cp
= _("<Unknown date>");
198 linelen
= strlen(cp
);
199 if (linelen
>= n_FROM_DATEBUF
)
200 linelen
= n_FROM_DATEBUF
;
206 a_header__from_skipword(char const *wp
)
212 while ((c
= *wp
++) != '\0' && !blankchar(c
)) {
214 while ((c
= *wp
++) != '\0' && c
!= '"')
220 for (; blankchar(c
); c
= *wp
++)
224 return (c
== 0 ? NULL
: wp
- 1);
228 _cmatch(size_t len
, char const *date
, char const *tp
)
253 if (c
!= ' ' && !digitchar(c
))
261 if (c
!= '+' && c
!= '-')
273 _is_date(char const *date
)
275 struct cmatch_data
const *cmdp
;
280 if ((dl
= strlen(date
)) >= a_HEADER_DATE_MINLEN
)
281 for (cmdp
= _cmatch_data
; cmdp
->tdata
!= NULL
; ++cmdp
)
282 if (dl
== cmdp
->tlen
&& (rv
= _cmatch(dl
, date
, cmdp
->tdata
)))
289 a_header_gregorian_to_jdn(ui32_t y
, ui32_t m
, ui32_t d
){
290 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
291 * (via third hand, plus adjustments).
292 * This algorithm is supposed to work for all dates in between 1582-10-15
293 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
332 a_header_jdn_to_gregorian(size_t jdn
, ui32_t
*yp
, ui32_t
*mp
, ui32_t
*dp
){
333 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
334 * (via third hand, plus adjustments) */
353 x
= jdn
/ 153; /* x -> month */
356 jdn
/= 5; /* jdn -> day */
364 *yp
= (ui32_t
)(y
& 0xFFFF);
365 *mp
= (ui32_t
)(x
& 0xFF);
366 *dp
= (ui32_t
)(jdn
& 0xFF);
372 a_header_parse_from_(struct message
*mp
, char date
[n_FROM_DATEBUF
]){
375 char *hline
= NULL
; /* TODO line pool */
379 if((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) != NULL
&&
380 (hlen
= readline_restart(ibuf
, &hline
, &hsize
, 0)) > 0)
381 a_header_extract_date_from_from_(hline
, hlen
, date
);
388 static struct n_addrguts
*
389 a_header_idna_apply(struct n_addrguts
*agp
){
390 struct n_string idna_ascii
;
393 n_string_creat_auto(&idna_ascii
);
395 if(!n_idna_to_ascii(&idna_ascii
, &agp
->ag_skinned
[agp
->ag_sdom_start
],
396 agp
->ag_slen
- agp
->ag_sdom_start
))
397 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
399 /* Replace the domain part of .ag_skinned with IDNA version */
400 n_string_unshift_buf(&idna_ascii
, agp
->ag_skinned
, agp
->ag_sdom_start
);
402 agp
->ag_skinned
= n_string_cp(&idna_ascii
);
403 agp
->ag_slen
= idna_ascii
.s_len
;
404 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
405 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
410 #endif /* HAVE_IDNA */
413 a_header_addrspec_check(struct n_addrguts
*agp
, bool_t skinned
,
414 bool_t issingle_hack
)
417 union {bool_t b
; char c
; unsigned char u
; ui32_t ui32
; si32_t si32
;} c
;
420 a_IDNA_ENABLE
= 1u<<0,
421 a_IDNA_APPLY
= 1u<<1,
422 a_REDO_NODE_AFTER_ADDR
= 1u<<2,
423 a_RESET_MASK
= a_IDNA_ENABLE
| a_IDNA_APPLY
| a_REDO_NODE_AFTER_ADDR
,
426 a_IN_DOMAIN
= 1u<<10,
427 a_DOMAIN_V6
= 1u<<11,
428 a_DOMAIN_MASK
= a_IN_DOMAIN
| a_DOMAIN_V6
434 if(!ok_blook(idna_disable
))
435 flags
= a_IDNA_ENABLE
;
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 addr
= agp
->ag_skinned
;
445 /* If the field is not a recipient, it cannot be a file or a pipe */
449 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
450 * grep the latter for the complete picture */
452 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
455 if (addr
[0] == '/' || (addr
[0] == '.' && addr
[1] == '/') ||
456 (addr
[0] == '-' && addr
[1] == '\0'))
458 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
461 for (p
= addr
; (c
.c
= *p
); ++p
) {
462 if (c
.c
== '!' || c
.c
== '%')
466 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
473 /* TODO This is false. If super correct this should work on wide
474 * TODO characters, just in case (some bytes of) the ASCII set is (are)
475 * TODO shared; it may yet tear apart multibyte sequences, possibly.
476 * TODO All this should interact with mime_enc_mustquote(), too!
477 * TODO That is: once this is an object, we need to do this in a way
478 * TODO that it is valid for the wire format (instead)! */
479 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
480 * TODO Note this correlats with addrspec_with_guts() which is in front
481 * TODO of us and encapsulates (what it thinks is, sigh) the address
482 * TODO boundary. ALL THIS should be one object that knows how to deal */
483 flags
&= a_RESET_MASK
;
484 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
487 } else if (c
.u
< 040 || c
.u
>= 0177) { /* TODO no magics: !bodychar()? */
489 if ((flags
& (a_IN_DOMAIN
| a_IDNA_ENABLE
)) ==
490 (a_IN_DOMAIN
| a_IDNA_ENABLE
))
491 flags
|= a_IDNA_APPLY
;
495 } else if ((flags
& a_DOMAIN_MASK
) == a_DOMAIN_MASK
) {
496 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
498 } else if ((flags
& (a_IN_QUOTE
| a_DOMAIN_MASK
)) == a_IN_QUOTE
) {
500 } else if (c
.c
== '\\' && *p
!= '\0') {
502 } else if (c
.c
== '@') {
504 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
508 agp
->ag_sdom_start
= PTR2SIZE(p
- addr
);
509 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
; /* TODO .. really? */
510 flags
&= ~a_DOMAIN_MASK
;
511 flags
|= (*p
== '[') ? a_IN_AT
| a_IN_DOMAIN
| a_DOMAIN_V6
512 : a_IN_AT
| a_IN_DOMAIN
;
515 /* TODO This interferes with our alias handling, which allows :!
516 * TODO Update manual on support (search the several ALIASCOLON)! */
517 else if (c
.c
== '(' || c
.c
== ')' || c
.c
== '<' || c
.c
== '>' ||
518 c
.c
== '[' || c
.c
== ']' || c
.c
== ':' || c
.c
== ';' ||
519 c
.c
== '\\' || c
.c
== ',' || blankchar(c
.c
))
524 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
528 /* If we do not think this is an address we may treat it as an alias name
529 * if and only if the original input is identical to the skinned version */
530 if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
) &&
531 !strcmp(agp
->ag_skinned
, agp
->ag_input
)){
532 /* TODO This may be an UUCP address */
533 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISNAME
;
534 if(!n_alias_is_valid_name(agp
->ag_input
))
535 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_NAME
, '.');
537 /* If we seem to know that this is an address. Ensure this is correct
538 * according to RFC 5322 TODO the entire address parser should be like
539 * TODO that for one, and then we should know whether structured or
540 * TODO unstructured, and just parse correctly overall!
541 * TODO In addition, this can be optimised a lot.
542 * TODO And it is far from perfect: it should not forget whether no
543 * TODO whitespace followed some snippet, and it was written hastily.
544 * TODO It is even wrong sometimes. Not only for strange cases */
546 struct a_token
*t_last
;
547 struct a_token
*t_next
;
553 a_T_TMASK
= (1u<<4) - 1,
555 a_T_SPECIAL
= 1u<<8 /* An atom actually needs to go TQUOTE */
560 } *thead
, *tcurr
, *tp
;
562 struct n_string ost
, *ostp
;
563 char const *cp
, *cp1st
, *cpmax
, *xp
;
566 /* Name and domain must be non-empty */
567 if(*addr
== '@' || &addr
[2] >= p
|| p
[-2] == '@'){
570 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
, c
.u
);
576 /* Nothing to do if there is only an address (in angle brackets) */
577 /* TODO This is wrong since we allow invalid constructs in local-part
578 * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
579 * TODO "abcd"@abc. Etc. */
580 if(agp
->ag_iaddr_start
== 0){
582 if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
))
584 if(agp
->ag_iaddr_aend
== agp
->ag_ilen
)
586 }else if(agp
->ag_iaddr_start
== 1 && *cp
== '<' &&
587 agp
->ag_iaddr_aend
== agp
->ag_ilen
- 1 &&
588 cp
[agp
->ag_iaddr_aend
] == '>'){
589 /* No @ seen? Possibly insert n_nodename() */
590 if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
)){
591 cp
= &agp
->ag_input
[agp
->ag_iaddr_start
];
592 cpmax
= &agp
->ag_input
[agp
->ag_iaddr_aend
];
598 /* It is not, so parse off all tokens, then resort and rejoin */
599 lofi_snap
= n_lofi_snap_create();
602 if((c
.ui32
= agp
->ag_iaddr_start
) > 0)
606 thead
= tcurr
= NULL
;
608 for(tp
= NULL
; cp
< cpmax
;){
612 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
613 tp
= n_lofi_alloc(sizeof *tp
);
615 if((tp
->t_last
= tcurr
) != NULL
)
621 tp
->t_start
= PTR2SIZE(++cp
- cp1st
);
622 xp
= skip_comment(cp
);
623 tp
->t_end
= PTR2SIZE(xp
- cp1st
);
625 if(tp
->t_end
> tp
->t_start
){
629 /* No closing comment - strip trailing whitespace */
630 while(blankchar(*--xp
))
631 if(--tp
->t_end
== tp
->t_start
)
640 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
641 tp
= n_lofi_alloc(sizeof *tp
);
643 if((tp
->t_last
= tcurr
) != NULL
)
648 tp
->t_f
= a_T_TQUOTE
;
649 tp
->t_start
= PTR2SIZE(++cp
- cp1st
);
650 for(xp
= cp
; xp
< cpmax
; ++xp
){
651 if((c
.c
= *xp
) == '"')
653 if(c
.c
== '\\' && xp
[1] != '\0')
656 tp
->t_end
= PTR2SIZE(xp
- cp1st
);
658 if(tp
->t_end
> tp
->t_start
){
659 /* No closing quote - strip trailing whitespace */
661 while(blankchar(*xp
--))
662 if(--tp
->t_end
== tp
->t_start
)
672 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
679 tp
= n_lofi_alloc(sizeof *tp
);
681 if((tp
->t_last
= tcurr
) != NULL
)
687 tp
->t_start
= PTR2SIZE(cp
- cp1st
);
691 /* Reverse solidus transforms the following into a quoted-pair, and
692 * therefore (must occur in comment or quoted-string only) the
693 * entire atom into a quoted string */
695 tp
->t_f
|= a_T_SPECIAL
;
701 /* Is this plain RFC 5322 "atext", or "specials"?
702 * TODO Because we don't know structured/unstructured, nor anything
703 * TODO else, we need to treat "dot-atom" as being identical to
705 * However, if the 8th bit is set, this will be RFC 2047 converted
706 * and the entire sequence is skipped */
707 if(!(c
.u
& 0x80) && !alnumchar(c
.c
) &&
708 c
.c
!= '!' && c
.c
!= '#' && c
.c
!= '$' && c
.c
!= '%' &&
709 c
.c
!= '&' && c
.c
!= '\'' && c
.c
!= '*' && c
.c
!= '+' &&
710 c
.c
!= '-' && c
.c
!= '/' && c
.c
!= '=' && c
.c
!= '?' &&
711 c
.c
!= '^' && c
.c
!= '_' && c
.c
!= '`' && c
.c
!= '{' &&
712 c
.c
!= '}' && c
.c
!= '|' && c
.c
!= '}' && c
.c
!= '~')
713 tp
->t_f
|= a_T_SPECIAL
;
718 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
720 if(!(flags
& a_REDO_NODE_AFTER_ADDR
)){
721 flags
|= a_REDO_NODE_AFTER_ADDR
;
723 /* The local-part may be in quotes.. */
724 if((tp
= tcurr
) != NULL
&& (tp
->t_f
& a_T_TQUOTE
) &&
725 tp
->t_end
== agp
->ag_iaddr_start
- 1){
726 /* ..so backward extend it, including the starting quote */
727 /* TODO This is false and the code below #if 0 away. We would
728 * TODO need to create a properly quoted local-part HERE AND NOW
729 * TODO and REPLACE the original data with that version, but the
730 * TODO current code cannot do that. The node needs the data,
731 * TODO not only offsets for that, for example. If we had all that
732 * TODO the code below could produce a really valid thing */
735 if(tp
->t_start
> 0 &&
736 (tp
->t_last
== NULL
|| tp
->t_last
->t_end
< tp
->t_start
) &&
737 agp
->ag_input
[tp
->t_start
- 1] == '\\')
739 tp
->t_f
= a_T_TADDR
| a_T_SPECIAL
;
741 tp
= n_lofi_alloc(sizeof *tp
);
743 if((tp
->t_last
= tcurr
) != NULL
)
749 tp
->t_start
= agp
->ag_iaddr_start
;
750 /* TODO Very special case because of our hacky non-object-based and
751 * TODO non-compliant address parser. Note */
752 if(tp
->t_last
== NULL
&& tp
->t_start
> 0)
754 if(agp
->ag_input
[tp
->t_start
] == '<')
757 /* TODO Very special check for whether we need to massage the
758 * TODO local part. This is wrong, but otherwise even more so */
760 cp
= &agp
->ag_input
[tp
->t_start
];
761 cpmax
= &agp
->ag_input
[agp
->ag_iaddr_aend
];
764 if(!(c
.u
& 0x80) && !alnumchar(c
.c
) &&
765 c
.c
!= '!' && c
.c
!= '#' && c
.c
!= '$' && c
.c
!= '%' &&
766 c
.c
!= '&' && c
.c
!= '\'' && c
.c
!= '*' && c
.c
!= '+' &&
767 c
.c
!= '-' && c
.c
!= '/' && c
.c
!= '=' && c
.c
!= '?' &&
768 c
.c
!= '^' && c
.c
!= '_' && c
.c
!= '`' && c
.c
!= '{' &&
769 c
.c
!= '}' && c
.c
!= '|' && c
.c
!= '}' && c
.c
!= '~'){
770 tp
->t_f
|= a_T_SPECIAL
;
776 tp
->t_end
= agp
->ag_iaddr_aend
;
777 assert(tp
->t_start
<= tp
->t_end
);
780 cp
= &agp
->ag_input
[agp
->ag_iaddr_aend
+ 1];
781 cpmax
= &agp
->ag_input
[agp
->ag_ilen
];
786 /* Nothing may follow the address, move it to the end */
787 assert(tcurr
!= NULL
);
788 if(tcurr
!= NULL
&& !(tcurr
->t_f
& a_T_TADDR
)){
789 for(tp
= thead
; tp
!= NULL
; tp
= tp
->t_next
){
790 if(tp
->t_f
& a_T_TADDR
){
791 if(tp
->t_last
!= NULL
)
792 tp
->t_last
->t_next
= tp
->t_next
;
795 if(tp
->t_next
!= NULL
)
796 tp
->t_next
->t_last
= tp
->t_last
;
799 while(tp
->t_next
!= NULL
)
803 tcurr
->t_next
= NULL
;
809 /* Make ranges contiguous: ensure a continuous range of atoms is converted
810 * to a SPECIAL one if at least one of them requires it */
811 for(tp
= thead
; tp
!= NULL
; tp
= tp
->t_next
){
812 if(tp
->t_f
& a_T_SPECIAL
){
814 while((tp
= tp
->t_last
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
815 tp
->t_f
|= a_T_SPECIAL
;
817 while((tp
= tp
->t_next
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
818 tp
->t_f
|= a_T_SPECIAL
;
824 /* And yes, we want quotes to extend as much as possible */
825 for(tp
= thead
; tp
!= NULL
; tp
= tp
->t_next
){
826 if(tp
->t_f
& a_T_TQUOTE
){
828 while((tp
= tp
->t_last
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
829 tp
->t_f
|= a_T_SPECIAL
;
831 while((tp
= tp
->t_next
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
832 tp
->t_f
|= a_T_SPECIAL
;
839 ostp
= n_string_creat_auto(&ost
);
840 if((c
.ui32
= agp
->ag_ilen
) <= UI32_MAX
>> 1)
841 ostp
= n_string_reserve(ostp
, c
.ui32
<<= 1);
843 for(tcurr
= thead
; tcurr
!= NULL
;){
845 ostp
= n_string_push_c(ostp
, ' ');
846 if(tcurr
->t_f
& a_T_TADDR
){
847 if(tcurr
->t_last
!= NULL
)
848 ostp
= n_string_push_c(ostp
, '<');
849 agp
->ag_iaddr_start
= ostp
->s_len
;
850 /* Now it is terrible to say, but if that thing contained
851 * quotes, then those may contain quoted-pairs! */
853 if(!(tcurr
->t_f
& a_T_SPECIAL
)){
855 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
856 (tcurr
->t_end
- tcurr
->t_start
));
861 ostp
= n_string_push_c(ostp
, '"');
864 cp
= &cp1st
[tcurr
->t_start
];
865 cpmax
= &cp1st
[tcurr
->t_end
];
866 for(esc
= FAL0
; cp
< cpmax
;){
867 if((c
.c
= *cp
++) == '\\' && !esc
){
868 if(cp
< cpmax
&& (*cp
== '"' || *cp
== '\\'))
871 if(esc
|| c
.c
== '"')
872 ostp
= n_string_push_c(ostp
, '\\');
874 ostp
= n_string_push_c(ostp
, '"');
877 ostp
= n_string_push_c(ostp
, c
.c
);
883 agp
->ag_iaddr_aend
= ostp
->s_len
;
885 if(tcurr
->t_last
!= NULL
)
886 ostp
= n_string_push_c(ostp
, '>');
887 tcurr
= tcurr
->t_next
;
888 }else if(tcurr
->t_f
& a_T_TCOMM
){
889 ostp
= n_string_push_c(ostp
, '(');
890 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
891 (tcurr
->t_end
- tcurr
->t_start
));
892 while((tp
= tcurr
->t_next
) != NULL
&& (tp
->t_f
& a_T_TCOMM
)){
894 ostp
= n_string_push_c(ostp
, ' '); /* XXX may be artificial */
895 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
896 (tcurr
->t_end
- tcurr
->t_start
));
898 ostp
= n_string_push_c(ostp
, ')');
899 tcurr
= tcurr
->t_next
;
900 }else if(tcurr
->t_f
& a_T_TQUOTE
){
902 ostp
= n_string_push_c(ostp
, '"');
904 do/* while tcurr && TATOM||TQUOTE */{
905 cp
= &cp1st
[tcurr
->t_start
];
906 cpmax
= &cp1st
[tcurr
->t_end
];
911 ostp
= n_string_push_c(ostp
, ' ');
913 if((tcurr
->t_f
& (a_T_TATOM
| a_T_SPECIAL
)) == a_T_TATOM
)
914 ostp
= n_string_push_buf(ostp
, cp
, PTR2SIZE(cpmax
- cp
));
918 for(esc
= FAL0
; cp
< cpmax
;){
919 if((c
.c
= *cp
++) == '\\' && !esc
){
920 if(cp
< cpmax
&& (*cp
== '"' || *cp
== '\\'))
923 if(esc
|| c
.c
== '"'){
925 ostp
= n_string_push_c(ostp
, '\\');
927 ostp
= n_string_push_c(ostp
, c
.c
);
936 }while((tcurr
= tcurr
->t_next
) != NULL
&&
937 (tcurr
->t_f
& (a_T_TATOM
| a_T_TQUOTE
)));
938 ostp
= n_string_push_c(ostp
, '"');
939 }else if(tcurr
->t_f
& a_T_SPECIAL
)
942 /* Can we use a fast join mode? */
943 for(tp
= tcurr
; tcurr
!= NULL
; tcurr
= tcurr
->t_next
){
944 if(!(tcurr
->t_f
& a_T_TATOM
))
947 ostp
= n_string_push_c(ostp
, ' ');
948 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
949 (tcurr
->t_end
- tcurr
->t_start
));
954 n_lofi_snap_unroll(lofi_snap
);
956 agp
->ag_input
= n_string_cp(ostp
);
957 agp
->ag_ilen
= ostp
->s_len
;
958 /*ostp = n_string_drop_ownership(ostp);*/
960 /* Name and domain must be non-empty, the second */
961 cp
= &agp
->ag_input
[agp
->ag_iaddr_start
];
962 cpmax
= &agp
->ag_input
[agp
->ag_iaddr_aend
];
963 if(*cp
== '@' || &cp
[2] > cpmax
|| cpmax
[-1] == '@'){
965 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
, c
.u
);
969 addr
= agp
->ag_skinned
= savestrbuf(cp
, PTR2SIZE(cpmax
- cp
));
971 /* TODO This parser is a mess. We do not know whether this is truly
972 * TODO valid, and all our checks are not truly RFC conforming.
973 * TODO Do check the skinned thing by itself once more, in order
974 * TODO to catch problems from reordering, e.g., this additional
975 * TODO test catches a final address without AT..
976 * TODO This is a plain copy+paste of the weird thing above, no care */
977 agp
->ag_n_flags
&= ~NAME_ADDRSPEC_ISADDR
;
978 flags
&= a_RESET_MASK
;
979 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
982 else if (c
.u
< 040 || c
.u
>= 0177) {
984 if(!(flags
& a_IN_DOMAIN
))
987 } else if ((flags
& a_DOMAIN_MASK
) == a_DOMAIN_MASK
) {
988 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
990 } else if ((flags
& (a_IN_QUOTE
| a_DOMAIN_MASK
)) == a_IN_QUOTE
) {
992 } else if (c
.c
== '\\' && *p
!= '\0') {
994 } else if (c
.c
== '@') {
996 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
1001 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
; /* TODO .. really? */
1002 flags
&= ~a_DOMAIN_MASK
;
1003 flags
|= (*p
== '[') ? a_IN_DOMAIN
| a_DOMAIN_V6
: a_IN_DOMAIN
;
1005 } else if (c
.c
== '(' || c
.c
== ')' || c
.c
== '<' || c
.c
== '>' ||
1006 c
.c
== '[' || c
.c
== ']' || c
.c
== ':' || c
.c
== ';' ||
1007 c
.c
== '\\' || c
.c
== ',' || blankchar(c
.c
))
1012 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
1013 else if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
)){
1014 /* This is not an address, but if we had seen angle brackets convert
1015 * it to a n_nodename() address if the name is a valid user */
1017 if(cp
> &agp
->ag_input
[0] && cp
[-1] == '<' &&
1018 cpmax
<= &agp
->ag_input
[agp
->ag_ilen
] && cpmax
[0] == '>' &&
1019 (!strcmp(addr
, ok_vlook(LOGNAME
)) || getpwnam(addr
) != NULL
)){
1020 /* XXX However, if hostname is set to the empty string this
1021 * XXX indicates that the used *mta* will perform the
1022 * XXX auto-expansion instead. Not so with `addrcodec' though */
1023 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
;
1024 if(!issingle_hack
&&
1025 (cp
= ok_vlook(hostname
)) != NULL
&& *cp
== '\0')
1026 agp
->ag_n_flags
|= NAME_ADDRSPEC_WITHOUT_DOMAIN
;
1028 c
.ui32
= strlen(cp
= n_nodename(TRU1
));
1029 /* This is yet IDNA converted.. */
1030 ostp
= n_string_creat_auto(&ost
);
1031 ostp
= n_string_assign_buf(ostp
, agp
->ag_input
, agp
->ag_ilen
);
1032 ostp
= n_string_insert_c(ostp
, agp
->ag_iaddr_aend
++, '@');
1033 ostp
= n_string_insert_buf(ostp
, agp
->ag_iaddr_aend
, cp
,
1035 agp
->ag_iaddr_aend
+= c
.ui32
;
1036 agp
->ag_input
= n_string_cp(ostp
);
1037 agp
->ag_ilen
= ostp
->s_len
;
1038 /*ostp = n_string_drop_ownership(ostp);*/
1040 cp
= &agp
->ag_input
[agp
->ag_iaddr_start
];
1041 cpmax
= &agp
->ag_input
[agp
->ag_iaddr_aend
];
1042 agp
->ag_skinned
= savestrbuf(cp
, PTR2SIZE(cpmax
- cp
));
1045 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
1052 if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) && (flags
& a_IDNA_APPLY
))
1053 agp
= a_header_idna_apply(agp
);
1056 return !(agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
);
1060 a_gethfield(enum n_header_extract_flags hef
, FILE *f
,
1061 char **linebuf
, size_t *linesize
, long rem
, char **colon
)
1063 char *line2
= NULL
, *cp
, *cp2
;
1064 size_t line2size
= 0;
1068 if (*linebuf
== NULL
)
1069 *linebuf
= n_realloc(*linebuf
, *linesize
= 1);
1076 if ((c
= readline_restart(f
, linebuf
, linesize
, 0)) <= 0) {
1080 if((hef
& n_HEADER_EXTRACT_IGNORE_SHELL_COMMENTS
) && **linebuf
== '#'){
1081 /* A comment may be last before body, too, ensure empty last line */
1086 for (cp
= *linebuf
; fieldnamechar(*cp
); ++cp
)
1089 while (blankchar(*cp
))
1093 /* XXX Not a header line, logging only for -t / compose mode? */
1095 if(!(hef
& n_HEADER_EXTRACT_IGNORE_FROM_
) ||
1096 !is_head(*linebuf
, c
, FAL0
))
1097 n_err(_("Not a header line, skipping: %s\n"),
1098 n_shexp_quote_cp(*linebuf
, FAL0
));
1102 /* I guess we got a headline. Handle wraparound */
1107 while (PTRCMP(--cp
, >=, *linebuf
) && blankchar(*cp
))
1112 if (PTRCMP(cp
- 8, >=, *linebuf
) && cp
[-1] == '=' && cp
[-2] == '?')
1114 ungetc(c
= getc(f
), f
);
1117 c
= readline_restart(f
, &line2
, &line2size
, 0); /* TODO linepool! */
1121 for (cp2
= line2
; blankchar(*cp2
); ++cp2
)
1123 c
-= (int)PTR2SIZE(cp2
- line2
);
1124 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
1126 if (PTRCMP(cp
+ c
, >=, *linebuf
+ *linesize
- 2)) {
1127 size_t diff
= PTR2SIZE(cp
- *linebuf
),
1128 colondiff
= PTR2SIZE(*colon
- *linebuf
);
1129 *linebuf
= n_realloc(*linebuf
, *linesize
+= c
+ 2);
1130 cp
= &(*linebuf
)[diff
];
1131 *colon
= &(*linebuf
)[colondiff
];
1149 msgidnextc(char const **cp
, int *status
)
1155 assert(*cp
!= NULL
);
1156 assert(status
!= NULL
);
1174 *cp
= skip_comment(&(*cp
)[1]);
1190 c
= *(*cp
)++ & 0377;
1191 c
= (*status
& 02) ? lowerconv(c
) : c
;
1201 nexttoken(char const *cp
)
1220 } while (nesting
> 0 && *cp
!= '\0'); /* XXX error? */
1221 } else if (blankchar(*cp
) || *cp
== ',')
1231 myaddrs(struct header
*hp
) /* TODO */
1234 char const *rv
, *mta
;
1237 if (hp
!= NULL
&& (np
= hp
->h_from
) != NULL
) {
1238 if ((rv
= np
->n_fullname
) != NULL
)
1240 if ((rv
= np
->n_name
) != NULL
)
1244 /* Verified once variable had been set */
1245 if((rv
= ok_vlook(from
)) != NULL
)
1248 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1249 * undeterminable From: address. However, if the user sets *hostname*,
1250 * accept his desire */
1251 if (ok_vlook(hostname
) != NULL
)
1253 if (ok_vlook(smtp
) != NULL
|| /* TODO obsolete -> mta */
1254 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1255 ((mta
= n_servbyname(ok_vlook(mta
), NULL
)) != NULL
&& *mta
!= '\0'))
1263 char const *hn
, *ln
;
1266 hn
= n_nodename(TRU1
);
1267 ln
= ok_vlook(LOGNAME
);
1268 i
= strlen(ln
) + strlen(hn
) + 1 +1;
1269 rv
= cp
= n_autorec_alloc(i
);
1270 sstpcpy(sstpcpy(sstpcpy(cp
, ln
), n_at
), hn
);
1276 myorigin(struct header
*hp
) /* TODO */
1278 char const *rv
= NULL
, *ccp
;
1282 if((ccp
= myaddrs(hp
)) != NULL
&&
1283 (np
= lextract(ccp
, GEXTRA
| GFULL
)) != NULL
){
1284 if(np
->n_flink
== NULL
)
1286 /* Verified upon variable set time */
1287 else if((ccp
= ok_vlook(sender
)) != NULL
)
1289 /* TODO why not else rv = n_poption_arg_r; ?? */
1296 is_head(char const *linebuf
, size_t linelen
, bool_t check_rfc4155
)
1298 char date
[n_FROM_DATEBUF
];
1302 if ((rv
= (linelen
>= 5 && !memcmp(linebuf
, "From ", 5))) && check_rfc4155
&&
1303 (a_header_extract_date_from_from_(linebuf
, linelen
, date
) <= 0 ||
1311 n_header_put4compose(FILE *fp
, struct header
*hp
){
1316 t
= GTO
| GSUBJECT
| GCC
| GBCC
| GBCC_IS_FCC
| GREF_IRT
| GNL
| GCOMMA
;
1317 if((hp
->h_from
!= NULL
|| myaddrs(hp
) != NULL
) ||
1318 (hp
->h_sender
!= NULL
|| ok_vlook(sender
) != NULL
) ||
1319 (hp
->h_reply_to
!= NULL
|| ok_vlook(reply_to
) != NULL
) ||
1320 ok_vlook(replyto
) != NULL
/* v15compat, OBSOLETE */ ||
1321 hp
->h_list_post
!= NULL
|| (hp
->h_flags
& HF_LIST_REPLY
))
1324 rv
= n_puthead(TRUM1
, hp
, fp
, t
, SEND_TODISP
, CONV_NONE
, NULL
, NULL
);
1330 n_header_extract(enum n_header_extract_flags hef
, FILE *fp
, struct header
*hp
,
1331 si8_t
*checkaddr_err_or_null
)
1333 struct n_header_field
**hftail
;
1334 struct header nh
, *hq
= &nh
;
1335 char *linebuf
= NULL
/* TODO line pool */, *colon
;
1336 size_t linesize
= 0, seenfields
= 0;
1340 char const *val
, *cp
;
1343 memset(hq
, 0, sizeof *hq
);
1344 if(hef
& n_HEADER_EXTRACT_PREFILL_RECEIVERS
){
1345 hq
->h_to
= hp
->h_to
;
1346 hq
->h_cc
= hp
->h_cc
;
1347 hq
->h_bcc
= hp
->h_bcc
;
1349 hftail
= &hq
->h_user_headers
;
1351 if((firstoff
= ftell(fp
)) == -1)
1353 for (lc
= 0; readline_restart(fp
, &linebuf
, &linesize
, 0) > 0; ++lc
)
1355 c
= fseek(fp
, firstoff
, SEEK_SET
);
1359 if(checkaddr_err_or_null
!= NULL
)
1360 *checkaddr_err_or_null
= -1;
1361 n_err("I/O error while parsing headers, operation aborted\n");
1365 /* TODO yippieia, cat(check(lextract)) :-) */
1366 while ((lc
= a_gethfield(hef
, fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
1369 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1370 * yet expanded when we parse these! */
1371 if ((val
= thisfield(linebuf
, "to")) != NULL
) {
1373 hq
->h_to
= cat(hq
->h_to
, checkaddrs(lextract(val
, GTO
| GFULL
),
1374 EACM_NORMAL
| EAF_NAME
| EAF_MAYKEEP
, checkaddr_err_or_null
));
1375 } else if ((val
= thisfield(linebuf
, "cc")) != NULL
) {
1377 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(lextract(val
, GCC
| GFULL
),
1378 EACM_NORMAL
| EAF_NAME
| EAF_MAYKEEP
, checkaddr_err_or_null
));
1379 } else if ((val
= thisfield(linebuf
, "bcc")) != NULL
) {
1381 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(lextract(val
, GBCC
| GFULL
),
1382 EACM_NORMAL
| EAF_NAME
| EAF_MAYKEEP
, checkaddr_err_or_null
));
1383 } else if ((val
= thisfield(linebuf
, "fcc")) != NULL
) {
1384 if(hef
& n_HEADER_EXTRACT__MODE_MASK
){
1386 hq
->h_fcc
= cat(hq
->h_fcc
, nalloc_fcc(val
));
1389 } else if ((val
= thisfield(linebuf
, "from")) != NULL
) {
1390 if(hef
& n_HEADER_EXTRACT_FULL
){
1392 hq
->h_from
= cat(hq
->h_from
,
1393 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
1394 EACM_STRICT
, NULL
));
1396 } else if ((val
= thisfield(linebuf
, "reply-to")) != NULL
) {
1398 hq
->h_reply_to
= cat(hq
->h_reply_to
,
1399 checkaddrs(lextract(val
, GEXTRA
| GFULL
), EACM_STRICT
, NULL
));
1400 } else if ((val
= thisfield(linebuf
, "sender")) != NULL
) {
1401 if(hef
& n_HEADER_EXTRACT_FULL
){
1403 hq
->h_sender
= cat(hq
->h_sender
, /* TODO cat? check! */
1404 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
1405 EACM_STRICT
, NULL
));
1408 } else if ((val
= thisfield(linebuf
, "subject")) != NULL
||
1409 (val
= thisfield(linebuf
, "subj")) != NULL
) {
1411 for (cp
= val
; blankchar(*cp
); ++cp
)
1413 hq
->h_subject
= (hq
->h_subject
!= NULL
)
1414 ? save2str(hq
->h_subject
, cp
) : savestr(cp
);
1416 /* The remaining are mostly hacked in and thus TODO -- at least in
1417 * TODO respect to their content checking */
1418 else if((val
= thisfield(linebuf
, "message-id")) != NULL
){
1419 if(hef
& n_HEADER_EXTRACT__MODE_MASK
){
1420 np
= checkaddrs(lextract(val
, GREF
),
1421 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1423 if (np
== NULL
|| np
->n_flink
!= NULL
)
1426 hq
->h_message_id
= np
;
1429 }else if((val
= thisfield(linebuf
, "in-reply-to")) != NULL
){
1430 if(hef
& n_HEADER_EXTRACT__MODE_MASK
){
1431 np
= checkaddrs(lextract(val
, GREF
),
1432 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1435 hq
->h_in_reply_to
= np
;
1438 }else if((val
= thisfield(linebuf
, "references")) != NULL
){
1439 if(hef
& n_HEADER_EXTRACT__MODE_MASK
){
1441 /* TODO Limit number of references TODO better on parser side */
1442 hq
->h_ref
= cat(hq
->h_ref
, checkaddrs(extract(val
, GREF
),
1443 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1448 /* and that is very hairy */
1449 else if((val
= thisfield(linebuf
, "mail-followup-to")) != NULL
){
1450 if(hef
& n_HEADER_EXTRACT__MODE_MASK
){
1452 hq
->h_mft
= cat(hq
->h_mft
, checkaddrs(lextract(val
, GEXTRA
| GFULL
),
1453 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME
,
1454 checkaddr_err_or_null
));
1458 /* A free-form header; a_gethfield() did some verification already.. */
1460 struct n_header_field
*hfp
;
1464 for(nstart
= cp
= linebuf
;; ++cp
)
1465 if(!fieldnamechar(*cp
))
1467 nl
= (ui32_t
)PTR2SIZE(cp
- nstart
);
1469 while(blankchar(*cp
))
1473 n_err(_("Ignoring header field: %s\n"), linebuf
);
1476 while(blankchar(*cp
))
1478 bl
= (ui32_t
)strlen(cp
) +1;
1482 hfp
= n_autorec_alloc(n_VSTRUCT_SIZEOF(struct n_header_field
,
1483 hf_dat
) + nl
+1 + bl
);
1484 hftail
= &hfp
->hf_next
;
1485 hfp
->hf_next
= NULL
;
1487 hfp
->hf_bl
= bl
- 1;
1488 memcpy(hfp
->hf_dat
, nstart
, nl
);
1489 hfp
->hf_dat
[nl
++] = '\0';
1490 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);
1494 /* In case the blank line after the header has been edited out. Otherwise,
1495 * fetch the header separator */
1496 if (linebuf
!= NULL
) {
1497 if (linebuf
[0] != '\0') {
1498 for (cp
= linebuf
; *(++cp
) != '\0';)
1500 fseek(fp
, (long)-PTR2SIZE(1 + cp
- linebuf
), SEEK_CUR
);
1502 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
1507 if (seenfields
> 0 &&
1508 (checkaddr_err_or_null
== NULL
|| *checkaddr_err_or_null
== 0)) {
1509 hp
->h_to
= hq
->h_to
;
1510 hp
->h_cc
= hq
->h_cc
;
1511 hp
->h_bcc
= hq
->h_bcc
;
1512 hp
->h_from
= hq
->h_from
;
1513 hp
->h_reply_to
= hq
->h_reply_to
;
1514 hp
->h_sender
= hq
->h_sender
;
1515 if(hq
->h_subject
!= NULL
||
1516 (hef
& n_HEADER_EXTRACT__MODE_MASK
) != n_HEADER_EXTRACT_FULL
)
1517 hp
->h_subject
= hq
->h_subject
;
1518 hp
->h_user_headers
= hq
->h_user_headers
;
1520 if(hef
& n_HEADER_EXTRACT__MODE_MASK
){
1521 hp
->h_fcc
= hq
->h_fcc
;
1522 if(hef
& n_HEADER_EXTRACT_FULL
)
1523 hp
->h_ref
= hq
->h_ref
;
1524 hp
->h_message_id
= hq
->h_message_id
;
1525 hp
->h_in_reply_to
= hq
->h_in_reply_to
;
1526 hp
->h_mft
= hq
->h_mft
;
1528 /* And perform additional validity checks so that we don't bail later
1529 * on TODO this is good and the place where this should occur,
1530 * TODO unfortunately a lot of other places do again and blabla */
1531 if(hp
->h_from
== NULL
)
1532 hp
->h_from
= n_poption_arg_r
;
1533 else if((hef
& n_HEADER_EXTRACT_FULL
) &&
1534 hp
->h_from
->n_flink
!= NULL
&& hp
->h_sender
== NULL
)
1535 hp
->h_sender
= lextract(ok_vlook(sender
),
1536 GEXTRA
| GFULL
| GFULLEXTRA
);
1539 n_err(_("Restoring deleted header lines\n"));
1542 if (linebuf
!= NULL
)
1548 hfield_mult(char const *field
, struct message
*mp
, int mult
)
1553 size_t linesize
= 0; /* TODO line pool */
1554 char *linebuf
= NULL
, *colon
;
1558 /* There are (spam) messages which have header bytes which are many KB when
1559 * joined, so resize a single heap storage until we are done if we shall
1560 * collect a field that may have multiple bodies; only otherwise use the
1561 * string dope directly */
1562 memset(&hfs
, 0, sizeof hfs
);
1564 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1566 if ((lc
= mp
->m_lines
- 1) < 0)
1569 if ((mp
->m_flag
& MNOFROM
) == 0 &&
1570 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1573 if ((lc
= a_gethfield(n_HEADER_EXTRACT_NONE
, ibuf
, &linebuf
, &linesize
,
1576 if ((hfield
= thisfield(linebuf
, field
)) != NULL
&& *hfield
!= '\0') {
1578 n_str_add_buf(&hfs
, hfield
, strlen(hfield
));
1580 hfs
.s
= savestr(hfield
);
1587 if (linebuf
!= NULL
)
1589 if (mult
&& hfs
.s
!= NULL
) {
1590 colon
= savestrbuf(hfs
.s
, hfs
.l
);
1599 thisfield(char const *linebuf
, char const *field
)
1601 char const *rv
= NULL
;
1604 while (lowerconv(*linebuf
) == lowerconv(*field
)) {
1611 while (blankchar(*linebuf
))
1613 if (*linebuf
++ != ':')
1616 while (blankchar(*linebuf
)) /* TODO header parser.. strip trailing WS?!? */
1625 skip_comment(char const *cp
)
1630 for (nesting
= 1; nesting
> 0 && *cp
; ++cp
) {
1649 routeaddr(char const *name
)
1651 char const *np
, *rp
= NULL
;
1654 for (np
= name
; *np
; np
++) {
1657 np
= skip_comment(np
+ 1) - 1;
1663 if (*np
== '\\' && np
[1])
1680 FL
enum expand_addr_flags
1681 expandaddr_to_eaf(void){
1684 bool_t eafd_is_target
;
1688 {"restrict", FAL0
, EAF_TARGET_MASK
, EAF_RESTRICT
| EAF_RESTRICT_TARGETS
},
1689 {"fail", FAL0
, EAF_NONE
, EAF_FAIL
},
1690 {"failinvaddr\0", FAL0
, EAF_NONE
, EAF_FAILINVADDR
| EAF_ADDR
},
1691 {"shquote", FAL0
, EAF_NONE
, EAF_SHEXP_PARSE
},
1692 {"all", TRU1
, EAF_NONE
, EAF_TARGET_MASK
},
1693 {"file", TRU1
, EAF_NONE
, EAF_FILE
},
1694 {"pipe", TRU1
, EAF_NONE
, EAF_PIPE
},
1695 {"name", TRU1
, EAF_NONE
, EAF_NAME
},
1696 {"addr", TRU1
, EAF_NONE
, EAF_ADDR
}
1700 enum expand_addr_flags rv
;
1704 if((cp
= ok_vlook(expandaddr
)) == NULL
)
1705 rv
= EAF_RESTRICT_TARGETS
;
1706 else if(*cp
== '\0')
1707 rv
= EAF_TARGET_MASK
;
1709 rv
= EAF_TARGET_MASK
;
1711 for(buf
= savestr(cp
); (cp
= n_strsep(&buf
, ',', TRU1
)) != NULL
;){
1714 if((minus
= (*cp
== '-')) || (*cp
== '+' ? (minus
= TRUM1
) : FAL0
))
1717 for(eafp
= eafa
;; ++eafp
) {
1718 if(eafp
== &eafa
[n_NELEM(eafa
)]){
1719 if(n_poption
& n_PO_D_V
)
1720 n_err(_("Unknown *expandaddr* value: %s\n"), cp
);
1722 }else if(!asccasecmp(cp
, eafp
->eafd_name
)){
1724 if(eafp
->eafd_is_target
){
1728 rv
&= ~eafp
->eafd_or
;
1729 }else if(n_poption
& n_PO_D_V
)
1730 n_err(_("- or + prefix invalid for *expandaddr* value: "
1734 rv
&= ~eafp
->eafd_andoff
;
1735 rv
|= eafp
->eafd_or
;
1738 }else if(!asccasecmp(cp
, "noalias")){ /* TODO v15 OBSOLETE */
1739 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1746 if((rv
& EAF_RESTRICT
) && ((n_psonce
& n_PSO_INTERACTIVE
) ||
1747 (n_poption
& n_PO_TILDE_FLAG
)))
1748 rv
|= EAF_TARGET_MASK
;
1749 else if(n_poption
& n_PO_D_V
){
1750 if(!(rv
& EAF_TARGET_MASK
))
1751 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1752 else if((rv
& EAF_FAIL
) && (rv
& EAF_TARGET_MASK
) == EAF_TARGET_MASK
)
1753 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1761 is_addr_invalid(struct name
*np
, enum expand_addr_check_mode eacm
)
1763 char cbuf
[sizeof "'\\U12340'"];
1767 enum expand_addr_flags eaf
;
1770 eaf
= expandaddr_to_eaf();
1773 if ((rv
= ((f
& NAME_ADDRSPEC_INVALID
) != 0))) {
1774 if (eaf
& EAF_FAILINVADDR
)
1777 if ((eacm
& EACM_NOLOG
) || (f
& NAME_ADDRSPEC_ERR_EMPTY
)) {
1781 char const *fmt
= "'\\x%02X'";
1782 bool_t ok8bit
= TRU1
;
1784 if (f
& NAME_ADDRSPEC_ERR_IDNA
) {
1785 cs
= _("Invalid domain name: %s, character %s\n");
1788 } else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
1789 cs
= _("%s contains invalid %s sequence\n");
1790 else if (f
& NAME_ADDRSPEC_ERR_NAME
) {
1791 cs
= _("%s is an invalid alias name\n");
1793 cs
= _("%s contains invalid byte %s\n");
1795 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
1796 snprintf(cbuf
, sizeof cbuf
,
1797 (ok8bit
&& c
>= 040 && c
<= 0177 ? "'%c'" : fmt
), c
);
1803 /* *expandaddr* stuff */
1804 if (!(rv
= ((eacm
& EACM_MODE_MASK
) != EACM_NONE
)))
1807 if ((eacm
& EACM_STRICT
) && (f
& NAME_ADDRSPEC_ISFILEORPIPE
)) {
1810 cs
= _("%s%s: file or pipe addressees not allowed here\n");
1811 if (eacm
& EACM_NOLOG
)
1817 eaf
|= (eacm
& EAF_TARGET_MASK
);
1818 if (eacm
& EACM_NONAME
)
1821 if (eaf
== EAF_NONE
) {
1828 if (!(eaf
& EAF_FILE
) && (f
& NAME_ADDRSPEC_ISFILE
)) {
1829 cs
= _("%s%s: *expandaddr* does not allow file target\n");
1830 if (eacm
& EACM_NOLOG
)
1832 } else if (!(eaf
& EAF_PIPE
) && (f
& NAME_ADDRSPEC_ISPIPE
)) {
1833 cs
= _("%s%s: *expandaddr* does not allow command pipe target\n");
1834 if (eacm
& EACM_NOLOG
)
1836 } else if (!(eaf
& EAF_NAME
) && (f
& NAME_ADDRSPEC_ISNAME
)) {
1837 cs
= _("%s%s: *expandaddr* does not allow user name target\n");
1838 if (eacm
& EACM_NOLOG
)
1840 } else if (!(eaf
& EAF_ADDR
) && (f
& NAME_ADDRSPEC_ISADDR
)) {
1841 cs
= _("%s%s: *expandaddr* does not allow mail address target\n");
1842 if (eacm
& EACM_NOLOG
)
1852 n_err(cs
, n_shexp_quote_cp(np
->n_name
, TRU1
), cbuf
);
1859 skin(char const *name
)
1861 struct n_addrguts ag
;
1866 /*name =*/ n_addrspec_with_guts(&ag
, name
, TRU1
, FAL0
);
1868 if(!(ag
.ag_n_flags
& NAME_NAME_SALLOC
))
1869 rv
= savestrbuf(rv
, ag
.ag_slen
);
1876 /* TODO addrspec_with_guts: RFC 5322
1877 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1879 n_addrspec_with_guts(struct n_addrguts
*agp
, char const *name
, bool_t doskin
,
1880 bool_t issingle_hack
){
1882 char *cp2
, *bufend
, *nbuf
, c
;
1892 memset(agp
, 0, sizeof *agp
);
1894 if((agp
->ag_input
= name
) == NULL
|| (agp
->ag_ilen
= strlen(name
)) == 0){
1895 agp
->ag_skinned
= n_UNCONST(n_empty
); /* ok: NAME_SALLOC is not set */
1897 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
1900 /*agp->ag_iaddr_start = 0;*/
1901 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1902 agp
->ag_skinned
= n_UNCONST(name
); /* (NAME_SALLOC not set) */
1903 agp
->ag_slen
= agp
->ag_ilen
;
1904 agp
->ag_n_flags
= NAME_SKINNED
;
1909 nbuf
= n_lofi_alloc(agp
->ag_ilen
+1);
1910 /*agp->ag_iaddr_start = 0;*/
1911 cp2
= bufend
= nbuf
;
1913 /* TODO This is complete crap and should use a token parser */
1914 for(cp
= name
++; (c
= *cp
++) != '\0';){
1917 cp
= skip_comment(cp
);
1921 /* Start of a "quoted-string". Copy it in its entirety */
1922 /* XXX RFC: quotes are "semantically invisible"
1923 * XXX But it was explicitly added (Changelog.Heirloom,
1924 * XXX [9.23] released 11/15/00, "Do not remove quotes
1925 * XXX when skinning names"? No more info.. */
1927 while ((c
= *cp
) != '\0') { /* TODO improve */
1935 else if ((c
= *cp
) != '\0') {
1944 if((flags
& (a_GOTADDR
| a_GOTSPACE
)) == a_GOTADDR
){
1945 flags
|= a_GOTSPACE
;
1946 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1948 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
1949 cp
+= 3, *cp2
++ = '@';
1950 else if (cp
[0] == '@' && blankchar(cp
[1]))
1951 cp
+= 2, *cp2
++ = '@';
1956 agp
->ag_iaddr_start
= PTR2SIZE(cp
- (name
- 1));
1958 flags
&= ~(a_GOTSPACE
| a_LASTSP
);
1959 flags
|= a_GOTLT
| a_GOTADDR
;
1962 if(flags
& a_GOTLT
){
1963 /* (_addrspec_check() verifies these later!) */
1964 flags
&= ~(a_GOTLT
| a_LASTSP
);
1965 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1967 /* Skip over the entire remaining field */
1968 while((c
= *cp
) != '\0' && c
!= ','){
1971 cp
= skip_comment(cp
);
1973 while ((c
= *cp
) != '\0') {
1977 if (c
== '\\' && *cp
!= '\0')
1985 if(flags
& a_LASTSP
){
1987 if(flags
& a_GOTADDR
)
1991 /* This character is forbidden here, but it may nonetheless be
1992 * present: ensure we turn this into something valid! (E.g., if the
1993 * next character would be a "..) */
1994 if(c
== '\\' && *cp
!= '\0')
1996 if(c
== ',' && !issingle_hack
){
1997 if(!(flags
& a_GOTLT
)){
1999 for(; blankchar(*cp
); ++cp
)
2004 }else if(!(flags
& a_GOTADDR
)){
2006 agp
->ag_iaddr_start
= PTR2SIZE(cp
- name
);
2011 agp
->ag_slen
= PTR2SIZE(cp2
- nbuf
);
2012 if (agp
->ag_iaddr_aend
== 0)
2013 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
2015 else if (agp
->ag_iaddr_aend
< agp
->ag_iaddr_start
) {
2016 cp2
= n_autorec_alloc(agp
->ag_ilen
+ 1 +1);
2017 memcpy(cp2
, agp
->ag_input
, agp
->ag_ilen
);
2018 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
2019 cp2
[agp
->ag_ilen
++] = '>';
2020 cp2
[agp
->ag_ilen
] = '\0';
2021 agp
->ag_input
= cp2
;
2023 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
2025 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
2027 if(a_header_addrspec_check(agp
, doskin
, issingle_hack
) <= FAL0
)
2030 name
= agp
->ag_input
;
2037 realname(char const *name
)
2039 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
2042 int quoted
, good
, nogood
;
2045 if ((cp
= n_UNCONST(name
)) == NULL
)
2047 for (; *cp
!= '\0'; ++cp
) {
2050 if (cstart
!= NULL
) {
2051 /* More than one comment in address, doesn't make sense to display
2052 * it without context. Return the entire field */
2053 cp
= mime_fromaddr(name
);
2057 cp
= skip_comment(cp
);
2060 cend
= cstart
= NULL
;
2066 if (*cp
== '\\' && cp
[1])
2077 /* More than one address. Just use the first one */
2083 if (cstart
== NULL
) {
2085 /* If name contains only a route-addr, the surrounding angle brackets
2086 * don't serve any useful purpose when displaying, so remove */
2087 cp
= prstr(skin(name
));
2089 cp
= mime_fromaddr(name
);
2093 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
2094 * not stripped. The idea is to strip only syntactical relevant things (but
2095 * this is not necessarily the most sensible way in practice) */
2096 rp
= rname
= n_lofi_alloc(PTR2SIZE(cend
- cstart
+1));
2098 for (cp
= cstart
; cp
< cend
; ++cp
) {
2099 if (*cp
== '(' && !quoted
) {
2100 cq
= skip_comment(++cp
);
2101 if (PTRCMP(--cq
, >, cend
))
2104 if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cq
))
2108 } else if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cend
))
2110 else if (*cp
== '"') {
2119 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
2121 rname
= savestr(out
.s
);
2124 while (blankchar(*rname
))
2126 for (rp
= rname
; *rp
!= '\0'; ++rp
)
2128 while (PTRCMP(--rp
, >=, rname
) && blankchar(*rp
))
2131 cp
= mime_fromaddr(name
);
2135 /* mime_fromhdr() has converted all nonprintable characters to question
2136 * marks now. These and blanks are considered uninteresting; if the
2137 * displayed part of the real name contains more than 25% of them, it is
2138 * probably better to display the plain email address instead */
2141 for (rp
= rname
; *rp
!= '\0' && PTRCMP(rp
, <, rname
+ 20); ++rp
)
2142 if (*rp
== '?' || blankchar(*rp
))
2146 cp
= (good
* 3 < nogood
) ? prstr(skin(name
)) : rname
;
2149 return n_UNCONST(cp
);
2153 n_header_senderfield_of(struct message
*mp
){
2157 if((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
2159 else if((cp
= hfield1("sender", mp
)) != NULL
&& *cp
!= '\0')
2162 char *namebuf
, *cp2
, *linebuf
= NULL
/* TODO line pool */;
2163 size_t namesize
, linesize
= 0;
2167 /* And fallback only works for MBOX */
2168 namebuf
= n_alloc(namesize
= 1);
2170 if (mp
->m_flag
& MNOFROM
)
2172 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
2174 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
2178 if (namesize
<= linesize
)
2179 namebuf
= n_realloc(namebuf
, namesize
= linesize
+1);
2180 for (cp
= linebuf
; *cp
!= '\0' && *cp
!= ' '; ++cp
)
2182 for (; blankchar(*cp
); ++cp
)
2184 for (cp2
= namebuf
+ strlen(namebuf
);
2185 *cp
&& !blankchar(*cp
) && PTRCMP(cp2
, <, namebuf
+ namesize
-1);)
2189 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
2191 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
2193 if (strncmp(cp
, "From", 4))
2195 if (namesize
<= linesize
)
2196 namebuf
= n_realloc(namebuf
, namesize
= linesize
+ 1);
2198 /* UUCP from 976 (we do not support anyway!) */
2199 while ((cp
= strchr(cp
, 'r')) != NULL
) {
2200 if (!strncmp(cp
, "remote", 6)) {
2201 if ((cp
= strchr(cp
, 'f')) == NULL
)
2203 if (strncmp(cp
, "from", 4) != 0)
2205 if ((cp
= strchr(cp
, ' ')) == NULL
)
2209 strncpy(namebuf
, cp
, namesize
);
2212 cp2
= strrchr(namebuf
, '!') + 1;
2213 strncpy(cp2
, cp
, PTR2SIZE(namebuf
+ namesize
- cp2
));
2215 namebuf
[namesize
- 2] = '!';
2216 namebuf
[namesize
- 1] = '\0';
2222 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
2224 cp
= savestr(namebuf
);
2226 if (linebuf
!= NULL
)
2236 subject_re_trim(char const *s
){
2240 }const *pp
, ignored
[] = { /* Update *reply-strings* manual upon change! */
2242 {3, "aw:"}, {5, "antw:"}, /* de */
2243 {3, "wg:"}, /* Seen too often in the wild */
2248 char *re_st
, *re_st_x
;
2258 if((re_st_x
= ok_vlook(reply_strings
)) != NULL
&&
2259 (re_l
= strlen(re_st_x
)) > 0){
2260 re_st
= n_lofi_alloc(++re_l
* 2);
2261 memcpy(re_st
, re_st_x
, re_l
);
2266 while(spacechar(*s
))
2269 for(pp
= ignored
; pp
->len
> 0; ++pp
)
2270 if(is_asccaseprefix(pp
->dat
, s
)){
2279 memcpy(re_st_x
= &re_st
[re_l
], re_st
, re_l
);
2280 while((cp
= n_strsep(&re_st_x
, ',', TRU1
)) != NULL
)
2281 if(is_asccaseprefix(cp
, s
)){
2293 return any
? s
: orig_s
;
2297 msgidcmp(char const *s1
, char const *s2
)
2299 int q1
= 0, q2
= 0, c1
, c2
;
2308 c1
= msgidnextc(&s1
, &q1
);
2309 c2
= msgidnextc(&s2
, &q2
);
2318 fakefrom(struct message
*mp
)
2323 if (((name
= skin(hfield1("return-path", mp
))) == NULL
|| *name
== '\0' ) &&
2324 ((name
= skin(hfield1("from", mp
))) == NULL
|| *name
== '\0'))
2325 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2326 * RFC 4155 however requires a RFC 5322 (2822) conforming
2327 * "addr-spec", but we simply can't provide that */
2328 name
= "MAILER-DAEMON";
2333 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2335 unixtime(char const *fromline
)
2337 char const *fp
, *xp
;
2339 si32_t i
, year
, month
, day
, hour
, minute
, second
, tzdiff
;
2343 for (fp
= fromline
; *fp
!= '\0' && *fp
!= '\n'; ++fp
)
2346 if (PTR2SIZE(fp
- fromline
) < 7)
2351 if (!strncmp(fp
+ 4, n_month_names
[i
], 3))
2353 if (n_month_names
[++i
][0] == '\0')
2359 n_idec_si32_cp(&day
, &fp
[8], 10, &xp
);
2360 if (*xp
!= ' ' || xp
!= fp
+ 10)
2362 n_idec_si32_cp(&hour
, &fp
[11], 10, &xp
);
2363 if (*xp
!= ':' || xp
!= fp
+ 13)
2365 n_idec_si32_cp(&minute
, &fp
[14], 10, &xp
);
2366 if (*xp
!= ':' || xp
!= fp
+ 16)
2368 n_idec_si32_cp(&second
, &fp
[17], 10, &xp
);
2369 if (*xp
!= ' ' || xp
!= fp
+ 19)
2371 n_idec_si32_cp(&year
, &fp
[20], 10, &xp
);
2374 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
2376 if((t2
= mktime(gmtime(&t
))) == (time_t)-1)
2379 if((tmptr
= localtime(&t
)) == NULL
)
2381 if (tmptr
->tm_isdst
> 0)
2382 tzdiff
+= 3600; /* TODO simply adding an hour for ISDST is .. buuh */
2391 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2394 rfctime(char const *date
) /* TODO n_idec_ return tests */
2398 si32_t i
, year
, month
, day
, hour
, minute
, second
;
2403 if ((cp
= nexttoken(cp
)) == NULL
)
2405 if (alphachar(cp
[0]) && alphachar(cp
[1]) && alphachar(cp
[2]) &&
2407 if ((cp
= nexttoken(&cp
[4])) == NULL
)
2410 n_idec_si32_cp(&day
, cp
, 10, &x
);
2411 if ((cp
= nexttoken(x
)) == NULL
)
2414 if (!strncmp(cp
, n_month_names
[i
], 3))
2416 if (n_month_names
[++i
][0] == '\0')
2420 if ((cp
= nexttoken(&cp
[3])) == NULL
)
2423 * Where a two or three digit year occurs in a date, the year is to be
2424 * interpreted as follows: If a two digit year is encountered whose
2425 * value is between 00 and 49, the year is interpreted by adding 2000,
2426 * ending up with a value between 2000 and 2049. If a two digit year
2427 * is encountered with a value between 50 and 99, or any three digit
2428 * year is encountered, the year is interpreted by adding 1900 */
2429 n_idec_si32_cp(&year
, cp
, 10, &x
);
2430 i
= (int)PTR2SIZE(x
- cp
);
2431 if (i
== 2 && year
>= 0 && year
<= 49)
2433 else if (i
== 3 || (i
== 2 && year
>= 50 && year
<= 99))
2435 if ((cp
= nexttoken(x
)) == NULL
)
2437 n_idec_si32_cp(&hour
, cp
, 10, &x
);
2441 n_idec_si32_cp(&minute
, cp
, 10, &x
);
2444 n_idec_si32_cp(&second
, cp
, 10, &x
);
2448 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
2450 if ((cp
= nexttoken(x
)) != NULL
) {
2462 if (digitchar(cp
[0]) && digitchar(cp
[1]) && digitchar(cp
[2]) &&
2469 n_idec_si32_cp(&i
, buf
, 10, NULL
);
2470 tadj
= (si64_t
)i
* 3600; /* XXX */
2473 n_idec_si32_cp(&i
, buf
, 10, NULL
);
2474 tadj
+= (si64_t
)i
* 60; /* XXX */
2479 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2480 * TODO once again, Christos Zoulas and NetBSD Mail have done
2481 * TODO a really good job already, but using strptime(3), which
2482 * TODO is not portable. Nonetheless, WE must improve, not
2483 * TODO at last because we simply ignore obsolete timezones!!
2484 * TODO See RFC 5322, 4.3! */
2495 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
){
2496 size_t const jdn_epoch
= 2440588;
2497 bool_t
const y2038p
= (sizeof(time_t) == 4);
2503 if(UICMP(32, second
, >/*XXX leap=*/, n_DATE_SECSMIN
) ||
2504 UICMP(32, minute
, >=, n_DATE_MINSHOUR
) ||
2505 UICMP(32, hour
, >=, n_DATE_HOURSDAY
) ||
2506 day
< 1 || day
> 31 ||
2507 month
< 1 || month
> 12 ||
2511 if(year
>= 1970 + ((y2038p
? SI32_MAX
: SI64_MAX
) /
2512 (n_DATE_SECSDAY
* n_DATE_DAYSYEAR
))){
2513 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2514 * test by stepping second-wise around the flip. Don't care otherwise */
2517 if(year
> 2038 || month
> 1 || day
> 19 ||
2518 hour
> 3 || minute
> 14 || second
> 7)
2523 t
+= minute
* n_DATE_SECSMIN
;
2524 t
+= hour
* n_DATE_SECSHOUR
;
2526 jdn
= a_header_gregorian_to_jdn(year
, month
, day
);
2528 t
+= (time_t)jdn
* n_DATE_SECSDAY
;
2538 substdate(struct message
*m
)
2540 /* The Date: of faked From_ lines is traditionally the date the message was
2541 * written to the mail file. Try to determine this using RFC message header
2542 * fields, or fall back to current time */
2547 if ((cp
= hfield1("received", m
)) != NULL
) {
2548 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
2551 while (alnumchar(*cp
));
2554 m
->m_time
= rfctime(cp
);
2556 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
) {
2557 if ((cp
= hfield1("date", m
)) != NULL
)
2558 m
->m_time
= rfctime(cp
);
2560 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
)
2561 m
->m_time
= time_current
.tc_time
;
2566 n_header_textual_date_info(struct message
*mp
, char const **color_tag_or_null
){
2569 char const *fmt
, *cp
;
2572 n_UNUSED(color_tag_or_null
);
2575 fmt
= ok_vlook(datefield
);
2581 cp
= hfield1("date", mp
);/* TODO use m_date field! */
2588 rv
= n_time_ctime(t
, NULL
);
2589 cp
= ok_vlook(datefield_markout_older
);
2592 i
|= (*cp
!= '\0') ? 2 | 4 : 2; /* XXX no magics */
2594 /* May we strftime(3)? */
2596 /* This localtime(3) should not fail since rfctime(3).. but .. */
2600 /* TODO the datetime stuff is horror: mails should be parsed into
2601 * TODO an object tree, and date: etc. have a datetime object, which
2602 * TODO verifies upon parse time; then ALL occurrences of datetime are
2603 * TODO valid all through the program; and: to_wire, to_user! */
2606 if((tmp
= localtime(&t2
)) == NULL
){
2608 goto jredo_localtime
;
2610 memcpy(&tmlocal
, tmp
, sizeof *tmp
);
2614 (UICMP(64, t
, >, time_current
.tc_time
+ n_DATE_SECSDAY
) ||
2615 #define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY)
2616 UICMP(64, t
+ _6M
, <, time_current
.tc_time
))){
2618 if((fmt
= (i
& 4) ? cp
: NULL
) == NULL
){
2620 n_LCTA(n_FROM_DATEBUF
>= 4 + 7 + 1 + 4, "buffer too small");
2622 x
= n_autorec_alloc(n_FROM_DATEBUF
);
2623 memset(x
, ' ', 4 + 7 + 1 + 4);
2624 memcpy(&x
[4], &rv
[4], 7);
2626 memcpy(&x
[4 + 7 + 1], &rv
[20], 4);
2627 x
[4 + 7 + 1 + 4] = '\0';
2631 if(color_tag_or_null
!= NULL
)
2632 *color_tag_or_null
= n_COLOUR_TAG_SUM_OLDER
;
2634 }else if((i
& 1) == 0)
2640 for(j
= n_FROM_DATEBUF
;; j
<<= 1){
2641 i
= strftime(rv
= n_autorec_alloc(j
), j
, fmt
, &tmlocal
);
2645 n_err(_("Ignoring this date format: %s\n"),
2646 n_shexp_quote_cp(fmt
, FAL0
));
2647 n_strscpy(rv
, n_time_ctime(t
, NULL
), j
);
2651 }else if(t
== (time_t)0 && !(mp
->m_flag
& MNOFROM
)){
2652 /* TODO eliminate this path, query the FROM_ date in setptr(),
2653 * TODO all other codepaths do so by themselves ALREADY ?????
2654 * TODO assert(mp->m_time != 0);, then
2655 * TODO ALSO changes behaviour of datefield_markout_older */
2656 a_header_parse_from_(mp
, rv
= n_autorec_alloc(n_FROM_DATEBUF
));
2658 rv
= savestr(n_time_ctime(t
, NULL
));
2664 n_header_textual_sender_info(struct message
*mp
, char **cumulation_or_null
,
2665 char **addr_or_null
, char **name_real_or_null
, char **name_full_or_null
,
2666 bool_t
*is_to_or_null
){
2667 struct n_string s_b1
, s_b2
, *sp1
, *sp2
;
2668 struct name
*np
, *np2
;
2673 cp
= n_header_senderfield_of(mp
);
2676 if((np
= lextract(cp
, GFULL
| GSKIN
)) != NULL
){
2677 if(is_to_or_null
!= NULL
&& ok_blook(showto
) &&
2678 np
->n_flink
== NULL
&& n_is_myname(np
->n_name
)){
2679 if((cp
= hfield1("to", mp
)) != NULL
&&
2680 (np2
= lextract(cp
, GFULL
| GSKIN
)) != NULL
){
2686 if(((b
= ok_blook(showname
)) && cumulation_or_null
!= NULL
) ||
2687 name_real_or_null
!= NULL
|| name_full_or_null
!= NULL
){
2690 for(i
= 0, np2
= np
; np2
!= NULL
; np2
= np2
->n_flink
)
2691 i
+= strlen(np2
->n_fullname
) +3;
2693 sp1
= n_string_book(n_string_creat_auto(&s_b1
), i
);
2694 sp2
= (name_full_or_null
== NULL
) ? NULL
2695 : n_string_book(n_string_creat_auto(&s_b2
), i
);
2697 for(np2
= np
; np2
!= NULL
; np2
= np2
->n_flink
){
2699 sp1
= n_string_push_c(sp1
, ',');
2700 sp1
= n_string_push_c(sp1
, ' ');
2702 sp2
= n_string_push_c(sp2
, ',');
2703 sp2
= n_string_push_c(sp2
, ' ');
2707 if((cp
= realname(np2
->n_fullname
)) == NULL
)
2709 sp1
= n_string_push_cp(sp1
, cp
);
2711 sp2
= n_string_push_cp(sp2
, np2
->n_fullname
);
2715 if(b
&& cumulation_or_null
!= NULL
)
2716 *cumulation_or_null
= sp1
->s_dat
;
2717 if(name_real_or_null
!= NULL
)
2718 *name_real_or_null
= sp1
->s_dat
;
2719 if(name_full_or_null
!= NULL
)
2720 *name_full_or_null
= n_string_cp(sp2
);
2722 /* n_string_gut(n_string_drop_ownership(sp2)); */
2723 /* n_string_gut(n_string_drop_ownership(sp1)); */
2726 if((b
= (!b
&& cumulation_or_null
!= NULL
)) || addr_or_null
!= NULL
){
2727 cp
= detract(np
, GCOMMA
| GNAMEONLY
);
2729 *cumulation_or_null
= cp
;
2730 if(addr_or_null
!= NULL
)
2733 }else if(cumulation_or_null
!= NULL
|| addr_or_null
!= NULL
||
2734 name_real_or_null
!= NULL
|| name_full_or_null
!= NULL
){
2735 cp
= savestr(n_empty
);
2737 if(cumulation_or_null
!= NULL
)
2738 *cumulation_or_null
= cp
;
2739 if(addr_or_null
!= NULL
)
2741 if(name_real_or_null
!= NULL
)
2742 *name_real_or_null
= cp
;
2743 if(name_full_or_null
!= NULL
)
2744 *name_full_or_null
= cp
;
2747 if(is_to_or_null
!= NULL
)
2748 *is_to_or_null
= isto
;
2754 setup_from_and_sender(struct header
*hp
)
2760 /* If -t parsed or composed From: then take it. With -t we otherwise
2761 * want -r to be honoured in favour of *from* in order to have
2762 * a behaviour that is compatible with what users would expect from e.g.
2764 if ((np
= hp
->h_from
) != NULL
||
2765 ((n_poption
& n_PO_t_FLAG
) && (np
= n_poption_arg_r
) != NULL
)) {
2767 } else if ((addr
= myaddrs(hp
)) != NULL
)
2768 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
2771 if ((np
= hp
->h_sender
) != NULL
) {
2773 } else if ((addr
= ok_vlook(sender
)) != NULL
)
2774 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
2780 FL
struct name
const *
2781 check_from_and_sender(struct name
const *fromfield
,
2782 struct name
const *senderfield
)
2784 struct name
const *rv
= NULL
;
2787 if (senderfield
!= NULL
) {
2788 if (senderfield
->n_flink
!= NULL
) {
2789 n_err(_("The Sender: field may contain only one address\n"));
2795 if (fromfield
!= NULL
) {
2796 if (fromfield
->n_flink
!= NULL
&& senderfield
== NULL
) {
2797 n_err(_("A Sender: is required when there are multiple "
2798 "addresses in From:\n"));
2806 rv
= (struct name
*)0x1;
2814 getsender(struct message
*mp
)
2820 if ((cp
= hfield1("from", mp
)) == NULL
||
2821 (np
= lextract(cp
, GEXTRA
| GSKIN
)) == NULL
)
2824 cp
= (np
->n_flink
!= NULL
) ? skin(hfield1("sender", mp
)) : np
->n_name
;
2831 n_header_setup_in_reply_to(struct header
*hp
){
2838 if((np
= hp
->h_in_reply_to
) == NULL
&& (np
= hp
->h_ref
) != NULL
)
2839 while(np
->n_flink
!= NULL
)
2846 grab_headers(enum n_go_input_flags gif
, struct header
*hp
, enum gfield gflags
,
2849 /* TODO grab_headers: again, check counts etc. against RFC;
2850 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2856 comma
= (ok_blook(bsdcompat
) || ok_blook(bsdmsgs
)) ? 0 : GCOMMA
;
2859 hp
->h_to
= grab_names(gif
, "To: ", hp
->h_to
, comma
, GTO
| GFULL
);
2860 if (subjfirst
&& (gflags
& GSUBJECT
))
2861 hp
->h_subject
= n_go_input_cp(gif
, "Subject: ", hp
->h_subject
);
2863 hp
->h_cc
= grab_names(gif
, "Cc: ", hp
->h_cc
, comma
, GCC
| GFULL
);
2865 hp
->h_bcc
= grab_names(gif
, "Bcc: ", hp
->h_bcc
, comma
, GBCC
| GFULL
);
2867 if (gflags
& GEXTRA
) {
2868 if (hp
->h_from
== NULL
)
2869 hp
->h_from
= lextract(myaddrs(hp
), GEXTRA
| GFULL
| GFULLEXTRA
);
2870 hp
->h_from
= grab_names(gif
, "From: ", hp
->h_from
, comma
,
2871 GEXTRA
| GFULL
| GFULLEXTRA
);
2872 if (hp
->h_reply_to
== NULL
) {
2873 struct name
*v15compat
;
2875 if((v15compat
= lextract(ok_vlook(replyto
), GEXTRA
| GFULL
)) != NULL
)
2876 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2877 hp
->h_reply_to
= lextract(ok_vlook(reply_to
), GEXTRA
| GFULL
);
2878 if(hp
->h_reply_to
== NULL
) /* v15 */
2879 hp
->h_reply_to
= v15compat
;
2881 hp
->h_reply_to
= grab_names(gif
, "Reply-To: ", hp
->h_reply_to
, comma
,
2883 if (hp
->h_sender
== NULL
)
2884 hp
->h_sender
= extract(ok_vlook(sender
), GEXTRA
| GFULL
);
2885 hp
->h_sender
= grab_names(gif
, "Sender: ", hp
->h_sender
, comma
,
2889 if (!subjfirst
&& (gflags
& GSUBJECT
))
2890 hp
->h_subject
= n_go_input_cp(gif
, "Subject: ", hp
->h_subject
);
2897 n_header_match(struct message
*mp
, struct search_expr
const *sep
){
2898 struct str fiter
, in
, out
;
2903 char **linebuf
, *colon
;
2904 enum {a_NONE
, a_ALL
, a_ITER
, a_RE
} match
;
2910 linebuf
= &termios_state
.ts_linebuf
; /* XXX line pool */
2911 linesize
= &termios_state
.ts_linesize
; /* XXX line pool */
2912 n_UNINIT(fiter
.l
, 0);
2913 n_UNINIT(fiter
.s
, NULL
);
2915 if((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
2917 if((lc
= mp
->m_lines
- 1) < 0)
2920 if((mp
->m_flag
& MNOFROM
) == 0 &&
2921 readline_restart(ibuf
, linebuf
, linesize
, 0) < 0)
2925 if((field
= sep
->ss_field
) != NULL
){
2926 if(!asccasecmp(field
, "header") || (field
[0] == '<' && field
[1] == '\0'))
2929 fiter
.s
= n_lofi_alloc((fiter
.l
= strlen(field
)) +1);
2933 }else if(sep
->ss_fieldre
!= NULL
){
2939 /* Iterate over all the headers */
2943 if((lc
= a_gethfield(n_HEADER_EXTRACT_NONE
, ibuf
, linebuf
, linesize
,
2947 /* Is this a header we are interested in? */
2948 if(match
== a_ITER
){
2951 memcpy(itercp
= fiter
.s
, sep
->ss_field
, fiter
.l
+1);
2952 while((field
= n_strsep(&itercp
, ',', TRU1
)) != NULL
){
2953 /* It may be an abbreviation */
2954 char const x
[][8] = {"from", "to", "cc", "bcc", "subject"};
2958 if(field
[0] != '\0' && field
[1] == '\0'){
2959 c1
= lowerconv(field
[0]);
2960 for(i
= 0; i
< n_NELEM(x
); ++i
){
2968 if(!ascncasecmp(field
, *linebuf
, PTR2SIZE(colon
- *linebuf
)))
2974 }else if(match
== a_RE
){
2978 i
= PTR2SIZE(colon
- *linebuf
);
2979 cp
= n_lofi_alloc(i
+1);
2980 memcpy(cp
, *linebuf
, i
);
2982 i
= (regexec(sep
->ss_fieldre
, cp
, 0,NULL
, 0) != REG_NOMATCH
);
2989 /* It could be a plain existence test */
2990 if(sep
->ss_field_exists
){
2995 /* Need to check the body */
2996 while(blankchar(*++colon
))
3000 /* Shall we split into address list and match as/the addresses only?
3001 * TODO at some later time we should ignore and log efforts to search
3002 * TODO a skinned address list if we know the header has none such */
3004 if((np
= lextract(in
.s
, GSKIN
)) == NULL
)
3009 in
.l
= strlen(in
.s
);
3010 mime_fromhdr(&in
, &out
, TD_ICONV
);
3015 if(sep
->ss_bodyre
!= NULL
)
3016 rv
= (regexec(sep
->ss_bodyre
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
3019 rv
= substr(out
.s
, sep
->ss_body
);
3025 if(np
!= NULL
&& (np
= np
->n_flink
) != NULL
){
3033 n_lofi_free(fiter
.s
);
3039 n_header_is_known(char const *name
, size_t len
){
3040 static char const * const names
[] = {
3041 "Bcc", "Cc", "From",
3042 "In-Reply-To", "Mail-Followup-To",
3043 "Message-ID", "References", "Reply-To",
3044 "Sender", "Subject", "To",
3045 /* More known, here and there */
3047 /* Mailx internal temporaries */
3049 "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From", "Mailx-Orig-To",
3050 "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
3053 char const * const *rv
;
3059 for(rv
= names
; *rv
!= NULL
; ++rv
)
3060 if(!ascncasecmp(*rv
, name
, len
))
3067 n_header_add_custom(struct n_header_field
**hflp
, char const *dat
,
3072 struct n_header_field
*hfp
;
3077 /* For (-C) convenience, allow leading WS */
3078 while(blankchar(*dat
))
3081 /* Isolate the header field from the body */
3082 for(cp
= dat
;; ++cp
){
3083 if(fieldnamechar(*cp
))
3088 }else if(*cp
!= ':' && !blankchar(*cp
)){
3090 cp
= N_("Invalid custom header (not \"field: body\"): %s\n");
3095 nl
= (ui32_t
)PTR2SIZE(cp
- dat
);
3099 /* Verify the custom header does not use standard/managed field name */
3100 if(n_header_is_known(dat
, nl
) != NULL
){
3101 cp
= N_("Custom headers cannot use standard header names: %s\n");
3105 /* Skip on over to the body */
3106 while(blankchar(*cp
))
3110 while(blankchar(*cp
))
3112 bl
= (ui32_t
)strlen(cp
);
3113 for(i
= bl
++; i
-- != 0;)
3114 if(cntrlchar(cp
[i
])){
3115 cp
= N_("Invalid custom header: contains control characters: %s\n");
3119 i
= n_VSTRUCT_SIZEOF(struct n_header_field
, hf_dat
) + nl
+1 + bl
;
3120 *hflp
= hfp
= heap
? n_alloc(i
) : n_autorec_alloc(i
);
3121 hfp
->hf_next
= NULL
;
3123 hfp
->hf_bl
= bl
- 1;
3124 memcpy(hfp
->hf_dat
, dat
, nl
);
3125 hfp
->hf_dat
[nl
++] = '\0';
3126 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);
3129 return (hfp
!= NULL
);
3132 n_err(V_(cp
), n_shexp_quote_cp(dat
, FAL0
));