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>.
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 #ifndef HAVE_AMALGAMATION
44 size_t tlen
; /* Length of .tdata */
45 char const *tdata
; /* Template date - see _cmatch_data[] */
48 /* Template characters for cmatch_data.tdata:
49 * 'A' An upper case char
50 * 'a' A lower case char
53 * 'O' An optional digit or space
55 * '+' Either a plus or a minus sign */
56 static struct cmatch_data
const _cmatch_data
[] = {
57 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
58 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
59 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
60 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
61 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
62 * in the wild (by UW-imap) */
63 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
64 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
65 * zone strings; note that 1. is strictly speaking not correct as some
66 * letters are not used, and 2. is not because only "UT" is defined */
67 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
68 { 28 - 2, __reuse
}, { 28 - 1, __reuse
}, { 28 - 0, __reuse
},
71 #define a_HEAD_DATE_MINLEN 21
73 /* Skip over "word" as found in From_ line */
74 static char const * _from__skipword(char const *wp
);
76 /* Match the date string against the date template (tp), return if match.
77 * See _cmatch_data[] for template character description */
78 static int _cmatch(size_t len
, char const *date
,
81 /* Check whether date is a valid 'From_' date.
82 * (Rather ctime(3) generated dates, according to RFC 4155) */
83 static int _is_date(char const *date
);
85 /* JulianDayNumber converter(s) */
86 static size_t a_head_gregorian_to_jdn(ui32_t y
, ui32_t m
, ui32_t d
);
88 static void a_head_jdn_to_gregorian(size_t jdn
,
89 ui32_t
*yp
, ui32_t
*mp
, ui32_t
*dp
);
92 /* Convert the domain part of a skinned address to IDNA.
93 * If an error occurs before Unicode information is available, revert the IDNA
94 * error to a normal CHAR one so that the error message doesn't talk Unicode */
96 static struct n_addrguts
*a_head_idna_apply(struct n_addrguts
*agp
);
99 /* Classify and check a (possibly skinned) header body according to RFC
100 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
101 * also a file or a pipe command, so check that first, then.
102 * Otherwise perform content checking and isolate the domain part (for IDNA) */
103 static bool_t
a_head_addrspec_check(struct n_addrguts
*agp
, bool_t skinned
);
105 /* Return the next header field found in the given message.
106 * Return >= 0 if something found, < 0 elsewise.
107 * "colon" is set to point to the colon in the header.
108 * Must deal with \ continuations & other such fraud */
109 static long a_gethfield(FILE *f
, char **linebuf
, size_t *linesize
, long rem
,
112 static int msgidnextc(char const **cp
, int *status
);
114 /* Count the occurances of c in str */
115 static int charcount(char *str
, int c
);
117 static char const * nexttoken(char const *cp
);
120 _from__skipword(char const *wp
)
126 while ((c
= *wp
++) != '\0' && !blankchar(c
)) {
128 while ((c
= *wp
++) != '\0' && c
!= '"')
134 for (; blankchar(c
); c
= *wp
++)
138 return (c
== 0 ? NULL
: wp
- 1);
142 _cmatch(size_t len
, char const *date
, char const *tp
)
167 if (c
!= ' ' && !digitchar(c
))
175 if (c
!= '+' && c
!= '-')
187 _is_date(char const *date
)
189 struct cmatch_data
const *cmdp
;
194 if ((dl
= strlen(date
)) >= a_HEAD_DATE_MINLEN
)
195 for (cmdp
= _cmatch_data
; cmdp
->tdata
!= NULL
; ++cmdp
)
196 if (dl
== cmdp
->tlen
&& (rv
= _cmatch(dl
, date
, cmdp
->tdata
)))
203 a_head_gregorian_to_jdn(ui32_t y
, ui32_t m
, ui32_t d
){
204 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
205 * (via third hand, plus adjustments).
206 * This algorithm is supposed to work for all dates in between 1582-10-15
207 * (0001-01-01 but that not Gregorian) and 65535-12-31 */
246 a_head_jdn_to_gregorian(size_t jdn
, ui32_t
*yp
, ui32_t
*mp
, ui32_t
*dp
){
247 /* Algorithm is taken from Communications of the ACM, Vol 6, No 8.
248 * (via third hand, plus adjustments) */
267 x
= jdn
/ 153; /* x -> month */
270 jdn
/= 5; /* jdn -> day */
278 *yp
= (ui32_t
)(y
& 0xFFFF);
279 *mp
= (ui32_t
)(x
& 0xFF);
280 *dp
= (ui32_t
)(jdn
& 0xFF);
286 static struct n_addrguts
*
287 a_head_idna_apply(struct n_addrguts
*agp
){
288 struct n_string idna_ascii
;
291 n_string_creat_auto(&idna_ascii
);
293 if(!n_idna_to_ascii(&idna_ascii
, &agp
->ag_skinned
[agp
->ag_sdom_start
],
294 agp
->ag_slen
- agp
->ag_sdom_start
))
295 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
297 /* Replace the domain part of .ag_skinned with IDNA version */
298 n_string_unshift_buf(&idna_ascii
, agp
->ag_skinned
, agp
->ag_sdom_start
);
300 agp
->ag_skinned
= n_string_cp(&idna_ascii
);
301 agp
->ag_slen
= idna_ascii
.s_len
;
302 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
303 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
308 #endif /* HAVE_IDNA */
311 a_head_addrspec_check(struct n_addrguts
*agp
, bool_t skinned
)
315 ui8_t in_domain
, hadat
;
316 union {bool_t b
; char c
; unsigned char u
; ui32_t ui32
; si32_t si32
;} c
;
323 use_idna
= ok_blook(idna_disable
) ? 0 : 1;
325 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
327 if (agp
->ag_iaddr_aend
- agp
->ag_iaddr_start
== 0) {
328 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
332 addr
= agp
->ag_skinned
;
334 /* If the field is not a recipient, it cannot be a file or a pipe */
338 /* When changing any of the following adjust any RECIPIENTADDRSPEC;
339 * grep the latter for the complete picture */
341 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
344 if (addr
[0] == '/' || (addr
[0] == '.' && addr
[1] == '/') ||
345 (addr
[0] == '-' && addr
[1] == '\0'))
347 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
350 for (p
= addr
; (c
.c
= *p
); ++p
) {
351 if (c
.c
== '!' || c
.c
== '%')
355 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
362 /* TODO This is false. If super correct this should work on wide
363 * TODO characters, just in case (some bytes of) the ASCII set is (are)
364 * TODO shared; it may yet tear apart multibyte sequences, possibly.
365 * TODO All this should interact with mime_enc_mustquote(), too!
366 * TODO That is: once this is an object, we need to do this in a way
367 * TODO that it is valid for the wire format (instead)! */
368 /* TODO addrspec_check: we need a real RFC 5322 (un)?structured parser!
369 * TODO Note this correlats with addrspec_with_guts() which is in front
370 * TODO of us and encapsulates (what it thinks is, sigh) the address
371 * TODO boundary. ALL THIS should be one object that knows how to deal */
373 in_domain
= hadat
= 0;
375 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
377 in_quote
= !in_quote
;
378 } else if (c
.u
< 040 || c
.u
>= 0177) { /* TODO no magics: !bodychar()? */
380 if (in_domain
&& use_idna
> 0) {
382 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_IDNA
,
388 } else if (in_domain
== 2) {
389 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
391 } else if (in_quote
&& in_domain
== 0) {
393 } else if (c
.c
== '\\' && *p
!= '\0') {
395 } else if (c
.c
== '@') {
397 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
401 agp
->ag_sdom_start
= PTR2SIZE(p
- addr
);
402 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
; /* TODO .. really? */
403 in_domain
= (*p
== '[') ? 2 : 1;
405 } else if (c
.c
== '(' || c
.c
== ')' || c
.c
== '<' || c
.c
== '>' ||
406 c
.c
== '[' || c
.c
== ']' || c
.c
== ':' || c
.c
== ';' ||
407 c
.c
== '\\' || c
.c
== ',' || blankchar(c
.c
))
412 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
416 if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
)){
417 /* TODO This may be an UUCP address */
418 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISNAME
;
419 if(!n_alias_is_valid_name(agp
->ag_input
))
420 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_NAME
, '.');
422 /* If we seem to know that this is an address. Ensure this is correct
423 * according to RFC 5322 TODO the entire address parser should be like
424 * TODO that for one, and then we should know whether structured or
425 * TODO unstructured, and just parse correctly overall!
426 * TODO In addition, this can be optimised a lot.
427 * TODO And it is far from perfect: it should not forget whether no
428 * TODO whitespace followed some snippet, and it was written hastily.
429 * TODO It is even wrong sometimes. Not only for strange cases */
431 struct a_token
*t_last
;
432 struct a_token
*t_next
;
438 a_T_TMASK
= (1u<<4) - 1,
440 a_T_SPECIAL
= 1u<<8 /* An atom actually needs to go TQUOTE */
445 } *thead
, *tcurr
, *tp
;
447 struct n_string ost
, *ostp
;
448 char const *cp
, *cp1st
, *cpmax
, *xp
;
451 /* Name and domain must be non-empty */
452 if(*addr
== '@' || &addr
[2] >= p
|| p
[-2] == '@'){
454 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
, c
.u
);
460 agp
= a_head_idna_apply(agp
);
465 /* Nothing to do if there is only an address (in angle brackets) */
466 /* TODO This is wrong since we allow invalid constructs in local-part
467 * TODO and domain, AT LEAST in so far as a"bc"d@abc should become
468 * TODO "abcd"@abc. Etc. */
469 if(agp
->ag_iaddr_start
== 0){
470 if(agp
->ag_iaddr_aend
== agp
->ag_ilen
)
472 }else if(agp
->ag_iaddr_start
== 1 && *cp
== '<' &&
473 agp
->ag_iaddr_aend
== agp
->ag_ilen
- 1 &&
474 cp
[agp
->ag_iaddr_aend
] == '>')
477 /* It is not, so parse off all tokens, then resort and rejoin */
478 lofi_snap
= n_lofi_snap_create();
481 if((c
.ui32
= agp
->ag_iaddr_start
) > 0)
485 thead
= tcurr
= NULL
;
488 for(tp
= NULL
; cp
< cpmax
;){
492 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
493 tp
= n_lofi_alloc(sizeof *tp
);
495 if((tp
->t_last
= tcurr
) != NULL
)
501 tp
->t_start
= PTR2SIZE(++cp
- cp1st
);
502 xp
= skip_comment(cp
);
503 tp
->t_end
= PTR2SIZE(xp
- cp1st
);
505 if(tp
->t_end
> tp
->t_start
){
509 /* No closing comment - strip trailing whitespace */
510 while(blankchar(*--xp
))
511 if(--tp
->t_end
== tp
->t_start
)
520 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
521 tp
= n_lofi_alloc(sizeof *tp
);
523 if((tp
->t_last
= tcurr
) != NULL
)
528 tp
->t_f
= a_T_TQUOTE
;
529 tp
->t_start
= PTR2SIZE(++cp
- cp1st
);
530 for(xp
= cp
; xp
< cpmax
; ++xp
){
531 if((c
.c
= *xp
) == '"')
533 if(c
.c
== '\\' && xp
[1] != '\0')
536 tp
->t_end
= PTR2SIZE(xp
- cp1st
);
538 if(tp
->t_end
> tp
->t_start
){
539 /* No closing quote - strip trailing whitespace */
541 while(blankchar(*xp
--))
542 if(--tp
->t_end
== tp
->t_start
)
552 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
559 tp
= n_lofi_alloc(sizeof *tp
);
561 if((tp
->t_last
= tcurr
) != NULL
)
567 tp
->t_start
= PTR2SIZE(cp
- cp1st
);
571 /* Reverse solidus transforms the following into a quoted-pair, and
572 * therefore (must occur in comment or quoted-string only) the
573 * entire atom into a quoted string */
575 tp
->t_f
|= a_T_SPECIAL
;
581 /* Is this plain RFC 5322 "atext", or "specials"?
582 * TODO Because we don't know structured/unstructured, nor anything
583 * TODO else, we need to treat "dot-atom" as being identical to
585 * However, if the 8th bit is set, this will be RFC 2047 converted
586 * and the entire sequence is skipped */
587 if(!(c
.u
& 0x80) && !alnumchar(c
.c
) &&
588 c
.c
!= '!' && c
.c
!= '#' && c
.c
!= '$' && c
.c
!= '%' &&
589 c
.c
!= '&' && c
.c
!= '\'' && c
.c
!= '*' && c
.c
!= '+' &&
590 c
.c
!= '-' && c
.c
!= '/' && c
.c
!= '=' && c
.c
!= '?' &&
591 c
.c
!= '^' && c
.c
!= '_' && c
.c
!= '`' && c
.c
!= '{' &&
592 c
.c
!= '}' && c
.c
!= '|' && c
.c
!= '}' && c
.c
!= '~')
593 tp
->t_f
|= a_T_SPECIAL
;
598 tp
->t_end
= PTR2SIZE(cp
- cp1st
);
602 /* The local-part may be in quotes.. */
603 if((tp
= tcurr
) != NULL
&& (tp
->t_f
& a_T_TQUOTE
) &&
604 tp
->t_end
== agp
->ag_iaddr_start
- 1){
605 /* ..so backward extend it, including the starting quote */
606 /* TODO This is false and the code below #if 0 away. We would
607 * TODO need to create a properly quoted local-part HERE AND NOW
608 * TODO and REPLACE the original data with that version, but the
609 * TODO current code cannot do that. The node needs the data,
610 * TODO not only offsets for that, for example. If we had all that
611 * TODO the code below could produce a really valid thing */
614 if(tp
->t_start
> 0 &&
615 (tp
->t_last
== NULL
|| tp
->t_last
->t_end
< tp
->t_start
) &&
616 agp
->ag_input
[tp
->t_start
- 1] == '\\')
618 tp
->t_f
= a_T_TADDR
| a_T_SPECIAL
;
620 tp
= n_lofi_alloc(sizeof *tp
);
622 if((tp
->t_last
= tcurr
) != NULL
)
628 tp
->t_start
= agp
->ag_iaddr_start
;
629 /* TODO Very special case because of our hacky non-object-based and
630 * TODO non-compliant address parser. Note */
631 if(tp
->t_last
== NULL
&& tp
->t_start
> 0)
633 if(agp
->ag_input
[tp
->t_start
] == '<')
636 /* TODO Very special check for whether we need to massage the
637 * TODO local part. This is wrong, but otherwise even more so */
639 cp
= &agp
->ag_input
[tp
->t_start
];
640 cpmax
= &agp
->ag_input
[agp
->ag_iaddr_aend
];
643 if(!(c
.u
& 0x80) && !alnumchar(c
.c
) &&
644 c
.c
!= '!' && c
.c
!= '#' && c
.c
!= '$' && c
.c
!= '%' &&
645 c
.c
!= '&' && c
.c
!= '\'' && c
.c
!= '*' && c
.c
!= '+' &&
646 c
.c
!= '-' && c
.c
!= '/' && c
.c
!= '=' && c
.c
!= '?' &&
647 c
.c
!= '^' && c
.c
!= '_' && c
.c
!= '`' && c
.c
!= '{' &&
648 c
.c
!= '}' && c
.c
!= '|' && c
.c
!= '}' && c
.c
!= '~'){
649 tp
->t_f
|= a_T_SPECIAL
;
655 tp
->t_end
= agp
->ag_iaddr_aend
;
656 assert(tp
->t_start
<= tp
->t_end
);
659 cp
= &agp
->ag_input
[agp
->ag_iaddr_aend
+ 1];
660 cpmax
= &agp
->ag_input
[agp
->ag_ilen
];
665 /* Nothing may follow the address, move it to the end */
666 if(!(tcurr
->t_f
& a_T_TADDR
)){
667 for(tp
= thead
; tp
!= NULL
; tp
= tp
->t_next
){
668 if(tp
->t_f
& a_T_TADDR
){
669 if(tp
->t_last
!= NULL
)
670 tp
->t_last
->t_next
= tp
->t_next
;
673 if(tp
->t_next
!= NULL
)
674 tp
->t_next
->t_last
= tp
->t_last
;
677 while(tp
->t_next
!= NULL
)
681 tcurr
->t_next
= NULL
;
687 /* Make ranges contiguous: ensure a continuous range of atoms is converted
688 * to a SPECIAL one if at least one of them requires it */
689 for(tp
= thead
; tp
!= NULL
; tp
= tp
->t_next
){
690 if(tp
->t_f
& a_T_SPECIAL
){
692 while((tp
= tp
->t_last
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
693 tp
->t_f
|= a_T_SPECIAL
;
695 while((tp
= tp
->t_next
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
696 tp
->t_f
|= a_T_SPECIAL
;
702 /* And yes, we want quotes to extend as much as possible */
703 for(tp
= thead
; tp
!= NULL
; tp
= tp
->t_next
){
704 if(tp
->t_f
& a_T_TQUOTE
){
706 while((tp
= tp
->t_last
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
707 tp
->t_f
|= a_T_SPECIAL
;
709 while((tp
= tp
->t_next
) != NULL
&& (tp
->t_f
& a_T_TATOM
))
710 tp
->t_f
|= a_T_SPECIAL
;
717 ostp
= n_string_creat_auto(&ost
);
718 if((c
.ui32
= agp
->ag_ilen
) <= UI32_MAX
>> 1)
719 ostp
= n_string_reserve(ostp
, c
.ui32
<<= 1);
721 for(tcurr
= thead
; tcurr
!= NULL
;){
723 ostp
= n_string_push_c(ostp
, ' ');
724 if(tcurr
->t_f
& a_T_TADDR
){
725 if(tcurr
->t_last
!= NULL
)
726 ostp
= n_string_push_c(ostp
, '<');
727 agp
->ag_iaddr_start
= ostp
->s_len
;
728 /* Now it is terrible to say, but if that thing contained
729 * quotes, then those may contain quoted-pairs! */
731 if(!(tcurr
->t_f
& a_T_SPECIAL
)){
733 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
734 (tcurr
->t_end
- tcurr
->t_start
));
739 ostp
= n_string_push_c(ostp
, '"');
742 cp
= &cp1st
[tcurr
->t_start
];
743 cpmax
= &cp1st
[tcurr
->t_end
];
744 for(esc
= FAL0
; cp
< cpmax
;){
745 if((c
.c
= *cp
++) == '\\' && !esc
){
746 if(cp
< cpmax
&& (*cp
== '"' || *cp
== '\\'))
749 if(esc
|| c
.c
== '"')
750 ostp
= n_string_push_c(ostp
, '\\');
752 ostp
= n_string_push_c(ostp
, '"');
755 ostp
= n_string_push_c(ostp
, c
.c
);
761 agp
->ag_iaddr_aend
= ostp
->s_len
;
763 if(tcurr
->t_last
!= NULL
)
764 ostp
= n_string_push_c(ostp
, '>');
765 tcurr
= tcurr
->t_next
;
766 }else if(tcurr
->t_f
& a_T_TCOMM
){
767 ostp
= n_string_push_c(ostp
, '(');
768 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
769 (tcurr
->t_end
- tcurr
->t_start
));
770 while((tp
= tcurr
->t_next
) != NULL
&& (tp
->t_f
& a_T_TCOMM
)){
772 ostp
= n_string_push_c(ostp
, ' '); /* XXX may be artificial */
773 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
774 (tcurr
->t_end
- tcurr
->t_start
));
776 ostp
= n_string_push_c(ostp
, ')');
777 tcurr
= tcurr
->t_next
;
778 }else if(tcurr
->t_f
& a_T_TQUOTE
){
780 ostp
= n_string_push_c(ostp
, '"');
782 do/* while tcurr && TATOM||TQUOTE */{
783 cp
= &cp1st
[tcurr
->t_start
];
784 cpmax
= &cp1st
[tcurr
->t_end
];
789 ostp
= n_string_push_c(ostp
, ' ');
791 if((tcurr
->t_f
& (a_T_TATOM
| a_T_SPECIAL
)) == a_T_TATOM
)
792 ostp
= n_string_push_buf(ostp
, cp
, PTR2SIZE(cpmax
- cp
));
796 for(esc
= FAL0
; cp
< cpmax
;){
797 if((c
.c
= *cp
++) == '\\' && !esc
){
798 if(cp
< cpmax
&& (*cp
== '"' || *cp
== '\\'))
801 if(esc
|| c
.c
== '"'){
803 ostp
= n_string_push_c(ostp
, '\\');
805 ostp
= n_string_push_c(ostp
, c
.c
);
814 }while((tcurr
= tcurr
->t_next
) != NULL
&&
815 (tcurr
->t_f
& (a_T_TATOM
| a_T_TQUOTE
)));
816 ostp
= n_string_push_c(ostp
, '"');
817 }else if(tcurr
->t_f
& a_T_SPECIAL
)
820 /* Can we use a fast join mode? */
821 for(tp
= tcurr
; tcurr
!= NULL
; tcurr
= tcurr
->t_next
){
822 if(!(tcurr
->t_f
& a_T_TATOM
))
825 ostp
= n_string_push_c(ostp
, ' ');
826 ostp
= n_string_push_buf(ostp
, &cp1st
[tcurr
->t_start
],
827 (tcurr
->t_end
- tcurr
->t_start
));
832 n_lofi_snap_unroll(lofi_snap
);
834 agp
->ag_input
= n_string_cp(ostp
);
835 agp
->ag_ilen
= ostp
->s_len
;
836 /*ostp = n_string_drop_ownership(ostp);*/
838 /* Name and domain must be non-empty, the second */
839 cp
= &agp
->ag_input
[agp
->ag_iaddr_start
];
840 cpmax
= &agp
->ag_input
[agp
->ag_iaddr_aend
];
841 if(*cp
== '@' || &cp
[2] > cpmax
|| cpmax
[-1] == '@'){
843 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
, c
.u
);
847 agp
->ag_skinned
= savestrbuf(cp
, PTR2SIZE(cpmax
- cp
));
849 /* TODO This parser is a mess. We do not know whether this is truly
850 * TODO valid, and all our checks are not truly RFC conforming.
851 * TODO Do check the skinned thing by itself once more, in order
852 * TODO to catch problems from reordering, e.g., this additional
853 * TODO test catches a final address without AT..
854 * TODO This is a plain copy+paste of the weird thing above, no care */
855 agp
->ag_n_flags
&= ~NAME_ADDRSPEC_ISADDR
;
856 in_domain
= hadat
= 0;
857 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
859 in_quote
= !in_quote
;
860 } else if (c
.u
< 040 || c
.u
>= 0177) {
862 } else if (in_domain
== 2) {
863 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
865 } else if (in_quote
&& in_domain
== 0) {
867 } else if (c
.c
== '\\' && *p
!= '\0') {
869 } else if (c
.c
== '@') {
871 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
875 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISADDR
; /* TODO .. really? */
876 in_domain
= (*p
== '[') ? 2 : 1;
878 } else if (c
.c
== '(' || c
.c
== ')' || c
.c
== '<' || c
.c
== '>' ||
879 c
.c
== '[' || c
.c
== ']' || c
.c
== ':' || c
.c
== ';' ||
880 c
.c
== '\\' || c
.c
== ',' || blankchar(c
.c
))
885 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
886 else if(!(agp
->ag_n_flags
& NAME_ADDRSPEC_ISADDR
))
887 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
892 return ((agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) == 0);
896 a_gethfield(FILE *f
, char **linebuf
, size_t *linesize
, long rem
, char **colon
)
898 char *line2
= NULL
, *cp
, *cp2
;
899 size_t line2size
= 0;
903 if (*linebuf
== NULL
)
904 *linebuf
= srealloc(*linebuf
, *linesize
= 1);
911 if ((c
= readline_restart(f
, linebuf
, linesize
, 0)) <= 0) {
915 for (cp
= *linebuf
; fieldnamechar(*cp
); ++cp
)
918 while (blankchar(*cp
))
920 if (*cp
!= ':' || cp
== *linebuf
)
923 /* I guess we got a headline. Handle wraparound */
928 while (PTRCMP(--cp
, >=, *linebuf
) && blankchar(*cp
))
933 if (PTRCMP(cp
- 8, >=, *linebuf
) && cp
[-1] == '=' && cp
[-2] == '?')
935 ungetc(c
= getc(f
), f
);
938 c
= readline_restart(f
, &line2
, &line2size
, 0); /* TODO linepool! */
942 for (cp2
= line2
; blankchar(*cp2
); ++cp2
)
944 c
-= (int)PTR2SIZE(cp2
- line2
);
945 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
947 if (PTRCMP(cp
+ c
, >=, *linebuf
+ *linesize
- 2)) {
948 size_t diff
= PTR2SIZE(cp
- *linebuf
),
949 colondiff
= PTR2SIZE(*colon
- *linebuf
);
950 *linebuf
= srealloc(*linebuf
, *linesize
+= c
+ 2);
951 cp
= &(*linebuf
)[diff
];
952 *colon
= &(*linebuf
)[colondiff
];
970 msgidnextc(char const **cp
, int *status
)
977 assert(status
!= NULL
);
995 *cp
= skip_comment(&(*cp
)[1]);
1011 c
= *(*cp
)++ & 0377;
1012 c
= (*status
& 02) ? lowerconv(c
) : c
;
1022 charcount(char *str
, int c
)
1028 for (i
= 0, cp
= str
; *cp
; ++cp
)
1036 nexttoken(char const *cp
)
1055 } while (nesting
> 0 && *cp
!= '\0'); /* XXX error? */
1056 } else if (blankchar(*cp
) || *cp
== ',')
1066 myaddrs(struct header
*hp
) /* TODO */
1069 char const *rv
, *mta
;
1072 if (hp
!= NULL
&& (np
= hp
->h_from
) != NULL
) {
1073 if ((rv
= np
->n_fullname
) != NULL
)
1075 if ((rv
= np
->n_name
) != NULL
)
1079 if((rv
= ok_vlook(from
)) != NULL
){
1080 if((np
= lextract(rv
, GEXTRA
| GFULL
)) == NULL
)
1082 n_err(_("An address given in *from* is invalid: %s\n"), rv
);
1083 else for(; np
!= NULL
; np
= np
->n_flink
)
1084 if(is_addr_invalid(np
, EACM_STRICT
| EACM_NOLOG
| EACM_NONAME
))
1089 /* When invoking *sendmail* directly, it's its task to generate an otherwise
1090 * undeterminable From: address. However, if the user sets *hostname*,
1091 * accept his desire */
1092 if (ok_vlook(hostname
) != NULL
)
1094 if (ok_vlook(smtp
) != NULL
|| /* TODO obsolete -> mta */
1095 /* TODO pretty hacky for now (this entire fun), later: url_creat()! */
1096 ((mta
= n_servbyname(ok_vlook(mta
), NULL
)) != NULL
&& *mta
!= '\0'))
1104 char const *hn
, *ln
;
1107 hn
= n_nodename(TRU1
);
1108 ln
= ok_vlook(LOGNAME
);
1109 i
= strlen(ln
) + strlen(hn
) + 1 +1;
1110 rv
= cp
= salloc(i
);
1111 sstpcpy(sstpcpy(sstpcpy(cp
, ln
), n_at
), hn
);
1117 myorigin(struct header
*hp
) /* TODO */
1119 char const *rv
= NULL
, *ccp
;
1123 if((ccp
= myaddrs(hp
)) != NULL
&&
1124 (np
= lextract(ccp
, GEXTRA
| GFULL
)) != NULL
){
1125 if(np
->n_flink
== NULL
)
1127 else if((ccp
= ok_vlook(sender
)) != NULL
) {
1128 if((np
= lextract(ccp
, GEXTRA
| GFULL
)) == NULL
||
1129 np
->n_flink
!= NULL
||
1130 is_addr_invalid(np
, EACM_STRICT
| EACM_NOLOG
| EACM_NONAME
))
1131 n_err(_("The address given in *sender* is invalid: %s\n"), ccp
);
1141 is_head(char const *linebuf
, size_t linelen
, bool_t check_rfc4155
)
1143 char date
[n_FROM_DATEBUF
];
1147 if ((rv
= (linelen
>= 5 && !memcmp(linebuf
, "From ", 5))) && check_rfc4155
&&
1148 (extract_date_from_from_(linebuf
, linelen
, date
) <= 0 ||
1156 extract_date_from_from_(char const *line
, size_t linelen
,
1157 char datebuf
[n_FROM_DATEBUF
])
1160 char const *cp
= line
;
1166 cp
= _from__skipword(cp
);
1170 cp
= _from__skipword(cp
);
1173 if((cp
[0] == 't' || cp
[0] == 'T') && (cp
[1] == 't' || cp
[1] == 'T') &&
1174 (cp
[2] == 'y' || cp
[2] == 'Y')){
1175 cp
= _from__skipword(cp
);
1179 /* It seems there are invalid MBOX archives in the wild, compare
1180 * . http://bugs.debian.org/624111
1181 * . [Mutt] #3868: mutt should error if the imported mailbox is invalid
1182 * What they do is that they obfuscate the address to "name at host",
1183 * and even "name at host dot dom dot dom.
1184 * The [Aa][Tt] is also RFC 733, so be tolerant */
1185 else if((cp
[0] == 'a' || cp
[0] == 'A') && (cp
[1] == 't' || cp
[1] == 'T') &&
1190 cp
= _from__skipword(cp
);
1193 if((cp
[0] == 'd' || cp
[0] == 'D') && (cp
[1] == 'o' || cp
[1] == 'O') &&
1194 (cp
[2] == 't' || cp
[2] == 'T') && cp
[3] == ' '){
1200 linelen
-= PTR2SIZE(cp
- line
);
1201 if (linelen
< a_HEAD_DATE_MINLEN
)
1203 if (cp
[linelen
- 1] == '\n') {
1205 /* (Rather IMAP/POP3 only) */
1206 if (cp
[linelen
- 1] == '\r')
1208 if (linelen
< a_HEAD_DATE_MINLEN
)
1211 if (linelen
>= n_FROM_DATEBUF
)
1215 memcpy(datebuf
, cp
, linelen
);
1216 datebuf
[linelen
] = '\0';
1220 cp
= _("<Unknown date>");
1221 linelen
= strlen(cp
);
1222 if (linelen
>= n_FROM_DATEBUF
)
1223 linelen
= n_FROM_DATEBUF
;
1229 extract_header(FILE *fp
, struct header
*hp
, si8_t
*checkaddr_err
)
1231 /* See the prototype declaration for the hairy relationship of
1232 * n_poption&n_PO_t_FLAG and/or n_psonce&n_PSO_t_FLAG in here */
1233 struct n_header_field
**hftail
;
1234 struct header nh
, *hq
= &nh
;
1235 char *linebuf
= NULL
/* TODO line pool */, *colon
;
1236 size_t linesize
= 0, seenfields
= 0;
1239 char const *val
, *cp
;
1242 memset(hq
, 0, sizeof *hq
);
1243 if ((n_psonce
& n_PSO_t_FLAG
) && (n_poption
& n_PO_t_FLAG
)) {
1244 hq
->h_to
= hp
->h_to
;
1245 hq
->h_cc
= hp
->h_cc
;
1246 hq
->h_bcc
= hp
->h_bcc
;
1248 hftail
= &hq
->h_user_headers
;
1250 for (lc
= 0; readline_restart(fp
, &linebuf
, &linesize
, 0) > 0; ++lc
)
1253 /* TODO yippieia, cat(check(lextract)) :-) */
1255 while ((lc
= a_gethfield(fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
1258 /* We explicitly allow EAF_NAME for some addressees since aliases are not
1259 * yet expanded when we parse these! */
1260 if ((val
= thisfield(linebuf
, "to")) != NULL
) {
1262 hq
->h_to
= cat(hq
->h_to
, checkaddrs(lextract(val
, GTO
| GFULL
),
1263 EACM_NORMAL
| EAF_NAME
| EAF_MAYKEEP
, checkaddr_err
));
1264 } else if ((val
= thisfield(linebuf
, "cc")) != NULL
) {
1266 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(lextract(val
, GCC
| GFULL
),
1267 EACM_NORMAL
| EAF_NAME
| EAF_MAYKEEP
, checkaddr_err
));
1268 } else if ((val
= thisfield(linebuf
, "bcc")) != NULL
) {
1270 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(lextract(val
, GBCC
| GFULL
),
1271 EACM_NORMAL
| EAF_NAME
| EAF_MAYKEEP
, checkaddr_err
));
1272 } else if ((val
= thisfield(linebuf
, "from")) != NULL
) {
1273 if (!(n_psonce
& n_PSO_t_FLAG
) || (n_poption
& n_PO_t_FLAG
)) {
1275 hq
->h_from
= cat(hq
->h_from
,
1276 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
1277 EACM_STRICT
, NULL
));
1279 } else if ((val
= thisfield(linebuf
, "reply-to")) != NULL
) {
1281 hq
->h_reply_to
= cat(hq
->h_reply_to
,
1282 checkaddrs(lextract(val
, GEXTRA
| GFULL
), EACM_STRICT
, NULL
));
1283 } else if ((val
= thisfield(linebuf
, "sender")) != NULL
) {
1284 if (!(n_psonce
& n_PSO_t_FLAG
) || (n_poption
& n_PO_t_FLAG
)) {
1286 hq
->h_sender
= cat(hq
->h_sender
, /* TODO cat? check! */
1287 checkaddrs(lextract(val
, GEXTRA
| GFULL
| GFULLEXTRA
),
1288 EACM_STRICT
, NULL
));
1291 } else if ((val
= thisfield(linebuf
, "subject")) != NULL
||
1292 (val
= thisfield(linebuf
, "subj")) != NULL
) {
1294 for (cp
= val
; blankchar(*cp
); ++cp
)
1296 hq
->h_subject
= (hq
->h_subject
!= NULL
)
1297 ? save2str(hq
->h_subject
, cp
) : savestr(cp
);
1299 /* The remaining are mostly hacked in and thus TODO -- at least in
1300 * TODO respect to their content checking */
1301 else if((val
= thisfield(linebuf
, "message-id")) != NULL
){
1302 if(n_psonce
& n_PSO_t_FLAG
){
1303 np
= checkaddrs(lextract(val
, GREF
),
1304 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1306 if (np
== NULL
|| np
->n_flink
!= NULL
)
1309 hq
->h_message_id
= np
;
1312 }else if((val
= thisfield(linebuf
, "in-reply-to")) != NULL
){
1313 if(n_psonce
& n_PSO_t_FLAG
){
1314 np
= checkaddrs(lextract(val
, GREF
),
1315 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1318 hq
->h_in_reply_to
= np
;
1321 }else if((val
= thisfield(linebuf
, "references")) != NULL
){
1322 if(n_psonce
& n_PSO_t_FLAG
){
1324 /* TODO Limit number of references TODO better on parser side */
1325 hq
->h_ref
= cat(hq
->h_ref
, checkaddrs(extract(val
, GREF
),
1326 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG
| EACM_NONAME
,
1331 /* and that is very hairy */
1332 else if((val
= thisfield(linebuf
, "mail-followup-to")) != NULL
){
1333 if(n_psonce
& n_PSO_t_FLAG
){
1335 hq
->h_mft
= cat(hq
->h_mft
, checkaddrs(lextract(val
, GEXTRA
| GFULL
),
1336 /*EACM_STRICT | TODO '/' valid!! | EACM_NOLOG | */EACM_NONAME
,
1341 /* A free-form header; a_gethfield() did some verification already.. */
1343 struct n_header_field
*hfp
;
1347 for(nstart
= cp
= linebuf
;; ++cp
)
1348 if(!fieldnamechar(*cp
))
1350 nl
= (ui32_t
)PTR2SIZE(cp
- nstart
);
1352 while(blankchar(*cp
))
1356 n_err(_("Ignoring header field: %s\n"), linebuf
);
1359 while(blankchar(*cp
))
1361 bl
= (ui32_t
)strlen(cp
) +1;
1364 *hftail
= hfp
= salloc(n_VSTRUCT_SIZEOF(struct n_header_field
, hf_dat
1366 hftail
= &hfp
->hf_next
;
1367 hfp
->hf_next
= NULL
;
1369 hfp
->hf_bl
= bl
- 1;
1370 memcpy(hfp
->hf_dat
, nstart
, nl
);
1371 hfp
->hf_dat
[nl
++] = '\0';
1372 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);
1376 /* In case the blank line after the header has been edited out. Otherwise,
1377 * fetch the header separator */
1378 if (linebuf
!= NULL
) {
1379 if (linebuf
[0] != '\0') {
1380 for (cp
= linebuf
; *(++cp
) != '\0';)
1382 fseek(fp
, (long)-PTR2SIZE(1 + cp
- linebuf
), SEEK_CUR
);
1384 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
1389 if (seenfields
> 0 && (checkaddr_err
== NULL
|| *checkaddr_err
== 0)) {
1390 hp
->h_to
= hq
->h_to
;
1391 hp
->h_cc
= hq
->h_cc
;
1392 hp
->h_bcc
= hq
->h_bcc
;
1393 hp
->h_from
= hq
->h_from
;
1394 hp
->h_reply_to
= hq
->h_reply_to
;
1395 hp
->h_sender
= hq
->h_sender
;
1396 if (hq
->h_subject
!= NULL
|| !(n_psonce
& n_PSO_t_FLAG
) ||
1397 !(n_poption
& n_PO_t_FLAG
))
1398 hp
->h_subject
= hq
->h_subject
;
1399 hp
->h_user_headers
= hq
->h_user_headers
;
1401 if (n_psonce
& n_PSO_t_FLAG
) {
1402 hp
->h_ref
= hq
->h_ref
;
1403 hp
->h_message_id
= hq
->h_message_id
;
1404 hp
->h_in_reply_to
= hq
->h_in_reply_to
;
1405 hp
->h_mft
= hq
->h_mft
;
1407 /* And perform additional validity checks so that we don't bail later
1408 * on TODO this is good and the place where this should occur,
1409 * TODO unfortunately a lot of other places do again and blabla */
1410 if (hp
->h_from
== NULL
)
1411 hp
->h_from
= n_poption_arg_r
;
1412 else if (hp
->h_from
->n_flink
!= NULL
&& hp
->h_sender
== NULL
)
1413 hp
->h_sender
= lextract(ok_vlook(sender
),
1414 GEXTRA
| GFULL
| GFULLEXTRA
);
1417 n_err(_("Restoring deleted header lines\n"));
1419 if (linebuf
!= NULL
)
1425 hfield_mult(char const *field
, struct message
*mp
, int mult
)
1430 size_t linesize
= 0; /* TODO line pool */
1431 char *linebuf
= NULL
, *colon
;
1435 /* There are (spam) messages which have header bytes which are many KB when
1436 * joined, so resize a single heap storage until we are done if we shall
1437 * collect a field that may have multiple bodies; only otherwise use the
1438 * string dope directly */
1439 memset(&hfs
, 0, sizeof hfs
);
1441 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1443 if ((lc
= mp
->m_lines
- 1) < 0)
1446 if ((mp
->m_flag
& MNOFROM
) == 0 &&
1447 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1450 if ((lc
= a_gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
)) < 0)
1452 if ((hfield
= thisfield(linebuf
, field
)) != NULL
&& *hfield
!= '\0') {
1454 n_str_add_buf(&hfs
, hfield
, strlen(hfield
));
1456 hfs
.s
= savestr(hfield
);
1463 if (linebuf
!= NULL
)
1465 if (mult
&& hfs
.s
!= NULL
) {
1466 colon
= savestrbuf(hfs
.s
, hfs
.l
);
1475 thisfield(char const *linebuf
, char const *field
)
1477 char const *rv
= NULL
;
1480 while (lowerconv(*linebuf
) == lowerconv(*field
)) {
1487 while (blankchar(*linebuf
))
1489 if (*linebuf
++ != ':')
1492 while (blankchar(*linebuf
)) /* TODO header parser.. strip trailing WS?!? */
1501 nameof(struct message
*mp
, int reptype
)
1506 cp
= skin(name1(mp
, reptype
));
1507 if (reptype
!= 0 || charcount(cp
, '!') < 2)
1509 cp2
= strrchr(cp
, '!');
1511 while (cp2
> cp
&& *cp2
!= '!')
1521 skip_comment(char const *cp
)
1526 for (nesting
= 1; nesting
> 0 && *cp
; ++cp
) {
1545 routeaddr(char const *name
)
1547 char const *np
, *rp
= NULL
;
1550 for (np
= name
; *np
; np
++) {
1553 np
= skip_comment(np
+ 1) - 1;
1559 if (*np
== '\\' && np
[1])
1576 FL
enum expand_addr_flags
1577 expandaddr_to_eaf(void)
1580 char const *eafd_name
;
1581 bool_t eafd_is_target
;
1585 {"restrict", FAL0
, EAF_TARGET_MASK
, EAF_RESTRICT
| EAF_RESTRICT_TARGETS
},
1586 {"fail", FAL0
, EAF_NONE
, EAF_FAIL
},
1587 {"failinvaddr", FAL0
, EAF_NONE
, EAF_FAILINVADDR
| EAF_ADDR
},
1588 {"all", TRU1
, EAF_NONE
, EAF_TARGET_MASK
},
1589 {"file", TRU1
, EAF_NONE
, EAF_FILE
},
1590 {"pipe", TRU1
, EAF_NONE
, EAF_PIPE
},
1591 {"name", TRU1
, EAF_NONE
, EAF_NAME
},
1592 {"addr", TRU1
, EAF_NONE
, EAF_ADDR
}
1596 enum expand_addr_flags rv
;
1600 if ((cp
= ok_vlook(expandaddr
)) == NULL
)
1601 rv
= EAF_RESTRICT_TARGETS
;
1602 else if (*cp
== '\0')
1603 rv
= EAF_TARGET_MASK
;
1605 rv
= EAF_TARGET_MASK
;
1607 for (buf
= savestr(cp
); (cp
= n_strsep(&buf
, ',', TRU1
)) != NULL
;) {
1610 if ((minus
= (*cp
== '-')) || *cp
== '+')
1612 for (eafp
= eafa
;; ++eafp
) {
1613 if (eafp
== eafa
+ n_NELEM(eafa
)) {
1614 if (n_poption
& n_PO_D_V
)
1615 n_err(_("Unknown *expandaddr* value: %s\n"), cp
);
1617 } else if (!asccasecmp(cp
, eafp
->eafd_name
)) {
1619 rv
&= ~eafp
->eafd_andoff
;
1620 rv
|= eafp
->eafd_or
;
1622 if (eafp
->eafd_is_target
)
1623 rv
&= ~eafp
->eafd_or
;
1624 else if (n_poption
& n_PO_D_V
)
1625 n_err(_("minus - prefix invalid for *expandaddr* value: "
1629 } else if (!asccasecmp(cp
, "noalias")) { /* TODO v15 OBSOLETE */
1630 n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
1637 if((rv
& EAF_RESTRICT
) && ((n_psonce
& n_PSO_INTERACTIVE
) ||
1638 (n_poption
& n_PO_TILDE_FLAG
)))
1639 rv
|= EAF_TARGET_MASK
;
1640 else if(n_poption
& n_PO_D_V
){
1641 if(!(rv
& EAF_TARGET_MASK
))
1642 n_err(_("*expandaddr* doesn't allow any addressees\n"));
1643 else if((rv
& EAF_FAIL
) && (rv
& EAF_TARGET_MASK
) == EAF_TARGET_MASK
)
1644 n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
1652 is_addr_invalid(struct name
*np
, enum expand_addr_check_mode eacm
)
1654 char cbuf
[sizeof "'\\U12340'"];
1658 enum expand_addr_flags eaf
;
1661 eaf
= expandaddr_to_eaf();
1664 if ((rv
= ((f
& NAME_ADDRSPEC_INVALID
) != 0))) {
1665 if (eaf
& EAF_FAILINVADDR
)
1668 if ((eacm
& EACM_NOLOG
) || (f
& NAME_ADDRSPEC_ERR_EMPTY
)) {
1672 char const *fmt
= "'\\x%02X'";
1673 bool_t ok8bit
= TRU1
;
1675 if (f
& NAME_ADDRSPEC_ERR_IDNA
) {
1676 cs
= _("Invalid domain name: %s, character %s\n");
1679 } else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
1680 cs
= _("%s contains invalid %s sequence\n");
1681 else if (f
& NAME_ADDRSPEC_ERR_NAME
) {
1682 cs
= _("%s is an invalid alias name\n");
1684 cs
= _("%s contains invalid byte %s\n");
1686 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
1687 snprintf(cbuf
, sizeof cbuf
,
1688 (ok8bit
&& c
>= 040 && c
<= 0177 ? "'%c'" : fmt
), c
);
1694 /* *expandaddr* stuff */
1695 if (!(rv
= ((eacm
& EACM_MODE_MASK
) != EACM_NONE
)))
1698 if ((eacm
& EACM_STRICT
) && (f
& NAME_ADDRSPEC_ISFILEORPIPE
)) {
1701 cs
= _("%s%s: file or pipe addressees not allowed here\n");
1702 if (eacm
& EACM_NOLOG
)
1708 eaf
|= (eacm
& EAF_TARGET_MASK
);
1709 if (eacm
& EACM_NONAME
)
1712 if (eaf
== EAF_NONE
) {
1719 if (!(eaf
& EAF_FILE
) && (f
& NAME_ADDRSPEC_ISFILE
)) {
1720 cs
= _("%s%s: *expandaddr* doesn't allow file target\n");
1721 if (eacm
& EACM_NOLOG
)
1723 } else if (!(eaf
& EAF_PIPE
) && (f
& NAME_ADDRSPEC_ISPIPE
)) {
1724 cs
= _("%s%s: *expandaddr* doesn't allow command pipe target\n");
1725 if (eacm
& EACM_NOLOG
)
1727 } else if (!(eaf
& EAF_NAME
) && (f
& NAME_ADDRSPEC_ISNAME
)) {
1728 cs
= _("%s%s: *expandaddr* doesn't allow user name target\n");
1729 if (eacm
& EACM_NOLOG
)
1731 } else if (!(eaf
& EAF_ADDR
) && (f
& NAME_ADDRSPEC_ISADDR
)) {
1732 cs
= _("%s%s: *expandaddr* doesn't allow mail address target\n");
1733 if (eacm
& EACM_NOLOG
)
1743 n_err(cs
, n_shexp_quote_cp(np
->n_name
, TRU1
), cbuf
);
1750 skin(char const *name
)
1752 struct n_addrguts ag
;
1757 /*name =*/ n_addrspec_with_guts(&ag
, name
, TRU1
, FAL0
);
1759 if(!(ag
.ag_n_flags
& NAME_NAME_SALLOC
))
1760 rv
= savestrbuf(rv
, ag
.ag_slen
);
1767 /* TODO addrspec_with_guts: RFC 5322
1768 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC.!!! */
1770 n_addrspec_with_guts(struct n_addrguts
*agp
, char const *name
, bool_t doskin
,
1771 bool_t issingle_hack
){
1773 char *cp2
, *bufend
, *nbuf
, c
;
1783 memset(agp
, 0, sizeof *agp
);
1785 if((agp
->ag_input
= name
) == NULL
|| (agp
->ag_ilen
= strlen(name
)) == 0){
1786 agp
->ag_skinned
= n_UNCONST(n_empty
); /* ok: NAME_SALLOC is not set */
1788 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
1789 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
1792 /*agp->ag_iaddr_start = 0;*/
1793 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1794 agp
->ag_skinned
= n_UNCONST(name
); /* (NAME_SALLOC not set) */
1795 agp
->ag_slen
= agp
->ag_ilen
;
1796 agp
->ag_n_flags
= NAME_SKINNED
;
1801 nbuf
= n_lofi_alloc(agp
->ag_ilen
+1);
1802 /*agp->ag_iaddr_start = 0;*/
1803 cp2
= bufend
= nbuf
;
1805 /* TODO This is complete crap and should use a token parser */
1806 for(cp
= name
++; (c
= *cp
++) != '\0';){
1809 cp
= skip_comment(cp
);
1813 /* Start of a "quoted-string". Copy it in its entirety */
1814 /* XXX RFC: quotes are "semantically invisible"
1815 * XXX But it was explicitly added (Changelog.Heirloom,
1816 * XXX [9.23] released 11/15/00, "Do not remove quotes
1817 * XXX when skinning names"? No more info.. */
1819 while ((c
= *cp
) != '\0') { /* TODO improve */
1827 else if ((c
= *cp
) != '\0') {
1836 if((flags
& (a_GOTADDR
| a_GOTSPACE
)) == a_GOTADDR
){
1837 flags
|= a_GOTSPACE
;
1838 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1840 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
1841 cp
+= 3, *cp2
++ = '@';
1842 else if (cp
[0] == '@' && blankchar(cp
[1]))
1843 cp
+= 2, *cp2
++ = '@';
1848 agp
->ag_iaddr_start
= PTR2SIZE(cp
- (name
- 1));
1850 flags
&= ~(a_GOTSPACE
| a_LASTSP
);
1851 flags
|= a_GOTLT
| a_GOTADDR
;
1854 if(flags
& a_GOTLT
){
1855 /* (_addrspec_check() verifies these later!) */
1856 flags
&= ~(a_GOTLT
| a_LASTSP
);
1857 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1859 /* Skip over the entire remaining field */
1860 while((c
= *cp
) != '\0' && c
!= ','){
1863 cp
= skip_comment(cp
);
1865 while ((c
= *cp
) != '\0') {
1869 if (c
== '\\' && *cp
!= '\0')
1877 if(flags
& a_LASTSP
){
1879 if(flags
& a_GOTADDR
)
1883 /* This character is forbidden here, but it may nonetheless be
1884 * present: ensure we turn this into something valid! (E.g., if the
1885 * next character would be a "..) */
1886 if(c
== '\\' && *cp
!= '\0')
1888 if(c
== ',' && !issingle_hack
){
1889 if(!(flags
& a_GOTLT
)){
1891 for(; blankchar(*cp
); ++cp
)
1896 }else if(!(flags
& a_GOTADDR
)){
1898 agp
->ag_iaddr_start
= PTR2SIZE(cp
- name
);
1903 agp
->ag_slen
= PTR2SIZE(cp2
- nbuf
);
1904 if (agp
->ag_iaddr_aend
== 0)
1905 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1907 else if (agp
->ag_iaddr_aend
< agp
->ag_iaddr_start
) {
1908 cp2
= n_autorec_alloc(agp
->ag_ilen
+ 1 +1);
1909 memcpy(cp2
, agp
->ag_input
, agp
->ag_ilen
);
1910 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1911 cp2
[agp
->ag_ilen
++] = '>';
1912 cp2
[agp
->ag_ilen
] = '\0';
1914 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
1916 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
1918 if(a_head_addrspec_check(agp
, doskin
) <= FAL0
)
1921 name
= agp
->ag_input
;
1928 realname(char const *name
)
1930 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
1933 int quoted
, good
, nogood
;
1936 if ((cp
= n_UNCONST(name
)) == NULL
)
1938 for (; *cp
!= '\0'; ++cp
) {
1941 if (cstart
!= NULL
) {
1942 /* More than one comment in address, doesn't make sense to display
1943 * it without context. Return the entire field */
1944 cp
= mime_fromaddr(name
);
1948 cp
= skip_comment(cp
);
1951 cend
= cstart
= NULL
;
1957 if (*cp
== '\\' && cp
[1])
1968 /* More than one address. Just use the first one */
1974 if (cstart
== NULL
) {
1976 /* If name contains only a route-addr, the surrounding angle brackets
1977 * don't serve any useful purpose when displaying, so remove */
1978 cp
= prstr(skin(name
));
1980 cp
= mime_fromaddr(name
);
1984 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1985 * not stripped. The idea is to strip only syntactical relevant things (but
1986 * this is not necessarily the most sensible way in practice) */
1987 rp
= rname
= ac_alloc(PTR2SIZE(cend
- cstart
+1));
1989 for (cp
= cstart
; cp
< cend
; ++cp
) {
1990 if (*cp
== '(' && !quoted
) {
1991 cq
= skip_comment(++cp
);
1992 if (PTRCMP(--cq
, >, cend
))
1995 if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cq
))
1999 } else if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cend
))
2001 else if (*cp
== '"') {
2010 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
2012 rname
= savestr(out
.s
);
2015 while (blankchar(*rname
))
2017 for (rp
= rname
; *rp
!= '\0'; ++rp
)
2019 while (PTRCMP(--rp
, >=, rname
) && blankchar(*rp
))
2022 cp
= mime_fromaddr(name
);
2026 /* mime_fromhdr() has converted all nonprintable characters to question
2027 * marks now. These and blanks are considered uninteresting; if the
2028 * displayed part of the real name contains more than 25% of them, it is
2029 * probably better to display the plain email address instead */
2032 for (rp
= rname
; *rp
!= '\0' && PTRCMP(rp
, <, rname
+ 20); ++rp
)
2033 if (*rp
== '?' || blankchar(*rp
))
2037 cp
= (good
* 3 < nogood
) ? prstr(skin(name
)) : rname
;
2040 return n_UNCONST(cp
);
2044 name1(struct message
*mp
, int reptype
)
2046 char *namebuf
, *cp
, *cp2
, *linebuf
= NULL
/* TODO line pool */;
2047 size_t namesize
, linesize
= 0;
2052 if ((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
2054 if (reptype
== 0 && (cp
= hfield1("sender", mp
)) != NULL
&& *cp
!= '\0')
2057 namebuf
= smalloc(namesize
= 1);
2059 if (mp
->m_flag
& MNOFROM
)
2061 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
2063 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
2067 if (namesize
<= linesize
)
2068 namebuf
= srealloc(namebuf
, namesize
= linesize
+1);
2069 for (cp
= linebuf
; *cp
!= '\0' && *cp
!= ' '; ++cp
)
2071 for (; blankchar(*cp
); ++cp
)
2073 for (cp2
= namebuf
+ strlen(namebuf
);
2074 *cp
&& !blankchar(*cp
) && PTRCMP(cp2
, <, namebuf
+ namesize
-1);)
2078 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
2080 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
2082 if (strncmp(cp
, "From", 4))
2084 if (namesize
<= linesize
)
2085 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
2087 while ((cp
= strchr(cp
, 'r')) != NULL
) {
2088 if (!strncmp(cp
, "remote", 6)) {
2089 if ((cp
= strchr(cp
, 'f')) == NULL
)
2091 if (strncmp(cp
, "from", 4) != 0)
2093 if ((cp
= strchr(cp
, ' ')) == NULL
)
2097 strncpy(namebuf
, cp
, namesize
);
2100 cp2
= strrchr(namebuf
, '!') + 1;
2101 strncpy(cp2
, cp
, PTR2SIZE(namebuf
+ namesize
- cp2
));
2103 namebuf
[namesize
- 2] = '!';
2104 namebuf
[namesize
- 1] = '\0';
2110 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
2112 cp
= savestr(namebuf
);
2114 if (linebuf
!= NULL
)
2123 subject_re_trim(char const *s
){
2127 }const *pp
, ignored
[] = { /* Update *reply-strings* manual upon change! */
2129 {3, "aw:"}, {5, "antw:"}, /* de */
2130 {3, "wg:"}, /* Seen too often in the wild */
2135 char *re_st
, *re_st_x
;
2145 if((re_st_x
= ok_vlook(reply_strings
)) != NULL
&&
2146 (re_l
= strlen(re_st_x
)) > 0){
2147 re_st
= n_lofi_alloc(++re_l
* 2);
2148 memcpy(re_st
, re_st_x
, re_l
);
2153 while(spacechar(*s
))
2156 for(pp
= ignored
; pp
->len
> 0; ++pp
)
2157 if(is_asccaseprefix(pp
->dat
, s
)){
2166 memcpy(re_st_x
= &re_st
[re_l
], re_st
, re_l
);
2167 while((cp
= n_strsep(&re_st_x
, ',', TRU1
)) != NULL
)
2168 if(is_asccaseprefix(cp
, s
)){
2180 return any
? s
: orig_s
;
2184 msgidcmp(char const *s1
, char const *s2
)
2186 int q1
= 0, q2
= 0, c1
, c2
;
2195 c1
= msgidnextc(&s1
, &q1
);
2196 c2
= msgidnextc(&s2
, &q2
);
2205 fakefrom(struct message
*mp
)
2210 if (((name
= skin(hfield1("return-path", mp
))) == NULL
|| *name
== '\0' ) &&
2211 ((name
= skin(hfield1("from", mp
))) == NULL
|| *name
== '\0'))
2212 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
2213 * RFC 4155 however requires a RFC 5322 (2822) conforming
2214 * "addr-spec", but we simply can't provide that */
2215 name
= "MAILER-DAEMON";
2227 for (cq
= cp
; *cq
!= '\0' && *cq
!= '\n'; ++cq
)
2235 #if defined HAVE_IMAP_SEARCH || defined HAVE_IMAP
2237 unixtime(char const *fromline
)
2239 char const *fp
, *xp
;
2241 si32_t i
, year
, month
, day
, hour
, minute
, second
, tzdiff
;
2245 for (fp
= fromline
; *fp
!= '\0' && *fp
!= '\n'; ++fp
)
2248 if (PTR2SIZE(fp
- fromline
) < 7)
2253 if (!strncmp(fp
+ 4, n_month_names
[i
], 3))
2255 if (n_month_names
[++i
][0] == '\0')
2261 n_idec_si32_cp(&day
, &fp
[8], 10, &xp
);
2262 if (*xp
!= ' ' || xp
!= fp
+ 10)
2264 n_idec_si32_cp(&hour
, &fp
[11], 10, &xp
);
2265 if (*xp
!= ':' || xp
!= fp
+ 13)
2267 n_idec_si32_cp(&minute
, &fp
[14], 10, &xp
);
2268 if (*xp
!= ':' || xp
!= fp
+ 16)
2270 n_idec_si32_cp(&second
, &fp
[17], 10, &xp
);
2271 if (*xp
!= ' ' || xp
!= fp
+ 19)
2273 n_idec_si32_cp(&year
, &fp
[20], 10, &xp
);
2276 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
2278 tzdiff
= t
- mktime(gmtime(&t
));
2279 tmptr
= localtime(&t
);
2280 if (tmptr
->tm_isdst
> 0)
2290 #endif /* HAVE_IMAP_SEARCH || HAVE_IMAP */
2293 rfctime(char const *date
) /* TODO n_idec_ return tests */
2297 si32_t i
, year
, month
, day
, hour
, minute
, second
;
2302 if ((cp
= nexttoken(cp
)) == NULL
)
2304 if (alphachar(cp
[0]) && alphachar(cp
[1]) && alphachar(cp
[2]) &&
2306 if ((cp
= nexttoken(&cp
[4])) == NULL
)
2309 n_idec_si32_cp(&day
, cp
, 10, &x
);
2310 if ((cp
= nexttoken(x
)) == NULL
)
2313 if (!strncmp(cp
, n_month_names
[i
], 3))
2315 if (n_month_names
[++i
][0] == '\0')
2319 if ((cp
= nexttoken(&cp
[3])) == NULL
)
2322 * Where a two or three digit year occurs in a date, the year is to be
2323 * interpreted as follows: If a two digit year is encountered whose
2324 * value is between 00 and 49, the year is interpreted by adding 2000,
2325 * ending up with a value between 2000 and 2049. If a two digit year
2326 * is encountered with a value between 50 and 99, or any three digit
2327 * year is encountered, the year is interpreted by adding 1900 */
2328 n_idec_si32_cp(&year
, cp
, 10, &x
);
2329 i
= (int)PTR2SIZE(x
- cp
);
2330 if (i
== 2 && year
>= 0 && year
<= 49)
2332 else if (i
== 3 || (i
== 2 && year
>= 50 && year
<= 99))
2334 if ((cp
= nexttoken(x
)) == NULL
)
2336 n_idec_si32_cp(&hour
, cp
, 10, &x
);
2340 n_idec_si32_cp(&minute
, cp
, 10, &x
);
2343 n_idec_si32_cp(&second
, cp
, 10, &x
);
2347 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
2349 if ((cp
= nexttoken(x
)) != NULL
) {
2361 if (digitchar(cp
[0]) && digitchar(cp
[1]) && digitchar(cp
[2]) &&
2368 n_idec_si32_cp(&i
, buf
, 10, NULL
);
2369 tadj
= (si64_t
)i
* 3600; /* XXX */
2372 n_idec_si32_cp(&i
, buf
, 10, NULL
);
2373 tadj
+= (si64_t
)i
* 60; /* XXX */
2378 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
2379 * TODO once again, Christos Zoulas and NetBSD Mail have done
2380 * TODO a really good job already, but using strptime(3), which
2381 * TODO is not portable. Nonetheless, WE must improve, not
2382 * TODO at last because we simply ignore obsolete timezones!!
2383 * TODO See RFC 5322, 4.3! */
2394 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
){
2395 size_t const jdn_epoch
= 2440588;
2396 bool_t
const y2038p
= (sizeof(time_t) == 4);
2402 if(UICMP(32, second
, >/*XXX leap=*/, n_DATE_SECSMIN
) ||
2403 UICMP(32, minute
, >=, n_DATE_MINSHOUR
) ||
2404 UICMP(32, hour
, >=, n_DATE_HOURSDAY
) ||
2405 day
< 1 || day
> 31 ||
2406 month
< 1 || month
> 12 ||
2410 if(year
>= 1970 + ((y2038p
? SI32_MAX
: SI64_MAX
) /
2411 (n_DATE_SECSDAY
* n_DATE_DAYSYEAR
))){
2412 /* Be a coward regarding Y2038, many people (mostly myself, that is) do
2413 * test by stepping second-wise around the flip. Don't care otherwise */
2416 if(year
> 2038 || month
> 1 || day
> 19 ||
2417 hour
> 3 || minute
> 14 || second
> 7)
2422 t
+= minute
* n_DATE_SECSMIN
;
2423 t
+= hour
* n_DATE_SECSHOUR
;
2425 jdn
= a_head_gregorian_to_jdn(year
, month
, day
);
2427 t
+= (time_t)jdn
* n_DATE_SECSDAY
;
2437 substdate(struct message
*m
)
2442 /* Determine the date to print in faked 'From ' lines. This is traditionally
2443 * the date the message was written to the mail file. Try to determine this
2444 * using RFC message header fields, or fall back to current time */
2446 if ((cp
= hfield1("received", m
)) != NULL
) {
2447 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
2450 while (alnumchar(*cp
));
2453 m
->m_time
= rfctime(cp
);
2455 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
) {
2456 if ((cp
= hfield1("date", m
)) != NULL
)
2457 m
->m_time
= rfctime(cp
);
2459 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
)
2460 m
->m_time
= time_current
.tc_time
;
2465 setup_from_and_sender(struct header
*hp
)
2471 /* If -t parsed or composed From: then take it. With -t we otherwise
2472 * want -r to be honoured in favour of *from* in order to have
2473 * a behaviour that is compatible with what users would expect from e.g.
2475 if ((np
= hp
->h_from
) != NULL
||
2476 ((n_psonce
& n_PSO_t_FLAG
) && (np
= n_poption_arg_r
) != NULL
)) {
2478 } else if ((addr
= myaddrs(hp
)) != NULL
)
2479 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
2482 if ((np
= hp
->h_sender
) != NULL
) {
2484 } else if ((addr
= ok_vlook(sender
)) != NULL
)
2485 np
= lextract(addr
, GEXTRA
| GFULL
| GFULLEXTRA
);
2491 FL
struct name
const *
2492 check_from_and_sender(struct name
const *fromfield
,
2493 struct name
const *senderfield
)
2495 struct name
const *rv
= NULL
;
2498 if (senderfield
!= NULL
) {
2499 if (senderfield
->n_flink
!= NULL
) {
2500 n_err(_("The Sender: field may contain only one address\n"));
2506 if (fromfield
!= NULL
) {
2507 if (fromfield
->n_flink
!= NULL
&& senderfield
== NULL
) {
2508 n_err(_("A Sender: is required when there are multiple "
2509 "addresses in From:\n"));
2517 rv
= (struct name
*)0x1;
2525 getsender(struct message
*mp
)
2531 if ((cp
= hfield1("from", mp
)) == NULL
||
2532 (np
= lextract(cp
, GEXTRA
| GSKIN
)) == NULL
)
2535 cp
= (np
->n_flink
!= NULL
) ? skin(hfield1("sender", mp
)) : np
->n_name
;
2542 grab_headers(enum n_go_input_flags gif
, struct header
*hp
, enum gfield gflags
,
2545 /* TODO grab_headers: again, check counts etc. against RFC;
2546 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
2552 comma
= (ok_blook(bsdcompat
) || ok_blook(bsdmsgs
)) ? 0 : GCOMMA
;
2555 hp
->h_to
= grab_names(gif
, "To: ", hp
->h_to
, comma
, GTO
| GFULL
);
2556 if (subjfirst
&& (gflags
& GSUBJECT
))
2557 hp
->h_subject
= n_go_input_cp(gif
, "Subject: ", hp
->h_subject
);
2559 hp
->h_cc
= grab_names(gif
, "Cc: ", hp
->h_cc
, comma
, GCC
| GFULL
);
2561 hp
->h_bcc
= grab_names(gif
, "Bcc: ", hp
->h_bcc
, comma
, GBCC
| GFULL
);
2563 if (gflags
& GEXTRA
) {
2564 if (hp
->h_from
== NULL
)
2565 hp
->h_from
= lextract(myaddrs(hp
), GEXTRA
| GFULL
| GFULLEXTRA
);
2566 hp
->h_from
= grab_names(gif
, "From: ", hp
->h_from
, comma
,
2567 GEXTRA
| GFULL
| GFULLEXTRA
);
2568 if (hp
->h_reply_to
== NULL
) {
2569 struct name
*v15compat
;
2571 if((v15compat
= lextract(ok_vlook(replyto
), GEXTRA
| GFULL
)) != NULL
)
2572 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
2573 hp
->h_reply_to
= lextract(ok_vlook(reply_to
), GEXTRA
| GFULL
);
2574 if(hp
->h_reply_to
== NULL
) /* v15 */
2575 hp
->h_reply_to
= v15compat
;
2577 hp
->h_reply_to
= grab_names(gif
, "Reply-To: ", hp
->h_reply_to
, comma
,
2579 if (hp
->h_sender
== NULL
)
2580 hp
->h_sender
= extract(ok_vlook(sender
), GEXTRA
| GFULL
);
2581 hp
->h_sender
= grab_names(gif
, "Sender: ", hp
->h_sender
, comma
,
2585 if (!subjfirst
&& (gflags
& GSUBJECT
))
2586 hp
->h_subject
= n_go_input_cp(gif
, "Subject: ", hp
->h_subject
);
2593 n_header_match(struct message
*mp
, struct search_expr
const *sep
){
2594 struct str fiter
, in
, out
;
2599 char **linebuf
, *colon
;
2600 enum {a_NONE
, a_ALL
, a_ITER
, a_RE
} match
;
2606 linebuf
= &termios_state
.ts_linebuf
; /* XXX line pool */
2607 linesize
= &termios_state
.ts_linesize
; /* XXX line pool */
2608 n_UNINIT(fiter
.l
, 0);
2609 n_UNINIT(fiter
.s
, NULL
);
2611 if((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
2613 if((lc
= mp
->m_lines
- 1) < 0)
2616 if((mp
->m_flag
& MNOFROM
) == 0 &&
2617 readline_restart(ibuf
, linebuf
, linesize
, 0) < 0)
2621 if((field
= sep
->ss_field
) != NULL
){
2622 if(!asccasecmp(field
, "header") || (field
[0] == '<' && field
[1] == '\0'))
2625 fiter
.s
= n_lofi_alloc((fiter
.l
= strlen(field
)) +1);
2629 }else if(sep
->ss_fieldre
!= NULL
){
2635 /* Iterate over all the headers */
2639 if((lc
= a_gethfield(ibuf
, linebuf
, linesize
, lc
, &colon
)) <= 0)
2642 /* Is this a header we are interested in? */
2643 if(match
== a_ITER
){
2646 memcpy(itercp
= fiter
.s
, sep
->ss_field
, fiter
.l
+1);
2647 while((field
= n_strsep(&itercp
, ',', TRU1
)) != NULL
){
2648 /* It may be an abbreviation */
2649 char const x
[][8] = {"from", "to", "cc", "bcc", "subject"};
2653 if(field
[0] != '\0' && field
[1] == '\0'){
2654 c1
= lowerconv(field
[0]);
2655 for(i
= 0; i
< n_NELEM(x
); ++i
){
2663 if(!ascncasecmp(field
, *linebuf
, PTR2SIZE(colon
- *linebuf
)))
2669 }else if(match
== a_RE
){
2673 i
= PTR2SIZE(colon
- *linebuf
);
2674 cp
= n_lofi_alloc(i
+1);
2675 memcpy(cp
, *linebuf
, i
);
2677 i
= (regexec(sep
->ss_fieldre
, cp
, 0,NULL
, 0) != REG_NOMATCH
);
2684 /* It could be a plain existence test */
2685 if(sep
->ss_field_exists
){
2690 /* Need to check the body */
2691 while(blankchar(*++colon
))
2695 /* Shall we split into address list and match as/the addresses only?
2696 * TODO at some later time we should ignore and log efforts to search
2697 * TODO a skinned address list if we know the header has none such */
2699 if((np
= lextract(in
.s
, GSKIN
)) == NULL
)
2704 in
.l
= strlen(in
.s
);
2705 mime_fromhdr(&in
, &out
, TD_ICONV
);
2710 if(sep
->ss_bodyre
!= NULL
)
2711 rv
= (regexec(sep
->ss_bodyre
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
2714 rv
= substr(out
.s
, sep
->ss_body
);
2720 if(np
!= NULL
&& (np
= np
->n_flink
) != NULL
){
2728 n_lofi_free(fiter
.s
);
2733 FL
struct n_header_field
*
2734 n_customhdr_query(void){
2736 struct n_header_field
*rv
, **tail
, *hfp
;
2741 if((vp
= ok_vlook(customhdr
)) != NULL
){
2747 while((vp
= n_strsep_esc(&buf
, ',', TRU1
)) != NULL
){
2749 char const *nstart
, *cp
;
2751 for(nstart
= cp
= vp
;; ++cp
){
2752 if(fieldnamechar(*cp
))
2756 n_err(_("Invalid nameless *customhdr* entry\n"));
2759 }else if(*cp
!= ':' && !blankchar(*cp
)){
2761 n_err(_("Invalid *customhdr* entry: %s\n"), vp
);
2766 nl
= (ui32_t
)PTR2SIZE(cp
- nstart
);
2768 while(blankchar(*cp
))
2772 while(blankchar(*cp
))
2774 bl
= (ui32_t
)strlen(cp
) +1;
2777 hfp
= salloc(n_VSTRUCT_SIZEOF(struct n_header_field
, hf_dat
) +
2779 tail
= &hfp
->hf_next
;
2780 hfp
->hf_next
= NULL
;
2782 hfp
->hf_bl
= bl
- 1;
2783 memcpy(hfp
->hf_dat
, nstart
, nl
);
2784 hfp
->hf_dat
[nl
++] = '\0';
2785 memcpy(hfp
->hf_dat
+ nl
, cp
, bl
);