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