Home | History | Annotate | Line # | Download | only in mail
format.c revision 1.1
      1  1.1  christos /*	$NetBSD: format.c,v 1.1 2006/10/31 22:36:37 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.1  christos __RCSID("$NetBSD: format.c,v 1.1 2006/10/31 22:36:37 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.1  christos 
     53  1.1  christos 
     54  1.1  christos #define DEBUG(a)
     55  1.1  christos 
     56  1.1  christos static void
     57  1.1  christos check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt)
     58  1.1  christos {
     59  1.1  christos 	char *q;
     60  1.1  christos 	if (*p + cnt < *buf + *bufsize)
     61  1.1  christos 		return;
     62  1.1  christos 	*bufsize *= 2;
     63  1.1  christos 	q = realloc(*buf, *bufsize);
     64  1.1  christos 	*p = q + (*p - *buf);
     65  1.1  christos 	*buf = q;
     66  1.1  christos }
     67  1.1  christos 
     68  1.1  christos static const char *
     69  1.1  christos sfmtoff(const char **fmtbeg, const char *fmtch, off_t off)
     70  1.1  christos {
     71  1.1  christos 	char *newfmt;	/* pointer to new format string */
     72  1.1  christos 	size_t len;	/* space for "lld" including '\0' */
     73  1.1  christos 	len = fmtch - *fmtbeg + sizeof(PRId64);
     74  1.1  christos 	newfmt = salloc(len);
     75  1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len - sizeof(sizeof(PRId64)) + 1);
     76  1.1  christos 	(void)strlcat(newfmt, PRId64, len);
     77  1.1  christos 	*fmtbeg = fmtch + 1;
     78  1.1  christos 	{
     79  1.1  christos 		char *p;
     80  1.1  christos 		char *q;
     81  1.1  christos 		(void)easprintf(&p, newfmt, off);
     82  1.1  christos 		q = savestr(p);
     83  1.1  christos 		free(p);
     84  1.1  christos 		return q;
     85  1.1  christos 	}
     86  1.1  christos }
     87  1.1  christos 
     88  1.1  christos static const char *
     89  1.1  christos sfmtint(const char **fmtbeg, const char *fmtch, int num)
     90  1.1  christos {
     91  1.1  christos 	char *newfmt;
     92  1.1  christos 	size_t len;
     93  1.1  christos 
     94  1.1  christos 	len = fmtch - *fmtbeg + 2;	/* space for 'd' and '\0' */
     95  1.1  christos 	newfmt = salloc(len);
     96  1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len);
     97  1.1  christos 	newfmt[len-2] = 'd';		/* convert to printf format */
     98  1.1  christos 
     99  1.1  christos 	*fmtbeg = fmtch + 1;
    100  1.1  christos 	{
    101  1.1  christos 		char *p;
    102  1.1  christos 		char *q;
    103  1.1  christos 		(void)easprintf(&p, newfmt, num);
    104  1.1  christos 		q = savestr(p);
    105  1.1  christos 		free(p);
    106  1.1  christos 		return q;
    107  1.1  christos 	}
    108  1.1  christos }
    109  1.1  christos 
    110  1.1  christos static const char *
    111  1.1  christos sfmtstr(const char **fmtbeg, const char *fmtch, const char *str)
    112  1.1  christos {
    113  1.1  christos 	char *newfmt;
    114  1.1  christos 	size_t len;
    115  1.1  christos 
    116  1.1  christos 	len = fmtch - *fmtbeg + 2;	/* space for 's' and '\0' */
    117  1.1  christos 	newfmt = salloc(len);
    118  1.1  christos 	(void)strlcpy(newfmt, *fmtbeg, len);
    119  1.1  christos 	newfmt[len-2] = 's';		/* convert to printf format */
    120  1.1  christos 
    121  1.1  christos 	*fmtbeg = fmtch + 1;
    122  1.1  christos 	{
    123  1.1  christos 		char *p;
    124  1.1  christos 		char *q;
    125  1.1  christos 		(void)easprintf(&p, newfmt, str ? str : "");
    126  1.1  christos 		q = savestr(p);
    127  1.1  christos 		free(p);
    128  1.1  christos 		return q;
    129  1.1  christos 	}
    130  1.1  christos }
    131  1.1  christos 
    132  1.1  christos static const char *
    133  1.1  christos sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp)
    134  1.1  christos {
    135  1.1  christos 	char *q;
    136  1.1  christos 	q = strchr(fmtch + 1, '?');
    137  1.1  christos 	if (q) {
    138  1.1  christos 		size_t len;
    139  1.1  christos 		char *p;
    140  1.1  christos 		const char *str;
    141  1.1  christos 		int skin_it;
    142  1.1  christos 
    143  1.1  christos 		skin_it = fmtch[1] == '-' ? 1 : 0;
    144  1.1  christos 		len = q - fmtch - skin_it;
    145  1.1  christos 		p = salloc(len + 1);
    146  1.1  christos 		(void)strlcpy(p, fmtch + skin_it + 1, len);
    147  1.1  christos 		str = sfmtstr(fmtbeg, fmtch, hfield(p, mp));
    148  1.1  christos 		if (skin_it)
    149  1.1  christos 			str = skin(__UNCONST(str));
    150  1.1  christos 		*fmtbeg = q + 1;
    151  1.1  christos 		return str;
    152  1.1  christos 	}
    153  1.1  christos 	return NULL;
    154  1.1  christos }
    155  1.1  christos 
    156  1.1  christos static const char *
    157  1.1  christos sfmtflag(const char **fmtbeg, const char *fmtch, int flags)
    158  1.1  christos {
    159  1.1  christos 	char disp[2];
    160  1.1  christos 	disp[0] = ' ';
    161  1.1  christos 	disp[1] = '\0';
    162  1.1  christos 	if (flags & MSAVED)
    163  1.1  christos 		disp[0] = '*';
    164  1.1  christos 	if (flags & MPRESERVE)
    165  1.1  christos 		disp[0] = 'P';
    166  1.1  christos 	if ((flags & (MREAD|MNEW)) == MNEW)
    167  1.1  christos 		disp[0] = 'N';
    168  1.1  christos 	if ((flags & (MREAD|MNEW)) == 0)
    169  1.1  christos 		disp[0] = 'U';
    170  1.1  christos 	if (flags & MBOX)
    171  1.1  christos 		disp[0] = 'M';
    172  1.1  christos 	return sfmtstr(fmtbeg, fmtch, disp);
    173  1.1  christos }
    174  1.1  christos 
    175  1.1  christos static const char *
    176  1.1  christos login_name(const char *addr)
    177  1.1  christos {
    178  1.1  christos 	char *p;
    179  1.1  christos 	p = strchr(addr, '@');
    180  1.1  christos 	if (p) {
    181  1.1  christos 		char *q;
    182  1.1  christos 		size_t len;
    183  1.1  christos 		len = p - addr + 1;
    184  1.1  christos 		q = salloc(len);
    185  1.1  christos 		(void)strlcpy(q, addr, len);
    186  1.1  christos 		return q;
    187  1.1  christos 	}
    188  1.1  christos 	return addr;
    189  1.1  christos }
    190  1.1  christos 
    191  1.1  christos static const char *
    192  1.1  christos subformat(const char **src, struct message *mp, const char *addr,
    193  1.1  christos     const char *user, const char *subj, const char *gmtoff, const char *zone)
    194  1.1  christos {
    195  1.1  christos #define MP(a)	mp ? a : NULL
    196  1.1  christos 	const char *p;
    197  1.1  christos 
    198  1.1  christos 	p = *src;
    199  1.1  christos 	if (p[1] == '%') {
    200  1.1  christos 		*src += 2;
    201  1.1  christos 		return "%%";
    202  1.1  christos 	}
    203  1.1  christos 	for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++)
    204  1.1  christos 		continue;
    205  1.1  christos 
    206  1.1  christos 	switch (*p) {
    207  1.1  christos 	case '?':
    208  1.1  christos 		return MP(sfmtfield(src, p, mp));
    209  1.1  christos 	case 'J':
    210  1.1  christos  		return MP(sfmtint(src, p, (int)(mp->m_lines - mp->m_blines)));
    211  1.1  christos 	case 'K':
    212  1.1  christos  		return MP(sfmtint(src, p, (int)mp->m_blines));
    213  1.1  christos 	case 'L':
    214  1.1  christos  		return MP(sfmtint(src, p, (int)mp->m_lines));
    215  1.1  christos 	case 'N':
    216  1.1  christos 		return sfmtstr(src, p, user);
    217  1.1  christos 	case 'O':
    218  1.1  christos  		return MP(sfmtoff(src, p, mp->m_size));
    219  1.1  christos 	case 'P':
    220  1.1  christos  		return MP(sfmtstr(src, p, mp == dot ? ">" : " "));
    221  1.1  christos 	case 'Q':
    222  1.1  christos  		return MP(sfmtflag(src, p, mp->m_flag));
    223  1.1  christos 	case 'Z':
    224  1.1  christos 		*src = p + 1;
    225  1.1  christos 		return zone;
    226  1.1  christos 
    227  1.1  christos 	case 'f':
    228  1.1  christos 		return sfmtstr(src, p, addr);
    229  1.1  christos 	case 'i':
    230  1.1  christos 		if (mp == NULL && (mp = dot) == NULL)
    231  1.1  christos 			return NULL;
    232  1.1  christos  		return sfmtint(src, p, (mp - message) + 1);
    233  1.1  christos 	case 'n':
    234  1.1  christos 		return sfmtstr(src, p, login_name(addr));
    235  1.1  christos 	case 'q':
    236  1.1  christos 		return sfmtstr(src, p, subj);
    237  1.1  christos 	case 't':
    238  1.1  christos 		return sfmtint(src, p, msgCount);
    239  1.1  christos 	case 'z':
    240  1.1  christos 		*src = p + 1;
    241  1.1  christos 		return gmtoff;
    242  1.1  christos 	default:
    243  1.1  christos 		return NULL;
    244  1.1  christos 	}
    245  1.1  christos #undef MP
    246  1.1  christos }
    247  1.1  christos 
    248  1.1  christos static const char *
    249  1.1  christos snarf_comment(char **buf, char *bufend, const char *string)
    250  1.1  christos {
    251  1.1  christos 	const char *p;
    252  1.1  christos 	char *q;
    253  1.1  christos 	char *qend;
    254  1.1  christos 	int clevel;
    255  1.1  christos 
    256  1.1  christos 	q    = buf ? *buf : NULL;
    257  1.1  christos 	qend = buf ? bufend : NULL;
    258  1.1  christos 
    259  1.1  christos 	clevel = 1;
    260  1.1  christos 	for (p = string + 1; *p != '\0'; p++) {
    261  1.1  christos 		DEBUG(("snarf_comment: %s\n", p));
    262  1.1  christos 		if (*p == '(') {
    263  1.1  christos 			clevel++;
    264  1.1  christos 			continue;
    265  1.1  christos 		}
    266  1.1  christos 		if (*p == ')') {
    267  1.1  christos 			if (--clevel == 0)
    268  1.1  christos 				break;
    269  1.1  christos 			continue;
    270  1.1  christos 		}
    271  1.1  christos 		if (*p == '\\' && p[1] != 0)
    272  1.1  christos 			p++;
    273  1.1  christos 
    274  1.1  christos 		if (q < qend)
    275  1.1  christos 			*q++ = *p;
    276  1.1  christos 	}
    277  1.1  christos 	if (buf) {
    278  1.1  christos 		*q = '\0';
    279  1.1  christos 		DEBUG(("snarf_comment: terminating: %s\n", *buf));
    280  1.1  christos 		*buf = q;
    281  1.1  christos 	}
    282  1.1  christos 	if (*p == '\0')
    283  1.1  christos 		p--;
    284  1.1  christos 	return p;
    285  1.1  christos }
    286  1.1  christos 
    287  1.1  christos static const char *
    288  1.1  christos snarf_quote(char **buf, char *bufend, const char *string)
    289  1.1  christos {
    290  1.1  christos 	const char *p;
    291  1.1  christos 	char *q;
    292  1.1  christos 	char *qend;
    293  1.1  christos 
    294  1.1  christos 	q    = buf ? *buf : NULL;
    295  1.1  christos 	qend = buf ? bufend : NULL;
    296  1.1  christos 
    297  1.1  christos 	for (p = string + 1; *p != '\0' && *p != '"'; p++) {
    298  1.1  christos 		DEBUG(("snarf_quote: %s\n", p));
    299  1.1  christos 		if (*p == '\\' && p[1] != '\0')
    300  1.1  christos 			p++;
    301  1.1  christos 
    302  1.1  christos 		if (q < qend)
    303  1.1  christos 			*q++ = *p;
    304  1.1  christos 	}
    305  1.1  christos 	if (buf) {
    306  1.1  christos 		*q = '\0';
    307  1.1  christos 		DEBUG(("snarf_quote: terminating: %s\n", *buf));
    308  1.1  christos 		*buf = q;
    309  1.1  christos 	}
    310  1.1  christos 	if (*p == '\0')
    311  1.1  christos 		p--;
    312  1.1  christos 	return p;
    313  1.1  christos }
    314  1.1  christos 
    315  1.1  christos /*
    316  1.1  christos  * Grab the comments, separating each by a space.
    317  1.1  christos  */
    318  1.1  christos static char *
    319  1.1  christos get_comments(char *name)
    320  1.1  christos {
    321  1.1  christos 	char nbuf[BUFSIZ];
    322  1.1  christos 	const char *p;
    323  1.1  christos 	char *qend;
    324  1.1  christos 	char *q;
    325  1.1  christos 	char *lastq;
    326  1.1  christos 
    327  1.1  christos 	if (name == NULL)
    328  1.1  christos 		return(NULL);
    329  1.1  christos 
    330  1.1  christos 	p = name;
    331  1.1  christos 	q = nbuf;
    332  1.1  christos 	lastq = nbuf;
    333  1.1  christos 	qend = nbuf + sizeof(nbuf) - 1;
    334  1.1  christos 	for (p = skip_white(name); *p != '\0'; p++) {
    335  1.1  christos 		DEBUG(("get_comments: %s\n", p));
    336  1.1  christos 		switch (*p) {
    337  1.1  christos 		case '"': /* quoted-string ... skip it! */
    338  1.1  christos 			p = snarf_quote(NULL, NULL, p);
    339  1.1  christos 			break;
    340  1.1  christos 
    341  1.1  christos 		case '(':
    342  1.1  christos 			p = snarf_comment(&q, qend, p);
    343  1.1  christos 			lastq = q;
    344  1.1  christos 			if (q < qend) /* separate comments by space */
    345  1.1  christos 				*q++ = ' ';
    346  1.1  christos 			break;
    347  1.1  christos 
    348  1.1  christos 		default:
    349  1.1  christos 			break;
    350  1.1  christos 		}
    351  1.1  christos 	}
    352  1.1  christos 	*lastq = '\0';
    353  1.1  christos 	return savestr(nbuf);
    354  1.1  christos }
    355  1.1  christos 
    356  1.1  christos static char *
    357  1.1  christos my_strptime(const char *buf, const char *fmtstr, struct tm *tm)
    358  1.1  christos {
    359  1.1  christos 	char *tail;
    360  1.1  christos 	char zone[4];
    361  1.1  christos 
    362  1.1  christos 	zone[0] = '\0';
    363  1.1  christos 	tail = strptime(buf, fmtstr, tm);
    364  1.1  christos 	if (tail) {
    365  1.1  christos 		int len;
    366  1.1  christos 		if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) {
    367  1.1  christos 			if (zone[0])
    368  1.1  christos 				tm->tm_zone = savestr(zone);
    369  1.1  christos 			tail += len;
    370  1.1  christos 		}
    371  1.1  christos 		tail = strptime(tail, " %Y ", tm);
    372  1.1  christos 	}
    373  1.1  christos 	return tail;
    374  1.1  christos }
    375  1.1  christos 
    376  1.1  christos /*
    377  1.1  christos  * Get the date and time info from the "Date:" line, parse it into a
    378  1.1  christos  * tm structure as much as possible.
    379  1.1  christos  *
    380  1.1  christos  * Note: We return the gmtoff as a string as "-0000" has special
    381  1.1  christos  * meaning.  See RFC 2822, sec 3.3.
    382  1.1  christos  */
    383  1.1  christos static const char *
    384  1.1  christos dateof(struct tm *tm, struct message *mp, int use_hl_date)
    385  1.1  christos {
    386  1.1  christos 	char *tail;
    387  1.1  christos 	char *gmtoff;
    388  1.1  christos 	const char *date;
    389  1.1  christos 
    390  1.1  christos 	(void)memset(tm, 0, sizeof(*tm));
    391  1.1  christos 
    392  1.1  christos 	if (mp == NULL) {	/* use local time */
    393  1.1  christos 		char buf[6];	/* space for "+0000" */
    394  1.1  christos 		int hour;
    395  1.1  christos 		int min;
    396  1.1  christos 		time_t now;
    397  1.1  christos 		tzset();
    398  1.1  christos 		(void)time(&now);
    399  1.1  christos 		(void)localtime_r(&now, tm);
    400  1.1  christos 		min = (tm->tm_gmtoff / 60) % 60;
    401  1.1  christos 		hour = tm->tm_gmtoff / 3600;
    402  1.1  christos 		if (hour > 12)
    403  1.1  christos 			hour = 24 - hour;
    404  1.1  christos 		(void)snprintf(buf, sizeof(buf), "%+03d%02d", hour, min);
    405  1.1  christos 		return savestr(buf);
    406  1.1  christos 	}
    407  1.1  christos 	gmtoff = NULL;
    408  1.1  christos 	tail = NULL;
    409  1.1  christos 	/*
    410  1.1  christos 	 * See RFC 2822 sec 3.3 for date-time format used in
    411  1.1  christos 	 * the "Date:" field.
    412  1.1  christos 	 *
    413  1.1  christos 	 * Notes:
    414  1.1  christos 	 * 1) The 'day-of-week' and 'second' fields are optional so we
    415  1.1  christos 	 *    check 4 possibilities.  This could be optimized.
    416  1.1  christos 	 *
    417  1.1  christos 	 * 2) The timezone is frequently in a comment following the
    418  1.1  christos 	 *    zone offset.
    419  1.1  christos 	 *
    420  1.1  christos 	 * 3) The range for the time is 00:00 to 23:60 (for a leep
    421  1.1  christos 	 *    second), but I have seen this violated (e.g., Date: Tue,
    422  1.1  christos 	 *    24 Oct 2006 24:07:58 +0400) making strptime() fail.
    423  1.1  christos 	 *    Thus we fall back on the headline time which was written
    424  1.1  christos 	 *    locally when the message was received.  Of course, this
    425  1.1  christos 	 *    is not the same time as in the Date field.
    426  1.1  christos 	 */
    427  1.1  christos 	if (use_hl_date == 0 &&
    428  1.1  christos 	    (date = hfield("date", mp)) != NULL &&
    429  1.1  christos 	    ((tail = strptime(date, " %a, %d %b %Y %T ", tm)) != NULL ||
    430  1.1  christos 	     (tail = strptime(date,     " %d %b %Y %T ", tm)) != NULL ||
    431  1.1  christos 	     (tail = strptime(date, " %a, %d %b %Y %R ", tm)) != NULL ||
    432  1.1  christos 	     (tail = strptime(date,     " %d %b %Y %R ", tm)) != NULL)) {
    433  1.1  christos 		char *cp;
    434  1.1  christos 		if ((cp = strchr(tail, '(')) != NULL)
    435  1.1  christos 			tm->tm_zone = get_comments(cp);
    436  1.1  christos 		else
    437  1.1  christos 			tm->tm_zone = NULL;
    438  1.1  christos 		gmtoff = skin(tail);
    439  1.1  christos 	}
    440  1.1  christos 	else {
    441  1.1  christos 		/*
    442  1.1  christos 		 * The BSD and System V headline date formats differ
    443  1.1  christos 		 * and each have an optional timezone field between
    444  1.1  christos 		 * the time and date (see head.c).  Unfortunately,
    445  1.1  christos 		 * strptime(3) doesn't know about timezone fields, so
    446  1.1  christos 		 * we have to handle it ourselves.
    447  1.1  christos 		 *
    448  1.1  christos 		 * char ctype[]        = "Aaa Aaa O0 00:00:00 0000";
    449  1.1  christos 		 * char tmztype[]      = "Aaa Aaa O0 00:00:00 AAA 0000";
    450  1.1  christos 		 * char SysV_ctype[]   = "Aaa Aaa O0 00:00 0000";
    451  1.1  christos 		 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
    452  1.1  christos 		 */
    453  1.1  christos 		struct headline hl;
    454  1.1  christos 		char headline[LINESIZE];
    455  1.1  christos 		char pbuf[BUFSIZ];
    456  1.1  christos 
    457  1.1  christos 		headline[0] = '\0';
    458  1.1  christos 		date = headline;
    459  1.1  christos 		(void)mail_readline(setinput(mp), headline, LINESIZE);
    460  1.1  christos 		parse(headline, &hl, pbuf);
    461  1.1  christos 		if (hl.l_date != NULL &&
    462  1.1  christos 		    (tail = my_strptime(hl.l_date, " %a %b %d %T ", tm)) == NULL &&
    463  1.1  christos 		    (tail = my_strptime(hl.l_date, " %a %b %d %R ", tm)) == NULL) {
    464  1.1  christos 			warnx("dateof: cannot determine date: %s", hl.l_date);
    465  1.1  christos 		}
    466  1.1  christos 	}
    467  1.1  christos 	/* tail will be NULL here if the mail file is empty, so don't
    468  1.1  christos 	 * check it. */
    469  1.1  christos 
    470  1.1  christos 	/* mark the zone and gmtoff info as invalid for strftime. */
    471  1.1  christos 	tm->tm_isdst = -1;
    472  1.1  christos 
    473  1.1  christos 	return gmtoff;
    474  1.1  christos }
    475  1.1  christos 
    476  1.1  christos /*
    477  1.1  christos  * Get the sender's address for display.  Let nameof() do this.
    478  1.1  christos  */
    479  1.1  christos static const char *
    480  1.1  christos addrof(struct message *mp)
    481  1.1  christos {
    482  1.1  christos 	if (mp == NULL)
    483  1.1  christos 		return NULL;
    484  1.1  christos 
    485  1.1  christos 	return nameof(mp, 0);
    486  1.1  christos }
    487  1.1  christos 
    488  1.1  christos /************************************************************************
    489  1.1  christos  * The 'address' syntax - from rfc 2822:
    490  1.1  christos  *
    491  1.1  christos  * specials        =       "(" / ")" /     ; Special characters used in
    492  1.1  christos  *                         "<" / ">" /     ;  other parts of the syntax
    493  1.1  christos  *                         "[" / "]" /
    494  1.1  christos  *                         ":" / ";" /
    495  1.1  christos  *                         "@" / "\" /
    496  1.1  christos  *                         "," / "." /
    497  1.1  christos  *                         DQUOTE
    498  1.1  christos  * qtext           =       NO-WS-CTL /     ; Non white space controls
    499  1.1  christos  *                         %d33 /          ; The rest of the US-ASCII
    500  1.1  christos  *                         %d35-91 /       ;  characters not including "\"
    501  1.1  christos  *                         %d93-126        ;  or the quote character
    502  1.1  christos  * qcontent        =       qtext / quoted-pair
    503  1.1  christos  * quoted-string   =       [CFWS]
    504  1.1  christos  *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
    505  1.1  christos  *                         [CFWS]
    506  1.1  christos  * atext           =       ALPHA / DIGIT / ; Any character except controls,
    507  1.1  christos  *                         "!" / "#" /     ;  SP, and specials.
    508  1.1  christos  *                         "$" / "%" /     ;  Used for atoms
    509  1.1  christos  *                         "&" / "'" /
    510  1.1  christos  *                         "*" / "+" /
    511  1.1  christos  *                         "-" / "/" /
    512  1.1  christos  *                         "=" / "?" /
    513  1.1  christos  *                         "^" / "_" /
    514  1.1  christos  *                         "`" / "{" /
    515  1.1  christos  *                         "|" / "}" /
    516  1.1  christos  *                         "~"
    517  1.1  christos  * atom            =       [CFWS] 1*atext [CFWS]
    518  1.1  christos  * word            =       atom / quoted-string
    519  1.1  christos  * phrase          =       1*word / obs-phrase
    520  1.1  christos  * display-name    =       phrase
    521  1.1  christos  * dtext           =       NO-WS-CTL /     ; Non white space controls
    522  1.1  christos  *                         %d33-90 /       ; The rest of the US-ASCII
    523  1.1  christos  *                         %d94-126        ;  characters not including "[",
    524  1.1  christos  *                                         ;  "]", or "\"
    525  1.1  christos  * dcontent        =       dtext / quoted-pair
    526  1.1  christos  * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
    527  1.1  christos  * domain          =       dot-atom / domain-literal / obs-domain
    528  1.1  christos  * local-part      =       dot-atom / quoted-string / obs-local-part
    529  1.1  christos  * addr-spec       =       local-part "@" domain
    530  1.1  christos  * angle-addr      =       [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
    531  1.1  christos  * name-addr       =       [display-name] angle-addr
    532  1.1  christos  * mailbox         =       name-addr / addr-spec
    533  1.1  christos  * mailbox-list    =       (mailbox *("," mailbox)) / obs-mbox-list
    534  1.1  christos  * group           =       display-name ":" [mailbox-list / CFWS] ";"
    535  1.1  christos  *                         [CFWS]
    536  1.1  christos  * address         =       mailbox / group
    537  1.1  christos  ************************************************************************/
    538  1.1  christos static char *
    539  1.1  christos get_display_name(char *name)
    540  1.1  christos {
    541  1.1  christos 	char nbuf[BUFSIZ];
    542  1.1  christos 	const char *p;
    543  1.1  christos 	char *q;
    544  1.1  christos 	char *qend;
    545  1.1  christos 	char *lastq;
    546  1.1  christos 	int quoted;
    547  1.1  christos 
    548  1.1  christos 	if (name == NULL)
    549  1.1  christos 		return(NULL);
    550  1.1  christos 
    551  1.1  christos 	q = nbuf;
    552  1.1  christos 	lastq = nbuf;
    553  1.1  christos 	qend = nbuf + sizeof(nbuf) - 1;	/* reserve space for '\0' */
    554  1.1  christos 	quoted = 0;
    555  1.1  christos 	for (p = skip_white(name); *p != '\0'; p++) {
    556  1.1  christos 		DEBUG(("get_display_name: %s\n", p));
    557  1.1  christos 		switch (*p) {
    558  1.1  christos 		case '"':	/* quoted-string */
    559  1.1  christos 			q = nbuf;
    560  1.1  christos 			p = snarf_quote(&q, qend, p);
    561  1.1  christos 			if (!quoted)
    562  1.1  christos 				lastq = q;
    563  1.1  christos 			quoted = 1;
    564  1.1  christos 			break;
    565  1.1  christos 
    566  1.1  christos 		case ':':	/* group */
    567  1.1  christos 		case '<':	/* angle-address */
    568  1.1  christos 			if (lastq == nbuf)
    569  1.1  christos 				return NULL;
    570  1.1  christos 			*lastq = '\0';	/* NULL termination */
    571  1.1  christos 			return(savestr(nbuf));
    572  1.1  christos 
    573  1.1  christos 		case '(':	/* comment - skip it! */
    574  1.1  christos 			p = snarf_comment(NULL, NULL, p);
    575  1.1  christos 			break;
    576  1.1  christos 
    577  1.1  christos 		default:
    578  1.1  christos 			if (!quoted && q < qend) {
    579  1.1  christos 				*q++ = *p;
    580  1.1  christos 				if (!isblank((unsigned char)*p)
    581  1.1  christos 				    /* && !is_specials((unsigned char)*p) */ )
    582  1.1  christos 					lastq = q;
    583  1.1  christos 			}
    584  1.1  christos 			break;
    585  1.1  christos 		}
    586  1.1  christos 	}
    587  1.1  christos 	return NULL;	/* no group or angle-address */
    588  1.1  christos }
    589  1.1  christos 
    590  1.1  christos /*
    591  1.1  christos  * See RFC 2822 sec 3.4 and 3.6.2.
    592  1.1  christos  */
    593  1.1  christos static const char *
    594  1.1  christos userof(struct message *mp)
    595  1.1  christos {
    596  1.1  christos 	char *sender;
    597  1.1  christos 	char *dispname;
    598  1.1  christos 
    599  1.1  christos 	if (mp == NULL)
    600  1.1  christos 		return NULL;
    601  1.1  christos 
    602  1.1  christos 	if ((sender = hfield("from", mp)) != NULL ||
    603  1.1  christos 	    (sender = hfield("sender", mp)) != NULL)
    604  1.1  christos 		/*
    605  1.1  christos 		 * Try to get the display-name.  If one doesn't exist,
    606  1.1  christos 		 * then the best we can hope for is that the user's
    607  1.1  christos 		 * name is in the comments.
    608  1.1  christos 		 */
    609  1.1  christos 		if ((dispname = get_display_name(sender)) != NULL ||
    610  1.1  christos 		    (dispname = get_comments(sender)) != NULL)
    611  1.1  christos 			return dispname;
    612  1.1  christos 	return NULL;
    613  1.1  christos }
    614  1.1  christos 
    615  1.1  christos /*
    616  1.1  christos  * Grab the subject line.
    617  1.1  christos  */
    618  1.1  christos static const char *
    619  1.1  christos subjof(struct message *mp)
    620  1.1  christos {
    621  1.1  christos 	const char *subj;
    622  1.1  christos 
    623  1.1  christos 	if (mp == NULL)
    624  1.1  christos 		return NULL;
    625  1.1  christos 
    626  1.1  christos 	if ((subj = hfield("subject", mp)) == NULL)
    627  1.1  christos 		subj = hfield("subj", mp);
    628  1.1  christos 	return subj;
    629  1.1  christos }
    630  1.1  christos 
    631  1.1  christos static char *
    632  1.1  christos preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
    633  1.1  christos {
    634  1.1  christos 	const char *gmtoff;
    635  1.1  christos 	const char *zone;
    636  1.1  christos 	const char *subj;
    637  1.1  christos 	const char *addr;
    638  1.1  christos 	const char *user;
    639  1.1  christos 	const char *p;
    640  1.1  christos 	char *q;
    641  1.1  christos 	char *newfmt;
    642  1.1  christos 	size_t fmtsize;
    643  1.1  christos 
    644  1.1  christos 	if (mp != NULL && (mp->m_flag & MDELETED) != 0)
    645  1.1  christos 		mp = NULL; /* deleted mail shouldn't show up! */
    646  1.1  christos 
    647  1.1  christos 	subj = subjof(mp);
    648  1.1  christos 	addr = addrof(mp);
    649  1.1  christos 	user = userof(mp);
    650  1.1  christos 	gmtoff = dateof(tm, mp, use_hl_date);
    651  1.1  christos 	zone = tm->tm_zone;
    652  1.1  christos 	fmtsize = LINESIZE;
    653  1.1  christos 	newfmt = malloc(fmtsize); /* so we can realloc() in check_bufsize() */
    654  1.1  christos 	q = newfmt;
    655  1.1  christos 	p = oldfmt;
    656  1.1  christos 	while (*p) {
    657  1.1  christos 		if (*p == '%') {
    658  1.1  christos 			const char *fp;
    659  1.1  christos 			fp = subformat(&p, mp, addr, user, subj, gmtoff, zone);
    660  1.1  christos 			if (fp) {
    661  1.1  christos 				size_t len;
    662  1.1  christos 				len = strlen(fp);
    663  1.1  christos 				check_bufsize(&newfmt, &fmtsize, &q, len);
    664  1.1  christos 				(void)strcpy(q, fp);
    665  1.1  christos 				q += len;
    666  1.1  christos 				continue;
    667  1.1  christos 			}
    668  1.1  christos 		}
    669  1.1  christos 		check_bufsize(&newfmt, &fmtsize, &q, 1);
    670  1.1  christos 		*q++ = *p++;
    671  1.1  christos 	}
    672  1.1  christos 	*q = '\0';
    673  1.1  christos 
    674  1.1  christos 	return newfmt;
    675  1.1  christos }
    676  1.1  christos 
    677  1.1  christos 
    678  1.1  christos /*
    679  1.1  christos  * If a format string begins with the USE_HL_DATE string, smsgprintf
    680  1.1  christos  * will use the headerline date rather than trying to extract the date
    681  1.1  christos  * from the Date field.
    682  1.1  christos  *
    683  1.1  christos  * Note: If a 'valid' date cannot be extracted from the Date field,
    684  1.1  christos  * then the headline date is used.
    685  1.1  christos  */
    686  1.1  christos #define USE_HL_DATE "%??"
    687  1.1  christos 
    688  1.1  christos PUBLIC char *
    689  1.1  christos smsgprintf(const char *fmtstr, struct message *mp)
    690  1.1  christos {
    691  1.1  christos 	struct tm tm;
    692  1.1  christos 	int use_hl_date;
    693  1.1  christos 	char *newfmt;
    694  1.1  christos 	char *buf;
    695  1.1  christos 	size_t bufsize;
    696  1.1  christos 
    697  1.1  christos 	if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
    698  1.1  christos 		use_hl_date = 0;
    699  1.1  christos 	else {
    700  1.1  christos 		use_hl_date = 1;
    701  1.1  christos 		fmtstr += sizeof(USE_HL_DATE) - 1;
    702  1.1  christos 	}
    703  1.1  christos 	bufsize = LINESIZE;
    704  1.1  christos 	buf = salloc(bufsize);
    705  1.1  christos 	newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
    706  1.1  christos 	(void)strftime(buf, bufsize, newfmt, &tm);
    707  1.1  christos 	free(newfmt);	/* preformat() uses malloc()/realloc() */
    708  1.1  christos 	return buf;
    709  1.1  christos }
    710  1.1  christos 
    711  1.1  christos 
    712  1.1  christos PUBLIC void
    713  1.1  christos fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
    714  1.1  christos {
    715  1.1  christos 	char *buf;
    716  1.1  christos 
    717  1.1  christos 	buf = smsgprintf(fmtstr, mp);
    718  1.1  christos 	(void)fprintf(fo, "%s\n", buf);	/* XXX - add the newline here? */
    719  1.1  christos }
    720