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