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