makeconfig: add WANT_IDNA -> USE_IDNA test
[s-mailx.git] / head.c
blobe13ec89031f425638ec992c258eaddede933f0e9
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
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
13 * are met:
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
37 * SUCH DAMAGE.
40 #include "rcv.h"
41 #include "extern.h"
42 #include <time.h>
45 * Mail -- a mail program
47 * Routines for processing and detecting headlines.
50 static char * copyin(char *src, char **space);
51 static char * nextword(char *wp, char *wbuf);
52 static int gethfield(FILE *f, char **linebuf, size_t *linesize, int rem,
53 char **colon);
54 static int addrspec_check(int doskin, struct addrguts *agp);
55 static int msgidnextc(const char **cp, int *status);
56 static int charcount(char *str, int c);
59 * See if the passed line buffer is a mail header.
60 * Return true if yes. POSIX.2 leaves the content
61 * following 'From ' unspecified, so don't care about
62 * it.
64 /*ARGSUSED 2*/
65 int
66 is_head(char *linebuf, size_t linelen)
68 char *cp;
69 (void)linelen;
71 cp = linebuf;
72 if (*cp++ != 'F' || *cp++ != 'r' || *cp++ != 'o' || *cp++ != 'm' ||
73 *cp++ != ' ')
74 return (0);
75 return(1);
79 * Split a headline into its useful components.
80 * Copy the line into dynamic string space, then set
81 * pointers into the copied line in the passed headline
82 * structure. Actually, it scans.
84 void
85 parse(char *line, size_t linelen, struct headline *hl, char *pbuf)
87 char *cp;
88 char *sp;
89 char *word;
91 hl->l_from = NULL;
92 hl->l_tty = NULL;
93 hl->l_date = NULL;
94 cp = line;
95 sp = pbuf;
96 word = ac_alloc(linelen + 1);
98 * Skip over "From" first.
100 cp = nextword(cp, word);
101 cp = nextword(cp, word);
102 if (*word)
103 hl->l_from = copyin(word, &sp);
104 if (cp != NULL && cp[0] == 't' && cp[1] == 't' && cp[2] == 'y') {
105 cp = nextword(cp, word);
106 hl->l_tty = copyin(word, &sp);
108 if (cp != NULL)
109 hl->l_date = copyin(cp, &sp);
110 else
111 hl->l_date = catgets(catd, CATSET, 213, "<Unknown date>");
112 ac_free(word);
116 * Copy the string on the left into the string on the right
117 * and bump the right (reference) string pointer by the length.
118 * Thus, dynamically allocate space in the right string, copying
119 * the left string into it.
121 static char *
122 copyin(char *src, char **space)
124 char *cp;
125 char *top;
127 top = cp = *space;
128 while ((*cp++ = *src++) != '\0')
130 *space = cp;
131 return (top);
134 #ifdef notdef
135 static int cmatch(char *, char *);
137 * Test to see if the passed string is a ctime(3) generated
138 * date string as documented in the manual. The template
139 * below is used as the criterion of correctness.
140 * Also, we check for a possible trailing time zone using
141 * the tmztype template.
145 * 'A' An upper case char
146 * 'a' A lower case char
147 * ' ' A space
148 * '0' A digit
149 * 'O' An optional digit or space
150 * ':' A colon
151 * '+' A sign
152 * 'N' A new line
154 static char *tmztype[] = {
155 "Aaa Aaa O0 00:00:00 0000",
156 "Aaa Aaa O0 00:00 0000",
157 "Aaa Aaa O0 00:00:00 AAA 0000",
158 "Aaa Aaa O0 00:00 AAA 0000",
160 * Sommer time, e.g. MET DST
162 "Aaa Aaa O0 00:00:00 AAA AAA 0000",
163 "Aaa Aaa O0 00:00 AAA AAA 0000",
165 * time zone offset, e.g.
166 * +0200 or +0200 MET or +0200 MET DST
168 "Aaa Aaa O0 00:00:00 +0000 0000",
169 "Aaa Aaa O0 00:00 +0000 0000",
170 "Aaa Aaa O0 00:00:00 +0000 AAA 0000",
171 "Aaa Aaa O0 00:00 +0000 AAA 0000",
172 "Aaa Aaa O0 00:00:00 +0000 AAA AAA 0000",
173 "Aaa Aaa O0 00:00 +0000 AAA AAA 0000",
175 * time zone offset without time zone specification (pine)
177 "Aaa Aaa O0 00:00:00 0000 +0000",
178 NULL,
181 static int
182 is_date(char *date)
184 int ret = 0, form = 0;
186 while (tmztype[form]) {
187 if ( (ret = cmatch(date, tmztype[form])) == 1 )
188 break;
189 form++;
192 return ret;
196 * Match the given string (cp) against the given template (tp).
197 * Return 1 if they match, 0 if they don't
199 static int
200 cmatch(char *cp, char *tp)
202 int c;
204 while (*cp && *tp)
205 switch (*tp++) {
206 case 'a':
207 if (c = *cp++, !lowerchar(c))
208 return 0;
209 break;
210 case 'A':
211 if (c = *cp++, !upperchar(c))
212 return 0;
213 break;
214 case ' ':
215 if (*cp++ != ' ')
216 return 0;
217 break;
218 case '0':
219 if (c = *cp++, !digitchar(c))
220 return 0;
221 break;
222 case 'O':
223 if (c = *cp, c != ' ' && !digitchar(c))
224 return 0;
225 cp++;
226 break;
227 case ':':
228 if (*cp++ != ':')
229 return 0;
230 break;
231 case '+':
232 if (*cp != '+' && *cp != '-')
233 return 0;
234 cp++;
235 break;
236 case 'N':
237 if (*cp++ != '\n')
238 return 0;
239 break;
241 if (*cp || *tp)
242 return 0;
243 return (1);
245 #endif /* notdef */
248 * Collect a liberal (space, tab delimited) word into the word buffer
249 * passed. Also, return a pointer to the next word following that,
250 * or NULL if none follow.
252 static char *
253 nextword(char *wp, char *wbuf)
255 int c;
257 if (wp == NULL) {
258 *wbuf = 0;
259 return (NULL);
261 while ((c = *wp++) != '\0' && !blankchar(c)) {
262 *wbuf++ = c;
263 if (c == '"') {
264 while ((c = *wp++) != '\0' && c != '"')
265 *wbuf++ = c;
266 if (c == '"')
267 *wbuf++ = c;
268 else
269 wp--;
272 *wbuf = '\0';
273 for (; blankchar(c); c = *wp++)
275 if (c == 0)
276 return (NULL);
277 return (wp - 1);
280 void
281 extract_header(FILE *fp, struct header *hp) /* XXX no header occur-cnt check */
283 char *linebuf = NULL;
284 size_t linesize = 0;
285 int seenfields = 0;
286 char *colon, *cp, *value;
287 struct header nh;
288 struct header *hq = &nh;
289 int lc, c;
291 memset(hq, 0, sizeof *hq);
292 for (lc = 0; readline(fp, &linebuf, &linesize) > 0; lc++);
293 rewind(fp);
294 while ((lc = gethfield(fp, &linebuf, &linesize, lc, &colon)) >= 0) {
295 if ((value = thisfield(linebuf, "to")) != NULL) {
296 seenfields++;
297 hq->h_to = cat(hq->h_to, checkaddrs(
298 lextract(value, GTO|GFULL)));
299 } else if ((value = thisfield(linebuf, "cc")) != NULL) {
300 seenfields++;
301 hq->h_cc = cat(hq->h_cc, checkaddrs(
302 lextract(value, GCC|GFULL)));
303 } else if ((value = thisfield(linebuf, "bcc")) != NULL) {
304 seenfields++;
305 hq->h_bcc = cat(hq->h_bcc, checkaddrs(
306 lextract(value, GBCC|GFULL)));
307 } else if ((value = thisfield(linebuf, "from")) != NULL) {
308 seenfields++;
309 hq->h_from = cat(hq->h_from, checkaddrs(
310 sextract(value, GEXTRA|GFULL)));
311 } else if ((value = thisfield(linebuf, "reply-to")) != NULL) {
312 seenfields++;
313 hq->h_replyto = cat(hq->h_replyto, checkaddrs(
314 sextract(value, GEXTRA|GFULL)));
315 } else if ((value = thisfield(linebuf, "sender")) != NULL) {
316 seenfields++;
317 hq->h_sender = cat(hq->h_sender, checkaddrs(
318 sextract(value, GEXTRA|GFULL)));
319 } else if ((value = thisfield(linebuf,
320 "organization")) != NULL) {
321 seenfields++;
322 for (cp = value; blankchar(*cp & 0377); cp++);
323 hq->h_organization = hq->h_organization ?
324 save2str(hq->h_organization, cp) :
325 savestr(cp);
326 } else if ((value = thisfield(linebuf, "subject")) != NULL ||
327 (value = thisfield(linebuf, "subj")) != NULL) {
328 seenfields++;
329 for (cp = value; blankchar(*cp & 0377); cp++);
330 hq->h_subject = hq->h_subject ?
331 save2str(hq->h_subject, cp) :
332 savestr(cp);
333 } else
334 fprintf(stderr, catgets(catd, CATSET, 266,
335 "Ignoring header field \"%s\"\n"),
336 linebuf);
339 * In case the blank line after the header has been edited out.
340 * Otherwise, fetch the header separator.
342 if (linebuf) {
343 if (linebuf[0] != '\0') {
344 for (cp = linebuf; *(++cp) != '\0'; );
345 fseek(fp, (long)-(1 + cp - linebuf), SEEK_CUR);
346 } else {
347 if ((c = getc(fp)) != '\n' && c != EOF)
348 ungetc(c, fp);
351 if (seenfields) {
352 hp->h_to = hq->h_to;
353 hp->h_cc = hq->h_cc;
354 hp->h_bcc = hq->h_bcc;
355 hp->h_from = hq->h_from;
356 hp->h_replyto = hq->h_replyto;
357 hp->h_sender = hq->h_sender;
358 hp->h_organization = hq->h_organization;
359 hp->h_subject = hq->h_subject;
360 } else
361 fprintf(stderr, catgets(catd, CATSET, 267,
362 "Restoring deleted header lines\n"));
363 if (linebuf)
364 free(linebuf);
368 * Return the desired header line from the passed message
369 * pointer (or NULL if the desired header field is not available).
370 * If mult is zero, return the content of the first matching header
371 * field only, the content of all matching header fields else.
373 char *
374 hfield_mult(char *field, struct message *mp, int mult)
376 FILE *ibuf;
377 char *linebuf = NULL;
378 size_t linesize = 0;
379 int lc;
380 char *hfield;
381 char *colon, *oldhfield = NULL;
383 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
384 return NULL;
385 if ((lc = mp->m_lines - 1) < 0)
386 return NULL;
387 if ((mp->m_flag & MNOFROM) == 0) {
388 if (readline(ibuf, &linebuf, &linesize) < 0) {
389 if (linebuf)
390 free(linebuf);
391 return NULL;
394 while (lc > 0) {
395 if ((lc = gethfield(ibuf, &linebuf, &linesize, lc, &colon))
396 < 0) {
397 if (linebuf)
398 free(linebuf);
399 return oldhfield;
401 if ((hfield = thisfield(linebuf, field)) != NULL) {
402 oldhfield = save2str(hfield, oldhfield);
403 if (mult == 0)
404 break;
407 if (linebuf)
408 free(linebuf);
409 return oldhfield;
413 * Return the next header field found in the given message.
414 * Return >= 0 if something found, < 0 elsewise.
415 * "colon" is set to point to the colon in the header.
416 * Must deal with \ continuations & other such fraud.
418 static int
419 gethfield(FILE *f, char **linebuf, size_t *linesize, int rem, char **colon)
421 char *line2 = NULL;
422 size_t line2size = 0;
423 char *cp, *cp2;
424 int c, isenc;
426 if (*linebuf == NULL)
427 *linebuf = srealloc(*linebuf, *linesize = 1);
428 **linebuf = '\0';
429 for (;;) {
430 if (--rem < 0)
431 return -1;
432 if ((c = readline(f, linebuf, linesize)) <= 0)
433 return -1;
434 for (cp = *linebuf; fieldnamechar(*cp & 0377); cp++);
435 if (cp > *linebuf)
436 while (blankchar(*cp & 0377))
437 cp++;
438 if (*cp != ':' || cp == *linebuf)
439 continue;
441 * I guess we got a headline.
442 * Handle wraparounding
444 *colon = cp;
445 cp = *linebuf + c;
446 for (;;) {
447 isenc = 0;
448 while (--cp >= *linebuf && blankchar(*cp & 0377));
449 cp++;
450 if (rem <= 0)
451 break;
452 if (cp-8 >= *linebuf && cp[-1] == '=' && cp[-2] == '?')
453 isenc |= 1;
454 ungetc(c = getc(f), f);
455 if (!blankchar(c))
456 break;
457 if ((c = readline(f, &line2, &line2size)) < 0)
458 break;
459 rem--;
460 for (cp2 = line2; blankchar(*cp2 & 0377); cp2++);
461 c -= cp2 - line2;
462 if (cp2[0] == '=' && cp2[1] == '?' && c > 8)
463 isenc |= 2;
464 if (cp + c >= *linebuf + *linesize - 2) {
465 size_t diff = cp - *linebuf;
466 size_t colondiff = *colon - *linebuf;
467 *linebuf = srealloc(*linebuf,
468 *linesize += c + 2);
469 cp = &(*linebuf)[diff];
470 *colon = &(*linebuf)[colondiff];
472 if (isenc != 3)
473 *cp++ = ' ';
474 memcpy(cp, cp2, c);
475 cp += c;
477 *cp = 0;
478 if (line2)
479 free(line2);
480 return rem;
482 /* NOTREACHED */
486 * Check whether the passed line is a header line of
487 * the desired breed. Return the field body, or 0.
489 char *
490 thisfield(const char *linebuf, const char *field)
492 while (lowerconv(*linebuf&0377) == lowerconv(*field&0377)) {
493 linebuf++;
494 field++;
496 if (*field != '\0')
497 return NULL;
498 while (blankchar(*linebuf&0377))
499 linebuf++;
500 if (*linebuf++ != ':')
501 return NULL;
502 while (blankchar(*linebuf&0377))
503 linebuf++;
504 return (char *)linebuf;
508 * Get sender's name from this message. If the message has
509 * a bunch of arpanet stuff in it, we may have to skin the name
510 * before returning it.
512 char *
513 nameof(struct message *mp, int reptype)
515 char *cp, *cp2;
517 cp = skin(name1(mp, reptype));
518 if (reptype != 0 || charcount(cp, '!') < 2)
519 return(cp);
520 cp2 = strrchr(cp, '!');
521 cp2--;
522 while (cp2 > cp && *cp2 != '!')
523 cp2--;
524 if (*cp2 == '!')
525 return(cp2 + 1);
526 return(cp);
530 * Start of a "comment".
531 * Ignore it.
533 char const *
534 skip_comment(char const *cp)
536 int nesting = 1;
538 for (; nesting > 0 && *cp; cp++) {
539 switch (*cp) {
540 case '\\':
541 if (cp[1])
542 cp++;
543 break;
544 case '(':
545 nesting++;
546 break;
547 case ')':
548 nesting--;
549 break;
552 return (cp);
556 * Return the start of a route-addr (address in angle brackets),
557 * if present.
559 char *
560 routeaddr(const char *name)
562 const char *np, *rp = NULL;
564 for (np = name; *np; np++) {
565 switch (*np) {
566 case '(':
567 np = skip_comment(&np[1]) - 1;
568 break;
569 case '"':
570 while (*np) {
571 if (*++np == '"')
572 break;
573 if (*np == '\\' && np[1])
574 np++;
576 break;
577 case '<':
578 rp = np;
579 break;
580 case '>':
581 return (char *)rp;
584 return NULL;
588 * Check if a name's address part contains invalid characters.
590 int
591 is_addr_invalid(struct name *np, int putmsg)
593 char *name = np->n_name;
594 int f = np->n_flags;
596 if ((f & NAME_ADDRSPEC_INVALID) == 0 || ! putmsg ||
597 (f & NAME_ADDRSPEC_ERR_EMPTY) != 0)
599 else if (f & NAME_ADDRSPEC_ERR_ATSEQ)
600 fprintf(stderr, tr(142, "%s contains invalid @@ sequence\n"),
601 name);
602 else {
603 char ce[sizeof(1ul)];
604 unsigned char c = NAME_ADDRSPEC_ERR_GETC(f);
606 if ((unsigned char)c >= 040 && (unsigned char)c <= 0177)
607 ce[0] = c, ce[1] = '\0';
608 else
609 snprintf(ce, sizeof(ce), "\\%03o", (unsigned int)c);
610 fprintf(stderr, tr(143,
611 "%s contains invalid character '%s'\n"),
612 name, ce);
614 return ((f & NAME_ADDRSPEC_INVALID) != 0);
618 * Returned the skinned n_name, use the cached value if available.
619 * Note well that it may *not* create a duplicate.
621 char *
622 skinned_name(struct name *np) /* TODO !HAVE_ASSERTS legacy */
624 #ifdef HAVE_ASSERTS
625 assert(np->n_flags & NAME_SKINNED);
626 return (np->n_name);
627 #else
628 return ((np->n_flags & NAME_SKINNED) ? np->n_name : skin(np->n_name));
629 #endif
633 * Skin an arpa net address according to the RFC 822 interpretation
634 * of "host-phrase."
636 char *
637 skin(char *name)
639 struct addrguts ag;
641 if (name == NULL)
642 return (NULL);
644 (void)addrspec_with_guts(1, name, &ag);
645 name = ag.ag_skinned;
646 if ((ag.ag_n_flags & NAME_NAME_SALLOC) == 0)
647 name = savestrbuf(name, ag.ag_slen);
648 return (name);
652 * Classify and check a (possibly skinned) header body according to RFC
653 * *addr-spec* rules; if it (is assumed to has been) skinned it may however be
654 * also a file or a pipe command, so check that first, then.
655 * Otherwise perform content checking and isolate the domain part (for IDNA).
657 static int
658 addrspec_check(int skinned, struct addrguts *agp)
660 char *addr, *p, in_quote, in_domain, hadat;
661 union {char c; unsigned char u;} c;
663 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED;
664 addr = agp->ag_skinned;
666 if (agp->ag_iaddr_end <= agp->ag_iaddr_start) {
667 agp->ag_n_flags |= NAME_ADDRSPEC_INVALID |
668 NAME_ADDRSPEC_ERR_EMPTY;
669 goto jleave;
672 /* If the field is not a recipient, it cannot be a file or a pipe */
673 if (! skinned) /* XXX || (gfield & (GTO | GCC | GBCC)) == 0) */
674 goto jaddr_check;
677 * Excerpt from nail.1:
679 * Recipient address specifications
680 * The rules are: Any name which starts with a `|' character specifies
681 * a pipe, the command string following the `|' is executed and
682 * the message is sent to its standard input; any other name which
683 * contains a `@' character is treated as a mail address; any other
684 * name which starts with a `+' character specifies a folder name; any
685 * other name which contains a `/' character but no `!' or `%'
686 * character before also specifies a folder name; what remains is
687 * treated as a mail address.
689 if (*addr == '|') {
690 agp->ag_n_flags |= NAME_ADDRSPEC_ISPIPE;
691 goto jleave;
693 if (memchr(addr, '@', agp->ag_slen) == NULL) {
694 if (*addr == '+')
695 goto jisfile;
696 for (p = addr; (c.c = *p); ++p) {
697 if (c.c == '!' || c.c == '%')
698 break;
699 if (c.c == '/') {
700 jisfile: agp->ag_n_flags |= NAME_ADDRSPEC_ISFILE;
701 goto jleave;
706 jaddr_check:
707 in_quote = in_domain = hadat = 0;
709 for (p = addr; (c.c = *p++) != '\0';) {
710 if (c.c == '"') {
711 in_quote = ! in_quote;
712 } else if (c.u < 040 || c.u >= 0177) { /*FIXME IDNA!!in_domin */
714 if (in_domain)
715 agp->ag_n_flags |= NAME_ADDRSPEC_IDNA;
716 else*/
717 break;
718 } else if (in_domain == 2) {
719 if ((c.c == ']' && *p != '\0') || c.c == '\\' ||
720 whitechar(c.c))
721 break;
722 } else if (in_quote && in_domain == 0) {
723 /*EMPTY*/;
724 } else if (c.c == '\\' && *p != '\0') {
725 ++p;
726 } else if (c.c == '@') {
727 if (hadat++) {
728 agp->ag_n_flags |= NAME_ADDRSPEC_INVALID |
729 NAME_ADDRSPEC_ERR_ATSEQ |
730 NAME_ADDRSPEC_ERR_SETC('@');
731 goto jleave;
733 agp->ag_sdom_start = (size_t)(p - addr);
734 in_domain = (*p == '[') ? 2 : 1;
735 continue;
736 } else if (c.c == '(' || c.c == ')' ||
737 c.c == '<' || c.c == '>' ||
738 c.c == ',' || c.c == ';' || c.c == ':' ||
739 c.c == '\\' || c.c == '[' || c.c == ']')
740 break;
741 hadat = 0;
744 if (c.c == '\0') {
745 agp->ag_sdom_end = (size_t)(--p - addr);
746 } else
747 agp->ag_n_flags |= NAME_ADDRSPEC_INVALID |
748 NAME_ADDRSPEC_ERR_SETC(c.c);
749 jleave:
750 return ((agp->ag_n_flags & NAME_ADDRSPEC_INVALID) != 0);
754 * TODO addrspec_with_guts(!DOSKIN): 'want to release v13, but the code is evil
755 * TODO in that {,GSKIN,GFULL} are not really enough to handle all names.
756 * TODO We will have to classify *exactly* those fields we really care about,
757 * TODO and simply perform high-bit-set checking only (?) for all the others.
758 * TODO For those we do care for, provide special parsers that classify and
759 * TODO extract the stuff *exactly* (after a short glance i think NetBSD mailx
760 * TODO does this). And *do* see namecache and header object TODO notes.
763 * Skin *name* and extract the *addr-spec* according to RFC 5322. TODO 822:5322
764 * Store the result in .ag_skinned and also fill in those .ag_ fields that have
765 * actually been seen.
766 * Return 0 if something good has been parsed, 1 if fun didn't exactly know how
767 * to deal with the input, or if that was plain invalid.
770 addrspec_with_guts(int doskin, char const *name, struct addrguts *agp)
772 char *cp, *cp2, *bufend, *nbuf, c;
773 int gotlt, lastsp;
775 memset(agp, 0, sizeof *agp);
777 if ((agp->ag_input = name) == NULL || /* XXX ever? */
778 (agp->ag_ilen = strlen(name)) == 0) {
779 agp->ag_n_flags |= NAME_ADDRSPEC_CHECKED |
780 NAME_ADDRSPEC_INVALID | NAME_ADDRSPEC_ERR_EMPTY;
781 agp->ag_skinned = ""; /* NAME_SALLOC not set */
782 agp->ag_slen = 0;
783 return (1);
786 if (! doskin || ! anyof(name, "(< ")) {
787 /*agp->ag_iaddr_start = 0;*/
788 agp->ag_iaddr_end = agp->ag_ilen - 1;
789 agp->ag_skinned = (char*)name; /* XXX (NAME_SALLOC not set) */
790 agp->ag_slen = agp->ag_ilen;
791 agp->ag_n_flags = NAME_SKINNED;
792 return (addrspec_check(doskin, agp));
795 /* Something makes us think we have to perform the skin operation */
796 nbuf = ac_alloc(agp->ag_ilen + 1);
797 /*agp->ag_iaddr_start = 0;*/
798 cp2 = bufend = nbuf;
799 gotlt = lastsp = 0;
801 for (cp = name; (c = *cp++) != '\0'; ) {
802 switch (c) {
803 case '(':
804 cp = skip_comment(cp);
805 lastsp = 0;
806 break;
808 case '"':
810 * Start of a "quoted-string".
811 * Copy it in its entirety.
812 * XXX RFC: quotes are "semantically invisible"
813 * XXX But it was explicitly added (Changelog.Heirloom,
814 * XXX [9.23] released 11/15/00, "Do not remove quotes
815 * XXX when skinning names"? No more info..
817 *cp2++ = c;
818 while ((c = *cp) != '\0') {
819 cp++;
820 if (c == '"') {
821 *cp2++ = c;
822 break;
824 if (c != '\\')
825 *cp2++ = c;
826 else if ((c = *cp) != '\0') {
827 *cp2++ = c;
828 cp++;
831 lastsp = 0;
832 break;
834 case ' ':
835 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
836 cp += 3, *cp2++ = '@';
837 else
838 if (cp[0] == '@' && cp[1] == ' ')
839 cp += 2, *cp2++ = '@';
840 else
841 lastsp = 1;
842 break;
844 case '<':
845 agp->ag_iaddr_start = (size_t)(cp - name);
846 cp2 = bufend;
847 gotlt++;
848 lastsp = 0;
849 break;
851 case '>':
852 if (gotlt) {
853 /* (addrspec_check() verifies these later!) */
854 agp->ag_iaddr_end = (size_t)(cp - 1 - name);
855 gotlt = 0;
856 while ((c = *cp) != '\0' && c != ',') {
857 cp++;
858 if (c == '(')
859 cp = skip_comment(cp);
860 else if (c == '"')
861 while ((c = *cp) != '\0') {
862 cp++;
863 if (c == '"')
864 break;
865 if (c == '\\' && *cp)
866 cp++;
869 lastsp = 0;
870 break;
872 /* FALLTRHOUGH */
874 default:
875 if (lastsp) {
876 lastsp = 0;
877 *cp2++ = ' ';
879 *cp2++ = c;
880 if (c == ',' && ! gotlt) {
881 *cp2++ = ' ';
882 for (; *cp == ' '; ++cp)
884 lastsp = 0;
885 bufend = cp2;
889 agp->ag_slen = (size_t)(cp2 - nbuf);
890 if (agp->ag_iaddr_end == 0)
891 agp->ag_iaddr_end = agp->ag_iaddr_start + agp->ag_slen;
893 agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
894 ac_free(nbuf);
895 agp->ag_n_flags = NAME_NAME_SALLOC | NAME_SKINNED;
896 return (addrspec_check(doskin, agp));
900 * Fetch the real name from an internet mail address field.
902 char *
903 realname(char *name)
905 char const *cp, *cq, *cstart = NULL, *cend = NULL;
906 char *rname, *rp;
907 struct str in, out;
908 int quoted, good, nogood;
910 if (name == NULL)
911 return NULL;
912 for (cp = (char*)name; *cp; cp++) {
913 switch (*cp) {
914 case '(':
915 if (cstart)
917 * More than one comment in address, doesn't
918 * make sense to display it without context.
919 * Return the entire field,
921 return mime_fromaddr(name);
922 cstart = cp++;
923 cp = skip_comment(cp);
924 cend = cp--;
925 if (cend <= cstart)
926 cend = cstart = NULL;
927 break;
928 case '"':
929 while (*cp) {
930 if (*++cp == '"')
931 break;
932 if (*cp == '\\' && cp[1])
933 cp++;
935 break;
936 case '<':
937 if (cp > name) {
938 cstart = name;
939 cend = cp;
941 break;
942 case ',':
944 * More than one address. Just use the first one.
946 goto brk;
949 brk: if (cstart == NULL) {
950 if (*name == '<')
952 * If name contains only a route-addr, the
953 * surrounding angle brackets don't serve any
954 * useful purpose when displaying, so they
955 * are removed.
957 return prstr(skin(name));
958 return mime_fromaddr(name);
960 rp = rname = ac_alloc(cend - cstart + 1);
962 * Strip quotes. Note that quotes that appear within a MIME-
963 * encoded word are not stripped. The idea is to strip only
964 * syntactical relevant things (but this is not necessarily
965 * the most sensible way in practice).
967 quoted = 0;
968 for (cp = cstart; cp < cend; cp++) {
969 if (*cp == '(' && !quoted) {
970 cq = skip_comment(++cp);
971 if (--cq > cend)
972 cq = cend;
973 while (cp < cq) {
974 if (*cp == '\\' && &cp[1] < cq)
975 cp++;
976 *rp++ = *cp++;
978 } else if (*cp == '\\' && &cp[1] < cend)
979 *rp++ = *++cp;
980 else if (*cp == '"') {
981 quoted = !quoted;
982 continue;
983 } else
984 *rp++ = *cp;
986 *rp = '\0';
987 in.s = rname;
988 in.l = rp - rname;
989 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
990 ac_free(rname);
991 rname = savestr(out.s);
992 free(out.s);
993 while (blankchar(*rname & 0377))
994 rname++;
995 for (rp = rname; *rp; rp++);
996 while (--rp >= rname && blankchar(*rp & 0377))
997 *rp = '\0';
998 if (rp == rname)
999 return mime_fromaddr(name);
1001 * mime_fromhdr() has converted all nonprintable characters to
1002 * question marks now. These and blanks are considered uninteresting;
1003 * if the displayed part of the real name contains more than 25% of
1004 * them, it is probably better to display the plain email address
1005 * instead.
1007 good = 0;
1008 nogood = 0;
1009 for (rp = rname; *rp && rp < &rname[20]; rp++)
1010 if (*rp == '?' || blankchar(*rp & 0377))
1011 nogood++;
1012 else
1013 good++;
1014 if (good*3 < nogood)
1015 return prstr(skin(name));
1016 return rname;
1020 * Fetch the sender's name from the passed message.
1021 * Reptype can be
1022 * 0 -- get sender's name for display purposes
1023 * 1 -- get sender's name for reply
1024 * 2 -- get sender's name for Reply
1026 char *
1027 name1(struct message *mp, int reptype)
1029 char *namebuf;
1030 size_t namesize;
1031 char *linebuf = NULL;
1032 size_t linesize = 0;
1033 char *cp, *cp2;
1034 FILE *ibuf;
1035 int first = 1;
1037 if ((cp = hfield1("from", mp)) != NULL && *cp != '\0')
1038 return cp;
1039 if (reptype == 0 && (cp = hfield1("sender", mp)) != NULL &&
1040 *cp != '\0')
1041 return cp;
1042 namebuf = smalloc(namesize = 1);
1043 namebuf[0] = 0;
1044 if (mp->m_flag & MNOFROM)
1045 goto out;
1046 if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
1047 goto out;
1048 if (readline(ibuf, &linebuf, &linesize) < 0)
1049 goto out;
1050 newname:
1051 if (namesize <= linesize)
1052 namebuf = srealloc(namebuf, namesize = linesize + 1);
1053 for (cp = linebuf; *cp && *cp != ' '; cp++)
1055 for (; blankchar(*cp & 0377); cp++);
1056 for (cp2 = &namebuf[strlen(namebuf)];
1057 *cp && !blankchar(*cp & 0377) && cp2 < namebuf + namesize - 1;)
1058 *cp2++ = *cp++;
1059 *cp2 = '\0';
1060 if (readline(ibuf, &linebuf, &linesize) < 0)
1061 goto out;
1062 if ((cp = strchr(linebuf, 'F')) == NULL)
1063 goto out;
1064 if (strncmp(cp, "From", 4) != 0)
1065 goto out;
1066 if (namesize <= linesize)
1067 namebuf = srealloc(namebuf, namesize = linesize + 1);
1068 while ((cp = strchr(cp, 'r')) != NULL) {
1069 if (strncmp(cp, "remote", 6) == 0) {
1070 if ((cp = strchr(cp, 'f')) == NULL)
1071 break;
1072 if (strncmp(cp, "from", 4) != 0)
1073 break;
1074 if ((cp = strchr(cp, ' ')) == NULL)
1075 break;
1076 cp++;
1077 if (first) {
1078 strncpy(namebuf, cp, namesize);
1079 first = 0;
1080 } else {
1081 cp2=strrchr(namebuf, '!')+1;
1082 strncpy(cp2, cp, (namebuf+namesize)-cp2);
1084 namebuf[namesize-2]='\0';
1085 strcat(namebuf, "!");
1086 goto newname;
1088 cp++;
1090 out:
1091 if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
1092 *cp == '\0')
1093 cp = savestr(namebuf);
1094 if (linebuf)
1095 free(linebuf);
1096 free(namebuf);
1097 return cp;
1100 static int
1101 msgidnextc(const char **cp, int *status)
1103 int c;
1105 for (;;) {
1106 if (*status & 01) {
1107 if (**cp == '"') {
1108 *status &= ~01;
1109 (*cp)++;
1110 continue;
1112 if (**cp == '\\') {
1113 (*cp)++;
1114 if (**cp == '\0')
1115 goto eof;
1117 goto dfl;
1119 switch (**cp) {
1120 case '(':
1121 *cp = skip_comment(&(*cp)[1]);
1122 continue;
1123 case '>':
1124 case '\0':
1125 eof:
1126 return '\0';
1127 case '"':
1128 (*cp)++;
1129 *status |= 01;
1130 continue;
1131 case '@':
1132 *status |= 02;
1133 /*FALLTHRU*/
1134 default:
1135 dfl:
1136 c = *(*cp)++ & 0377;
1137 return *status & 02 ? lowerconv(c) : c;
1142 int
1143 msgidcmp(const char *s1, const char *s2)
1145 int q1 = 0, q2 = 0;
1146 int c1, c2;
1148 do {
1149 c1 = msgidnextc(&s1, &q1);
1150 c2 = msgidnextc(&s2, &q2);
1151 if (c1 != c2)
1152 return c1 - c2;
1153 } while (c1 && c2);
1154 return c1 - c2;
1158 * Count the occurances of c in str
1160 static int
1161 charcount(char *str, int c)
1163 char *cp;
1164 int i;
1166 for (i = 0, cp = str; *cp; cp++)
1167 if (*cp == c)
1168 i++;
1169 return(i);
1173 * See if the given header field is supposed to be ignored.
1176 is_ign(char *field, size_t fieldlen, struct ignoretab ignore[2])
1178 char *realfld;
1179 int ret;
1181 if (ignore == NULL)
1182 return 0;
1183 if (ignore == allignore)
1184 return 1;
1186 * Lower-case the string, so that "Status" and "status"
1187 * will hash to the same place.
1189 realfld = ac_alloc(fieldlen + 1);
1190 i_strcpy(realfld, field, fieldlen + 1);
1191 if (ignore[1].i_count > 0)
1192 ret = !member(realfld, ignore + 1);
1193 else
1194 ret = member(realfld, ignore);
1195 ac_free(realfld);
1196 return ret;
1199 int
1200 member(char *realfield, struct ignoretab *table)
1202 struct ignore *igp;
1204 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
1205 if (*igp->i_field == *realfield &&
1206 strcmp(igp->i_field, realfield) == 0)
1207 return (1);
1208 return (0);
1212 * Fake Sender for From_ lines if missing, e. g. with POP3.
1214 char *
1215 fakefrom(struct message *mp)
1217 char *name;
1219 if (((name = skin(hfield1("return-path", mp))) == NULL ||
1220 *name == '\0' ) &&
1221 ((name = skin(hfield1("from", mp))) == NULL ||
1222 *name == '\0'))
1223 name = "-";
1224 return name;
1227 char *
1228 fakedate(time_t t)
1230 char *cp, *cq;
1232 cp = ctime(&t);
1233 for (cq = cp; *cq && *cq != '\n'; cq++);
1234 *cq = '\0';
1235 return savestr(cp);
1238 char const *
1239 nexttoken(char const *cp)
1241 for (;;) {
1242 if (*cp == '\0')
1243 return NULL;
1244 if (*cp == '(') {
1245 int nesting = 0;
1247 while (*cp != '\0') {
1248 switch (*cp++) {
1249 case '(':
1250 nesting++;
1251 break;
1252 case ')':
1253 nesting--;
1254 break;
1256 if (nesting <= 0)
1257 break;
1259 } else if (blankchar(*cp & 0377) || *cp == ',')
1260 cp++;
1261 else
1262 break;
1264 return cp;
1268 * From username Fri Jan 2 20:13:51 2004
1269 * | | | | |
1270 * 0 5 10 15 20
1272 time_t
1273 unixtime(char const *from)
1275 char const *fp;
1276 char *xp;
1277 time_t t;
1278 int i, year, month, day, hour, minute, second;
1279 int tzdiff;
1280 struct tm *tmptr;
1282 for (fp = from; *fp && *fp != '\n'; fp++);
1283 fp -= 24;
1284 if (fp - from < 7)
1285 goto invalid;
1286 if (fp[3] != ' ')
1287 goto invalid;
1288 for (i = 0; month_names[i]; i++)
1289 if (strncmp(&fp[4], month_names[i], 3) == 0)
1290 break;
1291 if (month_names[i] == 0)
1292 goto invalid;
1293 month = i + 1;
1294 if (fp[7] != ' ')
1295 goto invalid;
1296 day = strtol(&fp[8], &xp, 10);
1297 if (*xp != ' ' || xp != &fp[10])
1298 goto invalid;
1299 hour = strtol(&fp[11], &xp, 10);
1300 if (*xp != ':' || xp != &fp[13])
1301 goto invalid;
1302 minute = strtol(&fp[14], &xp, 10);
1303 if (*xp != ':' || xp != &fp[16])
1304 goto invalid;
1305 second = strtol(&fp[17], &xp, 10);
1306 if (*xp != ' ' || xp != &fp[19])
1307 goto invalid;
1308 year = strtol(&fp[20], &xp, 10);
1309 if (xp != &fp[24])
1310 goto invalid;
1311 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1312 (time_t)-1)
1313 goto invalid;
1314 tzdiff = t - mktime(gmtime(&t));
1315 tmptr = localtime(&t);
1316 if (tmptr->tm_isdst > 0)
1317 tzdiff += 3600;
1318 t -= tzdiff;
1319 return t;
1320 invalid:
1321 time(&t);
1322 return t;
1325 time_t
1326 rfctime(char const *date)
1328 char const *cp = date;
1329 char *x;
1330 time_t t;
1331 int i, year, month, day, hour, minute, second;
1333 if ((cp = nexttoken(cp)) == NULL)
1334 goto invalid;
1335 if (alphachar(cp[0] & 0377) && alphachar(cp[1] & 0377) &&
1336 alphachar(cp[2] & 0377) && cp[3] == ',') {
1337 if ((cp = nexttoken(&cp[4])) == NULL)
1338 goto invalid;
1340 day = strtol(cp, &x, 10);
1341 if ((cp = nexttoken(x)) == NULL)
1342 goto invalid;
1343 for (i = 0; month_names[i]; i++) {
1344 if (strncmp(cp, month_names[i], 3) == 0)
1345 break;
1347 if (month_names[i] == NULL)
1348 goto invalid;
1349 month = i + 1;
1350 if ((cp = nexttoken(&cp[3])) == NULL)
1351 goto invalid;
1352 year = strtol(cp, &x, 10);
1353 if ((cp = nexttoken(x)) == NULL)
1354 goto invalid;
1355 hour = strtol(cp, &x, 10);
1356 if (*x != ':')
1357 goto invalid;
1358 cp = &x[1];
1359 minute = strtol(cp, &x, 10);
1360 if (*x == ':') {
1361 cp = &x[1];
1362 second = strtol(cp, &x, 10);
1363 } else
1364 second = 0;
1365 if ((t = combinetime(year, month, day, hour, minute, second)) ==
1366 (time_t)-1)
1367 goto invalid;
1368 if ((cp = nexttoken(x)) != NULL) {
1369 int sign = -1;
1370 char buf[3];
1372 switch (*cp) {
1373 case '-':
1374 sign = 1;
1375 /*FALLTHRU*/
1376 case '+':
1377 cp++;
1379 if (digitchar(cp[0] & 0377) && digitchar(cp[1] & 0377) &&
1380 digitchar(cp[2] & 0377) &&
1381 digitchar(cp[3] & 0377)) {
1382 buf[2] = '\0';
1383 buf[0] = cp[0];
1384 buf[1] = cp[1];
1385 t += strtol(buf, NULL, 10) * sign * 3600;
1386 buf[0] = cp[2];
1387 buf[1] = cp[3];
1388 t += strtol(buf, NULL, 10) * sign * 60;
1391 return t;
1392 invalid:
1393 return 0;
1396 #define leapyear(year) ((year % 100 ? year : year / 100) % 4 == 0)
1398 time_t
1399 combinetime(int year, int month, int day, int hour, int minute, int second)
1401 time_t t;
1403 if (second < 0 || minute < 0 || hour < 0 || day < 1)
1404 return -1;
1405 t = second + minute * 60 + hour * 3600 + (day - 1) * 86400;
1406 if (year < 70)
1407 year += 2000;
1408 else if (year < 1900)
1409 year += 1900;
1410 if (month > 1)
1411 t += 86400 * 31;
1412 if (month > 2)
1413 t += 86400 * (leapyear(year) ? 29 : 28);
1414 if (month > 3)
1415 t += 86400 * 31;
1416 if (month > 4)
1417 t += 86400 * 30;
1418 if (month > 5)
1419 t += 86400 * 31;
1420 if (month > 6)
1421 t += 86400 * 30;
1422 if (month > 7)
1423 t += 86400 * 31;
1424 if (month > 8)
1425 t += 86400 * 31;
1426 if (month > 9)
1427 t += 86400 * 30;
1428 if (month > 10)
1429 t += 86400 * 31;
1430 if (month > 11)
1431 t += 86400 * 30;
1432 year -= 1900;
1433 t += (year - 70) * 31536000 + ((year - 69) / 4) * 86400 -
1434 ((year - 1) / 100) * 86400 + ((year + 299) / 400) * 86400;
1435 return t;
1438 void
1439 substdate(struct message *m)
1441 char const *cp;
1442 time_t now;
1445 * Determine the date to print in faked 'From ' lines. This is
1446 * traditionally the date the message was written to the mail
1447 * file. Try to determine this using RFC message header fields,
1448 * or fall back to current time.
1450 time(&now);
1451 if ((cp = hfield1("received", m)) != NULL) {
1452 while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
1454 cp++;
1455 while (alnumchar(*cp & 0377));
1457 if (cp && *++cp)
1458 m->m_time = rfctime(cp);
1460 if (m->m_time == 0 || m->m_time > now)
1461 if ((cp = hfield1("date", m)) != NULL)
1462 m->m_time = rfctime(cp);
1463 if (m->m_time == 0 || m->m_time > now)
1464 m->m_time = now;
1468 check_from_and_sender(struct name *fromfield, struct name *senderfield)
1470 if (fromfield && fromfield->n_flink && senderfield == NULL) {
1471 fprintf(stderr, "A Sender: field is required with multiple "
1472 "addresses in From: field.\n");
1473 return 1;
1475 if (senderfield && senderfield->n_flink) {
1476 fprintf(stderr, "The Sender: field may contain "
1477 "only one address.\n");
1478 return 2;
1480 return 0;
1483 char *
1484 getsender(struct message *mp)
1486 char *cp;
1487 struct name *np;
1489 if ((cp = hfield1("from", mp)) == NULL ||
1490 (np = sextract(cp, GEXTRA|GSKIN)) == NULL)
1491 return NULL;
1492 return np->n_flink != NULL ? skin(hfield1("sender", mp)) : np->n_name;