Home | History | Annotate | Line # | Download | only in mail
format.c revision 1.17
      1  1.17    andvar /*	$NetBSD: format.c,v 1.17 2022/08/07 10:12:19 andvar Exp $	*/
      2   1.1  christos 
      3   1.1  christos /*-
      4   1.1  christos  * Copyright (c) 2006 The NetBSD Foundation, Inc.
      5   1.1  christos  * All rights reserved.
      6   1.1  christos  *
      7   1.1  christos  * This code is derived from software contributed to The NetBSD Foundation
      8   1.1  christos  * by Anon Ymous.
      9   1.1  christos  *
     10   1.1  christos  * Redistribution and use in source and binary forms, with or without
     11   1.1  christos  * modification, are permitted provided that the following conditions
     12   1.1  christos  * are met:
     13   1.1  christos  * 1. Redistributions of source code must retain the above copyright
     14   1.1  christos  *    notice, this list of conditions and the following disclaimer.
     15   1.1  christos  * 2. Redistributions in binary form must reproduce the above copyright
     16   1.1  christos  *    notice, this list of conditions and the following disclaimer in the
     17   1.1  christos  *    documentation and/or other materials provided with the distribution.
     18   1.1  christos  *
     19   1.1  christos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20   1.1  christos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21   1.1  christos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22   1.1  christos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23   1.1  christos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24   1.1  christos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25   1.1  christos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26   1.1  christos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27   1.1  christos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28   1.1  christos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29   1.1  christos  * POSSIBILITY OF SUCH DAMAGE.
     30   1.1  christos  */
     31   1.1  christos 
     32   1.1  christos #include <sys/cdefs.h>
     33   1.1  christos #ifndef __lint__
     34  1.17    andvar __RCSID("$NetBSD: format.c,v 1.17 2022/08/07 10:12:19 andvar Exp $");
     35   1.1  christos #endif /* not __lint__ */
     36   1.1  christos 
     37   1.1  christos #include <time.h>
     38   1.1  christos #include <stdio.h>
     39   1.1  christos #include <util.h>
     40   1.1  christos 
     41   1.1  christos #include "def.h"
     42   1.1  christos #include "extern.h"
     43   1.1  christos #include "format.h"
     44   1.1  christos #include "glob.h"
     45   1.2  christos #include "thread.h"
     46   1.1  christos 
     47  1.11  christos #undef DEBUG
     48  1.11  christos #ifdef DEBUG
     49  1.11  christos #define DPRINTF(a) printf a
     50  1.11  christos #else
     51  1.11  christos #define DPRINTF(a)
     52  1.11  christos #endif
     53   1.1  christos 
     54   1.1  christos static void
     55   1.1  christos check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt)
     56   1.1  christos {
     57   1.1  christos 	char *q;
     58   1.1  christos 	if (*p + cnt < *buf + *bufsize)
     59   1.1  christos 		return;
     60   1.1  christos 	*bufsize *= 2;
     61  1.12  christos 	q = erealloc(*buf, *bufsize);
     62   1.1  christos 	*p = q + (*p - *buf);
     63   1.1  christos 	*buf = q;
     64   1.1  christos }
     65   1.1  christos 
     66   1.1  christos static const char *
     67   1.1  christos sfmtoff(const char **fmtbeg, const char *fmtch, off_t off)
     68   1.1  christos {
     69   1.1  christos 	char *newfmt;	/* pointer to new format string */
     70   1.1  christos 	size_t len;	/* space for "lld" including '\0' */
     71   1.2  christos 	char *p;
     72   1.2  christos 
     73   1.1  christos 	len = fmtch - *fmtbeg + sizeof(PRId64);
     74   1.1  christos 	newfmt = salloc(len);
     75  1.12  christos 	(void)strlcpy(newfmt, *fmtbeg, len - sizeof(PRId64) + 1);
     76   1.1  christos 	(void)strlcat(newfmt, PRId64, len);
     77   1.1  christos 	*fmtbeg = fmtch + 1;
     78   1.2  christos 	(void)sasprintf(&p, newfmt, off);
     79   1.2  christos 	return p;
     80   1.1  christos }
     81   1.1  christos 
     82   1.1  christos static const char *
     83   1.1  christos sfmtint(const char **fmtbeg, const char *fmtch, int num)
     84   1.1  christos {
     85   1.1  christos 	char *newfmt;
     86   1.1  christos 	size_t len;
     87   1.2  christos 	char *p;
     88   1.1  christos 
     89   1.1  christos 	len = fmtch - *fmtbeg + 2;	/* space for 'd' and '\0' */
     90   1.1  christos 	newfmt = salloc(len);
     91   1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len);
     92   1.1  christos 	newfmt[len-2] = 'd';		/* convert to printf format */
     93   1.1  christos 	*fmtbeg = fmtch + 1;
     94   1.2  christos 	(void)sasprintf(&p, newfmt, num);
     95   1.2  christos 	return p;
     96   1.1  christos }
     97   1.1  christos 
     98   1.1  christos static const char *
     99   1.1  christos sfmtstr(const char **fmtbeg, const char *fmtch, const char *str)
    100   1.1  christos {
    101   1.1  christos 	char *newfmt;
    102   1.1  christos 	size_t len;
    103   1.2  christos 	char *p;
    104   1.1  christos 
    105   1.1  christos 	len = fmtch - *fmtbeg + 2;	/* space for 's' and '\0' */
    106   1.1  christos 	newfmt = salloc(len);
    107   1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len);
    108   1.1  christos 	newfmt[len-2] = 's';		/* convert to printf format */
    109   1.1  christos 	*fmtbeg = fmtch + 1;
    110   1.2  christos 	(void)sasprintf(&p, newfmt, str ? str : "");
    111   1.2  christos 	return p;
    112   1.2  christos }
    113   1.2  christos 
    114   1.2  christos #ifdef THREAD_SUPPORT
    115   1.2  christos static char*
    116   1.2  christos sfmtdepth(char *str, int depth)
    117   1.2  christos {
    118   1.2  christos 	char *p;
    119   1.2  christos 	if (*str == '\0') {
    120   1.2  christos 		(void)sasprintf(&p, "%d", depth);
    121   1.2  christos 		return p;
    122   1.2  christos 	}
    123   1.2  christos 	p = __UNCONST("");
    124   1.2  christos 	for (/*EMPTY*/; depth > 0; depth--)
    125   1.2  christos 		(void)sasprintf(&p, "%s%s", p, str);
    126   1.2  christos 	return p;
    127   1.1  christos }
    128   1.2  christos #endif
    129   1.1  christos 
    130   1.1  christos static const char *
    131   1.1  christos sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp)
    132   1.1  christos {
    133   1.1  christos 	char *q;
    134   1.1  christos 	q = strchr(fmtch + 1, '?');
    135   1.1  christos 	if (q) {
    136   1.1  christos 		size_t len;
    137   1.1  christos 		char *p;
    138   1.1  christos 		const char *str;
    139   1.1  christos 		int skin_it;
    140   1.2  christos #ifdef THREAD_SUPPORT
    141   1.2  christos 		int depth;
    142   1.2  christos #endif
    143   1.2  christos 		if (mp == NULL) {
    144   1.2  christos 			*fmtbeg = q + 1;
    145   1.2  christos 			return NULL;
    146   1.2  christos 		}
    147   1.2  christos #ifdef THREAD_SUPPORT
    148   1.2  christos 		depth = mp->m_depth;
    149   1.2  christos #endif
    150   1.2  christos 		skin_it = 0;
    151   1.2  christos 		switch (fmtch[1]) { /* check the '?' modifier */
    152   1.2  christos #ifdef THREAD_SUPPORT
    153   1.2  christos 		case '&':	/* use the relative depth */
    154   1.2  christos 			depth -= thread_depth();
    155   1.2  christos 			/* FALLTHROUGH */
    156   1.2  christos 		case '*':	/* use the absolute depth */
    157   1.2  christos 			len = q - fmtch - 1;
    158   1.2  christos 			p = salloc(len);
    159   1.2  christos 			(void)strlcpy(p, fmtch + 2, len);
    160   1.2  christos 			p = sfmtdepth(p, depth);
    161   1.2  christos 			break;
    162  1.11  christos #endif
    163   1.2  christos 		case '-':
    164   1.2  christos 			skin_it = 1;
    165   1.2  christos 			/* FALLTHROUGH */
    166   1.2  christos 		default:
    167   1.2  christos 			len = q - fmtch - skin_it;
    168   1.2  christos 			p = salloc(len);
    169   1.2  christos 			(void)strlcpy(p, fmtch + skin_it + 1, len);
    170   1.2  christos 			p = hfield(p, mp);
    171   1.2  christos 			if (skin_it)
    172   1.2  christos 				p = skin(p);
    173   1.2  christos 			break;
    174   1.2  christos 		}
    175   1.2  christos 		str = sfmtstr(fmtbeg, fmtch, p);
    176   1.1  christos 		*fmtbeg = q + 1;
    177   1.1  christos 		return str;
    178   1.1  christos 	}
    179   1.1  christos 	return NULL;
    180   1.1  christos }
    181   1.1  christos 
    182   1.2  christos struct flags_s {
    183   1.2  christos 	int f_and;
    184   1.2  christos 	int f_or;
    185   1.2  christos 	int f_new;	/* some message in the thread is new */
    186   1.2  christos 	int f_unread;	/* some message in the thread is unread */
    187   1.2  christos };
    188   1.2  christos 
    189   1.2  christos static void
    190   1.2  christos get_and_or_flags(struct message *mp, struct flags_s *flags)
    191   1.2  christos {
    192   1.2  christos 	for (/*EMPTY*/; mp; mp = mp->m_flink) {
    193   1.2  christos 		flags->f_and &= mp->m_flag;
    194   1.2  christos 		flags->f_or  |= mp->m_flag;
    195   1.2  christos 		flags->f_new    |= (mp->m_flag & (MREAD|MNEW)) == MNEW;
    196   1.2  christos 		flags->f_unread |= (mp->m_flag & (MREAD|MNEW)) == 0;
    197   1.2  christos 		get_and_or_flags(mp->m_clink, flags);
    198   1.2  christos 	}
    199   1.2  christos }
    200   1.2  christos 
    201   1.1  christos static const char *
    202   1.2  christos sfmtflag(const char **fmtbeg, const char *fmtch, struct message *mp)
    203   1.1  christos {
    204   1.1  christos 	char disp[2];
    205   1.2  christos 	struct flags_s flags;
    206   1.2  christos 	int is_thread;
    207   1.2  christos 
    208   1.2  christos 	if (mp == NULL)
    209   1.2  christos 		return NULL;
    210  1.11  christos 
    211   1.2  christos 	is_thread = mp->m_clink != NULL;
    212   1.2  christos 	disp[0] = is_thread ? '+' : ' ';
    213   1.1  christos 	disp[1] = '\0';
    214   1.2  christos 
    215   1.2  christos 	flags.f_and = mp->m_flag;
    216   1.2  christos 	flags.f_or = mp->m_flag;
    217   1.2  christos 	flags.f_new = 0;
    218   1.2  christos 	flags.f_unread = 0;
    219   1.2  christos #ifdef THREAD_SUPPORT
    220   1.2  christos 	if (thread_hidden())
    221   1.2  christos 		get_and_or_flags(mp->m_clink, &flags);
    222   1.2  christos #endif
    223   1.2  christos 
    224   1.2  christos 	if (flags.f_or & MTAGGED)
    225   1.2  christos 		disp[0] = 't';
    226   1.2  christos 	if (flags.f_and & MTAGGED)
    227   1.2  christos 		disp[0] = 'T';
    228   1.2  christos 
    229   1.2  christos 	if (flags.f_or & MMODIFY)
    230   1.2  christos 		disp[0] = 'e';
    231   1.2  christos 	if (flags.f_and & MMODIFY)
    232   1.2  christos 		disp[0] = 'E';
    233   1.2  christos 
    234   1.2  christos 	if (flags.f_or & MSAVED)
    235   1.2  christos 		disp[0] = '&';
    236   1.2  christos 	if (flags.f_and & MSAVED)
    237   1.1  christos 		disp[0] = '*';
    238   1.2  christos 
    239   1.2  christos 	if (flags.f_or & MPRESERVE)
    240   1.2  christos 		disp[0] = 'p';
    241   1.2  christos 	if (flags.f_and & MPRESERVE)
    242   1.1  christos 		disp[0] = 'P';
    243   1.2  christos 
    244   1.2  christos 	if (flags.f_unread)
    245   1.2  christos 		disp[0] = 'u';
    246   1.2  christos 	if ((flags.f_or & (MREAD|MNEW)) == 0)
    247   1.2  christos 		disp[0] = 'U';
    248   1.2  christos 
    249   1.2  christos 	if (flags.f_new)
    250   1.2  christos 		disp[0] = 'n';
    251   1.2  christos 	if ((flags.f_and & (MREAD|MNEW)) == MNEW)
    252   1.1  christos 		disp[0] = 'N';
    253   1.2  christos 
    254   1.2  christos 	if (flags.f_or & MBOX)
    255   1.2  christos 		disp[0] = 'm';
    256   1.2  christos 	if (flags.f_and & MBOX)
    257   1.1  christos 		disp[0] = 'M';
    258   1.2  christos 
    259   1.1  christos 	return sfmtstr(fmtbeg, fmtch, disp);
    260   1.1  christos }
    261   1.1  christos 
    262   1.1  christos static const char *
    263   1.1  christos login_name(const char *addr)
    264   1.1  christos {
    265   1.1  christos 	char *p;
    266   1.1  christos 	p = strchr(addr, '@');
    267   1.1  christos 	if (p) {
    268   1.1  christos 		char *q;
    269   1.1  christos 		size_t len;
    270   1.1  christos 		len = p - addr + 1;
    271   1.1  christos 		q = salloc(len);
    272   1.1  christos 		(void)strlcpy(q, addr, len);
    273   1.1  christos 		return q;
    274   1.1  christos 	}
    275   1.1  christos 	return addr;
    276   1.1  christos }
    277   1.1  christos 
    278   1.2  christos /*
    279   1.2  christos  * A simple routine to get around a lint warning.
    280   1.2  christos  */
    281   1.2  christos static inline const char *
    282   1.2  christos skip_fmt(const char **src, const char *p)
    283   1.2  christos {
    284   1.2  christos 	*src = p;
    285   1.2  christos 	return NULL;
    286   1.2  christos }
    287   1.2  christos 
    288   1.1  christos static const char *
    289   1.1  christos subformat(const char **src, struct message *mp, const char *addr,
    290  1.11  christos     const char *user, const char *subj, int tm_isdst)
    291   1.1  christos {
    292   1.2  christos #if 0
    293   1.2  christos /* XXX - lint doesn't like this, hence skip_fmt(). */
    294   1.2  christos #define MP(a)	mp ? a : (*src = (p + 1), NULL)
    295   1.2  christos #else
    296   1.2  christos #define MP(a)	mp ? a : skip_fmt(src, p + 1);
    297   1.2  christos #endif
    298   1.1  christos 	const char *p;
    299   1.1  christos 
    300   1.1  christos 	p = *src;
    301   1.1  christos 	if (p[1] == '%') {
    302   1.1  christos 		*src += 2;
    303   1.1  christos 		return "%%";
    304   1.1  christos 	}
    305   1.1  christos 	for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++)
    306   1.1  christos 		continue;
    307   1.1  christos 
    308   1.1  christos 	switch (*p) {
    309  1.11  christos 	/*
    310  1.11  christos 	 * Our format extensions to strftime(3)
    311  1.11  christos 	 */
    312   1.1  christos 	case '?':
    313   1.2  christos 		return sfmtfield(src, p, mp);
    314   1.1  christos 	case 'J':
    315   1.2  christos 		return MP(sfmtint(src, p, (int)(mp->m_lines - mp->m_blines)));
    316   1.1  christos 	case 'K':
    317   1.1  christos  		return MP(sfmtint(src, p, (int)mp->m_blines));
    318   1.1  christos 	case 'L':
    319   1.1  christos  		return MP(sfmtint(src, p, (int)mp->m_lines));
    320   1.1  christos 	case 'N':
    321   1.1  christos 		return sfmtstr(src, p, user);
    322   1.1  christos 	case 'O':
    323   1.1  christos  		return MP(sfmtoff(src, p, mp->m_size));
    324   1.1  christos 	case 'P':
    325   1.1  christos  		return MP(sfmtstr(src, p, mp == dot ? ">" : " "));
    326   1.1  christos 	case 'Q':
    327   1.2  christos  		return MP(sfmtflag(src, p, mp));
    328   1.1  christos 	case 'f':
    329   1.1  christos 		return sfmtstr(src, p, addr);
    330   1.1  christos 	case 'i':
    331   1.2  christos  		return sfmtint(src, p, get_msgnum(mp));	/* '0' if mp == NULL */
    332   1.1  christos 	case 'n':
    333   1.1  christos 		return sfmtstr(src, p, login_name(addr));
    334   1.1  christos 	case 'q':
    335   1.1  christos 		return sfmtstr(src, p, subj);
    336   1.1  christos 	case 't':
    337   1.2  christos 		return sfmtint(src, p, get_msgCount());
    338  1.11  christos 
    339  1.11  christos 	/*
    340  1.11  christos 	 * strftime(3) special cases:
    341  1.11  christos 	 *
    342  1.11  christos 	 * When 'tm_isdst' was not determined (i.e., < 0), a C99
    343  1.11  christos 	 * compliant strftime(3) will output an empty string for the
    344  1.11  christos 	 * "%Z" and "%z" formats.  This messes up alignment so we
    345  1.11  christos 	 * handle these ourselves.
    346  1.11  christos 	 */
    347  1.11  christos 	case 'Z':
    348  1.11  christos 		if (tm_isdst < 0) {
    349  1.11  christos 			*src = p + 1;
    350  1.11  christos 			return "???";	/* XXX - not ideal */
    351  1.11  christos 		}
    352  1.11  christos 		return NULL;
    353   1.1  christos 	case 'z':
    354  1.11  christos 		if (tm_isdst < 0) {
    355  1.11  christos 			*src = p + 1;
    356  1.11  christos 			return "-0000";	/* consistent with RFC 2822 */
    357  1.11  christos 		}
    358  1.11  christos 		return NULL;
    359  1.11  christos 
    360  1.11  christos 	/* everything else is handled by strftime(3) */
    361   1.1  christos 	default:
    362   1.1  christos 		return NULL;
    363   1.1  christos 	}
    364   1.1  christos #undef MP
    365   1.1  christos }
    366   1.1  christos 
    367   1.1  christos static const char *
    368   1.1  christos snarf_comment(char **buf, char *bufend, const char *string)
    369   1.1  christos {
    370   1.1  christos 	const char *p;
    371   1.1  christos 	char *q;
    372   1.1  christos 	char *qend;
    373   1.1  christos 	int clevel;
    374   1.1  christos 
    375   1.1  christos 	q    = buf ? *buf : NULL;
    376   1.1  christos 	qend = buf ? bufend : NULL;
    377   1.1  christos 
    378   1.1  christos 	clevel = 1;
    379   1.1  christos 	for (p = string + 1; *p != '\0'; p++) {
    380  1.11  christos 		DPRINTF(("snarf_comment: %s\n", p));
    381   1.1  christos 		if (*p == '(') {
    382   1.1  christos 			clevel++;
    383   1.1  christos 			continue;
    384   1.1  christos 		}
    385   1.1  christos 		if (*p == ')') {
    386   1.1  christos 			if (--clevel == 0)
    387   1.1  christos 				break;
    388   1.1  christos 			continue;
    389   1.1  christos 		}
    390   1.1  christos 		if (*p == '\\' && p[1] != 0)
    391   1.1  christos 			p++;
    392   1.1  christos 
    393   1.1  christos 		if (q < qend)
    394   1.1  christos 			*q++ = *p;
    395   1.1  christos 	}
    396   1.1  christos 	if (buf) {
    397   1.1  christos 		*q = '\0';
    398  1.11  christos 		DPRINTF(("snarf_comment: terminating: %s\n", *buf));
    399   1.1  christos 		*buf = q;
    400   1.1  christos 	}
    401   1.1  christos 	if (*p == '\0')
    402   1.1  christos 		p--;
    403   1.1  christos 	return p;
    404   1.1  christos }
    405   1.1  christos 
    406   1.1  christos static const char *
    407   1.1  christos snarf_quote(char **buf, char *bufend, const char *string)
    408   1.1  christos {
    409   1.1  christos 	const char *p;
    410   1.1  christos 	char *q;
    411   1.1  christos 	char *qend;
    412   1.1  christos 
    413   1.1  christos 	q    = buf ? *buf : NULL;
    414   1.1  christos 	qend = buf ? bufend : NULL;
    415   1.1  christos 
    416   1.1  christos 	for (p = string + 1; *p != '\0' && *p != '"'; p++) {
    417  1.11  christos 		DPRINTF(("snarf_quote: %s\n", p));
    418   1.1  christos 		if (*p == '\\' && p[1] != '\0')
    419   1.1  christos 			p++;
    420   1.1  christos 
    421   1.1  christos 		if (q < qend)
    422   1.1  christos 			*q++ = *p;
    423   1.1  christos 	}
    424   1.1  christos 	if (buf) {
    425   1.1  christos 		*q = '\0';
    426  1.11  christos 		DPRINTF(("snarf_quote: terminating: %s\n", *buf));
    427   1.1  christos 		*buf = q;
    428   1.1  christos 	}
    429   1.1  christos 	if (*p == '\0')
    430   1.1  christos 		p--;
    431   1.1  christos 	return p;
    432   1.1  christos }
    433   1.1  christos 
    434   1.1  christos /*
    435   1.1  christos  * Grab the comments, separating each by a space.
    436   1.1  christos  */
    437   1.1  christos static char *
    438   1.1  christos get_comments(char *name)
    439   1.1  christos {
    440   1.2  christos 	char nbuf[LINESIZE];
    441   1.1  christos 	const char *p;
    442   1.1  christos 	char *qend;
    443   1.1  christos 	char *q;
    444   1.1  christos 	char *lastq;
    445   1.1  christos 
    446   1.1  christos 	if (name == NULL)
    447   1.2  christos 		return NULL;
    448   1.1  christos 
    449   1.1  christos 	p = name;
    450   1.1  christos 	q = nbuf;
    451   1.1  christos 	lastq = nbuf;
    452   1.1  christos 	qend = nbuf + sizeof(nbuf) - 1;
    453  1.11  christos 	for (p = skip_WSP(name); *p != '\0'; p++) {
    454  1.11  christos 		DPRINTF(("get_comments: %s\n", p));
    455   1.1  christos 		switch (*p) {
    456   1.1  christos 		case '"': /* quoted-string ... skip it! */
    457   1.1  christos 			p = snarf_quote(NULL, NULL, p);
    458   1.1  christos 			break;
    459   1.1  christos 
    460   1.1  christos 		case '(':
    461   1.1  christos 			p = snarf_comment(&q, qend, p);
    462   1.1  christos 			lastq = q;
    463   1.1  christos 			if (q < qend) /* separate comments by space */
    464  1.11  christos 				*q++ = ' ';
    465   1.1  christos 			break;
    466   1.1  christos 
    467   1.1  christos 		default:
    468   1.1  christos 			break;
    469   1.1  christos 		}
    470   1.1  christos 	}
    471   1.1  christos 	*lastq = '\0';
    472   1.1  christos 	return savestr(nbuf);
    473   1.1  christos }
    474   1.1  christos 
    475   1.8  christos /*
    476   1.8  christos  * Convert a possible obs_zone (see RFC 2822, sec 4.3) to a valid
    477   1.8  christos  * gmtoff string.
    478   1.8  christos  */
    479   1.8  christos static const char *
    480   1.8  christos convert_obs_zone(const char *obs_zone)
    481   1.8  christos {
    482   1.8  christos 	static const struct obs_zone_tbl_s {
    483   1.8  christos 		const char *zone;
    484   1.8  christos 		const char *gmtoff;
    485   1.8  christos 	} obs_zone_tbl[] = {
    486   1.8  christos 		{"UT",	"+0000"},
    487   1.8  christos 		{"GMT",	"+0000"},
    488   1.8  christos 		{"EST",	"-0500"},
    489   1.8  christos 		{"EDT",	"-0400"},
    490   1.8  christos 		{"CST",	"-0600"},
    491   1.8  christos 		{"CDT",	"-0500"},
    492   1.8  christos 		{"MST",	"-0700"},
    493   1.8  christos 		{"MDT",	"-0600"},
    494   1.8  christos 		{"PST",	"-0800"},
    495   1.8  christos 		{"PDT",	"-0700"},
    496   1.8  christos 		{NULL,	NULL},
    497   1.8  christos 	};
    498   1.8  christos 	const struct obs_zone_tbl_s *zp;
    499   1.8  christos 
    500   1.8  christos 	if (obs_zone[0] == '+' || obs_zone[0] == '-')
    501   1.8  christos 		return obs_zone;
    502   1.8  christos 
    503   1.8  christos 	if (obs_zone[1] == 0) { /* possible military zones */
    504  1.11  christos 		/* be explicit here - avoid C extensions and ctype(3) */
    505   1.8  christos 		switch((unsigned char)obs_zone[0]) {
    506  1.11  christos 		case 'A': case 'B': case 'C': case 'D':	case 'E':
    507  1.11  christos 		case 'F': case 'G': case 'H': case 'I':
    508  1.11  christos 		case 'K': case 'L': case 'M': case 'N': case 'O':
    509  1.11  christos 		case 'P': case 'Q': case 'R': case 'S': case 'T':
    510  1.11  christos 		case 'U': case 'V': case 'W': case 'X': case 'Y':
    511  1.11  christos 		case 'Z':
    512  1.11  christos 		case 'a': case 'b': case 'c': case 'd':	case 'e':
    513  1.11  christos 		case 'f': case 'g': case 'h': case 'i':
    514  1.11  christos 		case 'k': case 'l': case 'm': case 'n': case 'o':
    515  1.11  christos 		case 'p': case 'q': case 'r': case 's': case 't':
    516  1.11  christos 		case 'u': case 'v': case 'w': case 'x': case 'y':
    517  1.11  christos 		case 'z':
    518  1.11  christos 			return "-0000";	/* See RFC 2822, sec 4.3 */
    519   1.8  christos 		default:
    520   1.8  christos 			return obs_zone;
    521   1.8  christos 		}
    522   1.8  christos 	}
    523  1.11  christos 	for (zp = obs_zone_tbl; zp->zone; zp++) {
    524   1.8  christos 		if (strcmp(obs_zone, zp->zone) == 0)
    525   1.8  christos 			return zp->gmtoff;
    526   1.8  christos 	}
    527   1.8  christos 	return obs_zone;
    528   1.8  christos }
    529   1.8  christos 
    530   1.1  christos /*
    531  1.11  christos  * Parse the 'Date:" field into a tm structure and return the gmtoff
    532  1.11  christos  * string or NULL on error.
    533  1.11  christos  */
    534  1.11  christos static const char *
    535  1.11  christos date_to_tm(char *date, struct tm *tm)
    536  1.11  christos {
    537  1.11  christos 	/****************************************************************
    538  1.11  christos 	 * The header 'date-time' syntax - From RFC 2822 sec 3.3 and 4.3:
    539  1.11  christos 	 *
    540  1.11  christos 	 * date-time       =       [ day-of-week "," ] date FWS time [CFWS]
    541  1.11  christos 	 * day-of-week     =       ([FWS] day-name) / obs-day-of-week
    542  1.11  christos 	 * day-name        =       "Mon" / "Tue" / "Wed" / "Thu" /
    543  1.11  christos 	 *                         "Fri" / "Sat" / "Sun"
    544  1.11  christos 	 * date            =       day month year
    545  1.11  christos 	 * year            =       4*DIGIT / obs-year
    546  1.11  christos 	 * month           =       (FWS month-name FWS) / obs-month
    547  1.11  christos 	 * month-name      =       "Jan" / "Feb" / "Mar" / "Apr" /
    548  1.11  christos 	 *                         "May" / "Jun" / "Jul" / "Aug" /
    549  1.11  christos 	 *                         "Sep" / "Oct" / "Nov" / "Dec"
    550  1.11  christos 	 * day             =       ([FWS] 1*2DIGIT) / obs-day
    551  1.11  christos 	 * time            =       time-of-day FWS zone
    552  1.11  christos 	 * time-of-day     =       hour ":" minute [ ":" second ]
    553  1.11  christos 	 * hour            =       2DIGIT / obs-hour
    554  1.11  christos 	 * minute          =       2DIGIT / obs-minute
    555  1.11  christos 	 * second          =       2DIGIT / obs-second
    556  1.11  christos 	 * zone            =       (( "+" / "-" ) 4DIGIT) / obs-zone
    557  1.11  christos 	 *
    558  1.11  christos 	 * obs-day-of-week =       [CFWS] day-name [CFWS]
    559  1.11  christos 	 * obs-year        =       [CFWS] 2*DIGIT [CFWS]
    560  1.11  christos 	 * obs-month       =       CFWS month-name CFWS
    561  1.11  christos 	 * obs-day         =       [CFWS] 1*2DIGIT [CFWS]
    562  1.11  christos 	 * obs-hour        =       [CFWS] 2DIGIT [CFWS]
    563  1.11  christos 	 * obs-minute      =       [CFWS] 2DIGIT [CFWS]
    564  1.11  christos 	 * obs-second      =       [CFWS] 2DIGIT [CFWS]
    565  1.11  christos 	 ****************************************************************/
    566  1.11  christos 	/*
    567  1.11  christos 	 * For example, a typical date might look like:
    568  1.11  christos 	 *
    569  1.11  christos 	 * Date: Mon,  1 Oct 2007 05:38:10 +0000 (UTC)
    570  1.11  christos 	 */
    571  1.11  christos 	char *tail;
    572  1.11  christos 	char *p;
    573  1.11  christos 	struct tm tmp_tm;
    574  1.11  christos 	/*
    575  1.11  christos 	 * NOTE: Rather than depend on strptime(3) modifying only
    576  1.11  christos 	 * those fields specified in its format string, we use tmp_tm
    577  1.11  christos 	 * and copy the appropriate result to tm.  This is not
    578  1.11  christos 	 * required with the NetBSD strptime(3) implementation.
    579  1.11  christos 	 */
    580  1.11  christos 
    581  1.11  christos 	/* Check for an optional 'day-of-week' */
    582  1.16  christos 	if ((tail = strptime(date, " %a,", &tmp_tm)) == NULL)
    583  1.11  christos 		tail = date;
    584  1.16  christos 	else
    585  1.11  christos 		tm->tm_wday = tmp_tm.tm_wday;
    586  1.11  christos 
    587  1.11  christos 	/* Get the required 'day' and 'month' */
    588  1.11  christos 	if ((tail = strptime(tail, " %d %b", &tmp_tm)) == NULL)
    589  1.11  christos 		return NULL;
    590  1.11  christos 
    591  1.11  christos 	tm->tm_mday = tmp_tm.tm_mday;
    592  1.11  christos 	tm->tm_mon  = tmp_tm.tm_mon;
    593  1.11  christos 
    594  1.11  christos 	/* Check for 'obs-year' (2 digits) before 'year' (4 digits) */
    595  1.11  christos 	/* XXX - Portable?  This depends on strptime not scanning off
    596  1.11  christos 	 * trailing whitespace unless specified in the format string.
    597  1.11  christos 	 */
    598  1.11  christos 	if ((p = strptime(tail, " %y", &tmp_tm)) != NULL && is_WSP(*p))
    599  1.11  christos 		tail = p;
    600  1.11  christos 	else if ((tail = strptime(tail, " %Y", &tmp_tm)) == NULL)
    601  1.11  christos 		return NULL;
    602  1.11  christos 
    603  1.11  christos 	tm->tm_year = tmp_tm.tm_year;
    604  1.11  christos 
    605  1.11  christos 	/* Get the required 'hour' and 'minute' */
    606  1.11  christos 	if ((tail = strptime(tail, " %H:%M", &tmp_tm)) == NULL)
    607  1.11  christos 		return NULL;
    608  1.11  christos 
    609  1.11  christos 	tm->tm_hour = tmp_tm.tm_hour;
    610  1.11  christos 	tm->tm_min  = tmp_tm.tm_min;
    611  1.11  christos 
    612  1.11  christos 	/* Check for an optional 'seconds' field */
    613  1.11  christos 	if ((p = strptime(tail, ":%S", &tmp_tm)) != NULL) {
    614  1.11  christos 		tail = p;
    615  1.11  christos 		tm->tm_sec = tmp_tm.tm_sec;
    616  1.11  christos 	}
    617  1.11  christos 
    618  1.11  christos 	tail = skip_WSP(tail);
    619  1.11  christos 
    620  1.11  christos 	/*
    621  1.11  christos 	 * The timezone name is frequently in a comment following the
    622  1.11  christos 	 * zone offset.
    623  1.11  christos 	 *
    624  1.11  christos 	 * XXX - this will get overwritten later by timegm(3).
    625  1.11  christos 	 */
    626  1.11  christos 	if ((p = strchr(tail, '(')) != NULL)
    627  1.11  christos 		tm->tm_zone = get_comments(p);
    628  1.11  christos 	else
    629  1.11  christos 		tm->tm_zone = NULL;
    630  1.11  christos 
    631  1.11  christos 	/* what remains should be the gmtoff string */
    632  1.11  christos 	tail = skin(tail);
    633  1.11  christos 	return convert_obs_zone(tail);
    634  1.11  christos }
    635  1.11  christos 
    636  1.11  christos /*
    637  1.11  christos  * Parse the headline string into a tm structure.  Returns a pointer
    638  1.11  christos  * to first non-whitespace after the date or NULL on error.
    639  1.11  christos  *
    640  1.11  christos  * XXX - This needs to be consistent with isdate().
    641  1.11  christos  */
    642  1.11  christos static char *
    643  1.11  christos hl_date_to_tm(const char *buf, struct tm *tm)
    644  1.11  christos {
    645  1.11  christos 	/****************************************************************
    646  1.11  christos 	 * The BSD and System V headline date formats differ
    647  1.11  christos 	 * and each have an optional timezone field between
    648  1.11  christos 	 * the time and date (see head.c).  Unfortunately,
    649  1.11  christos 	 * strptime(3) doesn't know about timezone fields, so
    650  1.11  christos 	 * we have to handle it ourselves.
    651  1.11  christos 	 *
    652  1.11  christos 	 * char ctype[]        = "Aaa Aaa O0 00:00:00 0000";
    653  1.11  christos 	 * char tmztype[]      = "Aaa Aaa O0 00:00:00 AAA 0000";
    654  1.11  christos 	 * char SysV_ctype[]   = "Aaa Aaa O0 00:00 0000";
    655  1.11  christos 	 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
    656  1.11  christos 	 ****************************************************************/
    657  1.11  christos 	char *tail;
    658  1.11  christos 	char *p;
    659  1.11  christos 	char zone[4];
    660  1.11  christos 	struct tm tmp_tm; /* see comment in date_to_tm() */
    661  1.11  christos 	int len;
    662  1.11  christos 
    663  1.11  christos 	zone[0] = '\0';
    664  1.11  christos 	if ((tail = strptime(buf, " %a %b %d %H:%M", &tmp_tm)) == NULL)
    665  1.11  christos 		return NULL;
    666  1.11  christos 
    667  1.11  christos 	tm->tm_wday = tmp_tm.tm_wday;
    668  1.11  christos 	tm->tm_mday = tmp_tm.tm_mday;
    669  1.11  christos 	tm->tm_mon  = tmp_tm.tm_mon;
    670  1.11  christos 	tm->tm_hour = tmp_tm.tm_hour;
    671  1.11  christos 	tm->tm_min  = tmp_tm.tm_min;
    672  1.11  christos 
    673  1.11  christos 	/* Check for an optional 'seconds' field */
    674  1.11  christos 	if ((p = strptime(tail, ":%S", &tmp_tm)) != NULL) {
    675  1.11  christos 		tail = p;
    676  1.11  christos 		tm->tm_sec = tmp_tm.tm_sec;
    677  1.11  christos 	}
    678  1.11  christos 
    679  1.11  christos 	/* Grab an optional timezone name */
    680  1.11  christos 	/*
    681  1.11  christos 	 * XXX - Is the zone name always 3 characters as in isdate()?
    682  1.11  christos 	 */
    683  1.11  christos 	if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) {
    684  1.11  christos 		if (zone[0])
    685  1.11  christos 			tm->tm_zone = savestr(zone);
    686  1.11  christos 		tail += len;
    687  1.11  christos 	}
    688  1.11  christos 
    689  1.11  christos 	/* Grab the required year field */
    690  1.11  christos 	tail = strptime(tail, " %Y ", &tmp_tm);
    691  1.11  christos 	tm->tm_year = tmp_tm.tm_year; /* save this even if it failed */
    692  1.11  christos 
    693  1.11  christos 	return tail;
    694  1.11  christos }
    695  1.11  christos 
    696  1.11  christos /*
    697   1.1  christos  * Get the date and time info from the "Date:" line, parse it into a
    698   1.1  christos  * tm structure as much as possible.
    699   1.1  christos  *
    700   1.1  christos  * Note: We return the gmtoff as a string as "-0000" has special
    701   1.1  christos  * meaning.  See RFC 2822, sec 3.3.
    702   1.1  christos  */
    703  1.11  christos PUBLIC void
    704   1.1  christos dateof(struct tm *tm, struct message *mp, int use_hl_date)
    705   1.1  christos {
    706   1.3  christos 	static int tzinit = 0;
    707  1.11  christos 	char *date = NULL;
    708   1.8  christos 	const char *gmtoff;
    709   1.1  christos 
    710   1.1  christos 	(void)memset(tm, 0, sizeof(*tm));
    711   1.1  christos 
    712   1.3  christos 	/* Make sure the time zone info is initialized. */
    713   1.3  christos 	if (!tzinit) {
    714   1.3  christos 		tzinit = 1;
    715   1.3  christos 		tzset();
    716   1.3  christos 	}
    717   1.1  christos 	if (mp == NULL) {	/* use local time */
    718   1.1  christos 		time_t now;
    719   1.1  christos 		(void)time(&now);
    720   1.1  christos 		(void)localtime_r(&now, tm);
    721  1.11  christos 		return;
    722   1.1  christos 	}
    723  1.11  christos 
    724   1.1  christos 	/*
    725   1.1  christos 	 * See RFC 2822 sec 3.3 for date-time format used in
    726   1.1  christos 	 * the "Date:" field.
    727   1.1  christos 	 *
    728  1.11  christos 	 * NOTE: The range for the time is 00:00 to 23:60 (to allow
    729  1.17    andvar 	 * for a leap second), but I have seen this violated making
    730  1.11  christos 	 * strptime() fail, e.g.,
    731   1.1  christos 	 *
    732  1.11  christos 	 *   Date: Tue, 24 Oct 2006 24:07:58 +0400
    733   1.1  christos 	 *
    734  1.11  christos 	 * In this case we (silently) fall back to the headline time
    735  1.11  christos 	 * which was written locally when the message was received.
    736  1.11  christos 	 * Of course, this is not the same time as in the Date field.
    737   1.1  christos 	 */
    738   1.1  christos 	if (use_hl_date == 0 &&
    739   1.1  christos 	    (date = hfield("date", mp)) != NULL &&
    740  1.11  christos 	    (gmtoff = date_to_tm(date, tm)) != NULL) {
    741   1.2  christos 		int hour;
    742   1.2  christos 		int min;
    743   1.4  christos 		char sign[2];
    744  1.11  christos 		struct tm save_tm;
    745   1.2  christos 
    746   1.2  christos 		/*
    747   1.2  christos 		 * Scan the gmtoff and use it to convert the time to a
    748   1.2  christos 		 * local time.
    749   1.2  christos 		 *
    750   1.3  christos 		 * Note: "-0000" means no valid zone info.  See
    751   1.3  christos 		 *       RFC 2822, sec 3.3.
    752   1.3  christos 		 *
    753   1.2  christos 		 * XXX - This is painful!  Is there a better way?
    754   1.2  christos 		 */
    755  1.11  christos 
    756  1.11  christos 		tm->tm_isdst = -1;	/* let timegm(3) determine tm_isdst */
    757  1.11  christos 		save_tm = *tm;		/* use this if we fail */
    758  1.11  christos 
    759   1.3  christos 		if (strcmp(gmtoff, "-0000") != 0 &&
    760   1.4  christos 		    sscanf(gmtoff, " %1[+-]%2d%2d ", sign, &hour, &min) == 3) {
    761   1.2  christos 			time_t otime;
    762  1.11  christos 
    763   1.4  christos 			if (sign[0] == '-') {
    764   1.2  christos 				tm->tm_hour += hour;
    765   1.2  christos 				tm->tm_min += min;
    766   1.2  christos 			}
    767   1.2  christos 			else {
    768   1.2  christos 				tm->tm_hour -= hour;
    769   1.2  christos 				tm->tm_min -= min;
    770   1.2  christos 			}
    771   1.9  christos 			if ((otime = timegm(tm)) == (time_t)-1 ||
    772   1.9  christos 			    localtime_r(&otime, tm) == NULL) {
    773  1.11  christos 				if (debug)
    774  1.11  christos 					warnx("cannot convert date: \"%s\"", date);
    775  1.11  christos 				*tm = save_tm;
    776   1.9  christos 			}
    777   1.2  christos 		}
    778  1.11  christos 		else {	/* Unable to do the conversion to local time. */
    779  1.11  christos 			*tm = save_tm;
    780  1.11  christos 		     /* tm->tm_isdst = -1; */ /* Set above */
    781   1.2  christos 			tm->tm_gmtoff = 0;
    782  1.11  christos 			tm->tm_zone = NULL;
    783  1.11  christos 		}
    784   1.1  christos 	}
    785   1.1  christos 	else {
    786   1.1  christos 		struct headline hl;
    787   1.1  christos 		char headline[LINESIZE];
    788   1.2  christos 		char pbuf[LINESIZE];
    789  1.11  christos 
    790  1.11  christos 		if (debug && use_hl_date == 0)
    791  1.11  christos 			warnx("invalid date: \"%s\"", date ? date : "<null>");
    792  1.11  christos 
    793  1.11  christos 		/*
    794  1.11  christos 		 * The headline is written locally so failures here
    795  1.11  christos 		 * should be seen (i.e., not conditional on 'debug').
    796  1.11  christos 		 */
    797  1.11  christos 		tm->tm_isdst = -1; /* let mktime(3) determine tm_isdst */
    798   1.1  christos 		headline[0] = '\0';
    799  1.14  christos 		(void)readline(setinput(mp), headline, (int)sizeof(headline), 0);
    800   1.1  christos 		parse(headline, &hl, pbuf);
    801  1.11  christos 		if (hl.l_date == NULL)
    802  1.11  christos 			warnx("invalid headline: `%s'", headline);
    803   1.3  christos 
    804  1.11  christos 		else if (hl_date_to_tm(hl.l_date, tm) == NULL ||
    805  1.11  christos 		    mktime(tm) == -1)
    806  1.11  christos 			warnx("invalid headline date: `%s'", hl.l_date);
    807   1.1  christos 	}
    808   1.1  christos }
    809   1.1  christos 
    810   1.1  christos /*
    811   1.1  christos  * Get the sender's address for display.  Let nameof() do this.
    812   1.1  christos  */
    813   1.1  christos static const char *
    814   1.1  christos addrof(struct message *mp)
    815   1.1  christos {
    816   1.1  christos 	if (mp == NULL)
    817   1.1  christos 		return NULL;
    818   1.1  christos 
    819   1.1  christos 	return nameof(mp, 0);
    820   1.1  christos }
    821   1.1  christos 
    822   1.1  christos /************************************************************************
    823  1.11  christos  * The 'address' syntax - from RFC 2822:
    824   1.1  christos  *
    825   1.1  christos  * specials        =       "(" / ")" /     ; Special characters used in
    826   1.1  christos  *                         "<" / ">" /     ;  other parts of the syntax
    827   1.1  christos  *                         "[" / "]" /
    828   1.1  christos  *                         ":" / ";" /
    829   1.1  christos  *                         "@" / "\" /
    830   1.1  christos  *                         "," / "." /
    831   1.1  christos  *                         DQUOTE
    832   1.1  christos  * qtext           =       NO-WS-CTL /     ; Non white space controls
    833   1.1  christos  *                         %d33 /          ; The rest of the US-ASCII
    834   1.1  christos  *                         %d35-91 /       ;  characters not including "\"
    835   1.1  christos  *                         %d93-126        ;  or the quote character
    836   1.1  christos  * qcontent        =       qtext / quoted-pair
    837   1.1  christos  * quoted-string   =       [CFWS]
    838   1.1  christos  *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
    839   1.1  christos  *                         [CFWS]
    840   1.1  christos  * atext           =       ALPHA / DIGIT / ; Any character except controls,
    841   1.1  christos  *                         "!" / "#" /     ;  SP, and specials.
    842   1.1  christos  *                         "$" / "%" /     ;  Used for atoms
    843   1.1  christos  *                         "&" / "'" /
    844   1.1  christos  *                         "*" / "+" /
    845   1.1  christos  *                         "-" / "/" /
    846   1.1  christos  *                         "=" / "?" /
    847   1.1  christos  *                         "^" / "_" /
    848   1.1  christos  *                         "`" / "{" /
    849   1.1  christos  *                         "|" / "}" /
    850   1.1  christos  *                         "~"
    851   1.1  christos  * atom            =       [CFWS] 1*atext [CFWS]
    852   1.1  christos  * word            =       atom / quoted-string
    853   1.1  christos  * phrase          =       1*word / obs-phrase
    854   1.1  christos  * display-name    =       phrase
    855   1.1  christos  * dtext           =       NO-WS-CTL /     ; Non white space controls
    856   1.1  christos  *                         %d33-90 /       ; The rest of the US-ASCII
    857   1.1  christos  *                         %d94-126        ;  characters not including "[",
    858   1.1  christos  *                                         ;  "]", or "\"
    859   1.1  christos  * dcontent        =       dtext / quoted-pair
    860   1.1  christos  * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
    861   1.1  christos  * domain          =       dot-atom / domain-literal / obs-domain
    862   1.1  christos  * local-part      =       dot-atom / quoted-string / obs-local-part
    863   1.1  christos  * addr-spec       =       local-part "@" domain
    864   1.1  christos  * angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
    865   1.1  christos  * name-addr       =       [display-name] angle-addr
    866   1.1  christos  * mailbox         =       name-addr / addr-spec
    867   1.1  christos  * mailbox-list    =       (mailbox *("," mailbox)) / obs-mbox-list
    868   1.1  christos  * group           =       display-name ":" [mailbox-list / CFWS] ";"
    869   1.1  christos  *                         [CFWS]
    870   1.1  christos  * address         =       mailbox / group
    871   1.1  christos  ************************************************************************/
    872   1.1  christos static char *
    873   1.1  christos get_display_name(char *name)
    874   1.1  christos {
    875   1.2  christos 	char nbuf[LINESIZE];
    876   1.1  christos 	const char *p;
    877   1.1  christos 	char *q;
    878   1.1  christos 	char *qend;
    879   1.1  christos 	char *lastq;
    880   1.1  christos 	int quoted;
    881   1.1  christos 
    882   1.1  christos 	if (name == NULL)
    883   1.2  christos 		return NULL;
    884   1.1  christos 
    885   1.1  christos 	q = nbuf;
    886   1.1  christos 	lastq = nbuf;
    887   1.1  christos 	qend = nbuf + sizeof(nbuf) - 1;	/* reserve space for '\0' */
    888   1.1  christos 	quoted = 0;
    889  1.11  christos 	for (p = skip_WSP(name); *p != '\0'; p++) {
    890  1.11  christos 		DPRINTF(("get_display_name: %s\n", p));
    891   1.1  christos 		switch (*p) {
    892   1.1  christos 		case '"':	/* quoted-string */
    893   1.1  christos 			q = nbuf;
    894   1.1  christos 			p = snarf_quote(&q, qend, p);
    895   1.1  christos 			if (!quoted)
    896   1.1  christos 				lastq = q;
    897   1.1  christos 			quoted = 1;
    898   1.1  christos 			break;
    899   1.1  christos 
    900   1.1  christos 		case ':':	/* group */
    901   1.1  christos 		case '<':	/* angle-address */
    902   1.1  christos 			if (lastq == nbuf)
    903   1.1  christos 				return NULL;
    904   1.1  christos 			*lastq = '\0';	/* NULL termination */
    905   1.2  christos 			return savestr(nbuf);
    906   1.1  christos 
    907   1.1  christos 		case '(':	/* comment - skip it! */
    908   1.1  christos 			p = snarf_comment(NULL, NULL, p);
    909   1.1  christos 			break;
    910   1.1  christos 
    911   1.1  christos 		default:
    912   1.1  christos 			if (!quoted && q < qend) {
    913   1.1  christos 				*q++ = *p;
    914  1.11  christos 				if (!is_WSP(*p)
    915  1.15  christos 				    /* && !is_specials((unsigned char)*p) */)
    916   1.1  christos 					lastq = q;
    917   1.1  christos 			}
    918   1.1  christos 			break;
    919   1.1  christos 		}
    920   1.1  christos 	}
    921   1.1  christos 	return NULL;	/* no group or angle-address */
    922   1.1  christos }
    923   1.1  christos 
    924   1.1  christos /*
    925   1.1  christos  * See RFC 2822 sec 3.4 and 3.6.2.
    926   1.1  christos  */
    927   1.1  christos static const char *
    928   1.1  christos userof(struct message *mp)
    929   1.1  christos {
    930   1.1  christos 	char *sender;
    931   1.1  christos 	char *dispname;
    932   1.1  christos 
    933   1.1  christos 	if (mp == NULL)
    934   1.1  christos 		return NULL;
    935   1.1  christos 
    936   1.1  christos 	if ((sender = hfield("from", mp)) != NULL ||
    937   1.1  christos 	    (sender = hfield("sender", mp)) != NULL)
    938   1.1  christos 		/*
    939   1.1  christos 		 * Try to get the display-name.  If one doesn't exist,
    940   1.1  christos 		 * then the best we can hope for is that the user's
    941   1.1  christos 		 * name is in the comments.
    942   1.1  christos 		 */
    943   1.1  christos 		if ((dispname = get_display_name(sender)) != NULL ||
    944   1.1  christos 		    (dispname = get_comments(sender)) != NULL)
    945   1.1  christos 			return dispname;
    946   1.1  christos 	return NULL;
    947   1.1  christos }
    948   1.1  christos 
    949   1.1  christos /*
    950   1.1  christos  * Grab the subject line.
    951   1.1  christos  */
    952   1.1  christos static const char *
    953   1.1  christos subjof(struct message *mp)
    954   1.1  christos {
    955   1.1  christos 	const char *subj;
    956   1.1  christos 
    957   1.1  christos 	if (mp == NULL)
    958   1.1  christos 		return NULL;
    959   1.1  christos 
    960   1.1  christos 	if ((subj = hfield("subject", mp)) == NULL)
    961   1.1  christos 		subj = hfield("subj", mp);
    962   1.1  christos 	return subj;
    963   1.1  christos }
    964   1.1  christos 
    965   1.5  christos /*
    966   1.5  christos  * Protect a string against strftime() conversion.
    967   1.5  christos  */
    968   1.5  christos static const char*
    969   1.5  christos protect(const char *str)
    970   1.5  christos {
    971   1.5  christos 	char *p, *q;
    972   1.5  christos 	size_t size;
    973  1.11  christos 
    974   1.6  christos 	if (str == NULL || (size = strlen(str)) == 0)
    975   1.5  christos 		return str;
    976  1.11  christos 
    977   1.7  christos 	p = salloc(2 * size + 1);
    978   1.5  christos 	for (q = p; *str; str++) {
    979   1.5  christos 		*q = *str;
    980   1.5  christos 		if (*q++ == '%')
    981   1.5  christos 			*q++ = '%';
    982   1.5  christos 	}
    983   1.5  christos 	*q = '\0';
    984   1.5  christos 	return p;
    985   1.5  christos }
    986   1.5  christos 
    987   1.1  christos static char *
    988   1.1  christos preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
    989   1.1  christos {
    990   1.1  christos 	const char *subj;
    991   1.1  christos 	const char *addr;
    992   1.1  christos 	const char *user;
    993   1.1  christos 	const char *p;
    994   1.1  christos 	char *q;
    995   1.1  christos 	char *newfmt;
    996   1.1  christos 	size_t fmtsize;
    997   1.1  christos 
    998   1.1  christos 	if (mp != NULL && (mp->m_flag & MDELETED) != 0)
    999   1.1  christos 		mp = NULL; /* deleted mail shouldn't show up! */
   1000   1.1  christos 
   1001   1.5  christos 	subj = protect(subjof(mp));
   1002   1.5  christos 	addr = protect(addrof(mp));
   1003   1.5  christos 	user = protect(userof(mp));
   1004  1.11  christos 	dateof(tm, mp, use_hl_date);
   1005   1.1  christos 	fmtsize = LINESIZE;
   1006  1.12  christos 	newfmt = ecalloc(1, fmtsize); /* so we can realloc() in check_bufsize() */
   1007   1.1  christos 	q = newfmt;
   1008   1.1  christos 	p = oldfmt;
   1009   1.1  christos 	while (*p) {
   1010   1.1  christos 		if (*p == '%') {
   1011   1.1  christos 			const char *fp;
   1012  1.11  christos 			fp = subformat(&p, mp, addr, user, subj, tm->tm_isdst);
   1013   1.1  christos 			if (fp) {
   1014   1.1  christos 				size_t len;
   1015   1.1  christos 				len = strlen(fp);
   1016   1.1  christos 				check_bufsize(&newfmt, &fmtsize, &q, len);
   1017   1.1  christos 				(void)strcpy(q, fp);
   1018   1.1  christos 				q += len;
   1019   1.1  christos 				continue;
   1020   1.1  christos 			}
   1021   1.1  christos 		}
   1022   1.1  christos 		check_bufsize(&newfmt, &fmtsize, &q, 1);
   1023   1.1  christos 		*q++ = *p++;
   1024   1.1  christos 	}
   1025   1.1  christos 	*q = '\0';
   1026   1.1  christos 
   1027   1.1  christos 	return newfmt;
   1028   1.1  christos }
   1029   1.1  christos 
   1030   1.1  christos /*
   1031   1.1  christos  * If a format string begins with the USE_HL_DATE string, smsgprintf
   1032   1.1  christos  * will use the headerline date rather than trying to extract the date
   1033   1.1  christos  * from the Date field.
   1034   1.1  christos  *
   1035   1.1  christos  * Note: If a 'valid' date cannot be extracted from the Date field,
   1036   1.1  christos  * then the headline date is used.
   1037   1.1  christos  */
   1038   1.1  christos #define USE_HL_DATE "%??"
   1039   1.1  christos 
   1040   1.1  christos PUBLIC char *
   1041   1.1  christos smsgprintf(const char *fmtstr, struct message *mp)
   1042   1.1  christos {
   1043   1.1  christos 	struct tm tm;
   1044   1.1  christos 	int use_hl_date;
   1045   1.1  christos 	char *newfmt;
   1046   1.1  christos 	char *buf;
   1047   1.1  christos 	size_t bufsize;
   1048   1.1  christos 
   1049   1.1  christos 	if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
   1050   1.1  christos 		use_hl_date = 0;
   1051   1.1  christos 	else {
   1052   1.1  christos 		use_hl_date = 1;
   1053   1.1  christos 		fmtstr += sizeof(USE_HL_DATE) - 1;
   1054   1.1  christos 	}
   1055   1.1  christos 	bufsize = LINESIZE;
   1056   1.1  christos 	buf = salloc(bufsize);
   1057   1.1  christos 	newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
   1058   1.1  christos 	(void)strftime(buf, bufsize, newfmt, &tm);
   1059   1.1  christos 	free(newfmt);	/* preformat() uses malloc()/realloc() */
   1060   1.1  christos 	return buf;
   1061   1.1  christos }
   1062   1.1  christos 
   1063   1.1  christos 
   1064   1.1  christos PUBLIC void
   1065   1.1  christos fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
   1066   1.1  christos {
   1067   1.1  christos 	char *buf;
   1068   1.1  christos 
   1069   1.1  christos 	buf = smsgprintf(fmtstr, mp);
   1070   1.1  christos 	(void)fprintf(fo, "%s\n", buf);	/* XXX - add the newline here? */
   1071   1.1  christos }
   1072