1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Routines for processing and detecting headlines.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #ifndef HAVE_AMALGAMATION
46 # include <idn-free.h>
47 # include <stringprep.h>
51 size_t tlen
; /* Length of .tdata */
52 char const *tdata
; /* Template date - see _cmatch_data[] */
55 /* Template characters for cmatch_data.tdata:
56 * 'A' An upper case char
57 * 'a' A lower case char
60 * 'O' An optional digit or space
62 * '+' Either a plus or a minus sign */
63 static struct cmatch_data
const _cmatch_data
[] = {
64 { 24, "Aaa Aaa O0 00:00:00 0000" }, /* BSD/ISO C90 ctime */
65 { 28, "Aaa Aaa O0 00:00:00 AAA 0000" }, /* BSD tmz */
66 { 21, "Aaa Aaa O0 00:00 0000" }, /* SysV ctime */
67 { 25, "Aaa Aaa O0 00:00 AAA 0000" }, /* SysV tmz */
68 /* RFC 822-alike From_ lines do not conform to RFC 4155, but seem to be used
69 * in the wild (by UW-imap) */
70 { 30, "Aaa Aaa O0 00:00:00 0000 +0000" },
71 /* RFC 822 with zone spec; 1. military, 2. UT, 3. north america time
72 * zone strings; note that 1. is strictly speaking not correct as some
73 * letters are not used, and 2. is not because only "UT" is defined */
74 #define __reuse "Aaa Aaa O0 00:00:00 0000 AAA"
75 { 28 - 2, __reuse
}, { 28 - 1, __reuse
}, { 28 - 0, __reuse
},
78 #define _DATE_MINLEN 21
80 /* Skip over "word" as found in From_ line */
81 static char const * _from__skipword(char const *wp
);
83 /* Match the date string against the date template (tp), return if match.
84 * See _cmatch_data[] for template character description */
85 static int _cmatch(size_t len
, char const *date
,
88 /* Check wether date is a valid 'From_' date.
89 * (Rather ctime(3) generated dates, according to RFC 4155) */
90 static int _is_date(char const *date
);
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 addrguts
* _idna_apply(struct 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 int _addrspec_check(int doskin
, struct addrguts
*agp
);
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 int gethfield(FILE *f
, char **linebuf
, size_t *linesize
,
110 int rem
, char **colon
);
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
)) >= _DATE_MINLEN
)
195 for (cmdp
= _cmatch_data
; cmdp
->tdata
!= NULL
; ++cmdp
)
196 if (dl
== cmdp
->tlen
&& (rv
= _cmatch(dl
, date
, cmdp
->tdata
)))
203 static struct addrguts
*
204 _idna_apply(struct addrguts
*agp
)
206 char *idna_utf8
, *idna_ascii
, *cs
;
210 sz
= agp
->ag_slen
- agp
->ag_sdom_start
;
212 idna_utf8
= ac_alloc(sz
+1);
213 memcpy(idna_utf8
, agp
->ag_skinned
+ agp
->ag_sdom_start
, sz
);
214 idna_utf8
[sz
] = '\0';
216 /* GNU Libidn settles on top of iconv(3) without any fallback, so let's just
217 * let it perform the charset conversion, if any should be necessary */
219 char const *tcs
= charset_get_lc();
220 idna_ascii
= idna_utf8
;
221 idna_utf8
= stringprep_convert(idna_ascii
, "UTF-8", tcs
);
222 i
= (idna_utf8
== NULL
&& errno
== EINVAL
);
225 if (idna_utf8
== NULL
) {
227 fprintf(stderr
, tr(179, "Cannot convert from %s to %s\n"),
229 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
234 if (idna_to_ascii_8z(idna_utf8
, &idna_ascii
, 0) != IDNA_SUCCESS
) {
235 agp
->ag_n_flags
^= NAME_ADDRSPEC_ERR_IDNA
| NAME_ADDRSPEC_ERR_CHAR
;
239 /* Replace the domain part of .ag_skinned with IDNA version */
240 sz
= strlen(idna_ascii
);
241 i
= agp
->ag_sdom_start
;
242 cs
= salloc(agp
->ag_slen
- i
+ sz
+1);
243 memcpy(cs
, agp
->ag_skinned
, i
);
244 memcpy(cs
+ i
, idna_ascii
, sz
);
248 agp
->ag_skinned
= cs
;
250 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
,
251 NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_IDNA
, 0);
253 idn_free(idna_ascii
);
266 _addrspec_check(int skinned
, struct addrguts
*agp
)
270 ui8_t in_domain
, hadat
;
271 union {char c
; unsigned char u
;} c
;
278 use_idna
= ok_blook(idna_disable
) ? 0 : 1;
280 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
281 addr
= agp
->ag_skinned
;
283 if (agp
->ag_iaddr_aend
- agp
->ag_iaddr_start
== 0) {
284 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
288 /* If the field is not a recipient, it cannot be a file or a pipe */
292 /* Excerpt from nail.1:
293 * Recipient address specifications
294 * The rules are: Any name which starts with a `|' character specifies
295 * a pipe, the command string following the `|' is executed and
296 * the message is sent to its standard input; any other name which
297 * contains a `@' character is treated as a mail address; any other
298 * name which starts with a `+' character specifies a folder name; any
299 * other name which contains a `/' character but no `!' or `%'
300 * character before also specifies a folder name; what remains is
301 * treated as a mail address */
303 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISPIPE
;
306 if (memchr(addr
, '@', agp
->ag_slen
) == NULL
) {
309 for (p
= addr
; (c
.c
= *p
); ++p
) {
310 if (c
.c
== '!' || c
.c
== '%')
314 agp
->ag_n_flags
|= NAME_ADDRSPEC_ISFILE
;
322 in_domain
= hadat
= 0;
324 for (p
= addr
; (c
.c
= *p
++) != '\0';) {
326 in_quote
= !in_quote
;
327 } else if (c
.u
< 040 || c
.u
>= 0177) { /* TODO no magics: !bodychar()? */
329 if (in_domain
&& use_idna
> 0) {
331 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_IDNA
,
337 } else if (in_domain
== 2) {
338 if ((c
.c
== ']' && *p
!= '\0') || c
.c
== '\\' || whitechar(c
.c
))
340 } else if (in_quote
&& in_domain
== 0) {
342 } else if (c
.c
== '\\' && *p
!= '\0') {
344 } else if (c
.c
== '@') {
346 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_ATSEQ
,
350 agp
->ag_sdom_start
= PTR2SIZE(p
- addr
);
351 in_domain
= (*p
== '[') ? 2 : 1;
353 } else if (c
.c
== '(' || c
.c
== ')' || c
.c
== '<' || c
.c
== '>' ||
354 c
.c
== ',' || c
.c
== ';' || c
.c
== ':' || c
.c
== '\\' ||
355 c
.c
== '[' || c
.c
== ']')
361 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_CHAR
, c
.u
);
366 agp
= _idna_apply(agp
);
370 return ((agp
->ag_n_flags
& NAME_ADDRSPEC_INVALID
) != 0);
374 gethfield(FILE *f
, char **linebuf
, size_t *linesize
, int rem
, char **colon
)
376 char *line2
= NULL
, *cp
, *cp2
;
377 size_t line2size
= 0;
381 if (*linebuf
== NULL
)
382 *linebuf
= srealloc(*linebuf
, *linesize
= 1);
389 if ((c
= readline_restart(f
, linebuf
, linesize
, 0)) <= 0) {
393 for (cp
= *linebuf
; fieldnamechar(*cp
); ++cp
)
396 while (blankchar(*cp
))
398 if (*cp
!= ':' || cp
== *linebuf
)
401 /* I guess we got a headline. Handle wraparound */
406 while (PTRCMP(--cp
, >=, *linebuf
) && blankchar(*cp
))
411 if (PTRCMP(cp
- 8, >=, *linebuf
) && cp
[-1] == '=' && cp
[-2] == '?')
413 ungetc(c
= getc(f
), f
);
416 c
= readline_restart(f
, &line2
, &line2size
, 0);
420 for (cp2
= line2
; blankchar(*cp2
); ++cp2
)
422 c
-= (int)PTR2SIZE(cp2
- line2
);
423 if (cp2
[0] == '=' && cp2
[1] == '?' && c
> 8)
425 if (PTRCMP(cp
+ c
, >=, *linebuf
+ *linesize
- 2)) {
426 size_t diff
= PTR2SIZE(cp
- *linebuf
),
427 colondiff
= PTR2SIZE(*colon
- *linebuf
);
428 *linebuf
= srealloc(*linebuf
, *linesize
+= c
+ 2);
429 cp
= &(*linebuf
)[diff
];
430 *colon
= &(*linebuf
)[colondiff
];
448 msgidnextc(char const **cp
, int *status
)
469 *cp
= skip_comment(&(*cp
)[1]);
486 c
= (*status
& 02) ? lowerconv(c
) : c
;
496 charcount(char *str
, int c
)
502 for (i
= 0, cp
= str
; *cp
; ++cp
)
510 nexttoken(char const *cp
)
529 } while (nesting
> 0 && *cp
!= '\0'); /* XXX error? */
530 } else if (blankchar(*cp
) || *cp
== ',')
540 myaddrs(struct header
*hp
)
546 if (hp
!= NULL
&& (np
= hp
->h_from
) != NULL
) {
547 if ((rv
= np
->n_fullname
) != NULL
)
549 if ((rv
= np
->n_name
) != NULL
)
553 if ((rv
= ok_vlook(from
)) != NULL
)
556 /* When invoking *sendmail* directly, it's its task to generate an otherwise
557 * undeterminable From: address. However, if the user sets *hostname*,
558 * accept his desire */
559 if (ok_vlook(smtp
) != NULL
|| ok_vlook(hostname
) != NULL
) {
560 char *hn
= nodename(1);
561 size_t sz
= strlen(myname
) + strlen(hn
) + 1 +1;
563 sstpcpy(sstpcpy(sstpcpy(rv
, myname
), "@"), hn
);
571 myorigin(struct header
*hp
)
573 char const *rv
= NULL
, *ccp
;
577 if ((ccp
= myaddrs(hp
)) != NULL
&&
578 (np
= lextract(ccp
, GEXTRA
| GFULL
)) != NULL
)
579 rv
= (np
->n_flink
!= NULL
) ? ok_vlook(sender
) : ccp
;
585 is_head(char const *linebuf
, size_t linelen
) /* XXX verbose WARN */
587 char date
[FROM_DATEBUF
];
591 rv
= ((linelen
<= 5 || strncmp(linebuf
, "From ", 5) != 0 ||
592 !extract_date_from_from_(linebuf
, linelen
, date
) ||
593 !_is_date(date
)) ? 0 : 1);
599 extract_date_from_from_(char const *line
, size_t linelen
,
600 char datebuf
[FROM_DATEBUF
])
603 char const *cp
= line
;
607 cp
= _from__skipword(cp
);
611 cp
= _from__skipword(cp
);
614 if (cp
[0] == 't' && cp
[1] == 't' && cp
[2] == 'y') {
615 cp
= _from__skipword(cp
);
620 linelen
-= PTR2SIZE(cp
- line
);
621 if (linelen
< _DATE_MINLEN
)
623 if (cp
[linelen
- 1] == '\n') {
625 /* (Rather IMAP/POP3 only) */
626 if (cp
[linelen
- 1] == '\r')
628 if (linelen
< _DATE_MINLEN
)
631 if (linelen
>= FROM_DATEBUF
)
636 memcpy(datebuf
, cp
, linelen
);
637 datebuf
[linelen
] = '\0';
641 cp
= tr(213, "<Unknown date>");
642 linelen
= strlen(cp
);
643 if (linelen
>= FROM_DATEBUF
)
644 linelen
= FROM_DATEBUF
;
649 extract_header(FILE *fp
, struct header
*hp
) /* XXX no header occur-cnt check */
651 struct header nh
, *hq
= &nh
;
652 char *linebuf
= NULL
/* TODO line pool */, *colon
;
653 size_t linesize
= 0, seenfields
= 0;
655 char const *val
, *cp
;
658 memset(hq
, 0, sizeof *hq
);
659 for (lc
= 0; readline_restart(fp
, &linebuf
, &linesize
, 0) > 0; ++lc
)
662 /* TODO yippieia, cat(check(lextract)) :-) */
664 while ((lc
= gethfield(fp
, &linebuf
, &linesize
, lc
, &colon
)) >= 0) {
665 if ((val
= thisfield(linebuf
, "to")) != NULL
) {
667 hq
->h_to
= cat(hq
->h_to
, checkaddrs(lextract(val
, GTO
| GFULL
)));
668 } else if ((val
= thisfield(linebuf
, "cc")) != NULL
) {
670 hq
->h_cc
= cat(hq
->h_cc
, checkaddrs(lextract(val
, GCC
| GFULL
)));
671 } else if ((val
= thisfield(linebuf
, "bcc")) != NULL
) {
673 hq
->h_bcc
= cat(hq
->h_bcc
, checkaddrs(lextract(val
, GBCC
| GFULL
)));
674 } else if ((val
= thisfield(linebuf
, "from")) != NULL
) {
676 hq
->h_from
= cat(hq
->h_from
,
677 checkaddrs(lextract(val
, GEXTRA
| GFULL
)));
678 } else if ((val
= thisfield(linebuf
, "reply-to")) != NULL
) {
680 hq
->h_replyto
= cat(hq
->h_replyto
,
681 checkaddrs(lextract(val
, GEXTRA
| GFULL
)));
682 } else if ((val
= thisfield(linebuf
, "sender")) != NULL
) {
684 hq
->h_sender
= cat(hq
->h_sender
,
685 checkaddrs(lextract(val
, GEXTRA
| GFULL
)));
686 } else if ((val
= thisfield(linebuf
, "organization")) != NULL
) {
688 for (cp
= val
; blankchar(*cp
); ++cp
)
690 hq
->h_organization
= (hq
->h_organization
!= NULL
)
691 ? save2str(hq
->h_organization
, cp
) : savestr(cp
);
692 } else if ((val
= thisfield(linebuf
, "subject")) != NULL
||
693 (val
= thisfield(linebuf
, "subj")) != NULL
) {
695 for (cp
= val
; blankchar(*cp
); ++cp
)
697 hq
->h_subject
= (hq
->h_subject
!= NULL
)
698 ? save2str(hq
->h_subject
, cp
) : savestr(cp
);
700 fprintf(stderr
, tr(266, "Ignoring header field \"%s\"\n"), linebuf
);
703 /* In case the blank line after the header has been edited out. Otherwise,
704 * fetch the header separator */
705 if (linebuf
!= NULL
) {
706 if (linebuf
[0] != '\0') {
707 for (cp
= linebuf
; *(++cp
) != '\0';)
709 fseek(fp
, (long)-PTR2SIZE(1 + cp
- linebuf
), SEEK_CUR
);
711 if ((c
= getc(fp
)) != '\n' && c
!= EOF
)
716 if (seenfields
> 0) {
719 hp
->h_bcc
= hq
->h_bcc
;
720 hp
->h_from
= hq
->h_from
;
721 hp
->h_replyto
= hq
->h_replyto
;
722 hp
->h_sender
= hq
->h_sender
;
723 hp
->h_organization
= hq
->h_organization
;
724 hp
->h_subject
= hq
->h_subject
;
726 fprintf(stderr
, tr(267, "Restoring deleted header lines\n"));
734 hfield_mult(char const *field
, struct message
*mp
, int mult
)
738 size_t linesize
= 0; /* TODO line pool */
739 char *linebuf
= NULL
, *colon
, *oldhfield
= NULL
;
743 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
745 if ((lc
= mp
->m_lines
- 1) < 0)
748 if ((mp
->m_flag
& MNOFROM
) == 0 &&
749 readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
752 if ((lc
= gethfield(ibuf
, &linebuf
, &linesize
, lc
, &colon
)) < 0)
754 if ((hfield
= thisfield(linebuf
, field
)) != NULL
) {
755 oldhfield
= save2str(hfield
, oldhfield
);
769 thisfield(char const *linebuf
, char const *field
)
771 char const *rv
= NULL
;
774 while (lowerconv(*linebuf
) == lowerconv(*field
)) {
781 while (blankchar(*linebuf
))
783 if (*linebuf
++ != ':')
786 while (blankchar(*linebuf
))
795 nameof(struct message
*mp
, int reptype
)
800 cp
= skin(name1(mp
, reptype
));
801 if (reptype
!= 0 || charcount(cp
, '!') < 2)
803 cp2
= strrchr(cp
, '!');
805 while (cp2
> cp
&& *cp2
!= '!')
815 skip_comment(char const *cp
)
820 for (nesting
= 1; nesting
> 0 && *cp
; ++cp
) {
839 routeaddr(char const *name
)
841 char const *np
, *rp
= NULL
;
844 for (np
= name
; *np
; np
++) {
847 np
= skip_comment(np
+ 1) - 1;
853 if (*np
== '\\' && np
[1])
871 is_addr_invalid(struct name
*np
, int putmsg
)
873 char cbuf
[sizeof "'\\U12340'"], *name
;
876 char const *fmt
, *cs
;
884 if (!(f
& NAME_ADDRSPEC_INVALID
) || !putmsg
|| (f
& NAME_ADDRSPEC_ERR_EMPTY
))
887 if (f
& NAME_ADDRSPEC_ERR_IDNA
)
888 cs
= tr(284, "Invalid domain name: \"%s\", character %s\n"),
891 else if (f
& NAME_ADDRSPEC_ERR_ATSEQ
)
892 cs
= tr(142, "\"%s\" contains invalid %s sequence\n");
894 cs
= tr(143, "\"%s\" contains invalid character %s\n");
896 c
= NAME_ADDRSPEC_ERR_GETWC(f
);
897 if (ok8bit
&& c
>= 040 && c
<= 0177)
898 snprintf(cbuf
, sizeof cbuf
, "'%c'", c
);
900 snprintf(cbuf
, sizeof cbuf
, fmt
, c
);
902 fprintf(stderr
, cs
, name
, cbuf
);
905 return ((f
& NAME_ADDRSPEC_INVALID
) != 0);
909 skin(char const *name
)
916 addrspec_with_guts(1, name
, &ag
);
918 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
))
919 ret
= savestrbuf(ret
, ag
.ag_slen
);
925 /* TODO addrspec_with_guts: RFC 5322 */
927 addrspec_with_guts(int doskin
, char const *name
, struct addrguts
*agp
)
930 char *cp2
, *bufend
, *nbuf
, c
, gotlt
, gotaddr
, lastsp
;
934 memset(agp
, 0, sizeof *agp
);
936 if ((agp
->ag_input
= name
) == NULL
|| (agp
->ag_ilen
= strlen(name
)) == 0) {
937 agp
->ag_skinned
= UNCONST(""); /* ok: NAME_SALLOC is not set */
939 agp
->ag_n_flags
|= NAME_ADDRSPEC_CHECKED
;
940 NAME_ADDRSPEC_ERR_SET(agp
->ag_n_flags
, NAME_ADDRSPEC_ERR_EMPTY
, 0);
944 if (!doskin
|| !anyof(name
, "(< ")) {
945 /*agp->ag_iaddr_start = 0;*/
946 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
947 agp
->ag_skinned
= UNCONST(name
); /* (NAME_SALLOC not set) */
948 agp
->ag_slen
= agp
->ag_ilen
;
949 agp
->ag_n_flags
= NAME_SKINNED
;
953 /* Something makes us think we have to perform the skin operation */
954 nbuf
= ac_alloc(agp
->ag_ilen
+ 1);
955 /*agp->ag_iaddr_start = 0;*/
957 gotlt
= gotaddr
= lastsp
= 0;
959 for (cp
= name
++; (c
= *cp
++) != '\0'; ) {
962 cp
= skip_comment(cp
);
966 /* Start of a "quoted-string".
967 * Copy it in its entirety */
968 /* XXX RFC: quotes are "semantically invisible"
969 * XXX But it was explicitly added (Changelog.Heirloom,
970 * XXX [9.23] released 11/15/00, "Do not remove quotes
971 * XXX when skinning names"? No more info.. */
973 while ((c
= *cp
) != '\0') { /* TODO improve */
981 else if ((c
= *cp
) != '\0') {
992 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
994 if (cp
[0] == 'a' && cp
[1] == 't' && blankchar(cp
[2]))
995 cp
+= 3, *cp2
++ = '@';
996 else if (cp
[0] == '@' && blankchar(cp
[1]))
997 cp
+= 2, *cp2
++ = '@';
1002 agp
->ag_iaddr_start
= PTR2SIZE(cp
- (name
- 1));
1004 gotlt
= gotaddr
= 1;
1009 /* (_addrspec_check() verifies these later!) */
1010 agp
->ag_iaddr_aend
= PTR2SIZE(cp
- name
);
1012 while ((c
= *cp
) != '\0' && c
!= ',') {
1015 cp
= skip_comment(cp
);
1017 while ((c
= *cp
) != '\0') {
1021 if (c
== '\\' && *cp
!= '\0')
1039 for (; blankchar(*cp
); ++cp
)
1044 } else if (!gotaddr
) {
1046 agp
->ag_iaddr_start
= PTR2SIZE(cp
- name
);
1050 agp
->ag_slen
= PTR2SIZE(cp2
- nbuf
);
1051 if (agp
->ag_iaddr_aend
== 0)
1052 agp
->ag_iaddr_aend
= agp
->ag_ilen
;
1054 agp
->ag_skinned
= savestrbuf(nbuf
, agp
->ag_slen
);
1056 agp
->ag_n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
;
1058 rv
= _addrspec_check(doskin
, agp
);
1065 realname(char const *name
)
1067 char const *cp
, *cq
, *cstart
= NULL
, *cend
= NULL
;
1070 int quoted
, good
, nogood
;
1073 if ((cp
= UNCONST(name
)) == NULL
)
1075 for (; *cp
!= '\0'; ++cp
) {
1078 if (cstart
!= NULL
) {
1079 /* More than one comment in address, doesn't make sense to display
1080 * it without context. Return the entire field */
1081 cp
= mime_fromaddr(name
);
1085 cp
= skip_comment(cp
);
1088 cend
= cstart
= NULL
;
1094 if (*cp
== '\\' && cp
[1])
1105 /* More than one address. Just use the first one */
1111 if (cstart
== NULL
) {
1113 /* If name contains only a route-addr, the surrounding angle brackets
1114 * don't serve any useful purpose when displaying, so remove */
1115 cp
= prstr(skin(name
));
1117 cp
= mime_fromaddr(name
);
1121 /* Strip quotes. Note that quotes that appear within a MIME encoded word are
1122 * not stripped. The idea is to strip only syntactical relevant things (but
1123 * this is not necessarily the most sensible way in practice) */
1124 rp
= rname
= ac_alloc(PTR2SIZE(cend
- cstart
+1));
1126 for (cp
= cstart
; cp
< cend
; ++cp
) {
1127 if (*cp
== '(' && !quoted
) {
1128 cq
= skip_comment(++cp
);
1129 if (PTRCMP(--cq
, >, cend
))
1132 if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cq
))
1136 } else if (*cp
== '\\' && PTRCMP(cp
+ 1, <, cend
))
1138 else if (*cp
== '"') {
1147 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
1149 rname
= savestr(out
.s
);
1152 while (blankchar(*rname
))
1154 for (rp
= rname
; *rp
!= '\0'; ++rp
)
1156 while (PTRCMP(--rp
, >=, rname
) && blankchar(*rp
))
1159 cp
= mime_fromaddr(name
);
1163 /* mime_fromhdr() has converted all nonprintable characters to question
1164 * marks now. These and blanks are considered uninteresting; if the
1165 * displayed part of the real name contains more than 25% of them, it is
1166 * probably better to display the plain email address instead */
1169 for (rp
= rname
; *rp
!= '\0' && PTRCMP(rp
, <, rname
+ 20); ++rp
)
1170 if (*rp
== '?' || blankchar(*rp
))
1174 cp
= (good
* 3 < nogood
) ? prstr(skin(name
)) : rname
;
1181 name1(struct message
*mp
, int reptype
)
1183 char *namebuf
, *cp
, *cp2
, *linebuf
= NULL
/* TODO line pool */;
1184 size_t namesize
, linesize
= 0;
1189 if ((cp
= hfield1("from", mp
)) != NULL
&& *cp
!= '\0')
1191 if (reptype
== 0 && (cp
= hfield1("sender", mp
)) != NULL
&& *cp
!= '\0')
1194 namebuf
= smalloc(namesize
= 1);
1196 if (mp
->m_flag
& MNOFROM
)
1198 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
1200 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1204 if (namesize
<= linesize
)
1205 namebuf
= srealloc(namebuf
, namesize
= linesize
+1);
1206 for (cp
= linebuf
; *cp
!= '\0' && *cp
!= ' '; ++cp
)
1208 for (; blankchar(*cp
); ++cp
)
1210 for (cp2
= namebuf
+ strlen(namebuf
);
1211 *cp
&& !blankchar(*cp
) && PTRCMP(cp2
, <, namebuf
+ namesize
-1);)
1215 if (readline_restart(ibuf
, &linebuf
, &linesize
, 0) < 0)
1217 if ((cp
= strchr(linebuf
, 'F')) == NULL
)
1219 if (strncmp(cp
, "From", 4)) /* XXX is_head? */
1221 if (namesize
<= linesize
)
1222 namebuf
= srealloc(namebuf
, namesize
= linesize
+ 1);
1224 while ((cp
= strchr(cp
, 'r')) != NULL
) {
1225 if (!strncmp(cp
, "remote", 6)) {
1226 if ((cp
= strchr(cp
, 'f')) == NULL
)
1228 if (strncmp(cp
, "from", 4) != 0)
1230 if ((cp
= strchr(cp
, ' ')) == NULL
)
1234 strncpy(namebuf
, cp
, namesize
);
1237 cp2
= strrchr(namebuf
, '!') + 1;
1238 strncpy(cp2
, cp
, PTR2SIZE(namebuf
+ namesize
- cp2
));
1240 namebuf
[namesize
- 2] = '!';
1241 namebuf
[namesize
- 1] = '\0';
1247 if (*namebuf
!= '\0' || ((cp
= hfield1("return-path", mp
))) == NULL
||
1249 cp
= savestr(namebuf
);
1251 if (linebuf
!= NULL
)
1260 msgidcmp(char const *s1
, char const *s2
)
1262 int q1
= 0, q2
= 0, c1
, c2
;
1266 c1
= msgidnextc(&s1
, &q1
);
1267 c2
= msgidnextc(&s2
, &q2
);
1276 is_ign(char const *field
, size_t fieldlen
, struct ignoretab ignoret
[2])
1283 if (ignoret
== NULL
)
1286 if (ignoret
== allignore
)
1289 /* Lowercase it so that "Status" and "status" will hash to the same place */
1290 realfld
= ac_alloc(fieldlen
+1);
1291 i_strcpy(realfld
, field
, fieldlen
+1);
1292 if (ignoret
[1].i_count
> 0)
1293 rv
= !member(realfld
, ignoret
+ 1);
1295 rv
= member(realfld
, ignoret
);
1303 member(char const *realfield
, struct ignoretab
*table
)
1309 for (igp
= table
->i_head
[hash(realfield
)]; igp
!= 0; igp
= igp
->i_link
)
1310 if (*igp
->i_field
== *realfield
&& !strcmp(igp
->i_field
, realfield
)) {
1319 fakefrom(struct message
*mp
)
1324 if (((name
= skin(hfield1("return-path", mp
))) == NULL
|| *name
== '\0' ) &&
1325 ((name
= skin(hfield1("from", mp
))) == NULL
|| *name
== '\0'))
1326 /* XXX MAILER-DAEMON is what an old MBOX manual page says.
1327 * RFC 4155 however requires a RFC 5322 (2822) conforming
1328 * "addr-spec", but we simply can't provide that */
1329 name
= "MAILER-DAEMON";
1341 for (cq
= cp
; *cq
!= '\0' && *cq
!= '\n'; ++cq
)
1350 unixtime(char const *fromline
)
1355 int i
, year
, month
, day
, hour
, minute
, second
, tzdiff
;
1359 for (fp
= fromline
; *fp
!= '\0' && *fp
!= '\n'; ++fp
)
1362 if (PTR2SIZE(fp
- fromline
) < 7)
1367 if (!strncmp(fp
+ 4, month_names
[i
], 3))
1369 if (month_names
[++i
][0] == '\0')
1375 day
= strtol(fp
+ 8, &xp
, 10);
1376 if (*xp
!= ' ' || xp
!= fp
+ 10)
1378 hour
= strtol(fp
+ 11, &xp
, 10);
1379 if (*xp
!= ':' || xp
!= fp
+ 13)
1381 minute
= strtol(fp
+ 14, &xp
, 10);
1382 if (*xp
!= ':' || xp
!= fp
+ 16)
1384 second
= strtol(fp
+ 17, &xp
, 10);
1385 if (*xp
!= ' ' || xp
!= fp
+ 19)
1387 year
= strtol(fp
+ 20, &xp
, 10);
1390 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
1392 tzdiff
= t
- mktime(gmtime(&t
));
1393 tmptr
= localtime(&t
);
1394 if (tmptr
->tm_isdst
> 0)
1406 rfctime(char const *date
)
1408 char const *cp
= date
;
1411 int i
, year
, month
, day
, hour
, minute
, second
;
1414 if ((cp
= nexttoken(cp
)) == NULL
)
1416 if (alphachar(cp
[0]) && alphachar(cp
[1]) && alphachar(cp
[2]) &&
1418 if ((cp
= nexttoken(&cp
[4])) == NULL
)
1421 day
= strtol(cp
, &x
, 10); /* XXX strtol */
1422 if ((cp
= nexttoken(x
)) == NULL
)
1425 if (!strncmp(cp
, month_names
[i
], 3))
1427 if (month_names
[++i
][0] == '\0')
1431 if ((cp
= nexttoken(&cp
[3])) == NULL
)
1434 * Where a two or three digit year occurs in a date, the year is to be
1435 * interpreted as follows: If a two digit year is encountered whose
1436 * value is between 00 and 49, the year is interpreted by adding 2000,
1437 * ending up with a value between 2000 and 2049. If a two digit year
1438 * is encountered with a value between 50 and 99, or any three digit
1439 * year is encountered, the year is interpreted by adding 1900 */
1440 year
= strtol(cp
, &x
, 10); /* XXX strtol */
1441 i
= (int)PTR2SIZE(x
- cp
);
1442 if (i
== 2 && year
>= 0 && year
<= 49)
1444 else if (i
== 3 || (i
== 2 && year
>= 50 && year
<= 99))
1446 if ((cp
= nexttoken(x
)) == NULL
)
1448 hour
= strtol(cp
, &x
, 10); /* XXX strtol */
1452 minute
= strtol(cp
, &x
, 10);
1455 second
= strtol(cp
, &x
, 10);
1458 if ((t
= combinetime(year
, month
, day
, hour
, minute
, second
)) == (time_t)-1)
1460 if ((cp
= nexttoken(x
)) != NULL
) {
1472 if (digitchar(cp
[0]) && digitchar(cp
[1]) && digitchar(cp
[2]) &&
1477 t
+= strtol(buf
, NULL
, 10) * sign
* 3600;/*XXX strtrol*/
1480 t
+= strtol(buf
, NULL
, 10) * sign
* 60; /* XXX strtol*/
1482 /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
1483 * TODO once again, Christos Zoulas and NetBSD Mail have done
1484 * TODO a really good job already, but using strptime(3), which
1485 * TODO is not portable. Nonetheless, WE must improve, not
1486 * TODO at last because we simply ignore obsolete timezones!!
1487 * TODO See RFC 5322, 4.3! */
1497 #define is_leapyear(Y) ((((Y) % 100 ? (Y) : (Y) / 100) & 3) == 0)
1500 combinetime(int year
, int month
, int day
, int hour
, int minute
, int second
)
1505 if (second
< 0 || minute
< 0 || hour
< 0 || day
< 1) {
1510 t
= second
+ minute
* 60 + hour
* 3600 + (day
- 1) * 86400;
1514 t
+= 86400 * (is_leapyear(year
) ? 29 : 28);
1534 t
+= (year
- 70) * 31536000 + ((year
- 69) / 4) * 86400 -
1535 ((year
- 1) / 100) * 86400 + ((year
+ 299) / 400) * 86400;
1542 substdate(struct message
*m
)
1547 /* Determine the date to print in faked 'From ' lines. This is traditionally
1548 * the date the message was written to the mail file. Try to determine this
1549 * using RFC message header fields, or fall back to current time */
1550 if ((cp
= hfield1("received", m
)) != NULL
) {
1551 while ((cp
= nexttoken(cp
)) != NULL
&& *cp
!= ';') {
1554 while (alnumchar(*cp
));
1557 m
->m_time
= rfctime(cp
);
1559 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
) {
1560 if ((cp
= hfield1("date", m
)) != NULL
)
1561 m
->m_time
= rfctime(cp
);
1563 if (m
->m_time
== 0 || m
->m_time
> time_current
.tc_time
)
1564 m
->m_time
= time_current
.tc_time
;
1569 check_from_and_sender(struct name
*fromfield
, struct name
*senderfield
)
1574 if (fromfield
&& fromfield
->n_flink
&& senderfield
== NULL
) {
1575 fprintf(stderr
, tr(529, "A Sender: field is required with multiple "
1576 "addresses in From: field.\n"));
1578 } else if (senderfield
&& senderfield
->n_flink
) {
1579 fprintf(stderr
, tr(530,
1580 "The Sender: field may contain only one address.\n"));
1589 getsender(struct message
*mp
)
1595 if ((cp
= hfield1("from", mp
)) == NULL
||
1596 (np
= lextract(cp
, GEXTRA
| GSKIN
)) == NULL
)
1599 cp
= (np
->n_flink
!= NULL
) ? skin(hfield1("sender", mp
)) : np
->n_name
;
1605 grab_headers(struct header
*hp
, enum gfield gflags
, int subjfirst
)
1607 /* TODO grab_headers: again, check counts etc. against RFC;
1608 * TODO (now assumes check_from_and_sender() is called afterwards ++ */
1614 comma
= (ok_blook(bsdcompat
) || ok_blook(bsdmsgs
)) ? 0 : GCOMMA
;
1617 hp
->h_to
= grab_names("To: ", hp
->h_to
, comma
, GTO
| GFULL
);
1618 if (subjfirst
&& (gflags
& GSUBJECT
))
1619 hp
->h_subject
= readstr_input("Subject: ", hp
->h_subject
);
1621 hp
->h_cc
= grab_names("Cc: ", hp
->h_cc
, comma
, GCC
| GFULL
);
1623 hp
->h_bcc
= grab_names("Bcc: ", hp
->h_bcc
, comma
, GBCC
| GFULL
);
1625 if (gflags
& GEXTRA
) {
1626 if (hp
->h_from
== NULL
)
1627 hp
->h_from
= lextract(myaddrs(hp
), GEXTRA
| GFULL
);
1628 hp
->h_from
= grab_names("From: ", hp
->h_from
, comma
, GEXTRA
| GFULL
);
1629 if (hp
->h_replyto
== NULL
)
1630 hp
->h_replyto
= lextract(ok_vlook(replyto
), GEXTRA
| GFULL
);
1631 hp
->h_replyto
= grab_names("Reply-To: ", hp
->h_replyto
, comma
,
1633 if (hp
->h_sender
== NULL
)
1634 hp
->h_sender
= extract(ok_vlook(sender
), GEXTRA
| GFULL
);
1635 hp
->h_sender
= grab_names("Sender: ", hp
->h_sender
, comma
,
1637 if (hp
->h_organization
== NULL
)
1638 hp
->h_organization
= ok_vlook(ORGANIZATION
);
1639 hp
->h_organization
= readstr_input("Organization: ", hp
->h_organization
);
1642 if (!subjfirst
&& (gflags
& GSUBJECT
))
1643 hp
->h_subject
= readstr_input("Subject: ", hp
->h_subject
);
1649 /* vim:set fenc=utf-8:s-it-mode */