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