format.c revision 1.5 1 /* $NetBSD: format.c,v 1.5 2007/07/06 20:14:33 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.5 2007/07/06 20:14:33 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 * Get the date and time info from the "Date:" line, parse it into a
500 * tm structure as much as possible.
501 *
502 * Note: We return the gmtoff as a string as "-0000" has special
503 * meaning. See RFC 2822, sec 3.3.
504 */
505 PUBLIC const char *
506 dateof(struct tm *tm, struct message *mp, int use_hl_date)
507 {
508 static int tzinit = 0;
509 char *tail;
510 char *gmtoff;
511 const char *date;
512
513 (void)memset(tm, 0, sizeof(*tm));
514 tm->tm_isdst = -1;
515
516 /* Make sure the time zone info is initialized. */
517 if (!tzinit) {
518 tzinit = 1;
519 tzset();
520 }
521 if (mp == NULL) { /* use local time */
522 time_t now;
523 (void)time(&now);
524 (void)localtime_r(&now, tm);
525 return mk_gmtoff(tm);
526 }
527 gmtoff = NULL;
528 tail = NULL;
529 /*
530 * See RFC 2822 sec 3.3 for date-time format used in
531 * the "Date:" field.
532 *
533 * Date: Tue, 21 Mar 2006 20:45:30 -0500
534 *
535 * Notes:
536 * 1) The 'day-of-week' and 'second' fields are optional so we
537 * check 4 possibilities. This could be optimized.
538 *
539 * 2) The timezone is frequently in a comment following the
540 * zone offset.
541 *
542 * 3) The range for the time is 00:00 to 23:60 (for a leep
543 * second), but I have seen this violated (e.g., Date: Tue,
544 * 24 Oct 2006 24:07:58 +0400) making strptime() fail.
545 * Thus we fall back on the headline time which was written
546 * locally when the message was received. Of course, this
547 * is not the same time as in the Date field.
548 */
549 if (use_hl_date == 0 &&
550 (date = hfield("date", mp)) != NULL &&
551 ((tail = strptime(date, " %a, %d %b %Y %T ", tm)) != NULL ||
552 (tail = strptime(date, " %d %b %Y %T ", tm)) != NULL ||
553 (tail = strptime(date, " %a, %d %b %Y %R ", tm)) != NULL ||
554 (tail = strptime(date, " %d %b %Y %R ", tm)) != NULL)) {
555 int hour;
556 int min;
557 char sign[2];
558 char *cp;
559
560 if ((cp = strchr(tail, '(')) != NULL)
561 tm->tm_zone = get_comments(cp);
562 else
563 tm->tm_zone = NULL;
564 gmtoff = skin(tail);
565
566 /*
567 * Scan the gmtoff and use it to convert the time to a
568 * local time.
569 *
570 * Note: "-0000" means no valid zone info. See
571 * RFC 2822, sec 3.3.
572 *
573 * XXX - This is painful! Is there a better way?
574 */
575 if (strcmp(gmtoff, "-0000") != 0 &&
576 sscanf(gmtoff, " %1[+-]%2d%2d ", sign, &hour, &min) == 3) {
577 time_t otime;
578 if (sign[0] == '-') {
579 tm->tm_hour += hour;
580 tm->tm_min += min;
581 }
582 else {
583 tm->tm_hour -= hour;
584 tm->tm_min -= min;
585 }
586 tm->tm_isdst = -1;
587 if ((time_t)(otime = timegm(tm)) == -1)
588 warn("timegm: %s", date);
589
590 if(localtime_r(&otime, tm) == NULL)
591 warn("localtime: %s", date);
592
593 /* extract the new gmtoff string */
594 gmtoff = mk_gmtoff(tm);
595 }
596 else
597 tm->tm_gmtoff = 0;
598 }
599 else {
600 /*
601 * The BSD and System V headline date formats differ
602 * and each have an optional timezone field between
603 * the time and date (see head.c). Unfortunately,
604 * strptime(3) doesn't know about timezone fields, so
605 * we have to handle it ourselves.
606 *
607 * char ctype[] = "Aaa Aaa O0 00:00:00 0000";
608 * char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000";
609 * char SysV_ctype[] = "Aaa Aaa O0 00:00 0000";
610 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
611 */
612 struct headline hl;
613 char headline[LINESIZE];
614 char pbuf[LINESIZE];
615
616 headline[0] = '\0';
617 date = headline;
618 (void)mail_readline(setinput(mp), headline, sizeof(headline));
619 parse(headline, &hl, pbuf);
620 if (hl.l_date != NULL) {
621 if ((tail = my_strptime(hl.l_date, " %a %b %d %T ", tm)) == NULL &&
622 (tail = my_strptime(hl.l_date, " %a %b %d %R ", tm)) == NULL)
623 warnx("dateof: cannot determine date: %s", hl.l_date);
624 else {
625 tm->tm_isdst = -1;
626 if(mktime(tm) == -1)
627 warn("mktime: %s", date);
628
629 /* extract the gmtoff string */
630 gmtoff = mk_gmtoff(tm);
631 }
632 }
633 }
634 /* 'tail' will be NULL here if the mail file is empty, so
635 * don't check it with an assert(). */
636
637 return gmtoff;
638 }
639
640 /*
641 * Get the sender's address for display. Let nameof() do this.
642 */
643 static const char *
644 addrof(struct message *mp)
645 {
646 if (mp == NULL)
647 return NULL;
648
649 return nameof(mp, 0);
650 }
651
652 /************************************************************************
653 * The 'address' syntax - from rfc 2822:
654 *
655 * specials = "(" / ")" / ; Special characters used in
656 * "<" / ">" / ; other parts of the syntax
657 * "[" / "]" /
658 * ":" / ";" /
659 * "@" / "\" /
660 * "," / "." /
661 * DQUOTE
662 * qtext = NO-WS-CTL / ; Non white space controls
663 * %d33 / ; The rest of the US-ASCII
664 * %d35-91 / ; characters not including "\"
665 * %d93-126 ; or the quote character
666 * qcontent = qtext / quoted-pair
667 * quoted-string = [CFWS]
668 * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
669 * [CFWS]
670 * atext = ALPHA / DIGIT / ; Any character except controls,
671 * "!" / "#" / ; SP, and specials.
672 * "$" / "%" / ; Used for atoms
673 * "&" / "'" /
674 * "*" / "+" /
675 * "-" / "/" /
676 * "=" / "?" /
677 * "^" / "_" /
678 * "`" / "{" /
679 * "|" / "}" /
680 * "~"
681 * atom = [CFWS] 1*atext [CFWS]
682 * word = atom / quoted-string
683 * phrase = 1*word / obs-phrase
684 * display-name = phrase
685 * dtext = NO-WS-CTL / ; Non white space controls
686 * %d33-90 / ; The rest of the US-ASCII
687 * %d94-126 ; characters not including "[",
688 * ; "]", or "\"
689 * dcontent = dtext / quoted-pair
690 * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
691 * domain = dot-atom / domain-literal / obs-domain
692 * local-part = dot-atom / quoted-string / obs-local-part
693 * addr-spec = local-part "@" domain
694 * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
695 * name-addr = [display-name] angle-addr
696 * mailbox = name-addr / addr-spec
697 * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
698 * group = display-name ":" [mailbox-list / CFWS] ";"
699 * [CFWS]
700 * address = mailbox / group
701 ************************************************************************/
702 static char *
703 get_display_name(char *name)
704 {
705 char nbuf[LINESIZE];
706 const char *p;
707 char *q;
708 char *qend;
709 char *lastq;
710 int quoted;
711
712 if (name == NULL)
713 return NULL;
714
715 q = nbuf;
716 lastq = nbuf;
717 qend = nbuf + sizeof(nbuf) - 1; /* reserve space for '\0' */
718 quoted = 0;
719 for (p = skip_blank(name); *p != '\0'; p++) {
720 DEBUG(("get_display_name: %s\n", p));
721 switch (*p) {
722 case '"': /* quoted-string */
723 q = nbuf;
724 p = snarf_quote(&q, qend, p);
725 if (!quoted)
726 lastq = q;
727 quoted = 1;
728 break;
729
730 case ':': /* group */
731 case '<': /* angle-address */
732 if (lastq == nbuf)
733 return NULL;
734 *lastq = '\0'; /* NULL termination */
735 return savestr(nbuf);
736
737 case '(': /* comment - skip it! */
738 p = snarf_comment(NULL, NULL, p);
739 break;
740
741 default:
742 if (!quoted && q < qend) {
743 *q++ = *p;
744 if (!isblank((unsigned char)*p)
745 /* && !is_specials((unsigned char)*p) */ )
746 lastq = q;
747 }
748 break;
749 }
750 }
751 return NULL; /* no group or angle-address */
752 }
753
754 /*
755 * See RFC 2822 sec 3.4 and 3.6.2.
756 */
757 static const char *
758 userof(struct message *mp)
759 {
760 char *sender;
761 char *dispname;
762
763 if (mp == NULL)
764 return NULL;
765
766 if ((sender = hfield("from", mp)) != NULL ||
767 (sender = hfield("sender", mp)) != NULL)
768 /*
769 * Try to get the display-name. If one doesn't exist,
770 * then the best we can hope for is that the user's
771 * name is in the comments.
772 */
773 if ((dispname = get_display_name(sender)) != NULL ||
774 (dispname = get_comments(sender)) != NULL)
775 return dispname;
776 return NULL;
777 }
778
779 /*
780 * Grab the subject line.
781 */
782 static const char *
783 subjof(struct message *mp)
784 {
785 const char *subj;
786
787 if (mp == NULL)
788 return NULL;
789
790 if ((subj = hfield("subject", mp)) == NULL)
791 subj = hfield("subj", mp);
792 return subj;
793 }
794
795 /*
796 * Protect a string against strftime() conversion.
797 */
798 static const char*
799 protect(const char *str)
800 {
801 char *p, *q;
802 size_t size;
803
804 size = strlen(str);
805 if (size == 0)
806 return str;
807
808 p = salloc(2 * size);
809 for (q = p; *str; str++) {
810 *q = *str;
811 if (*q++ == '%')
812 *q++ = '%';
813 }
814 *q = '\0';
815 return p;
816 }
817
818 static char *
819 preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
820 {
821 const char *gmtoff;
822 const char *zone;
823 const char *subj;
824 const char *addr;
825 const char *user;
826 const char *p;
827 char *q;
828 char *newfmt;
829 size_t fmtsize;
830
831 if (mp != NULL && (mp->m_flag & MDELETED) != 0)
832 mp = NULL; /* deleted mail shouldn't show up! */
833
834 subj = protect(subjof(mp));
835 addr = protect(addrof(mp));
836 user = protect(userof(mp));
837 gmtoff = dateof(tm, mp, use_hl_date);
838 zone = tm->tm_zone;
839 fmtsize = LINESIZE;
840 newfmt = malloc(fmtsize); /* so we can realloc() in check_bufsize() */
841 q = newfmt;
842 p = oldfmt;
843 while (*p) {
844 if (*p == '%') {
845 const char *fp;
846 fp = subformat(&p, mp, addr, user, subj, gmtoff, zone);
847 if (fp) {
848 size_t len;
849 len = strlen(fp);
850 check_bufsize(&newfmt, &fmtsize, &q, len);
851 (void)strcpy(q, fp);
852 q += len;
853 continue;
854 }
855 }
856 check_bufsize(&newfmt, &fmtsize, &q, 1);
857 *q++ = *p++;
858 }
859 *q = '\0';
860
861 return newfmt;
862 }
863
864
865 /*
866 * If a format string begins with the USE_HL_DATE string, smsgprintf
867 * will use the headerline date rather than trying to extract the date
868 * from the Date field.
869 *
870 * Note: If a 'valid' date cannot be extracted from the Date field,
871 * then the headline date is used.
872 */
873 #define USE_HL_DATE "%??"
874
875 PUBLIC char *
876 smsgprintf(const char *fmtstr, struct message *mp)
877 {
878 struct tm tm;
879 int use_hl_date;
880 char *newfmt;
881 char *buf;
882 size_t bufsize;
883
884 if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
885 use_hl_date = 0;
886 else {
887 use_hl_date = 1;
888 fmtstr += sizeof(USE_HL_DATE) - 1;
889 }
890 bufsize = LINESIZE;
891 buf = salloc(bufsize);
892 newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
893 (void)strftime(buf, bufsize, newfmt, &tm);
894 free(newfmt); /* preformat() uses malloc()/realloc() */
895 return buf;
896 }
897
898
899 PUBLIC void
900 fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
901 {
902 char *buf;
903
904 buf = smsgprintf(fmtstr, mp);
905 (void)fprintf(fo, "%s\n", buf); /* XXX - add the newline here? */
906 }
907