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