1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1985-2011 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Eclipse Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.eclipse.org/org/documents/epl-v10.html * 11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * Glenn Fowler <gsf@research.att.com> * 18 * David Korn <dgk@research.att.com> * 19 * Phong Vo <kpv@research.att.com> * 20 * * 21 ***********************************************************************/ 22 #pragma prototyped 23 24 /* 25 * Glenn Fowler 26 * AT&T Research 27 * 28 * mime/mailcap support library 29 */ 30 31 static const char id[] = "\n@(#)$Id: mime library (AT&T Research) 2002-10-29 $\0\n"; 32 33 static const char lib[] = "libast:mime"; 34 35 #include "mimelib.h" 36 37 typedef struct Att_s 38 { 39 struct Att_s* next; 40 char* name; 41 char* value; 42 } Att_t; 43 44 typedef struct Cap_s 45 { 46 struct Cap_s* next; 47 unsigned long flags; 48 Att_t att; 49 char* test; 50 char data[1]; 51 } Cap_t; 52 53 typedef struct 54 { 55 Dtlink_t link; 56 Cap_t* cap; 57 Cap_t* pac; 58 char name[1]; 59 } Ent_t; 60 61 typedef struct 62 { 63 char* data; 64 int size; 65 } String_t; 66 67 typedef struct 68 { 69 char* next; 70 String_t name; 71 String_t value; 72 } Parse_t; 73 74 typedef struct 75 { 76 const char* pattern; 77 int prefix; 78 Sfio_t* fp; 79 int hit; 80 } Walk_t; 81 82 /* 83 * convert c to lower case 84 */ 85 86 static int 87 lower(int c) 88 { 89 return isupper(c) ? tolower(c) : c; 90 } 91 92 /* 93 * Ent_t case insensitive comparf 94 */ 95 96 static int 97 order(Dt_t* dt, void* a, void* b, Dtdisc_t* disc) 98 { 99 return strcasecmp(a, b); 100 } 101 102 /* 103 * Cap_t free 104 */ 105 106 static void 107 dropcap(Cap_t* cap) 108 { 109 Att_t* att; 110 111 while (att = cap->att.next) 112 { 113 cap->att.next = att->next; 114 free(att); 115 } 116 free(cap); 117 } 118 119 /* 120 * Ent_t freef 121 */ 122 123 static void 124 drop(Dt_t* dt, void* object, Dtdisc_t* disc) 125 { 126 Ent_t* ent = (Ent_t*)object; 127 Cap_t* cap; 128 129 while (cap = ent->cap) 130 { 131 ent->cap = cap->next; 132 dropcap(cap); 133 } 134 free(ent); 135 } 136 137 /* 138 * add mime type entry in s to mp 139 */ 140 141 int 142 mimeset(Mime_t* mp, char* s, unsigned long flags) 143 { 144 Ent_t* ent; 145 Cap_t* cap; 146 Att_t* att; 147 char* t; 148 char* v; 149 char* k; 150 char* x; 151 Att_t* tta; 152 int q; 153 154 for (; isspace(*s); s++); 155 if (*s && *s != '#') 156 { 157 cap = 0; 158 for (v = s; *v && *v != ';'; v++) 159 if (isspace(*v) || *v == '/' && *(v + 1) == '*') 160 *v = 0; 161 if (*v) 162 { 163 *v++ = 0; 164 do 165 { 166 for (; isspace(*v); v++); 167 if (cap) 168 { 169 for (t = v; *t && !isspace(*t) && *t != '='; t++); 170 for (k = t; isspace(*t); t++); 171 if (!*t || *t == '=' || *t == ';') 172 { 173 if (*t) 174 while (isspace(*++t)); 175 *k = 0; 176 k = v; 177 v = t; 178 } 179 else 180 k = 0; 181 } 182 if (*v == '"') 183 q = *v++; 184 else 185 q = 0; 186 for (t = v; *t; t++) 187 if (*t == '\\') 188 { 189 switch (*(t + 1)) 190 { 191 case 0: 192 case '\\': 193 case '%': 194 *t = *(t + 1); 195 break; 196 default: 197 *t = ' '; 198 break; 199 } 200 if (!*++t) 201 break; 202 } 203 else if (*t == q) 204 { 205 *t = ' '; 206 q = 0; 207 } 208 else if (*t == ';' && !q) 209 { 210 *t = ' '; 211 break; 212 } 213 for (; t > v && isspace(*(t - 1)); t--); 214 if (t <= v && (!cap || !k)) 215 break; 216 if (!cap) 217 { 218 if (!(cap = newof(0, Cap_t, 1, strlen(v) + 1))) 219 return -1; 220 if (*t) 221 *t++ = 0; 222 tta = &cap->att; 223 tta->name = "default"; 224 x = strcopy(tta->value = cap->data, v) + 1; 225 } 226 else if (k) 227 { 228 if (*t) 229 *t++ = 0; 230 if (!(att = newof(0, Att_t, 1, 0))) 231 return -1; 232 x = strcopy(att->name = x, k) + 1; 233 x = strcopy(att->value = x, v) + 1; 234 tta = tta->next = att; 235 if (!strcasecmp(k, "test")) 236 cap->test = att->value; 237 } 238 } while (*(v = t)); 239 } 240 ent = (Ent_t*)dtmatch(mp->cap, s); 241 if (cap) 242 { 243 if (ent) 244 { 245 Cap_t* dup; 246 Cap_t* pud; 247 248 for (pud = 0, dup = ent->cap; dup; pud = dup, dup = dup->next) 249 if (!cap->test && !dup->test || cap->test && dup->test && streq(cap->test, dup->test)) 250 { 251 if (flags & MIME_REPLACE) 252 { 253 if (pud) 254 pud->next = cap; 255 else 256 ent->cap = cap; 257 if (!(cap->next = dup->next)) 258 ent->pac = cap; 259 cap = dup; 260 } 261 dropcap(cap); 262 return 0; 263 } 264 ent->pac = ent->pac->next = cap; 265 } 266 else if (!(ent = newof(0, Ent_t, 1, strlen(s) + 1))) 267 return -1; 268 else 269 { 270 strcpy(ent->name, s); 271 ent->cap = ent->pac = cap; 272 dtinsert(mp->cap, ent); 273 } 274 } 275 else if (ent && (flags & MIME_REPLACE)) 276 dtdelete(mp->cap, ent); 277 } 278 return 0; 279 } 280 281 /* 282 * load mime type files into mp 283 */ 284 285 int 286 mimeload(Mime_t* mp, const char* file, unsigned long flags) 287 { 288 char* s; 289 char* t; 290 char* e; 291 int n; 292 Sfio_t* fp; 293 294 if (!(s = (char*)file)) 295 { 296 flags |= MIME_LIST; 297 if (!(s = getenv(MIME_FILES_ENV))) 298 s = MIME_FILES; 299 } 300 for (;;) 301 { 302 if (!(flags & MIME_LIST)) 303 e = 0; 304 else if (e = strchr(s, ':')) 305 { 306 /* 307 * ok, so ~ won't work for the last list element 308 * we do it for MIME_FILES_ENV anyway 309 */ 310 311 if ((strneq(s, "~/", n = 2) || strneq(s, "$HOME/", n = 6) || strneq(s, "${HOME}/", n = 8)) && (t = getenv("HOME"))) 312 { 313 sfputr(mp->buf, t, -1); 314 s += n - 1; 315 } 316 sfwrite(mp->buf, s, e - s); 317 if (!(s = sfstruse(mp->buf))) 318 return -1; 319 } 320 if (fp = tokline(s, SF_READ, NiL)) 321 { 322 while (t = sfgetr(fp, '\n', 1)) 323 if (mimeset(mp, t, flags)) 324 break; 325 sfclose(fp); 326 } 327 else if (!(flags & MIME_LIST)) 328 return -1; 329 if (!e) 330 break; 331 s = e + 1; 332 } 333 return 0; 334 } 335 336 /* 337 * mimelist walker 338 */ 339 340 static int 341 list(Dt_t* dt, void* object, void* context) 342 { 343 Walk_t* wp = (Walk_t*)context; 344 Ent_t* ent = (Ent_t*)object; 345 Cap_t* cap; 346 Att_t* att; 347 348 if (!wp->pattern || !strncasecmp(ent->name, wp->pattern, wp->prefix) && (!ent->name[wp->prefix] || ent->name[wp->prefix] == '/')) 349 { 350 wp->hit++; 351 for (cap = ent->cap; cap; cap = cap->next) 352 { 353 sfprintf(wp->fp, "%s", ent->name); 354 for (att = &cap->att; att; att = att->next) 355 { 356 sfprintf(wp->fp, "\n\t"); 357 if (att != &cap->att) 358 { 359 sfprintf(wp->fp, "%s", att->name); 360 if (*att->value) 361 sfprintf(wp->fp, " = "); 362 } 363 sfputr(wp->fp, att->value, -1); 364 } 365 sfprintf(wp->fp, "\n"); 366 } 367 } 368 return 0; 369 } 370 371 /* 372 * find entry matching type 373 * if exact match fails then left and right x- and right version number 374 * permutations are attempted 375 */ 376 377 static Ent_t* 378 find(Mime_t* mp, const char* type) 379 { 380 char* lp; 381 char* rp; 382 char* rb; 383 char* rv; 384 int rc; 385 int i; 386 char* s; 387 Ent_t* ent; 388 char buf[256]; 389 390 static const char* prefix[] = { "", "", "x-", "x-", "" }; 391 392 if ((ent = (Ent_t*)dtmatch(mp->cap, type)) || 393 !(rp = strchr(lp = (char*)type, '/')) || 394 strlen(lp) >= sizeof(buf)) 395 return ent; 396 strcpy(buf, type); 397 rp = buf + (rp - lp); 398 *rp++ = 0; 399 if (*rp == 'x' && *(rp + 1) == '-') 400 rp += 2; 401 lp = buf; 402 if (*lp == 'x' && *(lp + 1) == '-') 403 lp += 2; 404 rb = rp; 405 for (rv = rp + strlen(rp); rv > rp && (isdigit(*(rv - 1)) || *(rv - 1) == '.'); rv--); 406 rc = *rv; 407 do 408 { 409 rp = rb; 410 do 411 { 412 for (i = 0; i < elementsof(prefix) - 1; i++) 413 { 414 sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp); 415 if (!(s = sfstruse(mp->buf))) 416 return 0; 417 if (ent = (Ent_t*)dtmatch(mp->cap, s)) 418 return ent; 419 if (rc) 420 { 421 *rv = 0; 422 sfprintf(mp->buf, "%s%s/%s%s", prefix[i], lp, prefix[i + 1], rp); 423 if (!(s = sfstruse(mp->buf))) 424 return 0; 425 if (ent = (Ent_t*)dtmatch(mp->cap, s)) 426 return ent; 427 *rv = rc; 428 } 429 } 430 while (*rp && *rp++ != '-'); 431 } while (*rp); 432 while (*lp && *lp++ != '-'); 433 } while (*lp); 434 return (Ent_t*)dtmatch(mp->cap, buf); 435 } 436 437 /* 438 * list mime <type,data> for pat on fp 439 */ 440 441 int 442 mimelist(Mime_t* mp, Sfio_t* fp, const char* pattern) 443 { 444 Ent_t* ent; 445 Walk_t ws; 446 447 ws.fp = fp; 448 ws.hit = 0; 449 ws.prefix = 0; 450 if (ws.pattern = pattern) 451 { 452 while (*pattern && *pattern++ != '/'); 453 if (!*pattern || *pattern == '*' && !*(pattern + 1)) 454 ws.prefix = pattern - ws.pattern; 455 else if (ent = find(mp, ws.pattern)) 456 { 457 ws.pattern = 0; 458 list(mp->cap, ent, &ws); 459 return ws.hit; 460 } 461 } 462 dtwalk(mp->cap, list, &ws); 463 return ws.hit; 464 } 465 466 /* 467 * get next arg in pp 468 * 0 returned if no more args 469 */ 470 471 static int 472 arg(Parse_t* pp, int first) 473 { 474 char* s; 475 int c; 476 int q; 477 int x; 478 479 for (s = pp->next; isspace(*s) && *s != '\n'; s++); 480 if (!*s || *s == '\n') 481 { 482 pp->next = s; 483 return 0; 484 } 485 pp->name.data = s; 486 pp->value.data = 0; 487 q = 0; 488 x = 0; 489 while ((c = *s++) && c != ';' && c != '\n') 490 { 491 if (c == '"') 492 { 493 q = 1; 494 if (pp->value.data) 495 { 496 pp->value.data = s; 497 if (x) 498 x = -1; 499 else 500 x = 1; 501 } 502 else if (!x && pp->name.data == (s - 1)) 503 { 504 x = 1; 505 pp->name.data = s; 506 } 507 do 508 { 509 if (!(c = *s++) || c == '\n') 510 { 511 s--; 512 break; 513 } 514 } while (c != '"'); 515 if (first < 0 || x > 0) 516 { 517 c = ';'; 518 break; 519 } 520 } 521 else if (c == '=' && !first) 522 { 523 first = 1; 524 pp->name.size = s - pp->name.data - 1; 525 pp->value.data = s; 526 } 527 else if (first >= 0 && isspace(c)) 528 break; 529 } 530 pp->next = s - (c != ';'); 531 if (first >= 0 || !q) 532 for (s--; s > pp->name.data && isspace(*(s - 1)); s--); 533 if (pp->value.data) 534 pp->value.size = s - pp->value.data - (q && first < 0); 535 else 536 { 537 pp->value.size = 0; 538 pp->name.size = s - pp->name.data - (q && first < 0); 539 } 540 if (first >= 0 && pp->name.size > 0 && pp->name.data[pp->name.size - 1] == ':') 541 return 0; 542 return pp->name.size > 0; 543 } 544 545 /* 546 * low level for mimeview() 547 */ 548 549 static char* 550 expand(Mime_t* mp, char* s, const char* name, const char* type, const char* opts) 551 { 552 char* t; 553 char* v; 554 int c; 555 int e; 556 int n; 557 Parse_t pp; 558 559 mp->disc->flags |= MIME_PIPE; 560 for (;;) 561 { 562 switch (c = *s++) 563 { 564 case 0: 565 case '\n': 566 break; 567 case '%': 568 if ((c = *s++) == '{' && (e = '}') || c == '(' && (e = ')')) 569 { 570 for (t = s; *s && *s != e; s++); 571 n = s - t; 572 switch (*s) 573 { 574 case '}': 575 s++; 576 c = '{'; 577 break; 578 case ')': 579 s++; 580 if (c = *s) 581 s++; 582 break; 583 } 584 } 585 else 586 t = 0; 587 switch (c) 588 { 589 case 's': 590 v = (char*)name; 591 mp->disc->flags &= ~MIME_PIPE; 592 break; 593 case 't': 594 v = (char*)type; 595 break; 596 case '{': 597 for (t = s; *s && *s != '}'; s++); 598 if (*s && (c = s++ - t) && (pp.next = (char*)opts)) 599 while (arg(&pp, 0)) 600 if (pp.name.size == c && !strncasecmp(pp.name.data, t, c)) 601 { 602 if (pp.value.size) 603 sfwrite(mp->buf, pp.value.data, pp.value.size); 604 break; 605 } 606 continue; 607 default: 608 sfputc(mp->buf, c); 609 continue; 610 } 611 if (v && *v) 612 n = strlen(v); 613 else if (t) 614 v = t; 615 else 616 continue; 617 sfputr(mp->buf, fmtquote(v, 0, 0, n, FMT_SHELL), -1); 618 continue; 619 default: 620 sfputc(mp->buf, c); 621 continue; 622 } 623 break; 624 } 625 return sfstruse(mp->buf); 626 } 627 628 /* 629 * return expanded command/path/value for <view,name,type,opts> 630 * return value valid until next mime*() call 631 */ 632 633 char* 634 mimeview(Mime_t* mp, const char* view, const char* name, const char* type, const char* opts) 635 { 636 Ent_t* ent; 637 Cap_t* cap; 638 Att_t* att; 639 char* s; 640 int c; 641 642 if (ent = find(mp, type)) 643 { 644 cap = ent->cap; 645 if (!view || strcasecmp(view, "test")) 646 while (s = cap->test) 647 { 648 if (s = expand(mp, s, name, type, opts)) 649 { 650 Parse_t a1; 651 Parse_t a2; 652 Parse_t a3; 653 Parse_t a4; 654 655 /* 656 * try to do a few common cases here 657 * mailcap consistency is a winning 658 * strategy 659 */ 660 661 a1.next = s; 662 if (arg(&a1, -1)) 663 { 664 if ((c = *a1.name.data == '!') && --a1.name.size <= 0 && !arg(&a1, -1)) 665 goto lose; 666 if (a1.name.size == 6 && strneq(a1.name.data, "strcmp", 6) || a1.name.size == 10 && strneq(a1.name.data, "strcasecmp", 10)) 667 { 668 a2.next = a1.next; 669 if (!arg(&a2, -1)) 670 goto lose; 671 a3.next = a2.next; 672 if (!arg(&a3, -1)) 673 goto lose; 674 if (a2.name.size != a3.name.size) 675 c ^= 0; 676 else c ^= (a1.name.size == 6 ? strncmp : strncasecmp)(a2.name.data, a3.name.data, a2.name.size) == 0; 677 if (c) 678 break; 679 goto skip; 680 } 681 else if (a1.name.size == 4 && strneq(a1.name.data, "test", 4)) 682 { 683 if (!arg(&a1, -1)) 684 goto lose; 685 a2.next = a1.next; 686 if (!arg(&a2, -1) || a2.name.size > 2 || a2.name.size == 1 && *a2.name.data != '=' || a2.name.size == 2 && (!strneq(a1.name.data, "!=", 2) || !strneq(a2.name.data, "==", 2))) 687 goto lose; 688 a3.next = a2.next; 689 if (!arg(&a3, -1)) 690 goto lose; 691 if (*a3.name.data == '`' && *(a3.name.data + a3.name.size - 1) == '`') 692 { 693 a4 = a3; 694 a3 = a1; 695 a1 = a4; 696 } 697 if (*a1.name.data == '`' && *(a1.name.data + a1.name.size - 1) == '`') 698 { 699 a1.next = a1.name.data + 1; 700 if (!arg(&a1, -1) || a1.name.size != 4 || !strneq(a1.name.data, "echo", 4) || !arg(&a1, -1)) 701 goto lose; 702 a4.next = a1.next; 703 if (!arg(&a4, 1) || a4.name.size < 21 || !strneq(a4.name.data, "| tr '[A-Z]' '[a-z]'`", 21)) 704 goto lose; 705 } 706 else 707 a4.name.size = 0; 708 c = *a2.name.data == '!'; 709 if (a1.name.size != a3.name.size) 710 c ^= 0; 711 else c ^= (a4.name.size ? strncasecmp : strncmp)(a1.name.data, a3.name.data, a1.name.size) == 0; 712 if (c) 713 break; 714 goto skip; 715 } 716 } 717 lose: 718 if (!system(s)) 719 break; 720 } 721 skip: 722 if (!(cap = cap->next)) 723 return 0; 724 } 725 att = &cap->att; 726 if (view && *view && !streq(view, "-")) 727 while (strcasecmp(view, att->name)) 728 if (!(att = att->next)) 729 return 0; 730 return expand(mp, att->value, name, type, opts); 731 } 732 return 0; 733 } 734 735 /* 736 * lower case identifier prefix strcmp 737 * if e!=0 then it will point to the next char after the match 738 */ 739 740 int 741 mimecmp(const char* s, const char* v, char** e) 742 { 743 int n; 744 745 while (isalnum(*v) || *v == *s && (*v == '_' || *v == '-' || *v == '/')) 746 if (n = lower(*s++) - lower(*v++)) 747 return n; 748 if (!isalnum(*s) && *s != '_' && *s != '-') 749 { 750 if (e) 751 *e = (char*)s; 752 return 0; 753 } 754 return lower(*s) - lower(*v); 755 } 756 757 static int 758 _mimecmp(const char* s, const char* v) 759 { 760 return (mimecmp(s, v, NULL)); 761 } 762 763 /* 764 * parse mime headers in strsearch(tab,num,siz) from s 765 * return >0 if mime header consumed 766 */ 767 768 int 769 mimehead(Mime_t* mp, void* tab, size_t num, size_t siz, char* s) 770 { 771 void* p; 772 char* e; 773 Parse_t pp; 774 Mimevalue_f set; 775 776 set = mp->disc->valuef; 777 if (!strncasecmp(s, "original-", 9)) 778 s += 9; 779 if (!strncasecmp(s, "content-", 8)) 780 { 781 s += 8; 782 if ((p = strsearch(tab, num, siz, _mimecmp, s, &e)) && *e == ':') 783 { 784 pp.next = e + 1; 785 if (arg(&pp, 1)) 786 { 787 if ((*set)(mp, p, pp.name.data, pp.name.size, mp->disc)) 788 return 0; 789 while (arg(&pp, 0)) 790 if (pp.value.size && 791 (p = strsearch(tab, num, siz, _mimecmp, pp.name.data, &e)) && 792 (*set)(mp, p, pp.value.data, pp.value.size, mp->disc)) 793 return 0; 794 return 1; 795 } 796 } 797 else if (strchr(s, ':')) 798 return 1; 799 } 800 return !strncasecmp(s, "x-", 2); 801 } 802 803 /* 804 * open a mime library handle 805 */ 806 807 Mime_t* 808 mimeopen(Mimedisc_t* disc) 809 { 810 Mime_t* mp; 811 812 if (!(mp = newof(0, Mime_t, 1, 0))) 813 return 0; 814 mp->id = lib; 815 mp->disc = disc; 816 mp->dict.key = offsetof(Ent_t, name); 817 mp->dict.comparf = order; 818 mp->dict.freef = drop; 819 if (!(mp->buf = sfstropen()) || !(mp->cap = dtopen(&mp->dict, Dtoset))) 820 { 821 mimeclose(mp); 822 return 0; 823 } 824 return mp; 825 } 826 827 /* 828 * close a mimeopen() handle 829 */ 830 831 int 832 mimeclose(Mime_t* mp) 833 { 834 if (mp) 835 { 836 if (mp->buf) 837 sfclose(mp->buf); 838 if (mp->cap) 839 dtclose(mp->cap); 840 if (mp->freef) 841 (*mp->freef)(mp); 842 free(mp); 843 } 844 return 0; 845 } 846