format.c revision 1.8 1 /* $NetBSD: format.c,v 1.8 2007/09/12 13:09:46 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.8 2007/09/12 13:09:46 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 if (sign[0] == '-') {
629 tm->tm_hour += hour;
630 tm->tm_min += min;
631 }
632 else {
633 tm->tm_hour -= hour;
634 tm->tm_min -= min;
635 }
636 tm->tm_isdst = -1;
637 if ((time_t)(otime = timegm(tm)) == -1)
638 warn("timegm: %s", date);
639
640 if(localtime_r(&otime, tm) == NULL)
641 warn("localtime: %s", date);
642
643 /* extract the new gmtoff string */
644 gmtoff = mk_gmtoff(tm);
645 }
646 else
647 tm->tm_gmtoff = 0;
648 }
649 else {
650 /*
651 * The BSD and System V headline date formats differ
652 * and each have an optional timezone field between
653 * the time and date (see head.c). Unfortunately,
654 * strptime(3) doesn't know about timezone fields, so
655 * we have to handle it ourselves.
656 *
657 * char ctype[] = "Aaa Aaa O0 00:00:00 0000";
658 * char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000";
659 * char SysV_ctype[] = "Aaa Aaa O0 00:00 0000";
660 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
661 */
662 struct headline hl;
663 char headline[LINESIZE];
664 char pbuf[LINESIZE];
665
666 headline[0] = '\0';
667 date = headline;
668 (void)mail_readline(setinput(mp), headline, sizeof(headline));
669 parse(headline, &hl, pbuf);
670 if (hl.l_date != NULL) {
671 if ((tail = my_strptime(hl.l_date, " %a %b %d %T ", tm)) == NULL &&
672 (tail = my_strptime(hl.l_date, " %a %b %d %R ", tm)) == NULL)
673 warnx("dateof: cannot determine date: %s", hl.l_date);
674 else {
675 tm->tm_isdst = -1;
676 if(mktime(tm) == -1)
677 warn("mktime: %s", date);
678
679 /* extract the gmtoff string */
680 gmtoff = mk_gmtoff(tm);
681 }
682 }
683 }
684 /* 'tail' will be NULL here if the mail file is empty, so
685 * don't check it with an assert(). */
686
687 return gmtoff;
688 }
689
690 /*
691 * Get the sender's address for display. Let nameof() do this.
692 */
693 static const char *
694 addrof(struct message *mp)
695 {
696 if (mp == NULL)
697 return NULL;
698
699 return nameof(mp, 0);
700 }
701
702 /************************************************************************
703 * The 'address' syntax - from rfc 2822:
704 *
705 * specials = "(" / ")" / ; Special characters used in
706 * "<" / ">" / ; other parts of the syntax
707 * "[" / "]" /
708 * ":" / ";" /
709 * "@" / "\" /
710 * "," / "." /
711 * DQUOTE
712 * qtext = NO-WS-CTL / ; Non white space controls
713 * %d33 / ; The rest of the US-ASCII
714 * %d35-91 / ; characters not including "\"
715 * %d93-126 ; or the quote character
716 * qcontent = qtext / quoted-pair
717 * quoted-string = [CFWS]
718 * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
719 * [CFWS]
720 * atext = ALPHA / DIGIT / ; Any character except controls,
721 * "!" / "#" / ; SP, and specials.
722 * "$" / "%" / ; Used for atoms
723 * "&" / "'" /
724 * "*" / "+" /
725 * "-" / "/" /
726 * "=" / "?" /
727 * "^" / "_" /
728 * "`" / "{" /
729 * "|" / "}" /
730 * "~"
731 * atom = [CFWS] 1*atext [CFWS]
732 * word = atom / quoted-string
733 * phrase = 1*word / obs-phrase
734 * display-name = phrase
735 * dtext = NO-WS-CTL / ; Non white space controls
736 * %d33-90 / ; The rest of the US-ASCII
737 * %d94-126 ; characters not including "[",
738 * ; "]", or "\"
739 * dcontent = dtext / quoted-pair
740 * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
741 * domain = dot-atom / domain-literal / obs-domain
742 * local-part = dot-atom / quoted-string / obs-local-part
743 * addr-spec = local-part "@" domain
744 * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
745 * name-addr = [display-name] angle-addr
746 * mailbox = name-addr / addr-spec
747 * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
748 * group = display-name ":" [mailbox-list / CFWS] ";"
749 * [CFWS]
750 * address = mailbox / group
751 ************************************************************************/
752 static char *
753 get_display_name(char *name)
754 {
755 char nbuf[LINESIZE];
756 const char *p;
757 char *q;
758 char *qend;
759 char *lastq;
760 int quoted;
761
762 if (name == NULL)
763 return NULL;
764
765 q = nbuf;
766 lastq = nbuf;
767 qend = nbuf + sizeof(nbuf) - 1; /* reserve space for '\0' */
768 quoted = 0;
769 for (p = skip_blank(name); *p != '\0'; p++) {
770 DEBUG(("get_display_name: %s\n", p));
771 switch (*p) {
772 case '"': /* quoted-string */
773 q = nbuf;
774 p = snarf_quote(&q, qend, p);
775 if (!quoted)
776 lastq = q;
777 quoted = 1;
778 break;
779
780 case ':': /* group */
781 case '<': /* angle-address */
782 if (lastq == nbuf)
783 return NULL;
784 *lastq = '\0'; /* NULL termination */
785 return savestr(nbuf);
786
787 case '(': /* comment - skip it! */
788 p = snarf_comment(NULL, NULL, p);
789 break;
790
791 default:
792 if (!quoted && q < qend) {
793 *q++ = *p;
794 if (!isblank((unsigned char)*p)
795 /* && !is_specials((unsigned char)*p) */ )
796 lastq = q;
797 }
798 break;
799 }
800 }
801 return NULL; /* no group or angle-address */
802 }
803
804 /*
805 * See RFC 2822 sec 3.4 and 3.6.2.
806 */
807 static const char *
808 userof(struct message *mp)
809 {
810 char *sender;
811 char *dispname;
812
813 if (mp == NULL)
814 return NULL;
815
816 if ((sender = hfield("from", mp)) != NULL ||
817 (sender = hfield("sender", mp)) != NULL)
818 /*
819 * Try to get the display-name. If one doesn't exist,
820 * then the best we can hope for is that the user's
821 * name is in the comments.
822 */
823 if ((dispname = get_display_name(sender)) != NULL ||
824 (dispname = get_comments(sender)) != NULL)
825 return dispname;
826 return NULL;
827 }
828
829 /*
830 * Grab the subject line.
831 */
832 static const char *
833 subjof(struct message *mp)
834 {
835 const char *subj;
836
837 if (mp == NULL)
838 return NULL;
839
840 if ((subj = hfield("subject", mp)) == NULL)
841 subj = hfield("subj", mp);
842 return subj;
843 }
844
845 /*
846 * Protect a string against strftime() conversion.
847 */
848 static const char*
849 protect(const char *str)
850 {
851 char *p, *q;
852 size_t size;
853
854 if (str == NULL || (size = strlen(str)) == 0)
855 return str;
856
857 p = salloc(2 * size + 1);
858 for (q = p; *str; str++) {
859 *q = *str;
860 if (*q++ == '%')
861 *q++ = '%';
862 }
863 *q = '\0';
864 return p;
865 }
866
867 static char *
868 preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
869 {
870 const char *gmtoff;
871 const char *zone;
872 const char *subj;
873 const char *addr;
874 const char *user;
875 const char *p;
876 char *q;
877 char *newfmt;
878 size_t fmtsize;
879
880 if (mp != NULL && (mp->m_flag & MDELETED) != 0)
881 mp = NULL; /* deleted mail shouldn't show up! */
882
883 subj = protect(subjof(mp));
884 addr = protect(addrof(mp));
885 user = protect(userof(mp));
886 gmtoff = dateof(tm, mp, use_hl_date);
887 zone = tm->tm_zone;
888 fmtsize = LINESIZE;
889 newfmt = malloc(fmtsize); /* so we can realloc() in check_bufsize() */
890 q = newfmt;
891 p = oldfmt;
892 while (*p) {
893 if (*p == '%') {
894 const char *fp;
895 fp = subformat(&p, mp, addr, user, subj, gmtoff, zone);
896 if (fp) {
897 size_t len;
898 len = strlen(fp);
899 check_bufsize(&newfmt, &fmtsize, &q, len);
900 (void)strcpy(q, fp);
901 q += len;
902 continue;
903 }
904 }
905 check_bufsize(&newfmt, &fmtsize, &q, 1);
906 *q++ = *p++;
907 }
908 *q = '\0';
909
910 return newfmt;
911 }
912
913
914 /*
915 * If a format string begins with the USE_HL_DATE string, smsgprintf
916 * will use the headerline date rather than trying to extract the date
917 * from the Date field.
918 *
919 * Note: If a 'valid' date cannot be extracted from the Date field,
920 * then the headline date is used.
921 */
922 #define USE_HL_DATE "%??"
923
924 PUBLIC char *
925 smsgprintf(const char *fmtstr, struct message *mp)
926 {
927 struct tm tm;
928 int use_hl_date;
929 char *newfmt;
930 char *buf;
931 size_t bufsize;
932
933 if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
934 use_hl_date = 0;
935 else {
936 use_hl_date = 1;
937 fmtstr += sizeof(USE_HL_DATE) - 1;
938 }
939 bufsize = LINESIZE;
940 buf = salloc(bufsize);
941 newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
942 (void)strftime(buf, bufsize, newfmt, &tm);
943 free(newfmt); /* preformat() uses malloc()/realloc() */
944 return buf;
945 }
946
947
948 PUBLIC void
949 fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
950 {
951 char *buf;
952
953 buf = smsgprintf(fmtstr, mp);
954 (void)fprintf(fo, "%s\n", buf); /* XXX - add the newline here? */
955 }
956