Home | History | Annotate | Line # | Download | only in mail
format.c revision 1.5
      1  1.5  christos /*	$NetBSD: format.c,v 1.5 2007/07/06 20:14:33 christos 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  * 3. All advertising materials mentioning features or use of this software
     19  1.1  christos  *    must display the following acknowledgement:
     20  1.1  christos  *        This product includes software developed by the NetBSD
     21  1.1  christos  *        Foundation, Inc. and its contributors.
     22  1.1  christos  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  1.1  christos  *    contributors may be used to endorse or promote products derived
     24  1.1  christos  *    from this software without specific prior written permission.
     25  1.1  christos  *
     26  1.1  christos  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  1.1  christos  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  1.1  christos  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  1.1  christos  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  1.1  christos  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  1.1  christos  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  1.1  christos  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  1.1  christos  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  1.1  christos  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  1.1  christos  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  1.1  christos  * POSSIBILITY OF SUCH DAMAGE.
     37  1.1  christos  */
     38  1.1  christos 
     39  1.1  christos #include <sys/cdefs.h>
     40  1.1  christos #ifndef __lint__
     41  1.5  christos __RCSID("$NetBSD: format.c,v 1.5 2007/07/06 20:14:33 christos Exp $");
     42  1.1  christos #endif /* not __lint__ */
     43  1.1  christos 
     44  1.1  christos #include <time.h>
     45  1.1  christos #include <stdio.h>
     46  1.1  christos #include <util.h>
     47  1.1  christos 
     48  1.1  christos #include "def.h"
     49  1.1  christos #include "extern.h"
     50  1.1  christos #include "format.h"
     51  1.1  christos #include "glob.h"
     52  1.2  christos #include "thread.h"
     53  1.1  christos 
     54  1.1  christos 
     55  1.1  christos #define DEBUG(a)
     56  1.1  christos 
     57  1.1  christos static void
     58  1.1  christos check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt)
     59  1.1  christos {
     60  1.1  christos 	char *q;
     61  1.1  christos 	if (*p + cnt < *buf + *bufsize)
     62  1.1  christos 		return;
     63  1.1  christos 	*bufsize *= 2;
     64  1.1  christos 	q = realloc(*buf, *bufsize);
     65  1.1  christos 	*p = q + (*p - *buf);
     66  1.1  christos 	*buf = q;
     67  1.1  christos }
     68  1.1  christos 
     69  1.1  christos static const char *
     70  1.1  christos sfmtoff(const char **fmtbeg, const char *fmtch, off_t off)
     71  1.1  christos {
     72  1.1  christos 	char *newfmt;	/* pointer to new format string */
     73  1.1  christos 	size_t len;	/* space for "lld" including '\0' */
     74  1.2  christos 	char *p;
     75  1.2  christos 
     76  1.1  christos 	len = fmtch - *fmtbeg + sizeof(PRId64);
     77  1.1  christos 	newfmt = salloc(len);
     78  1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len - sizeof(sizeof(PRId64)) + 1);
     79  1.1  christos 	(void)strlcat(newfmt, PRId64, len);
     80  1.1  christos 	*fmtbeg = fmtch + 1;
     81  1.2  christos 	(void)sasprintf(&p, newfmt, off);
     82  1.2  christos 	return p;
     83  1.1  christos }
     84  1.1  christos 
     85  1.1  christos static const char *
     86  1.1  christos sfmtint(const char **fmtbeg, const char *fmtch, int num)
     87  1.1  christos {
     88  1.1  christos 	char *newfmt;
     89  1.1  christos 	size_t len;
     90  1.2  christos 	char *p;
     91  1.1  christos 
     92  1.1  christos 	len = fmtch - *fmtbeg + 2;	/* space for 'd' and '\0' */
     93  1.1  christos 	newfmt = salloc(len);
     94  1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len);
     95  1.1  christos 	newfmt[len-2] = 'd';		/* convert to printf format */
     96  1.1  christos 	*fmtbeg = fmtch + 1;
     97  1.2  christos 	(void)sasprintf(&p, newfmt, num);
     98  1.2  christos 	return p;
     99  1.1  christos }
    100  1.1  christos 
    101  1.1  christos static const char *
    102  1.1  christos sfmtstr(const char **fmtbeg, const char *fmtch, const char *str)
    103  1.1  christos {
    104  1.1  christos 	char *newfmt;
    105  1.1  christos 	size_t len;
    106  1.2  christos 	char *p;
    107  1.1  christos 
    108  1.1  christos 	len = fmtch - *fmtbeg + 2;	/* space for 's' and '\0' */
    109  1.1  christos 	newfmt = salloc(len);
    110  1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len);
    111  1.1  christos 	newfmt[len-2] = 's';		/* convert to printf format */
    112  1.1  christos 	*fmtbeg = fmtch + 1;
    113  1.2  christos 	(void)sasprintf(&p, newfmt, str ? str : "");
    114  1.2  christos 	return p;
    115  1.2  christos }
    116  1.2  christos 
    117  1.2  christos #ifdef THREAD_SUPPORT
    118  1.2  christos static char*
    119  1.2  christos sfmtdepth(char *str, int depth)
    120  1.2  christos {
    121  1.2  christos 	char *p;
    122  1.2  christos 	if (*str == '\0') {
    123  1.2  christos 		(void)sasprintf(&p, "%d", depth);
    124  1.2  christos 		return p;
    125  1.2  christos 	}
    126  1.2  christos 	p = __UNCONST("");
    127  1.2  christos 	for (/*EMPTY*/; depth > 0; depth--)
    128  1.2  christos 		(void)sasprintf(&p, "%s%s", p, str);
    129  1.2  christos 	return p;
    130  1.1  christos }
    131  1.2  christos #endif
    132  1.1  christos 
    133  1.1  christos static const char *
    134  1.1  christos sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp)
    135  1.1  christos {
    136  1.1  christos 	char *q;
    137  1.1  christos 	q = strchr(fmtch + 1, '?');
    138  1.1  christos 	if (q) {
    139  1.1  christos 		size_t len;
    140  1.1  christos 		char *p;
    141  1.1  christos 		const char *str;
    142  1.1  christos 		int skin_it;
    143  1.2  christos #ifdef THREAD_SUPPORT
    144  1.2  christos 		int depth;
    145  1.2  christos #endif
    146  1.2  christos 		if (mp == NULL) {
    147  1.2  christos 			*fmtbeg = q + 1;
    148  1.2  christos 			return NULL;
    149  1.2  christos 		}
    150  1.2  christos #ifdef THREAD_SUPPORT
    151  1.2  christos 		depth = mp->m_depth;
    152  1.2  christos #endif
    153  1.2  christos 		skin_it = 0;
    154  1.2  christos 		switch (fmtch[1]) { /* check the '?' modifier */
    155  1.2  christos #ifdef THREAD_SUPPORT
    156  1.2  christos 		case '&':	/* use the relative depth */
    157  1.2  christos 			depth -= thread_depth();
    158  1.2  christos 			/* FALLTHROUGH */
    159  1.2  christos 		case '*':	/* use the absolute depth */
    160  1.2  christos 			len = q - fmtch - 1;
    161  1.2  christos 			p = salloc(len);
    162  1.2  christos 			(void)strlcpy(p, fmtch + 2, len);
    163  1.2  christos 			p = sfmtdepth(p, depth);
    164  1.2  christos 			break;
    165  1.2  christos #endif
    166  1.2  christos 		case '-':
    167  1.2  christos 			skin_it = 1;
    168  1.2  christos 			/* FALLTHROUGH */
    169  1.2  christos 		default:
    170  1.2  christos 			len = q - fmtch - skin_it;
    171  1.2  christos 			p = salloc(len);
    172  1.2  christos 			(void)strlcpy(p, fmtch + skin_it + 1, len);
    173  1.2  christos 			p = hfield(p, mp);
    174  1.2  christos 			if (skin_it)
    175  1.2  christos 				p = skin(p);
    176  1.2  christos 			break;
    177  1.2  christos 		}
    178  1.2  christos 		str = sfmtstr(fmtbeg, fmtch, p);
    179  1.1  christos 		*fmtbeg = q + 1;
    180  1.1  christos 		return str;
    181  1.1  christos 	}
    182  1.1  christos 	return NULL;
    183  1.1  christos }
    184  1.1  christos 
    185  1.2  christos struct flags_s {
    186  1.2  christos 	int f_and;
    187  1.2  christos 	int f_or;
    188  1.2  christos 	int f_new;	/* some message in the thread is new */
    189  1.2  christos 	int f_unread;	/* some message in the thread is unread */
    190  1.2  christos };
    191  1.2  christos 
    192  1.2  christos static void
    193  1.2  christos get_and_or_flags(struct message *mp, struct flags_s *flags)
    194  1.2  christos {
    195  1.2  christos 	for (/*EMPTY*/; mp; mp = mp->m_flink) {
    196  1.2  christos 		flags->f_and &= mp->m_flag;
    197  1.2  christos 		flags->f_or  |= mp->m_flag;
    198  1.2  christos 		flags->f_new    |= (mp->m_flag & (MREAD|MNEW)) == MNEW;
    199  1.2  christos 		flags->f_unread |= (mp->m_flag & (MREAD|MNEW)) == 0;
    200  1.2  christos 		get_and_or_flags(mp->m_clink, flags);
    201  1.2  christos 	}
    202  1.2  christos }
    203  1.2  christos 
    204  1.1  christos static const char *
    205  1.2  christos sfmtflag(const char **fmtbeg, const char *fmtch, struct message *mp)
    206  1.1  christos {
    207  1.1  christos 	char disp[2];
    208  1.2  christos 	struct flags_s flags;
    209  1.2  christos 	int is_thread;
    210  1.2  christos 
    211  1.2  christos 	if (mp == NULL)
    212  1.2  christos 		return NULL;
    213  1.2  christos 
    214  1.2  christos 	is_thread = mp->m_clink != NULL;
    215  1.2  christos 	disp[0] = is_thread ? '+' : ' ';
    216  1.1  christos 	disp[1] = '\0';
    217  1.2  christos 
    218  1.2  christos 	flags.f_and = mp->m_flag;
    219  1.2  christos 	flags.f_or = mp->m_flag;
    220  1.2  christos 	flags.f_new = 0;
    221  1.2  christos 	flags.f_unread = 0;
    222  1.2  christos #ifdef THREAD_SUPPORT
    223  1.2  christos 	if (thread_hidden())
    224  1.2  christos 		get_and_or_flags(mp->m_clink, &flags);
    225  1.2  christos #endif
    226  1.2  christos 
    227  1.2  christos 	if (flags.f_or & MTAGGED)
    228  1.2  christos 		disp[0] = 't';
    229  1.2  christos 	if (flags.f_and & MTAGGED)
    230  1.2  christos 		disp[0] = 'T';
    231  1.2  christos 
    232  1.2  christos 	if (flags.f_or & MMODIFY)
    233  1.2  christos 		disp[0] = 'e';
    234  1.2  christos 	if (flags.f_and & MMODIFY)
    235  1.2  christos 		disp[0] = 'E';
    236  1.2  christos 
    237  1.2  christos 	if (flags.f_or & MSAVED)
    238  1.2  christos 		disp[0] = '&';
    239  1.2  christos 	if (flags.f_and & MSAVED)
    240  1.1  christos 		disp[0] = '*';
    241  1.2  christos 
    242  1.2  christos 	if (flags.f_or & MPRESERVE)
    243  1.2  christos 		disp[0] = 'p';
    244  1.2  christos 	if (flags.f_and & MPRESERVE)
    245  1.1  christos 		disp[0] = 'P';
    246  1.2  christos 
    247  1.2  christos 	if (flags.f_unread)
    248  1.2  christos 		disp[0] = 'u';
    249  1.2  christos 	if ((flags.f_or & (MREAD|MNEW)) == 0)
    250  1.2  christos 		disp[0] = 'U';
    251  1.2  christos 
    252  1.2  christos 	if (flags.f_new)
    253  1.2  christos 		disp[0] = 'n';
    254  1.2  christos 	if ((flags.f_and & (MREAD|MNEW)) == MNEW)
    255  1.1  christos 		disp[0] = 'N';
    256  1.2  christos 
    257  1.2  christos 	if (flags.f_or & MBOX)
    258  1.2  christos 		disp[0] = 'm';
    259  1.2  christos 	if (flags.f_and & MBOX)
    260  1.1  christos 		disp[0] = 'M';
    261  1.2  christos 
    262  1.1  christos 	return sfmtstr(fmtbeg, fmtch, disp);
    263  1.1  christos }
    264  1.1  christos 
    265  1.1  christos static const char *
    266  1.1  christos login_name(const char *addr)
    267  1.1  christos {
    268  1.1  christos 	char *p;
    269  1.1  christos 	p = strchr(addr, '@');
    270  1.1  christos 	if (p) {
    271  1.1  christos 		char *q;
    272  1.1  christos 		size_t len;
    273  1.1  christos 		len = p - addr + 1;
    274  1.1  christos 		q = salloc(len);
    275  1.1  christos 		(void)strlcpy(q, addr, len);
    276  1.1  christos 		return q;
    277  1.1  christos 	}
    278  1.1  christos 	return addr;
    279  1.1  christos }
    280  1.1  christos 
    281  1.2  christos /*
    282  1.2  christos  * A simple routine to get around a lint warning.
    283  1.2  christos  */
    284  1.2  christos static inline const char *
    285  1.2  christos skip_fmt(const char **src, const char *p)
    286  1.2  christos {
    287  1.2  christos 	*src = p;
    288  1.2  christos 	return NULL;
    289  1.2  christos }
    290  1.2  christos 
    291  1.1  christos static const char *
    292  1.1  christos subformat(const char **src, struct message *mp, const char *addr,
    293  1.1  christos     const char *user, const char *subj, const char *gmtoff, const char *zone)
    294  1.1  christos {
    295  1.2  christos #if 0
    296  1.2  christos /* XXX - lint doesn't like this, hence skip_fmt(). */
    297  1.2  christos #define MP(a)	mp ? a : (*src = (p + 1), NULL)
    298  1.2  christos #else
    299  1.2  christos #define MP(a)	mp ? a : skip_fmt(src, p + 1);
    300  1.2  christos #endif
    301  1.1  christos 	const char *p;
    302  1.1  christos 
    303  1.1  christos 	p = *src;
    304  1.1  christos 	if (p[1] == '%') {
    305  1.1  christos 		*src += 2;
    306  1.1  christos 		return "%%";
    307  1.1  christos 	}
    308  1.1  christos 	for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++)
    309  1.1  christos 		continue;
    310  1.1  christos 
    311  1.1  christos 	switch (*p) {
    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 'Z':
    329  1.1  christos 		*src = p + 1;
    330  1.1  christos 		return zone;
    331  1.1  christos 	case 'f':
    332  1.1  christos 		return sfmtstr(src, p, addr);
    333  1.1  christos 	case 'i':
    334  1.2  christos  		return sfmtint(src, p, get_msgnum(mp));	/* '0' if mp == NULL */
    335  1.1  christos 	case 'n':
    336  1.1  christos 		return sfmtstr(src, p, login_name(addr));
    337  1.1  christos 	case 'q':
    338  1.1  christos 		return sfmtstr(src, p, subj);
    339  1.1  christos 	case 't':
    340  1.2  christos 		return sfmtint(src, p, get_msgCount());
    341  1.1  christos 	case 'z':
    342  1.1  christos 		*src = p + 1;
    343  1.1  christos 		return gmtoff;
    344  1.1  christos 	default:
    345  1.1  christos 		return NULL;
    346  1.1  christos 	}
    347  1.1  christos #undef MP
    348  1.1  christos }
    349  1.1  christos 
    350  1.1  christos static const char *
    351  1.1  christos snarf_comment(char **buf, char *bufend, const char *string)
    352  1.1  christos {
    353  1.1  christos 	const char *p;
    354  1.1  christos 	char *q;
    355  1.1  christos 	char *qend;
    356  1.1  christos 	int clevel;
    357  1.1  christos 
    358  1.1  christos 	q    = buf ? *buf : NULL;
    359  1.1  christos 	qend = buf ? bufend : NULL;
    360  1.1  christos 
    361  1.1  christos 	clevel = 1;
    362  1.1  christos 	for (p = string + 1; *p != '\0'; p++) {
    363  1.1  christos 		DEBUG(("snarf_comment: %s\n", p));
    364  1.1  christos 		if (*p == '(') {
    365  1.1  christos 			clevel++;
    366  1.1  christos 			continue;
    367  1.1  christos 		}
    368  1.1  christos 		if (*p == ')') {
    369  1.1  christos 			if (--clevel == 0)
    370  1.1  christos 				break;
    371  1.1  christos 			continue;
    372  1.1  christos 		}
    373  1.1  christos 		if (*p == '\\' && p[1] != 0)
    374  1.1  christos 			p++;
    375  1.1  christos 
    376  1.1  christos 		if (q < qend)
    377  1.1  christos 			*q++ = *p;
    378  1.1  christos 	}
    379  1.1  christos 	if (buf) {
    380  1.1  christos 		*q = '\0';
    381  1.1  christos 		DEBUG(("snarf_comment: terminating: %s\n", *buf));
    382  1.1  christos 		*buf = q;
    383  1.1  christos 	}
    384  1.1  christos 	if (*p == '\0')
    385  1.1  christos 		p--;
    386  1.1  christos 	return p;
    387  1.1  christos }
    388  1.1  christos 
    389  1.1  christos static const char *
    390  1.1  christos snarf_quote(char **buf, char *bufend, const char *string)
    391  1.1  christos {
    392  1.1  christos 	const char *p;
    393  1.1  christos 	char *q;
    394  1.1  christos 	char *qend;
    395  1.1  christos 
    396  1.1  christos 	q    = buf ? *buf : NULL;
    397  1.1  christos 	qend = buf ? bufend : NULL;
    398  1.1  christos 
    399  1.1  christos 	for (p = string + 1; *p != '\0' && *p != '"'; p++) {
    400  1.1  christos 		DEBUG(("snarf_quote: %s\n", p));
    401  1.1  christos 		if (*p == '\\' && p[1] != '\0')
    402  1.1  christos 			p++;
    403  1.1  christos 
    404  1.1  christos 		if (q < qend)
    405  1.1  christos 			*q++ = *p;
    406  1.1  christos 	}
    407  1.1  christos 	if (buf) {
    408  1.1  christos 		*q = '\0';
    409  1.1  christos 		DEBUG(("snarf_quote: terminating: %s\n", *buf));
    410  1.1  christos 		*buf = q;
    411  1.1  christos 	}
    412  1.1  christos 	if (*p == '\0')
    413  1.1  christos 		p--;
    414  1.1  christos 	return p;
    415  1.1  christos }
    416  1.1  christos 
    417  1.1  christos /*
    418  1.1  christos  * Grab the comments, separating each by a space.
    419  1.1  christos  */
    420  1.1  christos static char *
    421  1.1  christos get_comments(char *name)
    422  1.1  christos {
    423  1.2  christos 	char nbuf[LINESIZE];
    424  1.1  christos 	const char *p;
    425  1.1  christos 	char *qend;
    426  1.1  christos 	char *q;
    427  1.1  christos 	char *lastq;
    428  1.1  christos 
    429  1.1  christos 	if (name == NULL)
    430  1.2  christos 		return NULL;
    431  1.1  christos 
    432  1.1  christos 	p = name;
    433  1.1  christos 	q = nbuf;
    434  1.1  christos 	lastq = nbuf;
    435  1.1  christos 	qend = nbuf + sizeof(nbuf) - 1;
    436  1.2  christos 	for (p = skip_blank(name); *p != '\0'; p++) {
    437  1.1  christos 		DEBUG(("get_comments: %s\n", p));
    438  1.1  christos 		switch (*p) {
    439  1.1  christos 		case '"': /* quoted-string ... skip it! */
    440  1.1  christos 			p = snarf_quote(NULL, NULL, p);
    441  1.1  christos 			break;
    442  1.1  christos 
    443  1.1  christos 		case '(':
    444  1.1  christos 			p = snarf_comment(&q, qend, p);
    445  1.1  christos 			lastq = q;
    446  1.1  christos 			if (q < qend) /* separate comments by space */
    447  1.1  christos 				*q++ = ' ';
    448  1.1  christos 			break;
    449  1.1  christos 
    450  1.1  christos 		default:
    451  1.1  christos 			break;
    452  1.1  christos 		}
    453  1.1  christos 	}
    454  1.1  christos 	*lastq = '\0';
    455  1.1  christos 	return savestr(nbuf);
    456  1.1  christos }
    457  1.1  christos 
    458  1.1  christos static char *
    459  1.1  christos my_strptime(const char *buf, const char *fmtstr, struct tm *tm)
    460  1.1  christos {
    461  1.1  christos 	char *tail;
    462  1.1  christos 	char zone[4];
    463  1.1  christos 
    464  1.1  christos 	zone[0] = '\0';
    465  1.1  christos 	tail = strptime(buf, fmtstr, tm);
    466  1.1  christos 	if (tail) {
    467  1.1  christos 		int len;
    468  1.1  christos 		if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) {
    469  1.1  christos 			if (zone[0])
    470  1.1  christos 				tm->tm_zone = savestr(zone);
    471  1.1  christos 			tail += len;
    472  1.1  christos 		}
    473  1.1  christos 		tail = strptime(tail, " %Y ", tm);
    474  1.1  christos 	}
    475  1.1  christos 	return tail;
    476  1.1  christos }
    477  1.1  christos 
    478  1.3  christos static char *
    479  1.3  christos mk_gmtoff(struct tm *tm)
    480  1.3  christos {
    481  1.3  christos 	char *gmtoff;
    482  1.3  christos 	char sign;
    483  1.3  christos 	int offset;
    484  1.3  christos 	int hour;
    485  1.3  christos 	int min;
    486  1.3  christos 
    487  1.3  christos 	offset = tm->tm_gmtoff / 60;
    488  1.3  christos 	sign = offset < 0 ? '-' : '+';
    489  1.3  christos 	if (offset < 0)
    490  1.3  christos 		offset = -offset;
    491  1.3  christos 	offset %= 24 * 60;
    492  1.3  christos 	min = offset % 60;
    493  1.3  christos 	hour = offset / 60;
    494  1.3  christos 	(void)sasprintf(&gmtoff, "%c%02d%02d", sign, hour, min);
    495  1.3  christos 	return gmtoff;
    496  1.3  christos }
    497  1.3  christos 
    498  1.1  christos /*
    499  1.1  christos  * Get the date and time info from the "Date:" line, parse it into a
    500  1.1  christos  * tm structure as much as possible.
    501  1.1  christos  *
    502  1.1  christos  * Note: We return the gmtoff as a string as "-0000" has special
    503  1.1  christos  * meaning.  See RFC 2822, sec 3.3.
    504  1.1  christos  */
    505  1.2  christos PUBLIC const char *
    506  1.1  christos dateof(struct tm *tm, struct message *mp, int use_hl_date)
    507  1.1  christos {
    508  1.3  christos 	static int tzinit = 0;
    509  1.1  christos 	char *tail;
    510  1.1  christos 	char *gmtoff;
    511  1.1  christos 	const char *date;
    512  1.1  christos 
    513  1.1  christos 	(void)memset(tm, 0, sizeof(*tm));
    514  1.3  christos 	tm->tm_isdst = -1;
    515  1.1  christos 
    516  1.3  christos 	/* Make sure the time zone info is initialized. */
    517  1.3  christos 	if (!tzinit) {
    518  1.3  christos 		tzinit = 1;
    519  1.3  christos 		tzset();
    520  1.3  christos 	}
    521  1.1  christos 	if (mp == NULL) {	/* use local time */
    522  1.1  christos 		time_t now;
    523  1.1  christos 		(void)time(&now);
    524  1.1  christos 		(void)localtime_r(&now, tm);
    525  1.3  christos 		return mk_gmtoff(tm);
    526  1.1  christos 	}
    527  1.1  christos 	gmtoff = NULL;
    528  1.1  christos 	tail = NULL;
    529  1.1  christos 	/*
    530  1.1  christos 	 * See RFC 2822 sec 3.3 for date-time format used in
    531  1.1  christos 	 * the "Date:" field.
    532  1.1  christos 	 *
    533  1.2  christos 	 * Date: Tue, 21 Mar 2006 20:45:30 -0500
    534  1.2  christos 	 *
    535  1.1  christos 	 * Notes:
    536  1.1  christos 	 * 1) The 'day-of-week' and 'second' fields are optional so we
    537  1.1  christos 	 *    check 4 possibilities.  This could be optimized.
    538  1.1  christos 	 *
    539  1.1  christos 	 * 2) The timezone is frequently in a comment following the
    540  1.1  christos 	 *    zone offset.
    541  1.1  christos 	 *
    542  1.1  christos 	 * 3) The range for the time is 00:00 to 23:60 (for a leep
    543  1.1  christos 	 *    second), but I have seen this violated (e.g., Date: Tue,
    544  1.1  christos 	 *    24 Oct 2006 24:07:58 +0400) making strptime() fail.
    545  1.1  christos 	 *    Thus we fall back on the headline time which was written
    546  1.1  christos 	 *    locally when the message was received.  Of course, this
    547  1.1  christos 	 *    is not the same time as in the Date field.
    548  1.1  christos 	 */
    549  1.1  christos 	if (use_hl_date == 0 &&
    550  1.1  christos 	    (date = hfield("date", mp)) != NULL &&
    551  1.1  christos 	    ((tail = strptime(date, " %a, %d %b %Y %T ", tm)) != NULL ||
    552  1.1  christos 	     (tail = strptime(date,     " %d %b %Y %T ", tm)) != NULL ||
    553  1.1  christos 	     (tail = strptime(date, " %a, %d %b %Y %R ", tm)) != NULL ||
    554  1.1  christos 	     (tail = strptime(date,     " %d %b %Y %R ", tm)) != NULL)) {
    555  1.2  christos 		int hour;
    556  1.2  christos 		int min;
    557  1.4  christos 		char sign[2];
    558  1.1  christos 		char *cp;
    559  1.2  christos 
    560  1.1  christos 		if ((cp = strchr(tail, '(')) != NULL)
    561  1.1  christos 			tm->tm_zone = get_comments(cp);
    562  1.1  christos 		else
    563  1.1  christos 			tm->tm_zone = NULL;
    564  1.1  christos 		gmtoff = skin(tail);
    565  1.2  christos 
    566  1.2  christos 		/*
    567  1.2  christos 		 * Scan the gmtoff and use it to convert the time to a
    568  1.2  christos 		 * local time.
    569  1.2  christos 		 *
    570  1.3  christos 		 * Note: "-0000" means no valid zone info.  See
    571  1.3  christos 		 *       RFC 2822, sec 3.3.
    572  1.3  christos 		 *
    573  1.2  christos 		 * XXX - This is painful!  Is there a better way?
    574  1.2  christos 		 */
    575  1.3  christos 		if (strcmp(gmtoff, "-0000") != 0 &&
    576  1.4  christos 		    sscanf(gmtoff, " %1[+-]%2d%2d ", sign, &hour, &min) == 3) {
    577  1.2  christos 			time_t otime;
    578  1.4  christos 			if (sign[0] == '-') {
    579  1.2  christos 				tm->tm_hour += hour;
    580  1.2  christos 				tm->tm_min += min;
    581  1.2  christos 			}
    582  1.2  christos 			else {
    583  1.2  christos 				tm->tm_hour -= hour;
    584  1.2  christos 				tm->tm_min -= min;
    585  1.2  christos 			}
    586  1.3  christos 			tm->tm_isdst = -1;
    587  1.2  christos 			if ((time_t)(otime = timegm(tm)) == -1)
    588  1.2  christos 				warn("timegm: %s", date);
    589  1.3  christos 
    590  1.2  christos 			if(localtime_r(&otime, tm) == NULL)
    591  1.2  christos 				warn("localtime: %s", date);
    592  1.3  christos 
    593  1.3  christos 			/* extract the new gmtoff string */
    594  1.3  christos 			gmtoff = mk_gmtoff(tm);
    595  1.2  christos 		}
    596  1.2  christos 		else
    597  1.2  christos 			tm->tm_gmtoff = 0;
    598  1.1  christos 	}
    599  1.1  christos 	else {
    600  1.1  christos 		/*
    601  1.1  christos 		 * The BSD and System V headline date formats differ
    602  1.1  christos 		 * and each have an optional timezone field between
    603  1.1  christos 		 * the time and date (see head.c).  Unfortunately,
    604  1.1  christos 		 * strptime(3) doesn't know about timezone fields, so
    605  1.1  christos 		 * we have to handle it ourselves.
    606  1.1  christos 		 *
    607  1.1  christos 		 * char ctype[]        = "Aaa Aaa O0 00:00:00 0000";
    608  1.1  christos 		 * char tmztype[]      = "Aaa Aaa O0 00:00:00 AAA 0000";
    609  1.1  christos 		 * char SysV_ctype[]   = "Aaa Aaa O0 00:00 0000";
    610  1.1  christos 		 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
    611  1.1  christos 		 */
    612  1.1  christos 		struct headline hl;
    613  1.1  christos 		char headline[LINESIZE];
    614  1.2  christos 		char pbuf[LINESIZE];
    615  1.1  christos 
    616  1.1  christos 		headline[0] = '\0';
    617  1.1  christos 		date = headline;
    618  1.2  christos 		(void)mail_readline(setinput(mp), headline, sizeof(headline));
    619  1.1  christos 		parse(headline, &hl, pbuf);
    620  1.3  christos 		if (hl.l_date != NULL) {
    621  1.3  christos 			if ((tail = my_strptime(hl.l_date, " %a %b %d %T ", tm)) == NULL &&
    622  1.3  christos 			    (tail = my_strptime(hl.l_date, " %a %b %d %R ", tm)) == NULL)
    623  1.3  christos 				warnx("dateof: cannot determine date: %s", hl.l_date);
    624  1.3  christos 			else {
    625  1.3  christos 				tm->tm_isdst = -1;
    626  1.3  christos 				if(mktime(tm) == -1)
    627  1.3  christos 					warn("mktime: %s", date);
    628  1.3  christos 
    629  1.3  christos 				/* extract the gmtoff string */
    630  1.3  christos 				gmtoff = mk_gmtoff(tm);
    631  1.3  christos 			}
    632  1.1  christos 		}
    633  1.1  christos 	}
    634  1.3  christos 	/* 'tail' will be NULL here if the mail file is empty, so
    635  1.3  christos 	 * don't check it with an assert(). */
    636  1.2  christos 
    637  1.1  christos 	return gmtoff;
    638  1.1  christos }
    639  1.1  christos 
    640  1.1  christos /*
    641  1.1  christos  * Get the sender's address for display.  Let nameof() do this.
    642  1.1  christos  */
    643  1.1  christos static const char *
    644  1.1  christos addrof(struct message *mp)
    645  1.1  christos {
    646  1.1  christos 	if (mp == NULL)
    647  1.1  christos 		return NULL;
    648  1.1  christos 
    649  1.1  christos 	return nameof(mp, 0);
    650  1.1  christos }
    651  1.1  christos 
    652  1.1  christos /************************************************************************
    653  1.1  christos  * The 'address' syntax - from rfc 2822:
    654  1.1  christos  *
    655  1.1  christos  * specials        =       "(" / ")" /     ; Special characters used in
    656  1.1  christos  *                         "<" / ">" /     ;  other parts of the syntax
    657  1.1  christos  *                         "[" / "]" /
    658  1.1  christos  *                         ":" / ";" /
    659  1.1  christos  *                         "@" / "\" /
    660  1.1  christos  *                         "," / "." /
    661  1.1  christos  *                         DQUOTE
    662  1.1  christos  * qtext           =       NO-WS-CTL /     ; Non white space controls
    663  1.1  christos  *                         %d33 /          ; The rest of the US-ASCII
    664  1.1  christos  *                         %d35-91 /       ;  characters not including "\"
    665  1.1  christos  *                         %d93-126        ;  or the quote character
    666  1.1  christos  * qcontent        =       qtext / quoted-pair
    667  1.1  christos  * quoted-string   =       [CFWS]
    668  1.1  christos  *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
    669  1.1  christos  *                         [CFWS]
    670  1.1  christos  * atext           =       ALPHA / DIGIT / ; Any character except controls,
    671  1.1  christos  *                         "!" / "#" /     ;  SP, and specials.
    672  1.1  christos  *                         "$" / "%" /     ;  Used for atoms
    673  1.1  christos  *                         "&" / "'" /
    674  1.1  christos  *                         "*" / "+" /
    675  1.1  christos  *                         "-" / "/" /
    676  1.1  christos  *                         "=" / "?" /
    677  1.1  christos  *                         "^" / "_" /
    678  1.1  christos  *                         "`" / "{" /
    679  1.1  christos  *                         "|" / "}" /
    680  1.1  christos  *                         "~"
    681  1.1  christos  * atom            =       [CFWS] 1*atext [CFWS]
    682  1.1  christos  * word            =       atom / quoted-string
    683  1.1  christos  * phrase          =       1*word / obs-phrase
    684  1.1  christos  * display-name    =       phrase
    685  1.1  christos  * dtext           =       NO-WS-CTL /     ; Non white space controls
    686  1.1  christos  *                         %d33-90 /       ; The rest of the US-ASCII
    687  1.1  christos  *                         %d94-126        ;  characters not including "[",
    688  1.1  christos  *                                         ;  "]", or "\"
    689  1.1  christos  * dcontent        =       dtext / quoted-pair
    690  1.1  christos  * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
    691  1.1  christos  * domain          =       dot-atom / domain-literal / obs-domain
    692  1.1  christos  * local-part      =       dot-atom / quoted-string / obs-local-part
    693  1.1  christos  * addr-spec       =       local-part "@" domain
    694  1.1  christos  * angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
    695  1.1  christos  * name-addr       =       [display-name] angle-addr
    696  1.1  christos  * mailbox         =       name-addr / addr-spec
    697  1.1  christos  * mailbox-list    =       (mailbox *("," mailbox)) / obs-mbox-list
    698  1.1  christos  * group           =       display-name ":" [mailbox-list / CFWS] ";"
    699  1.1  christos  *                         [CFWS]
    700  1.1  christos  * address         =       mailbox / group
    701  1.1  christos  ************************************************************************/
    702  1.1  christos static char *
    703  1.1  christos get_display_name(char *name)
    704  1.1  christos {
    705  1.2  christos 	char nbuf[LINESIZE];
    706  1.1  christos 	const char *p;
    707  1.1  christos 	char *q;
    708  1.1  christos 	char *qend;
    709  1.1  christos 	char *lastq;
    710  1.1  christos 	int quoted;
    711  1.1  christos 
    712  1.1  christos 	if (name == NULL)
    713  1.2  christos 		return NULL;
    714  1.1  christos 
    715  1.1  christos 	q = nbuf;
    716  1.1  christos 	lastq = nbuf;
    717  1.1  christos 	qend = nbuf + sizeof(nbuf) - 1;	/* reserve space for '\0' */
    718  1.1  christos 	quoted = 0;
    719  1.2  christos 	for (p = skip_blank(name); *p != '\0'; p++) {
    720  1.1  christos 		DEBUG(("get_display_name: %s\n", p));
    721  1.1  christos 		switch (*p) {
    722  1.1  christos 		case '"':	/* quoted-string */
    723  1.1  christos 			q = nbuf;
    724  1.1  christos 			p = snarf_quote(&q, qend, p);
    725  1.1  christos 			if (!quoted)
    726  1.1  christos 				lastq = q;
    727  1.1  christos 			quoted = 1;
    728  1.1  christos 			break;
    729  1.1  christos 
    730  1.1  christos 		case ':':	/* group */
    731  1.1  christos 		case '<':	/* angle-address */
    732  1.1  christos 			if (lastq == nbuf)
    733  1.1  christos 				return NULL;
    734  1.1  christos 			*lastq = '\0';	/* NULL termination */
    735  1.2  christos 			return savestr(nbuf);
    736  1.1  christos 
    737  1.1  christos 		case '(':	/* comment - skip it! */
    738  1.1  christos 			p = snarf_comment(NULL, NULL, p);
    739  1.1  christos 			break;
    740  1.1  christos 
    741  1.1  christos 		default:
    742  1.1  christos 			if (!quoted && q < qend) {
    743  1.1  christos 				*q++ = *p;
    744  1.1  christos 				if (!isblank((unsigned char)*p)
    745  1.1  christos 				    /* && !is_specials((unsigned char)*p) */ )
    746  1.1  christos 					lastq = q;
    747  1.1  christos 			}
    748  1.1  christos 			break;
    749  1.1  christos 		}
    750  1.1  christos 	}
    751  1.1  christos 	return NULL;	/* no group or angle-address */
    752  1.1  christos }
    753  1.1  christos 
    754  1.1  christos /*
    755  1.1  christos  * See RFC 2822 sec 3.4 and 3.6.2.
    756  1.1  christos  */
    757  1.1  christos static const char *
    758  1.1  christos userof(struct message *mp)
    759  1.1  christos {
    760  1.1  christos 	char *sender;
    761  1.1  christos 	char *dispname;
    762  1.1  christos 
    763  1.1  christos 	if (mp == NULL)
    764  1.1  christos 		return NULL;
    765  1.1  christos 
    766  1.1  christos 	if ((sender = hfield("from", mp)) != NULL ||
    767  1.1  christos 	    (sender = hfield("sender", mp)) != NULL)
    768  1.1  christos 		/*
    769  1.1  christos 		 * Try to get the display-name.  If one doesn't exist,
    770  1.1  christos 		 * then the best we can hope for is that the user's
    771  1.1  christos 		 * name is in the comments.
    772  1.1  christos 		 */
    773  1.1  christos 		if ((dispname = get_display_name(sender)) != NULL ||
    774  1.1  christos 		    (dispname = get_comments(sender)) != NULL)
    775  1.1  christos 			return dispname;
    776  1.1  christos 	return NULL;
    777  1.1  christos }
    778  1.1  christos 
    779  1.1  christos /*
    780  1.1  christos  * Grab the subject line.
    781  1.1  christos  */
    782  1.1  christos static const char *
    783  1.1  christos subjof(struct message *mp)
    784  1.1  christos {
    785  1.1  christos 	const char *subj;
    786  1.1  christos 
    787  1.1  christos 	if (mp == NULL)
    788  1.1  christos 		return NULL;
    789  1.1  christos 
    790  1.1  christos 	if ((subj = hfield("subject", mp)) == NULL)
    791  1.1  christos 		subj = hfield("subj", mp);
    792  1.1  christos 	return subj;
    793  1.1  christos }
    794  1.1  christos 
    795  1.5  christos /*
    796  1.5  christos  * Protect a string against strftime() conversion.
    797  1.5  christos  */
    798  1.5  christos static const char*
    799  1.5  christos protect(const char *str)
    800  1.5  christos {
    801  1.5  christos 	char *p, *q;
    802  1.5  christos 	size_t size;
    803  1.5  christos 
    804  1.5  christos 	size = strlen(str);
    805  1.5  christos 	if (size == 0)
    806  1.5  christos 		return str;
    807  1.5  christos 
    808  1.5  christos 	p = salloc(2 * size);
    809  1.5  christos 	for (q = p; *str; str++) {
    810  1.5  christos 		*q = *str;
    811  1.5  christos 		if (*q++ == '%')
    812  1.5  christos 			*q++ = '%';
    813  1.5  christos 	}
    814  1.5  christos 	*q = '\0';
    815  1.5  christos 	return p;
    816  1.5  christos }
    817  1.5  christos 
    818  1.1  christos static char *
    819  1.1  christos preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
    820  1.1  christos {
    821  1.1  christos 	const char *gmtoff;
    822  1.1  christos 	const char *zone;
    823  1.1  christos 	const char *subj;
    824  1.1  christos 	const char *addr;
    825  1.1  christos 	const char *user;
    826  1.1  christos 	const char *p;
    827  1.1  christos 	char *q;
    828  1.1  christos 	char *newfmt;
    829  1.1  christos 	size_t fmtsize;
    830  1.1  christos 
    831  1.1  christos 	if (mp != NULL && (mp->m_flag & MDELETED) != 0)
    832  1.1  christos 		mp = NULL; /* deleted mail shouldn't show up! */
    833  1.1  christos 
    834  1.5  christos 	subj = protect(subjof(mp));
    835  1.5  christos 	addr = protect(addrof(mp));
    836  1.5  christos 	user = protect(userof(mp));
    837  1.1  christos 	gmtoff = dateof(tm, mp, use_hl_date);
    838  1.1  christos 	zone = tm->tm_zone;
    839  1.1  christos 	fmtsize = LINESIZE;
    840  1.1  christos 	newfmt = malloc(fmtsize); /* so we can realloc() in check_bufsize() */
    841  1.1  christos 	q = newfmt;
    842  1.1  christos 	p = oldfmt;
    843  1.1  christos 	while (*p) {
    844  1.1  christos 		if (*p == '%') {
    845  1.1  christos 			const char *fp;
    846  1.1  christos 			fp = subformat(&p, mp, addr, user, subj, gmtoff, zone);
    847  1.1  christos 			if (fp) {
    848  1.1  christos 				size_t len;
    849  1.1  christos 				len = strlen(fp);
    850  1.1  christos 				check_bufsize(&newfmt, &fmtsize, &q, len);
    851  1.1  christos 				(void)strcpy(q, fp);
    852  1.1  christos 				q += len;
    853  1.1  christos 				continue;
    854  1.1  christos 			}
    855  1.1  christos 		}
    856  1.1  christos 		check_bufsize(&newfmt, &fmtsize, &q, 1);
    857  1.1  christos 		*q++ = *p++;
    858  1.1  christos 	}
    859  1.1  christos 	*q = '\0';
    860  1.1  christos 
    861  1.1  christos 	return newfmt;
    862  1.1  christos }
    863  1.1  christos 
    864  1.1  christos 
    865  1.1  christos /*
    866  1.1  christos  * If a format string begins with the USE_HL_DATE string, smsgprintf
    867  1.1  christos  * will use the headerline date rather than trying to extract the date
    868  1.1  christos  * from the Date field.
    869  1.1  christos  *
    870  1.1  christos  * Note: If a 'valid' date cannot be extracted from the Date field,
    871  1.1  christos  * then the headline date is used.
    872  1.1  christos  */
    873  1.1  christos #define USE_HL_DATE "%??"
    874  1.1  christos 
    875  1.1  christos PUBLIC char *
    876  1.1  christos smsgprintf(const char *fmtstr, struct message *mp)
    877  1.1  christos {
    878  1.1  christos 	struct tm tm;
    879  1.1  christos 	int use_hl_date;
    880  1.1  christos 	char *newfmt;
    881  1.1  christos 	char *buf;
    882  1.1  christos 	size_t bufsize;
    883  1.1  christos 
    884  1.1  christos 	if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
    885  1.1  christos 		use_hl_date = 0;
    886  1.1  christos 	else {
    887  1.1  christos 		use_hl_date = 1;
    888  1.1  christos 		fmtstr += sizeof(USE_HL_DATE) - 1;
    889  1.1  christos 	}
    890  1.1  christos 	bufsize = LINESIZE;
    891  1.1  christos 	buf = salloc(bufsize);
    892  1.1  christos 	newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
    893  1.1  christos 	(void)strftime(buf, bufsize, newfmt, &tm);
    894  1.1  christos 	free(newfmt);	/* preformat() uses malloc()/realloc() */
    895  1.1  christos 	return buf;
    896  1.1  christos }
    897  1.1  christos 
    898  1.1  christos 
    899  1.1  christos PUBLIC void
    900  1.1  christos fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
    901  1.1  christos {
    902  1.1  christos 	char *buf;
    903  1.1  christos 
    904  1.1  christos 	buf = smsgprintf(fmtstr, mp);
    905  1.1  christos 	(void)fprintf(fo, "%s\n", buf);	/* XXX - add the newline here? */
    906  1.1  christos }
    907