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