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