format.c revision 1.1 1 /* $NetBSD: format.c,v 1.1 2006/10/31 22:36:37 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.1 2006/10/31 22:36:37 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
53
54 #define DEBUG(a)
55
56 static void
57 check_bufsize(char **buf, size_t *bufsize, char **p, size_t cnt)
58 {
59 char *q;
60 if (*p + cnt < *buf + *bufsize)
61 return;
62 *bufsize *= 2;
63 q = realloc(*buf, *bufsize);
64 *p = q + (*p - *buf);
65 *buf = q;
66 }
67
68 static const char *
69 sfmtoff(const char **fmtbeg, const char *fmtch, off_t off)
70 {
71 char *newfmt; /* pointer to new format string */
72 size_t len; /* space for "lld" including '\0' */
73 len = fmtch - *fmtbeg + sizeof(PRId64);
74 newfmt = salloc(len);
75 (void)strlcpy(newfmt, *fmtbeg, len - sizeof(sizeof(PRId64)) + 1);
76 (void)strlcat(newfmt, PRId64, len);
77 *fmtbeg = fmtch + 1;
78 {
79 char *p;
80 char *q;
81 (void)easprintf(&p, newfmt, off);
82 q = savestr(p);
83 free(p);
84 return q;
85 }
86 }
87
88 static const char *
89 sfmtint(const char **fmtbeg, const char *fmtch, int num)
90 {
91 char *newfmt;
92 size_t len;
93
94 len = fmtch - *fmtbeg + 2; /* space for 'd' and '\0' */
95 newfmt = salloc(len);
96 (void)strlcpy(newfmt, *fmtbeg, len);
97 newfmt[len-2] = 'd'; /* convert to printf format */
98
99 *fmtbeg = fmtch + 1;
100 {
101 char *p;
102 char *q;
103 (void)easprintf(&p, newfmt, num);
104 q = savestr(p);
105 free(p);
106 return q;
107 }
108 }
109
110 static const char *
111 sfmtstr(const char **fmtbeg, const char *fmtch, const char *str)
112 {
113 char *newfmt;
114 size_t len;
115
116 len = fmtch - *fmtbeg + 2; /* space for 's' and '\0' */
117 newfmt = salloc(len);
118 (void)strlcpy(newfmt, *fmtbeg, len);
119 newfmt[len-2] = 's'; /* convert to printf format */
120
121 *fmtbeg = fmtch + 1;
122 {
123 char *p;
124 char *q;
125 (void)easprintf(&p, newfmt, str ? str : "");
126 q = savestr(p);
127 free(p);
128 return q;
129 }
130 }
131
132 static const char *
133 sfmtfield(const char **fmtbeg, const char *fmtch, struct message *mp)
134 {
135 char *q;
136 q = strchr(fmtch + 1, '?');
137 if (q) {
138 size_t len;
139 char *p;
140 const char *str;
141 int skin_it;
142
143 skin_it = fmtch[1] == '-' ? 1 : 0;
144 len = q - fmtch - skin_it;
145 p = salloc(len + 1);
146 (void)strlcpy(p, fmtch + skin_it + 1, len);
147 str = sfmtstr(fmtbeg, fmtch, hfield(p, mp));
148 if (skin_it)
149 str = skin(__UNCONST(str));
150 *fmtbeg = q + 1;
151 return str;
152 }
153 return NULL;
154 }
155
156 static const char *
157 sfmtflag(const char **fmtbeg, const char *fmtch, int flags)
158 {
159 char disp[2];
160 disp[0] = ' ';
161 disp[1] = '\0';
162 if (flags & MSAVED)
163 disp[0] = '*';
164 if (flags & MPRESERVE)
165 disp[0] = 'P';
166 if ((flags & (MREAD|MNEW)) == MNEW)
167 disp[0] = 'N';
168 if ((flags & (MREAD|MNEW)) == 0)
169 disp[0] = 'U';
170 if (flags & MBOX)
171 disp[0] = 'M';
172 return sfmtstr(fmtbeg, fmtch, disp);
173 }
174
175 static const char *
176 login_name(const char *addr)
177 {
178 char *p;
179 p = strchr(addr, '@');
180 if (p) {
181 char *q;
182 size_t len;
183 len = p - addr + 1;
184 q = salloc(len);
185 (void)strlcpy(q, addr, len);
186 return q;
187 }
188 return addr;
189 }
190
191 static const char *
192 subformat(const char **src, struct message *mp, const char *addr,
193 const char *user, const char *subj, const char *gmtoff, const char *zone)
194 {
195 #define MP(a) mp ? a : NULL
196 const char *p;
197
198 p = *src;
199 if (p[1] == '%') {
200 *src += 2;
201 return "%%";
202 }
203 for (p = *src; *p && !isalpha((unsigned char)*p) && *p != '?'; p++)
204 continue;
205
206 switch (*p) {
207 case '?':
208 return MP(sfmtfield(src, p, mp));
209 case 'J':
210 return MP(sfmtint(src, p, (int)(mp->m_lines - mp->m_blines)));
211 case 'K':
212 return MP(sfmtint(src, p, (int)mp->m_blines));
213 case 'L':
214 return MP(sfmtint(src, p, (int)mp->m_lines));
215 case 'N':
216 return sfmtstr(src, p, user);
217 case 'O':
218 return MP(sfmtoff(src, p, mp->m_size));
219 case 'P':
220 return MP(sfmtstr(src, p, mp == dot ? ">" : " "));
221 case 'Q':
222 return MP(sfmtflag(src, p, mp->m_flag));
223 case 'Z':
224 *src = p + 1;
225 return zone;
226
227 case 'f':
228 return sfmtstr(src, p, addr);
229 case 'i':
230 if (mp == NULL && (mp = dot) == NULL)
231 return NULL;
232 return sfmtint(src, p, (mp - message) + 1);
233 case 'n':
234 return sfmtstr(src, p, login_name(addr));
235 case 'q':
236 return sfmtstr(src, p, subj);
237 case 't':
238 return sfmtint(src, p, msgCount);
239 case 'z':
240 *src = p + 1;
241 return gmtoff;
242 default:
243 return NULL;
244 }
245 #undef MP
246 }
247
248 static const char *
249 snarf_comment(char **buf, char *bufend, const char *string)
250 {
251 const char *p;
252 char *q;
253 char *qend;
254 int clevel;
255
256 q = buf ? *buf : NULL;
257 qend = buf ? bufend : NULL;
258
259 clevel = 1;
260 for (p = string + 1; *p != '\0'; p++) {
261 DEBUG(("snarf_comment: %s\n", p));
262 if (*p == '(') {
263 clevel++;
264 continue;
265 }
266 if (*p == ')') {
267 if (--clevel == 0)
268 break;
269 continue;
270 }
271 if (*p == '\\' && p[1] != 0)
272 p++;
273
274 if (q < qend)
275 *q++ = *p;
276 }
277 if (buf) {
278 *q = '\0';
279 DEBUG(("snarf_comment: terminating: %s\n", *buf));
280 *buf = q;
281 }
282 if (*p == '\0')
283 p--;
284 return p;
285 }
286
287 static const char *
288 snarf_quote(char **buf, char *bufend, const char *string)
289 {
290 const char *p;
291 char *q;
292 char *qend;
293
294 q = buf ? *buf : NULL;
295 qend = buf ? bufend : NULL;
296
297 for (p = string + 1; *p != '\0' && *p != '"'; p++) {
298 DEBUG(("snarf_quote: %s\n", p));
299 if (*p == '\\' && p[1] != '\0')
300 p++;
301
302 if (q < qend)
303 *q++ = *p;
304 }
305 if (buf) {
306 *q = '\0';
307 DEBUG(("snarf_quote: terminating: %s\n", *buf));
308 *buf = q;
309 }
310 if (*p == '\0')
311 p--;
312 return p;
313 }
314
315 /*
316 * Grab the comments, separating each by a space.
317 */
318 static char *
319 get_comments(char *name)
320 {
321 char nbuf[BUFSIZ];
322 const char *p;
323 char *qend;
324 char *q;
325 char *lastq;
326
327 if (name == NULL)
328 return(NULL);
329
330 p = name;
331 q = nbuf;
332 lastq = nbuf;
333 qend = nbuf + sizeof(nbuf) - 1;
334 for (p = skip_white(name); *p != '\0'; p++) {
335 DEBUG(("get_comments: %s\n", p));
336 switch (*p) {
337 case '"': /* quoted-string ... skip it! */
338 p = snarf_quote(NULL, NULL, p);
339 break;
340
341 case '(':
342 p = snarf_comment(&q, qend, p);
343 lastq = q;
344 if (q < qend) /* separate comments by space */
345 *q++ = ' ';
346 break;
347
348 default:
349 break;
350 }
351 }
352 *lastq = '\0';
353 return savestr(nbuf);
354 }
355
356 static char *
357 my_strptime(const char *buf, const char *fmtstr, struct tm *tm)
358 {
359 char *tail;
360 char zone[4];
361
362 zone[0] = '\0';
363 tail = strptime(buf, fmtstr, tm);
364 if (tail) {
365 int len;
366 if (sscanf(tail, " %3[A-Z] %n", zone, &len) == 1) {
367 if (zone[0])
368 tm->tm_zone = savestr(zone);
369 tail += len;
370 }
371 tail = strptime(tail, " %Y ", tm);
372 }
373 return tail;
374 }
375
376 /*
377 * Get the date and time info from the "Date:" line, parse it into a
378 * tm structure as much as possible.
379 *
380 * Note: We return the gmtoff as a string as "-0000" has special
381 * meaning. See RFC 2822, sec 3.3.
382 */
383 static const char *
384 dateof(struct tm *tm, struct message *mp, int use_hl_date)
385 {
386 char *tail;
387 char *gmtoff;
388 const char *date;
389
390 (void)memset(tm, 0, sizeof(*tm));
391
392 if (mp == NULL) { /* use local time */
393 char buf[6]; /* space for "+0000" */
394 int hour;
395 int min;
396 time_t now;
397 tzset();
398 (void)time(&now);
399 (void)localtime_r(&now, tm);
400 min = (tm->tm_gmtoff / 60) % 60;
401 hour = tm->tm_gmtoff / 3600;
402 if (hour > 12)
403 hour = 24 - hour;
404 (void)snprintf(buf, sizeof(buf), "%+03d%02d", hour, min);
405 return savestr(buf);
406 }
407 gmtoff = NULL;
408 tail = NULL;
409 /*
410 * See RFC 2822 sec 3.3 for date-time format used in
411 * the "Date:" field.
412 *
413 * Notes:
414 * 1) The 'day-of-week' and 'second' fields are optional so we
415 * check 4 possibilities. This could be optimized.
416 *
417 * 2) The timezone is frequently in a comment following the
418 * zone offset.
419 *
420 * 3) The range for the time is 00:00 to 23:60 (for a leep
421 * second), but I have seen this violated (e.g., Date: Tue,
422 * 24 Oct 2006 24:07:58 +0400) making strptime() fail.
423 * Thus we fall back on the headline time which was written
424 * locally when the message was received. Of course, this
425 * is not the same time as in the Date field.
426 */
427 if (use_hl_date == 0 &&
428 (date = hfield("date", mp)) != NULL &&
429 ((tail = strptime(date, " %a, %d %b %Y %T ", tm)) != NULL ||
430 (tail = strptime(date, " %d %b %Y %T ", tm)) != NULL ||
431 (tail = strptime(date, " %a, %d %b %Y %R ", tm)) != NULL ||
432 (tail = strptime(date, " %d %b %Y %R ", tm)) != NULL)) {
433 char *cp;
434 if ((cp = strchr(tail, '(')) != NULL)
435 tm->tm_zone = get_comments(cp);
436 else
437 tm->tm_zone = NULL;
438 gmtoff = skin(tail);
439 }
440 else {
441 /*
442 * The BSD and System V headline date formats differ
443 * and each have an optional timezone field between
444 * the time and date (see head.c). Unfortunately,
445 * strptime(3) doesn't know about timezone fields, so
446 * we have to handle it ourselves.
447 *
448 * char ctype[] = "Aaa Aaa O0 00:00:00 0000";
449 * char tmztype[] = "Aaa Aaa O0 00:00:00 AAA 0000";
450 * char SysV_ctype[] = "Aaa Aaa O0 00:00 0000";
451 * char SysV_tmztype[] = "Aaa Aaa O0 00:00 AAA 0000";
452 */
453 struct headline hl;
454 char headline[LINESIZE];
455 char pbuf[BUFSIZ];
456
457 headline[0] = '\0';
458 date = headline;
459 (void)mail_readline(setinput(mp), headline, LINESIZE);
460 parse(headline, &hl, pbuf);
461 if (hl.l_date != NULL &&
462 (tail = my_strptime(hl.l_date, " %a %b %d %T ", tm)) == NULL &&
463 (tail = my_strptime(hl.l_date, " %a %b %d %R ", tm)) == NULL) {
464 warnx("dateof: cannot determine date: %s", hl.l_date);
465 }
466 }
467 /* tail will be NULL here if the mail file is empty, so don't
468 * check it. */
469
470 /* mark the zone and gmtoff info as invalid for strftime. */
471 tm->tm_isdst = -1;
472
473 return gmtoff;
474 }
475
476 /*
477 * Get the sender's address for display. Let nameof() do this.
478 */
479 static const char *
480 addrof(struct message *mp)
481 {
482 if (mp == NULL)
483 return NULL;
484
485 return nameof(mp, 0);
486 }
487
488 /************************************************************************
489 * The 'address' syntax - from rfc 2822:
490 *
491 * specials = "(" / ")" / ; Special characters used in
492 * "<" / ">" / ; other parts of the syntax
493 * "[" / "]" /
494 * ":" / ";" /
495 * "@" / "\" /
496 * "," / "." /
497 * DQUOTE
498 * qtext = NO-WS-CTL / ; Non white space controls
499 * %d33 / ; The rest of the US-ASCII
500 * %d35-91 / ; characters not including "\"
501 * %d93-126 ; or the quote character
502 * qcontent = qtext / quoted-pair
503 * quoted-string = [CFWS]
504 * DQUOTE *([FWS] qcontent) [FWS] DQUOTE
505 * [CFWS]
506 * atext = ALPHA / DIGIT / ; Any character except controls,
507 * "!" / "#" / ; SP, and specials.
508 * "$" / "%" / ; Used for atoms
509 * "&" / "'" /
510 * "*" / "+" /
511 * "-" / "/" /
512 * "=" / "?" /
513 * "^" / "_" /
514 * "`" / "{" /
515 * "|" / "}" /
516 * "~"
517 * atom = [CFWS] 1*atext [CFWS]
518 * word = atom / quoted-string
519 * phrase = 1*word / obs-phrase
520 * display-name = phrase
521 * dtext = NO-WS-CTL / ; Non white space controls
522 * %d33-90 / ; The rest of the US-ASCII
523 * %d94-126 ; characters not including "[",
524 * ; "]", or "\"
525 * dcontent = dtext / quoted-pair
526 * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
527 * domain = dot-atom / domain-literal / obs-domain
528 * local-part = dot-atom / quoted-string / obs-local-part
529 * addr-spec = local-part "@" domain
530 * angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
531 * name-addr = [display-name] angle-addr
532 * mailbox = name-addr / addr-spec
533 * mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list
534 * group = display-name ":" [mailbox-list / CFWS] ";"
535 * [CFWS]
536 * address = mailbox / group
537 ************************************************************************/
538 static char *
539 get_display_name(char *name)
540 {
541 char nbuf[BUFSIZ];
542 const char *p;
543 char *q;
544 char *qend;
545 char *lastq;
546 int quoted;
547
548 if (name == NULL)
549 return(NULL);
550
551 q = nbuf;
552 lastq = nbuf;
553 qend = nbuf + sizeof(nbuf) - 1; /* reserve space for '\0' */
554 quoted = 0;
555 for (p = skip_white(name); *p != '\0'; p++) {
556 DEBUG(("get_display_name: %s\n", p));
557 switch (*p) {
558 case '"': /* quoted-string */
559 q = nbuf;
560 p = snarf_quote(&q, qend, p);
561 if (!quoted)
562 lastq = q;
563 quoted = 1;
564 break;
565
566 case ':': /* group */
567 case '<': /* angle-address */
568 if (lastq == nbuf)
569 return NULL;
570 *lastq = '\0'; /* NULL termination */
571 return(savestr(nbuf));
572
573 case '(': /* comment - skip it! */
574 p = snarf_comment(NULL, NULL, p);
575 break;
576
577 default:
578 if (!quoted && q < qend) {
579 *q++ = *p;
580 if (!isblank((unsigned char)*p)
581 /* && !is_specials((unsigned char)*p) */ )
582 lastq = q;
583 }
584 break;
585 }
586 }
587 return NULL; /* no group or angle-address */
588 }
589
590 /*
591 * See RFC 2822 sec 3.4 and 3.6.2.
592 */
593 static const char *
594 userof(struct message *mp)
595 {
596 char *sender;
597 char *dispname;
598
599 if (mp == NULL)
600 return NULL;
601
602 if ((sender = hfield("from", mp)) != NULL ||
603 (sender = hfield("sender", mp)) != NULL)
604 /*
605 * Try to get the display-name. If one doesn't exist,
606 * then the best we can hope for is that the user's
607 * name is in the comments.
608 */
609 if ((dispname = get_display_name(sender)) != NULL ||
610 (dispname = get_comments(sender)) != NULL)
611 return dispname;
612 return NULL;
613 }
614
615 /*
616 * Grab the subject line.
617 */
618 static const char *
619 subjof(struct message *mp)
620 {
621 const char *subj;
622
623 if (mp == NULL)
624 return NULL;
625
626 if ((subj = hfield("subject", mp)) == NULL)
627 subj = hfield("subj", mp);
628 return subj;
629 }
630
631 static char *
632 preformat(struct tm *tm, const char *oldfmt, struct message *mp, int use_hl_date)
633 {
634 const char *gmtoff;
635 const char *zone;
636 const char *subj;
637 const char *addr;
638 const char *user;
639 const char *p;
640 char *q;
641 char *newfmt;
642 size_t fmtsize;
643
644 if (mp != NULL && (mp->m_flag & MDELETED) != 0)
645 mp = NULL; /* deleted mail shouldn't show up! */
646
647 subj = subjof(mp);
648 addr = addrof(mp);
649 user = userof(mp);
650 gmtoff = dateof(tm, mp, use_hl_date);
651 zone = tm->tm_zone;
652 fmtsize = LINESIZE;
653 newfmt = malloc(fmtsize); /* so we can realloc() in check_bufsize() */
654 q = newfmt;
655 p = oldfmt;
656 while (*p) {
657 if (*p == '%') {
658 const char *fp;
659 fp = subformat(&p, mp, addr, user, subj, gmtoff, zone);
660 if (fp) {
661 size_t len;
662 len = strlen(fp);
663 check_bufsize(&newfmt, &fmtsize, &q, len);
664 (void)strcpy(q, fp);
665 q += len;
666 continue;
667 }
668 }
669 check_bufsize(&newfmt, &fmtsize, &q, 1);
670 *q++ = *p++;
671 }
672 *q = '\0';
673
674 return newfmt;
675 }
676
677
678 /*
679 * If a format string begins with the USE_HL_DATE string, smsgprintf
680 * will use the headerline date rather than trying to extract the date
681 * from the Date field.
682 *
683 * Note: If a 'valid' date cannot be extracted from the Date field,
684 * then the headline date is used.
685 */
686 #define USE_HL_DATE "%??"
687
688 PUBLIC char *
689 smsgprintf(const char *fmtstr, struct message *mp)
690 {
691 struct tm tm;
692 int use_hl_date;
693 char *newfmt;
694 char *buf;
695 size_t bufsize;
696
697 if (strncmp(fmtstr, USE_HL_DATE, sizeof(USE_HL_DATE) - 1) != 0)
698 use_hl_date = 0;
699 else {
700 use_hl_date = 1;
701 fmtstr += sizeof(USE_HL_DATE) - 1;
702 }
703 bufsize = LINESIZE;
704 buf = salloc(bufsize);
705 newfmt = preformat(&tm, fmtstr, mp, use_hl_date);
706 (void)strftime(buf, bufsize, newfmt, &tm);
707 free(newfmt); /* preformat() uses malloc()/realloc() */
708 return buf;
709 }
710
711
712 PUBLIC void
713 fmsgprintf(FILE *fo, const char *fmtstr, struct message *mp)
714 {
715 char *buf;
716
717 buf = smsgprintf(fmtstr, mp);
718 (void)fprintf(fo, "%s\n", buf); /* XXX - add the newline here? */
719 }
720