format.c revision 1.5 1 1.5 christos /* $NetBSD: format.c,v 1.5 2007/07/06 20:14:33 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.5 christos __RCSID("$NetBSD: format.c,v 1.5 2007/07/06 20:14:33 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.1 christos
55 1.1 christos #define DEBUG(a)
56 1.1 christos
57 1.1 christos static void
58 1.1 christos check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt)
59 1.1 christos {
60 1.1 christos char *q;
61 1.1 christos if (*p + cnt < *buf + *bufsize)
62 1.1 christos return;
63 1.1 christos *bufsize *= 2;
64 1.1 christos q = realloc(*buf, *bufsize);
65 1.1 christos *p = q + (*p - *buf);
66 1.1 christos *buf = q;
67 1.1 christos }
68 1.1 christos
69 1.1 christos static const char *
70 1.1 christos sfmtoff(const char **fmtbeg, const char *fmtch, off_t off)
71 1.1 christos {
72 1.1 christos char *newfmt; /* pointer to new format string */
73 1.1 christos size_t len; /* space for "lld" including '\0' */
74 1.2 christos char *p;
75 1.2 christos
76 1.1 christos len = fmtch - *fmtbeg + sizeof(PRId64);
77 1.1 christos newfmt = salloc(len);
78 1.1 christos (void)strlcpy(newfmt, *fmtbeg, len - sizeof(sizeof(PRId64)) + 1);
79 1.1 christos (void)strlcat(newfmt, PRId64, len);
80 1.1 christos *fmtbeg = fmtch + 1;
81 1.2 christos (void)sasprintf(&p, newfmt, off);
82 1.2 christos return p;
83 1.1 christos }
84 1.1 christos
85 1.1 christos static const char *
86 1.1 christos sfmtint(const char **fmtbeg, const char *fmtch, int num)
87 1.1 christos {
88 1.1 christos char *newfmt;
89 1.1 christos size_t len;
90 1.2 christos char *p;
91 1.1 christos
92 1.1 christos len = fmtch - *fmtbeg + 2; /* space for 'd' and '\0' */
93 1.1 christos newfmt = salloc(len);
94 1.1 christos (void)strlcpy(newfmt, *fmtbeg, len);
95 1.1 christos newfmt[len-2] = 'd'; /* convert to printf format */
96 1.1 christos *fmtbeg = fmtch + 1;
97 1.2 christos (void)sasprintf(&p, newfmt, num);
98 1.2 christos return p;
99 1.1 christos }
100 1.1 christos
101 1.1 christos static const char *
102 1.1 christos sfmtstr(const char **fmtbeg, const char *fmtch, const char *str)
103 1.1 christos {
104 1.1 christos char *newfmt;
105 1.1 christos size_t len;
106 1.2 christos char *p;
107 1.1 christos
108 1.1 christos len = fmtch - *fmtbeg + 2; /* space for 's' and '\0' */
109 1.1 christos newfmt = salloc(len);
110 1.1 christos (void)strlcpy(newfmt, *fmtbeg, len);
111 1.1 christos newfmt[len-2] = 's'; /* convert to printf format */
112 1.1 christos *fmtbeg = fmtch + 1;
113 1.2 christos (void)sasprintf(&p, newfmt, str ? str : "");
114 1.2 christos return p;
115 1.2 christos }
116 1.2 christos
117 1.2 christos #ifdef THREAD_SUPPORT
118 1.2 christos static char*
119 1.2 christos sfmtdepth(char *str, int depth)
120 1.2 christos {
121 1.2 christos char *p;
122 1.2 christos if (*str == '\0') {
123 1.2 christos (void)sasprintf(&p, "%d", depth);
124 1.2 christos return p;
125 1.2 christos }
126 1.2 christos p = __UNCONST("");
127 1.2 christos for (/*EMPTY*/; depth > 0; depth--)
128 1.2 christos (void)sasprintf(&p, "%s%s", p, str);
129 1.2 christos return p;
130 1.1 christos }
131 1.2 christos #endif
132 1.1 christos
133 1.1 christos static const char *
134 1.1 christos sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp)
135 1.1 christos {
136 1.1 christos char *q;
137 1.1 christos q = strchr(fmtch + 1, '?');
138 1.1 christos if (q) {
139 1.1 christos size_t len;
140 1.1 christos char *p;
141 1.1 christos const char *str;
142 1.1 christos int skin_it;
143 1.2 christos #ifdef THREAD_SUPPORT
144 1.2 christos int depth;
145 1.2 christos #endif
146 1.2 christos if (mp == NULL) {
147 1.2 christos *fmtbeg = q + 1;
148 1.2 christos return NULL;
149 1.2 christos }
150 1.2 christos #ifdef THREAD_SUPPORT
151 1.2 christos depth = mp->m_depth;
152 1.2 christos #endif
153 1.2 christos skin_it = 0;
154 1.2 christos switch (fmtch[1]) { /* check the '?' modifier */
155 1.2 christos #ifdef THREAD_SUPPORT
156 1.2 christos case '&': /* use the relative depth */
157 1.2 christos depth -= thread_depth();
158 1.2 christos /* FALLTHROUGH */
159 1.2 christos case '*': /* use the absolute depth */
160 1.2 christos len = q - fmtch - 1;
161 1.2 christos p = salloc(len);
162 1.2 christos (void)strlcpy(p, fmtch + 2, len);
163 1.2 christos p = sfmtdepth(p, depth);
164 1.2 christos break;
165 1.2 christos #endif
166 1.2 christos case '-':
167 1.2 christos skin_it = 1;
168 1.2 christos /* FALLTHROUGH */
169 1.2 christos default:
170 1.2 christos len = q - fmtch - skin_it;
171 1.2 christos p = salloc(len);
172 1.2 christos (void)strlcpy(p, fmtch + skin_it + 1, len);
173 1.2 christos p = hfield(p, mp);
174 1.2 christos if (skin_it)
175 1.2 christos p = skin(p);
176 1.2 christos break;
177 1.2 christos }
178 1.2 christos str = sfmtstr(fmtbeg, fmtch, p);
179 1.1 christos *fmtbeg = q + 1;
180 1.1 christos return str;
181 1.1 christos }
182 1.1 christos return NULL;
183 1.1 christos }
184 1.1 christos
185 1.2 christos struct flags_s {
186 1.2 christos int f_and;
187 1.2 christos int f_or;
188 1.2 christos int f_new; /* some message in the thread is new */
189 1.2 christos int f_unread; /* some message in the thread is unread */
190 1.2 christos };
191 1.2 christos
192 1.2 christos static void
193 1.2 christos get_and_or_flags(struct message *mp, struct flags_s *flags)
194 1.2 christos {
195 1.2 christos for (/*EMPTY*/; mp; mp = mp->m_flink) {
196 1.2 christos flags->f_and &= mp->m_flag;
197 1.2 christos flags->f_or |= mp->m_flag;
198 1.2 christos flags->f_new |= (mp->m_flag & (MREAD|MNEW)) == MNEW;
199 1.2 christos flags->f_unread |= (mp->m_flag & (MREAD|MNEW)) == 0;
200 1.2 christos get_and_or_flags(mp->m_clink, flags);
201 1.2 christos }
202 1.2 christos }
203 1.2 christos
204 1.1 christos static const char *
205 1.2 christos sfmtflag(const char **fmtbeg, const char *fmtch, struct message *mp)
206 1.1 christos {
207 1.1 christos char disp[2];
208 1.2 christos struct flags_s flags;
209 1.2 christos int is_thread;
210 1.2 christos
211 1.2 christos if (mp == NULL)
212 1.2 christos return NULL;
213 1.2 christos
214 1.2 christos is_thread = mp->m_clink != NULL;
215 1.2 christos disp[0] = is_thread ? '+' : ' ';
216 1.1 christos disp[1] = '\0';
217 1.2 christos
218 1.2 christos flags.f_and = mp->m_flag;
219 1.2 christos flags.f_or = mp->m_flag;
220 1.2 christos flags.f_new = 0;
221 1.2 christos flags.f_unread = 0;
222 1.2 christos #ifdef THREAD_SUPPORT
223 1.2 christos if (thread_hidden())
224 1.2 christos get_and_or_flags(mp->m_clink, &flags);
225 1.2 christos #endif
226 1.2 christos
227 1.2 christos if (flags.f_or & MTAGGED)
228 1.2 christos disp[0] = 't';
229 1.2 christos if (flags.f_and & MTAGGED)
230 1.2 christos disp[0] = 'T';
231 1.2 christos
232 1.2 christos if (flags.f_or & MMODIFY)
233 1.2 christos disp[0] = 'e';
234 1.2 christos if (flags.f_and & MMODIFY)
235 1.2 christos disp[0] = 'E';
236 1.2 christos
237 1.2 christos if (flags.f_or & MSAVED)
238 1.2 christos disp[0] = '&';
239 1.2 christos if (flags.f_and & MSAVED)
240 1.1 christos disp[0] = '*';
241 1.2 christos
242 1.2 christos if (flags.f_or & MPRESERVE)
243 1.2 christos disp[0] = 'p';
244 1.2 christos if (flags.f_and & MPRESERVE)
245 1.1 christos disp[0] = 'P';
246 1.2 christos
247 1.2 christos if (flags.f_unread)
248 1.2 christos disp[0] = 'u';
249 1.2 christos if ((flags.f_or & (MREAD|MNEW)) == 0)
250 1.2 christos disp[0] = 'U';
251 1.2 christos
252 1.2 christos if (flags.f_new)
253 1.2 christos disp[0] = 'n';
254 1.2 christos if ((flags.f_and & (MREAD|MNEW)) == MNEW)
255 1.1 christos disp[0] = 'N';
256 1.2 christos
257 1.2 christos if (flags.f_or & MBOX)
258 1.2 christos disp[0] = 'm';
259 1.2 christos if (flags.f_and & MBOX)
260 1.1 christos disp[0] = 'M';
261 1.2 christos
262 1.1 christos return sfmtstr(fmtbeg, fmtch, disp);
263 1.1 christos }
264 1.1 christos
265 1.1 christos static const char *
266 1.1 christos login_name(const char *addr)
267 1.1 christos {
268 1.1 christos char *p;
269 1.1 christos p = strchr(addr, '@');
270 1.1 christos if (p) {
271 1.1 christos char *q;
272 1.1 christos size_t len;
273 1.1 christos len = p - addr + 1;
274 1.1 christos q = salloc(len);
275 1.1 christos (void)strlcpy(q, addr, len);
276 1.1 christos return q;
277 1.1 christos }
278 1.1 christos return addr;
279 1.1 christos }
280 1.1 christos
281 1.2 christos /*
282 1.2 christos * A simple routine to get around a lint warning.
283 1.2 christos */
284 1.2 christos static inline const char *
285 1.2 christos skip_fmt(const char **src, const char *p)
286 1.2 christos {
287 1.2 christos *src = p;
288 1.2 christos return NULL;
289 1.2 christos }
290 1.2 christos
291 1.1 christos static const char *
292 1.1 christos subformat(const char **src, struct message *mp, const char *addr,
293 1.1 christos const char *user, const char *subj, const char *gmtoff, const char *zone)
294 1.1 christos {
295 1.2 christos #if 0
296 1.2 christos /* XXX - lint doesn't like this, hence skip_fmt(). */
297 1.2 christos #define MP(a) mp ? a : (*src = (p + 1), NULL)
298 1.2 christos #else
299 1.2 christos #define MP(a) mp ? a : skip_fmt(src, p + 1);
300 1.2 christos #endif
301 1.1 christos const char *p;
302 1.1 christos
303 1.1 christos p = *src;
304 1.1 christos if (p[1] == '%') {
305 1.1 christos *src += 2;
306 1.1 christos return "%%";
307 1.1 christos }
308 1.1 christos for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++)
309 1.1 christos continue;
310 1.1 christos
311 1.1 christos switch (*p) {
312 1.1 christos case '?':
313 1.2 christos return sfmtfield(src, p, mp);
314 1.1 christos case 'J':
315 1.2 christos return MP(sfmtint(src, p, (int)(mp->m_lines - mp->m_blines)));
316 1.1 christos case 'K':
317 1.1 christos return MP(sfmtint(src, p, (int)mp->m_blines));
318 1.1 christos case 'L':
319 1.1 christos return MP(sfmtint(src, p, (int)mp->m_lines));
320 1.1 christos case 'N':
321 1.1 christos return sfmtstr(src, p, user);
322 1.1 christos case 'O':
323 1.1 christos return MP(sfmtoff(src, p, mp->m_size));
324 1.1 christos case 'P':
325 1.1 christos return MP(sfmtstr(src, p, mp == dot ? ">" : " "));
326 1.1 christos case 'Q':
327 1.2 christos return MP(sfmtflag(src, p, mp));
328 1.1 christos case 'Z':
329 1.1 christos *src = p + 1;
330 1.1 christos return zone;
331 1.1 christos case 'f':
332 1.1 christos return sfmtstr(src, p, addr);
333 1.1 christos case 'i':
334 1.2 christos return sfmtint(src, p, get_msgnum(mp)); /* '0' if mp == NULL */
335 1.1 christos case 'n':
336 1.1 christos return sfmtstr(src, p, login_name(addr));
337 1.1 christos case 'q':
338 1.1 christos return sfmtstr(src, p, subj);
339 1.1 christos case 't':
340 1.2 christos return sfmtint(src, p, get_msgCount());
341 1.1 christos case 'z':
342 1.1 christos *src = p + 1;
343 1.1 christos return gmtoff;
344 1.1 christos default:
345 1.1 christos return NULL;
346 1.1 christos }
347 1.1 christos #undef MP
348 1.1 christos }
349 1.1 christos
350 1.1 christos static const char *
351 1.1 christos snarf_comment(char **buf, char *bufend, const char *string)
352 1.1 christos {
353 1.1 christos const char *p;
354 1.1 christos char *q;
355 1.1 christos char *qend;
356 1.1 christos int clevel;
357 1.1 christos
358 1.1 christos q = buf ? *buf : NULL;
359 1.1 christos qend = buf ? bufend : NULL;
360 1.1 christos
361 1.1 christos clevel = 1;
362 1.1 christos for (p = string + 1; *p != '\0'; p++) {
363 1.1 christos DEBUG(("snarf_comment: %s\n", p));
364 1.1 christos if (*p == '(') {
365 1.1 christos clevel++;
366 1.1 christos continue;
367 1.1 christos }
368 1.1 christos if (*p == ')') {
369 1.1 christos if (--clevel == 0)
370 1.1 christos break;
371 1.1 christos continue;
372 1.1 christos }
373 1.1 christos if (*p == '\\' && p[1] != 0)
374 1.1 christos p++;
375 1.1 christos
376 1.1 christos if (q < qend)
377 1.1 christos *q++ = *p;
378 1.1 christos }
379 1.1 christos if (buf) {
380 1.1 christos *q = '\0';
381 1.1 christos DEBUG(("snarf_comment: terminating: %s\n", *buf));
382 1.1 christos *buf = q;
383 1.1 christos }
384 1.1 christos if (*p == '\0')
385 1.1 christos p--;
386 1.1 christos return p;
387 1.1 christos }
388 1.1 christos
389 1.1 christos static const char *
390 1.1 christos snarf_quote(char **buf, char *bufend, const char *string)
391 1.1 christos {
392 1.1 christos const char *p;
393 1.1 christos char *q;
394 1.1 christos char *qend;
395 1.1 christos
396 1.1 christos q = buf ? *buf : NULL;
397 1.1 christos qend = buf ? bufend : NULL;
398 1.1 christos
399 1.1 christos for (p = string + 1; *p != '\0' && *p != '"'; p++) {
400 1.1 christos DEBUG(("snarf_quote: %s\n", p));
401 1.1 christos if (*p == '\\' && p[1] != '\0')
402 1.1 christos p++;
403 1.1 christos
404 1.1 christos if (q < qend)
405 1.1 christos *q++ = *p;
406 1.1 christos }
407 1.1 christos if (buf) {
408 1.1 christos *q = '\0';
409 1.1 christos DEBUG(("snarf_quote: terminating: %s\n", *buf));
410 1.1 christos *buf = q;
411 1.1 christos }
412 1.1 christos if (*p == '\0')
413 1.1 christos p--;
414 1.1 christos return p;
415 1.1 christos }
416 1.1 christos
417 1.1 christos /*
418 1.1 christos * Grab the comments, separating each by a space.
419 1.1 christos */
420 1.1 christos static char *
421 1.1 christos get_comments(char *name)
422 1.1 christos {
423 1.2 christos char nbuf[LINESIZE];
424 1.1 christos const char *p;
425 1.1 christos char *qend;
426 1.1 christos char *q;
427 1.1 christos char *lastq;
428 1.1 christos
429 1.1 christos if (name == NULL)
430 1.2 christos return NULL;
431 1.1 christos
432 1.1 christos p = name;
433 1.1 christos q = nbuf;
434 1.1 christos lastq = nbuf;
435 1.1 christos qend = nbuf + sizeof(nbuf) - 1;
436 1.2 christos for (p = skip_blank(name); *p != '\0'; p++) {
437 1.1 christos DEBUG(("get_comments: %s\n", p));
438 1.1 christos switch (*p) {
439 1.1 christos case '"': /* quoted-string ... skip it! */
440 1.1 christos p = snarf_quote(NULL, NULL, p);
441 1.1 christos break;
442 1.1 christos
443 1.1 christos case '(':
444 1.1 christos p = snarf_comment(&q, qend, p);
445 1.1 christos lastq = q;
446 1.1 christos if (q < qend) /* separate comments by space */
447 1.1 christos *q++ = ' ';
448 1.1 christos break;
449 1.1 christos
450 1.1 christos default:
451 1.1 christos break;
452 1.1 christos }
453 1.1 christos }
454 1.1 christos *lastq = '\0';
455 1.1 christos return savestr(nbuf);
456 1.1 christos }
457 1.1 christos
458 1.1 christos static char *
459 1.1 christos my_strptime(const char *buf, const char *fmtstr, struct tm *tm)
460 1.1 christos {
461 1.1 christos char *tail;
462 1.1 christos char zone[4];
463 1.1 christos
464 1.1 christos zone[0] = '\0';
465 1.1 christos tail = strptime(buf, fmtstr, tm);
466 1.1 christos if (tail) {
467 1.1 christos int len;
468 1.1 christos if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) {
469 1.1 christos if (zone[0])
470 1.1 christos tm->tm_zone = savestr(zone);
471 1.1 christos tail += len;
472 1.1 christos }
473 1.1 christos tail = strptime(tail, " %Y ", tm);
474 1.1 christos }
475 1.1 christos return tail;
476 1.1 christos }
477 1.1 christos
478 1.3 christos static char *
479 1.3 christos mk_gmtoff(struct tm *tm)
480 1.3 christos {
481 1.3 christos char *gmtoff;
482 1.3 christos char sign;
483 1.3 christos int offset;
484 1.3 christos int hour;
485 1.3 christos int min;
486 1.3 christos
487 1.3 christos offset = tm->tm_gmtoff / 60;
488 1.3 christos sign = offset < 0 ? '-' : '+';
489 1.3 christos if (offset < 0)
490 1.3 christos offset = -offset;
491 1.3 christos offset %= 24 * 60;
492 1.3 christos min = offset % 60;
493 1.3 christos hour = offset / 60;
494 1.3 christos (void)sasprintf(&gmtoff, "%c%02d%02d", sign, hour, min);
495 1.3 christos return gmtoff;
496 1.3 christos }
497 1.3 christos
498 1.1 christos /*
499 1.1 christos * Get the date and time info from the "Date:" line, parse it into a
500 1.1 christos * tm structure as much as possible.
501 1.1 christos *
502 1.1 christos * Note: We return the gmtoff as a string as "-0000" has special
503 1.1 christos * meaning. See RFC 2822, sec 3.3.
504 1.1 christos */
505 1.2 christos PUBLIC const char *
506 1.1 christos dateof(struct tm *tm, struct message *mp, int use_hl_date)
507 1.1 christos {
508 1.3 christos static int tzinit = 0;
509 1.1 christos char *tail;
510 1.1 christos char *gmtoff;
511 1.1 christos const char *date;
512 1.1 christos
513 1.1 christos (void)memset(tm, 0, sizeof(*tm));
514 1.3 christos tm->tm_isdst = -1;
515 1.1 christos
516 1.3 christos /* Make sure the time zone info is initialized. */
517 1.3 christos if (!tzinit) {
518 1.3 christos tzinit = 1;
519 1.3 christos tzset();
520 1.3 christos }
521 1.1 christos if (mp == NULL) { /* use local time */
522 1.1 christos time_t now;
523 1.1 christos (void)time(&now);
524 1.1 christos (void)localtime_r(&now, tm);
525 1.3 christos return mk_gmtoff(tm);
526 1.1 christos }
527 1.1 christos gmtoff = NULL;
528 1.1 christos tail = NULL;
529 1.1 christos /*
530 1.1 christos * See RFC 2822 sec 3.3 for date-time format used in
531 1.1 christos * the "Date:" field.
532 1.1 christos *
533 1.2 christos * Date: Tue, 21 Mar 2006 20:45:30 -0500
534 1.2 christos *
535 1.1 christos * Notes:
536 1.1 christos * 1) The 'day-of-week' and 'second' fields are optional so we
537 1.1 christos * check 4 possibilities. This could be optimized.
538 1.1 christos *
539 1.1 christos * 2) The timezone is frequently in a comment following the
540 1.1 christos * zone offset.
541 1.1 christos *
542 1.1 christos * 3) The range for the time is 00:00 to 23:60 (for a leep
543 1.1 christos * second), but I have seen this violated (e.g., Date: Tue,
544 1.1 christos * 24 Oct 2006 24:07:58 +0400) making strptime() fail.
545 1.1 christos * Thus we fall back on the headline time which was written
546 1.1 christos * locally when the message was received. Of course, this
547 1.1 christos * is not the same time as in the Date field.
548 1.1 christos */
549 1.1 christos if (use_hl_date == 0 &&
550 1.1 christos (date = hfield("date", mp)) != NULL &&
551 1.1 christos ((tail = strptime(date, " %a, %d %b %Y %T ", tm)) != NULL ||
552 1.1 christos (tail = strptime(date, " %d %b %Y %T ", tm)) != NULL ||
553 1.1 christos (tail = strptime(date, " %a, %d %b %Y %R ", tm)) != NULL ||
554 1.1 christos (tail = strptime(date, " %d %b %Y %R ", tm)) != NULL)) {
555 1.2 christos int hour;
556 1.2 christos int min;
557 1.4 christos char sign[2];
558 1.1 christos char *cp;
559 1.2 christos
560 1.1 christos if ((cp = strchr(tail, '(')) != NULL)
561 1.1 christos tm->tm_zone = get_comments(cp);
562 1.1 christos else
563 1.1 christos tm->tm_zone = NULL;
564 1.1 christos gmtoff = skin(tail);
565 1.2 christos
566 1.2 christos /*
567 1.2 christos * Scan the gmtoff and use it to convert the time to a
568 1.2 christos * local time.
569 1.2 christos *
570 1.3 christos * Note: "-0000" means no valid zone info. See
571 1.3 christos * RFC 2822, sec 3.3.
572 1.3 christos *
573 1.2 christos * XXX - This is painful! Is there a better way?
574 1.2 christos */
575 1.3 christos if (strcmp(gmtoff, "-0000") != 0 &&
576 1.4 christos sscanf(gmtoff, " %1[+-]%2d%2d ", sign, &hour, &min) == 3) {
577 1.2 christos time_t otime;
578 1.4 christos if (sign[0] == '-') {
579 1.2 christos tm->tm_hour += hour;
580 1.2 christos tm->tm_min += min;
581 1.2 christos }
582 1.2 christos else {
583 1.2 christos tm->tm_hour -= hour;
584 1.2 christos tm->tm_min -= min;
585 1.2 christos }
586 1.3 christos tm->tm_isdst = -1;
587 1.2 christos if ((time_t)(otime = timegm(tm)) == -1)
588 1.2 christos warn("timegm: %s", date);
589 1.3 christos
590 1.2 christos if(localtime_r(&otime, tm) == NULL)
591 1.2 christos warn("localtime: %s", date);
592 1.3 christos
593 1.3 christos /* extract the new gmtoff string */
594 1.3 christos gmtoff = mk_gmtoff(tm);
595 1.2 christos }
596 1.2 christos else
597 1.2 christos tm->tm_gmtoff = 0;
598 1.1 christos }
599 1.1 christos else {
600 1.1 christos /*
601 1.1 christos * The BSD and System V headline date formats differ
602 1.1 christos * and each have an optional timezone field between
603 1.1 christos * the time and date (see head.c). Unfortunately,
604 1.1 christos * strptime(3) doesn't know about timezone fields, so
605 1.1 christos * we have to handle it ourselves.
606 1.1 christos *
607 1.1 christos * char ctype[] = "Aaa Aaa O0 00:00:00 0000";
608 1.1 christos * char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000";
609 1.1 christos * char SysV_ctype[] = "Aaa Aaa O0 00:00 0000";
610 1.1 christos * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
611 1.1 christos */
612 1.1 christos struct headline hl;
613 1.1 christos char headline[LINESIZE];
614 1.2 christos char pbuf[LINESIZE];
615 1.1 christos
616 1.1 christos headline[0] = '\0';
617 1.1 christos date = headline;
618 1.2 christos (void)mail_readline(setinput(mp), headline, sizeof(headline));
619 1.1 christos parse(headline, &hl, pbuf);
620 1.3 christos if (hl.l_date != NULL) {
621 1.3 christos if ((tail = my_strptime(hl.l_date, " %a %b %d %T ", tm)) == NULL &&
622 1.3 christos (tail = my_strptime(hl.l_date, " %a %b %d %R ", tm)) == NULL)
623 1.3 christos warnx("dateof: cannot determine date: %s", hl.l_date);
624 1.3 christos else {
625 1.3 christos tm->tm_isdst = -1;
626 1.3 christos if(mktime(tm) == -1)
627 1.3 christos warn("mktime: %s", date);
628 1.3 christos
629 1.3 christos /* extract the gmtoff string */
630 1.3 christos gmtoff = mk_gmtoff(tm);
631 1.3 christos }
632 1.1 christos }
633 1.1 christos }
634 1.3 christos /* 'tail' will be NULL here if the mail file is empty, so
635 1.3 christos * don't check it with an assert(). */
636 1.2 christos
637 1.1 christos return gmtoff;
638 1.1 christos }
639 1.1 christos
640 1.1 christos /*
641 1.1 christos * Get the sender's address for display. Let nameof() do this.
642 1.1 christos */
643 1.1 christos static const char *
644 1.1 christos addrof(struct message *mp)
645 1.1 christos {
646 1.1 christos if (mp == NULL)
647 1.1 christos return NULL;
648 1.1 christos
649 1.1 christos return nameof(mp, 0);
650 1.1 christos }
651 1.1 christos
652 1.1 christos /************************************************************************
653 1.1 christos * The 'address' syntax - from rfc 2822:
654 1.1 christos *
655 1.1 christos * specials = "(" / ")" / ; Special characters used in
656 1.1 christos * "<" / ">" / ; other parts of the syntax
657 1.1 christos * "[" / "]" /
658 1.1 christos * ":" / ";" /
659 1.1 christos * "@" / "\" /
660 1.1 christos * "," / "." /
661 1.1 christos * DQUOTE
662 1.1 christos * qtext = NO-WS-CTL / ; Non white space controls
663 1.1 christos * %d33 / ; The rest of the US-ASCII
664 1.1 christos * %d35-91 / ; characters not including "\"
665 1.1 christos * %d93-126 ; or the quote character
666 1.1 christos * qcontent = qtext / quoted-pair
667 1.1 christos * quoted-string = [CFWS]
668 1.1 christos * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
669 1.1 christos * [CFWS]
670 1.1 christos * atext = ALPHA / DIGIT / ; Any character except controls,
671 1.1 christos * "!" / "#" / ; SP, and specials.
672 1.1 christos * "$" / "%" / ; Used for atoms
673 1.1 christos * "&" / "'" /
674 1.1 christos * "*" / "+" /
675 1.1 christos * "-" / "/" /
676 1.1 christos * "=" / "?" /
677 1.1 christos * "^" / "_" /
678 1.1 christos * "`" / "{" /
679 1.1 christos * "|" / "}" /
680 1.1 christos * "~"
681 1.1 christos * atom = [CFWS] 1*atext [CFWS]
682 1.1 christos * word = atom / quoted-string
683 1.1 christos * phrase = 1*word / obs-phrase
684 1.1 christos * display-name = phrase
685 1.1 christos * dtext = NO-WS-CTL / ; Non white space controls
686 1.1 christos * %d33-90 / ; The rest of the US-ASCII
687 1.1 christos * %d94-126 ; characters not including "[",
688 1.1 christos * ; "]", or "\"
689 1.1 christos * dcontent = dtext / quoted-pair
690 1.1 christos * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
691 1.1 christos * domain = dot-atom / domain-literal / obs-domain
692 1.1 christos * local-part = dot-atom / quoted-string / obs-local-part
693 1.1 christos * addr-spec = local-part "@" domain
694 1.1 christos * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
695 1.1 christos * name-addr = [display-name] angle-addr
696 1.1 christos * mailbox = name-addr / addr-spec
697 1.1 christos * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
698 1.1 christos * group = display-name ":" [mailbox-list / CFWS] ";"
699 1.1 christos * [CFWS]
700 1.1 christos * address = mailbox / group
701 1.1 christos ************************************************************************/
702 1.1 christos static char *
703 1.1 christos get_display_name(char *name)
704 1.1 christos {
705 1.2 christos char nbuf[LINESIZE];
706 1.1 christos const char *p;
707 1.1 christos char *q;
708 1.1 christos char *qend;
709 1.1 christos char *lastq;
710 1.1 christos int quoted;
711 1.1 christos
712 1.1 christos if (name == NULL)
713 1.2 christos return NULL;
714 1.1 christos
715 1.1 christos q = nbuf;
716 1.1 christos lastq = nbuf;
717 1.1 christos qend = nbuf + sizeof(nbuf) - 1; /* reserve space for '\0' */
718 1.1 christos quoted = 0;
719 1.2 christos for (p = skip_blank(name); *p != '\0'; p++) {
720 1.1 christos DEBUG(("get_display_name: %s\n", p));
721 1.1 christos switch (*p) {
722 1.1 christos case '"': /* quoted-string */
723 1.1 christos q = nbuf;
724 1.1 christos p = snarf_quote(&q, qend, p);
725 1.1 christos if (!quoted)
726 1.1 christos lastq = q;
727 1.1 christos quoted = 1;
728 1.1 christos break;
729 1.1 christos
730 1.1 christos case ':': /* group */
731 1.1 christos case '<': /* angle-address */
732 1.1 christos if (lastq == nbuf)
733 1.1 christos return NULL;
734 1.1 christos *lastq = '\0'; /* NULL termination */
735 1.2 christos return savestr(nbuf);
736 1.1 christos
737 1.1 christos case '(': /* comment - skip it! */
738 1.1 christos p = snarf_comment(NULL, NULL, p);
739 1.1 christos break;
740 1.1 christos
741 1.1 christos default:
742 1.1 christos if (!quoted && q < qend) {
743 1.1 christos *q++ = *p;
744 1.1 christos if (!isblank((unsigned char)*p)
745 1.1 christos /* && !is_specials((unsigned char)*p) */ )
746 1.1 christos lastq = q;
747 1.1 christos }
748 1.1 christos break;
749 1.1 christos }
750 1.1 christos }
751 1.1 christos return NULL; /* no group or angle-address */
752 1.1 christos }
753 1.1 christos
754 1.1 christos /*
755 1.1 christos * See RFC 2822 sec 3.4 and 3.6.2.
756 1.1 christos */
757 1.1 christos static const char *
758 1.1 christos userof(struct message *mp)
759 1.1 christos {
760 1.1 christos char *sender;
761 1.1 christos char *dispname;
762 1.1 christos
763 1.1 christos if (mp == NULL)
764 1.1 christos return NULL;
765 1.1 christos
766 1.1 christos if ((sender = hfield("from", mp)) != NULL ||
767 1.1 christos (sender = hfield("sender", mp)) != NULL)
768 1.1 christos /*
769 1.1 christos * Try to get the display-name. If one doesn't exist,
770 1.1 christos * then the best we can hope for is that the user's
771 1.1 christos * name is in the comments.
772 1.1 christos */
773 1.1 christos if ((dispname = get_display_name(sender)) != NULL ||
774 1.1 christos (dispname = get_comments(sender)) != NULL)
775 1.1 christos return dispname;
776 1.1 christos return NULL;
777 1.1 christos }
778 1.1 christos
779 1.1 christos /*
780 1.1 christos * Grab the subject line.
781 1.1 christos */
782 1.1 christos static const char *
783 1.1 christos subjof(struct message *mp)
784 1.1 christos {
785 1.1 christos const char *subj;
786 1.1 christos
787 1.1 christos if (mp == NULL)
788 1.1 christos return NULL;
789 1.1 christos
790 1.1 christos if ((subj = hfield("subject", mp)) == NULL)
791 1.1 christos subj = hfield("subj", mp);
792 1.1 christos return subj;
793 1.1 christos }
794 1.1 christos
795 1.5 christos /*
796 1.5 christos * Protect a string against strftime() conversion.
797 1.5 christos */
798 1.5 christos static const char*
799 1.5 christos protect(const char *str)
800 1.5 christos {
801 1.5 christos char *p, *q;
802 1.5 christos size_t size;
803 1.5 christos
804 1.5 christos size = strlen(str);
805 1.5 christos if (size == 0)
806 1.5 christos return str;
807 1.5 christos
808 1.5 christos p = salloc(2 * size);
809 1.5 christos for (q = p; *str; str++) {
810 1.5 christos *q = *str;
811 1.5 christos if (*q++ == '%')
812 1.5 christos *q++ = '%';
813 1.5 christos }
814 1.5 christos *q = '\0';
815 1.5 christos return p;
816 1.5 christos }
817 1.5 christos
818 1.1 christos static char *
819 1.1 christos preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
820 1.1 christos {
821 1.1 christos const char *gmtoff;
822 1.1 christos const char *zone;
823 1.1 christos const char *subj;
824 1.1 christos const char *addr;
825 1.1 christos const char *user;
826 1.1 christos const char *p;
827 1.1 christos char *q;
828 1.1 christos char *newfmt;
829 1.1 christos size_t fmtsize;
830 1.1 christos
831 1.1 christos if (mp != NULL && (mp->m_flag & MDELETED) != 0)
832 1.1 christos mp = NULL; /* deleted mail shouldn't show up! */
833 1.1 christos
834 1.5 christos subj = protect(subjof(mp));
835 1.5 christos addr = protect(addrof(mp));
836 1.5 christos user = protect(userof(mp));
837 1.1 christos gmtoff = dateof(tm, mp, use_hl_date);
838 1.1 christos zone = tm->tm_zone;
839 1.1 christos fmtsize = LINESIZE;
840 1.1 christos newfmt = malloc(fmtsize); /* so we can realloc() in check_bufsize() */
841 1.1 christos q = newfmt;
842 1.1 christos p = oldfmt;
843 1.1 christos while (*p) {
844 1.1 christos if (*p == '%') {
845 1.1 christos const char *fp;
846 1.1 christos fp = subformat(&p, mp, addr, user, subj, gmtoff, zone);
847 1.1 christos if (fp) {
848 1.1 christos size_t len;
849 1.1 christos len = strlen(fp);
850 1.1 christos check_bufsize(&newfmt, &fmtsize, &q, len);
851 1.1 christos (void)strcpy(q, fp);
852 1.1 christos q += len;
853 1.1 christos continue;
854 1.1 christos }
855 1.1 christos }
856 1.1 christos check_bufsize(&newfmt, &fmtsize, &q, 1);
857 1.1 christos *q++ = *p++;
858 1.1 christos }
859 1.1 christos *q = '\0';
860 1.1 christos
861 1.1 christos return newfmt;
862 1.1 christos }
863 1.1 christos
864 1.1 christos
865 1.1 christos /*
866 1.1 christos * If a format string begins with the USE_HL_DATE string, smsgprintf
867 1.1 christos * will use the headerline date rather than trying to extract the date
868 1.1 christos * from the Date field.
869 1.1 christos *
870 1.1 christos * Note: If a 'valid' date cannot be extracted from the Date field,
871 1.1 christos * then the headline date is used.
872 1.1 christos */
873 1.1 christos #define USE_HL_DATE "%??"
874 1.1 christos
875 1.1 christos PUBLIC char *
876 1.1 christos smsgprintf(const char *fmtstr, struct message *mp)
877 1.1 christos {
878 1.1 christos struct tm tm;
879 1.1 christos int use_hl_date;
880 1.1 christos char *newfmt;
881 1.1 christos char *buf;
882 1.1 christos size_t bufsize;
883 1.1 christos
884 1.1 christos if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
885 1.1 christos use_hl_date = 0;
886 1.1 christos else {
887 1.1 christos use_hl_date = 1;
888 1.1 christos fmtstr += sizeof(USE_HL_DATE) - 1;
889 1.1 christos }
890 1.1 christos bufsize = LINESIZE;
891 1.1 christos buf = salloc(bufsize);
892 1.1 christos newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
893 1.1 christos (void)strftime(buf, bufsize, newfmt, &tm);
894 1.1 christos free(newfmt); /* preformat() uses malloc()/realloc() */
895 1.1 christos return buf;
896 1.1 christos }
897 1.1 christos
898 1.1 christos
899 1.1 christos PUBLIC void
900 1.1 christos fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
901 1.1 christos {
902 1.1 christos char *buf;
903 1.1 christos
904 1.1 christos buf = smsgprintf(fmtstr, mp);
905 1.1 christos (void)fprintf(fo, "%s\n", buf); /* XXX - add the newline here? */
906 1.1 christos }
907