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