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