/* mime.c */ #include "EXTERN.h" #include "common.h" #include "list.h" #include "hash.h" #include "cache.h" #include "head.h" #include "search.h" #include "art.h" #include "artio.h" #include "artstate.h" #include "ng.h" #include "term.h" #include "decode.h" #include "respond.h" #include "env.h" #include "color.h" #include "util.h" #include "util2.h" #include "backpage.h" #include "charsubst.h" #include "INTERN.h" #include "mime.h" #include "mime.ih" static char text_plain[] = "text/plain"; void mime_init() { char* s; char* t; char* mcname; mimecap_list = new_list(0,-1,sizeof(MIMECAP_ENTRY),40,LF_ZERO_MEM,NULL); if ((mcname = getenv("MIMECAPS")) == NULL) mcname = getval("MAILCAPS", MIMECAP); mcname = s = savestr(mcname); do { if ((t = index(s, ':')) != NULL) *t++ = '\0'; if (*s) mime_ReadMimecap(s); s = t; } while (s && *s); free(mcname); } void mime_ReadMimecap(mcname) char* mcname; { FILE* fp; char* bp; char* s; char* t; char* arg; int buflen = 2048; int linelen; MIMECAP_ENTRY* mcp; int i; if ((fp = fopen(filexp(mcname), "r")) == NULL) return; bp = safemalloc(buflen); for (i = mimecap_list->high; !feof(fp); ) { *(s = bp) = '\0'; linelen = 0; while (fgets(s, buflen - linelen, fp)) { if (*s == '#') continue; linelen += strlen(s); if (linelen == 0) continue; if (bp[linelen-1] == '\n') { if (--linelen == 0) continue; if (bp[linelen-1] != '\\') { bp[linelen] = '\0'; break; } bp[--linelen] = '\0'; } if (linelen+1024 > buflen) { buflen *= 2; bp = saferealloc(bp, buflen); } s = bp + linelen; } for (s = bp; isspace(*s); s++) ; if (!*s) continue; t = mime_ParseEntryArg(&s); if (!s) { fprintf(stderr, "trn: Ignoring invalid mimecap entry: %s\n", bp); continue; } mcp = mimecap_ptr(++i); mcp->contenttype = savestr(t); mcp->command = savestr(mime_ParseEntryArg(&s)); while (s) { t = mime_ParseEntryArg(&s); if ((arg = index(t, '=')) != NULL) { char* f = arg+1; while (arg != t && isspace(arg[-1])) arg--; *arg++ = '\0'; while (isspace(*f)) f++; if (*f == '"') f = cpytill(arg,f+1,'"'); else arg = f; } if (*t) { if (strcaseEQ(t, "needsterminal")) mcp->flags |= MCF_NEEDSTERMINAL; else if (strcaseEQ(t, "copiousoutput")) mcp->flags |= MCF_COPIOUSOUTPUT; else if (arg && strcaseEQ(t, "test")) mcp->testcommand = savestr(arg); else if (arg && strcaseEQ(t, "description")) mcp->label = savestr(arg); else if (arg && strcaseEQ(t, "label")) mcp->label = savestr(arg); /* bogus old name for description */ } } } mimecap_list->high = i; free(bp); fclose(fp); } static char* mime_ParseEntryArg(cpp) char** cpp; { char* s = *cpp; char* f; char* t; while (isspace(*s)) s++; for (f = t = s; *f && *f != ';'; ) { if (*f == '\\') { if (*++f == '%') *t++ = '%'; else if (!*f) break; } *t++ = *f++; } while (isspace(*f) || *f == ';') f++; if (!*f) f = NULL; while (t != s && isspace(t[-1])) t--; *t = '\0'; *cpp = f; return s; } MIMECAP_ENTRY* mime_FindMimecapEntry(contenttype, skip_flags) char* contenttype; int skip_flags; { MIMECAP_ENTRY* mcp; int i; for (i = 0; i <= mimecap_list->high; i++) { mcp = mimecap_ptr(i); if (!(mcp->flags & skip_flags) && mime_TypesMatch(contenttype, mcp->contenttype)) { if (!mcp->testcommand) return mcp; if (mime_Exec(mcp->testcommand) == 0) return mcp; } } return NULL; } bool mime_TypesMatch(ct,pat) char* ct; char* pat; { char* s = index(pat,'/'); int len = (s? s - pat : strlen(pat)); bool iswild = (!s || strEQ(s+1,"*")); return strcaseEQ(ct,pat) || (iswild && strncaseEQ(ct,pat,len) && ct[len] == '/'); } int mime_Exec(cmd) char* cmd; { char* f; char* t; for (f = cmd, t = cmd_buf; *f && t-cmd_buf < CBUFLEN-2; f++) { if (*f == '%') { switch (*++f) { case 's': safecpy(t, decode_filename, CBUFLEN-(t-cmd_buf)); t += strlen(t); break; case 't': *t++ = '\''; safecpy(t, mime_section->type_name, CBUFLEN-(t-cmd_buf)-1); t += strlen(t); *t++ = '\''; break; case '{': { char* s = index(f, '}'); char* p; if (!s) return -1; f++; *s = '\0'; p = mime_FindParam(mime_section->type_params, f); *s = '}'; /* restore */ f = s; *t++ = '\''; safecpy(t, p, CBUFLEN-(t-cmd_buf)-1); t += strlen(t); *t++ = '\''; break; } case '%': *t++ = '%'; break; case 'n': case 'F': return -1; } } else *t++ = *f; } *t = '\0'; return doshell(sh, cmd_buf); } void mime_InitSections() { while (mime_PopSection()) ; mime_ClearStruct(mime_section); mime_state = NOT_MIME; } void mime_PushSection() { MIME_SECT* mp = (MIME_SECT*)safemalloc(sizeof (MIME_SECT)); bzero((char*)mp, sizeof (MIME_SECT)); mp->prev = mime_section; mime_section = mp; } bool mime_PopSection() { MIME_SECT* mp = mime_section->prev; if (mp) { mime_ClearStruct(mime_section); free((char*)mime_section); mime_section = mp; mime_state = mp->type; return TRUE; } mime_state = mime_article.type; return FALSE; } /* Free up this mime structure's resources */ void mime_ClearStruct(mp) MIME_SECT* mp; { safefree0(mp->filename); safefree0(mp->type_name); safefree0(mp->type_params); safefree0(mp->boundary); safefree0(mp->html_blks); mp->type = NOT_MIME; mp->encoding = MENCODE_NONE; mp->part = mp->total = mp->boundary_len = mp->flags = mp->html = mp->html_blkcnt = 0; mp->html_line_start = 0; } /* Setup mime_article structure based on article's headers */ void mime_SetArticle() { char* s; mime_InitSections(); /*$$ Check mime version #? */ multimedia_mime = FALSE; is_mime = (htype[MIMEVER_LINE].flags & HT_MAGIC) && htype[MIMEVER_LINE].minpos >= 0; if (is_mime) { s = fetchlines(art,CONTXFER_LINE); mime_ParseEncoding(mime_section,s); free(s); s = fetchlines(art,CONTTYPE_LINE); mime_ParseType(mime_section,s); free(s); s = fetchlines(art,CONTDISP_LINE); mime_ParseDisposition(mime_section,s); free(s); mime_state = mime_section->type; if (mime_state == NOT_MIME || (mime_state == TEXT_MIME && mime_section->encoding == MENCODE_NONE)) is_mime = FALSE; else if (!mime_section->type_name) mime_section->type_name = savestr(text_plain); } } /* Use the Content-Type to set values in the mime structure */ void mime_ParseType(mp, s) MIME_SECT* mp; char* s; { char* t; safefree0(mp->type_name); safefree0(mp->type_params); mp->type_params = mime_ParseParams(s); if (!*s) { mp->type = NOT_MIME; return; } mp->type_name = savestr(s); t = mime_FindParam(mp->type_params,"name"); if (t) { safefree(mp->filename); mp->filename = savestr(t); } if (strncaseEQ(s, "text", 4)) { mp->type = TEXT_MIME; s += 4; if (*s++ != '/') return; #if 0 t = mime_FindParam(mp->type_params,"charset"); if (t && strncaseNE(t, "us-ascii", 8)) mp->type = ISOTEXT_MIME; #endif if (strncaseEQ(s, "html", 4)) mp->type = HTMLTEXT_MIME; else if (strncaseEQ(s, "x-vcard", 7)) mp->type = UNHANDLED_MIME; return; } if (strncaseEQ(s, "message/", 8)) { s += 8; mp->type = MESSAGE_MIME; if (strcaseEQ(s, "partial")) { t = mime_FindParam(mp->type_params,"id"); if (!t) return; safefree(mp->filename); mp->filename = savestr(t); t = mime_FindParam(mp->type_params,"number"); if (t) mp->part = (short)atoi(t); t = mime_FindParam(mp->type_params,"total"); if (t) mp->total = (short)atoi(t); if (!mp->total) { mp->part = 0; return; } return; } return; } if (strncaseEQ(s, "multipart/", 10)) { s += 10; t = mime_FindParam(mp->type_params,"boundary"); if (!t) { mp->type = UNHANDLED_MIME; return; } if (strncaseEQ(s, "alternative", 11)) mp->flags |= MSF_ALTERNATIVE; safefree(mp->boundary); mp->boundary = savestr(t); mp->boundary_len = (short)strlen(t); mp->type = MULTIPART_MIME; return; } if (strncaseEQ(s, "image/", 6)) { mp->type = IMAGE_MIME; return; } if (strncaseEQ(s, "audio/", 6)) { mp->type = AUDIO_MIME; return; } mp->type = UNHANDLED_MIME; } /* Use the Content-Disposition to set values in the mime structure */ void mime_ParseDisposition(mp, s) MIME_SECT* mp; char* s; { char* params; params = mime_ParseParams(s); if (strcaseEQ(s,"inline")) mp->flags |= MSF_INLINE; s = mime_FindParam(params,"filename"); if (s) { safefree(mp->filename); mp->filename = savestr(s); } safefree(params); } /* Use the Content-Transfer-Encoding to set values in the mime structure */ void mime_ParseEncoding(mp, s) MIME_SECT* mp; char* s; { s = mime_SkipWhitespace(s); if (!*s) { mp->encoding = MENCODE_NONE; return; } if (*s == '7' || *s == '8') { if (strncaseEQ(s+1, "bit", 3)) { s += 4; mp->encoding = MENCODE_NONE; } } else if (strncaseEQ(s, "quoted-printable", 16)) { s += 16; mp->encoding = MENCODE_QPRINT; } else if (strncaseEQ(s, "binary", 6)) { s += 6; mp->encoding = MENCODE_NONE; } else if (strncaseEQ(s, "base64", 6)) { s += 6; mp->encoding = MENCODE_BASE64; } else if (strncaseEQ(s, "x-uue", 5)) { s += 5; mp->encoding = MENCODE_UUE; if (strncaseEQ(s, "ncode", 5)) s += 5; } else { mp->encoding = MENCODE_UNHANDLED; return; } if (*s != '\0' && !isspace(*s) && *s != ';' && *s != '(') mp->encoding = MENCODE_UNHANDLED; } /* Parse a multipart mime header and affect the *mime_section structure */ void mime_ParseSubheader(ifp, next_line) FILE* ifp; char* next_line; { static char* line = NULL; static int line_size = 0; char* s; int pos, linetype, len; mime_ClearStruct(mime_section); mime_section->type = TEXT_MIME; for (;;) { for (pos = 0; ; pos += strlen(line+pos)) { len = pos + (next_line? strlen(next_line) : 0) + LBUFLEN; if (line_size < len) { line_size = len + LBUFLEN; line = saferealloc(line, line_size); } if (next_line) { safecpy(line+pos, next_line, line_size - pos); next_line = NULL; } else if (ifp) { if (!fgets(line + pos, LBUFLEN, ifp)) break; } else if (!readart(line + pos, LBUFLEN)) break; if (line[0] == '\n') break; if (pos && line[pos] != ' ' && line[pos] != '\t') { next_line = line + pos; line[pos-1] = '\0'; break; } } s = index(line,':'); if (s == NULL) break; linetype = set_line_type(line,s); switch (linetype) { case CONTTYPE_LINE: mime_ParseType(mime_section,s+1); break; case CONTXFER_LINE: mime_ParseEncoding(mime_section,s+1); break; case CONTDISP_LINE: mime_ParseDisposition(mime_section,s+1); break; case CONTNAME_LINE: safefree(mime_section->filename); s = mime_SkipWhitespace(s+1); mime_section->filename = savestr(s); break; #if 0 case CONTLEN_LINE: mime_section->content_len = atol(s+1); break; #endif } } mime_state = mime_section->type; if (!mime_section->type_name) mime_section->type_name = savestr(text_plain); } void mime_SetState(bp) char* bp; { int ret; if (mime_state == BETWEEN_MIME) { mime_ParseSubheader((FILE*)NULL,bp); *bp = '\0'; if (mime_section->prev->flags & MSF_ALTERNADONE) mime_state = ALTERNATE_MIME; else if (mime_section->prev->flags & MSF_ALTERNATIVE) mime_section->prev->flags |= MSF_ALTERNADONE; } while (mime_state == MESSAGE_MIME) { mime_PushSection(); mime_ParseSubheader((FILE*)NULL,*bp? bp : (char*)NULL); *bp = '\0'; } if (mime_state == MULTIPART_MIME) { mime_PushSection(); mime_state = SKIP_MIME; /* Skip anything before 1st part */ } ret = mime_EndOfSection(bp); switch (ret) { case 0: break; case 1: while (!mime_section->prev->boundary_len) mime_PopSection(); mime_state = BETWEEN_MIME; break; case 2: while (!mime_section->prev->boundary_len) mime_PopSection(); mime_PopSection(); mime_state = END_OF_MIME; break; } } int mime_EndOfSection(bp) char* bp; { MIME_SECT* mp = mime_section->prev; while (mp && !mp->boundary_len) mp = mp->prev; if (mp) { /* have we read all the data in this part? */ if (bp[0] == '-' && bp[1] == '-' && strnEQ(bp+2,mp->boundary,mp->boundary_len)) { int len = 2 + mp->boundary_len; /* have we found the last boundary? */ if (bp[len] == '-' && bp[len+1] == '-' && (bp[len+2] == '\n' || bp[len+2] == '\0')) return 2; return bp[len] == '\n' || bp[len] == '\0'; } } return 0; } /* Return a saved string of all the extra parameters on this mime * header line. The passed-in string is transformed into just the * first word on the line. */ char* mime_ParseParams(str) char* str; { char* s; char* t; char* e; s = e = mime_SkipWhitespace(str); while (*e && *e != ';' && !isspace(*e) && *e != '(') e++; t = savestr(mime_SkipWhitespace(e)); *e = '\0'; if (s != str) safecpy(str, s, e - s + 1); str = s = t; while (*s == ';') { s = mime_SkipWhitespace(s+1); while (*s && *s != ';' && *s != '(' && *s != '=' && !isspace(*s)) *t++ = *s++; s = mime_SkipWhitespace(s); if (*s == '=') { *t++ = *s; s = mime_SkipWhitespace(s+1); if (*s == '"') { s = cpytill(t,s+1,'"'); if (*s == '"') s++; t += strlen(t); } else while (*s && *s != ';' && !isspace(*s) && *s != '(') *t++ = *s++; } *t++ = '\0'; } *t = '\0'; return str; } char* mime_FindParam(s, param) char* s; char* param; { int param_len = strlen(param); while (s && *s) { if (strncaseEQ(s, param, param_len) && s[param_len] == '=') return s + param_len + 1; s += strlen(s) + 1; } return NULL; } /* Skip whitespace and RFC-822 comments. */ char* mime_SkipWhitespace(s) char* s; { int comment_level = 0; while (*s) { if (*s == '(') { s++; comment_level++; while (comment_level) { switch (*s++) { case '\0': return s-1; case '\\': s++; break; case '(': comment_level++; break; case ')': comment_level--; break; } } } else if (!isspace(*s)) break; else s++; } return s; } void mime_DecodeArticle(view) bool_int view; { MIMECAP_ENTRY* mcp = NULL; seekart(savefrom); *art_line = '\0'; while (1) { if (mime_state != MESSAGE_MIME || !mime_section->total) { if (!readart(art_line,sizeof art_line)) break; mime_SetState(art_line); } switch (mime_state) { case BETWEEN_MIME: case END_OF_MIME: break; case TEXT_MIME: case HTMLTEXT_MIME: case ISOTEXT_MIME: case MESSAGE_MIME: /* $$ Check for uuencoded file here? */ mime_state = SKIP_MIME; /* FALL THROUGH */ case SKIP_MIME: { MIME_SECT* mp = mime_section; while ((mp = mp->prev) != NULL && !mp->boundary_len) ; if (!mp) return; break; } default: if (view) { mcp = mime_FindMimecapEntry(mime_section->type_name,0); if (!mcp) { printf("No view method for %s -- skipping.\n", mime_section->type_name); mime_state = SKIP_MIME; break; } } mime_state = DECODE_MIME; if (decode_piece(mcp, *art_line == '\n'? NULL : art_line) != 0) { mime_SetState(art_line); if (mime_state == DECODE_MIME) mime_state = SKIP_MIME; } else { if (*msg) { newline(); fputs(msg,stdout); } mime_state = SKIP_MIME; } newline(); break; } } } void mime_Description(mp, s, limit) MIME_SECT* mp; char* s; int limit; { char* fn = decode_fix_fname(mp->filename); int len, flen = strlen(fn); limit -= 2; /* leave room for the trailing ']' and '\n' */ sprintf(s, "[Attachment type=%s, name=", mp->type_name); len = strlen(s); if (len + flen <= limit) sprintf(s+len, "%s]\n", fn); else if (len+3 >= limit) strcpy(s+limit-3, "...]\n"); else { #if 0 sprintf(s+len, "...%s]\n", fn + flen - (limit-(len+3))); #else safecpy(s+len, fn, limit - (len+3)); strcat(s, "...]\n"); #endif } } #define XX 255 static Uchar index_hex[256] = { XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,XX,XX, XX,XX,XX,XX, XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, }; int qp_decodestring(t, f, in_header) char* t; char* f; bool_int in_header; { char* save_t = t; while (*f) { switch (*f) { case '_': if (in_header) { *t++ = ' '; f++; } else *t++ = *f++; break; case '=': /* decode a hex-value */ if (f[1] == '\n') { f += 2; break; } if (index_hex[(Uchar)f[1]] != XX && index_hex[(Uchar)f[2]] != XX) { *t = (index_hex[(Uchar)f[1]] << 4) + index_hex[(Uchar)f[2]]; f += 3; if (*t != '\r') t++; break; } /* FALL THROUGH */ default: *t++ = *f++; break; } } *t = '\0'; return t - save_t; } int qp_decode(ifp,state) FILE* ifp; int state; { static FILE* ofp = NULL; int c1, c2; if (state == DECODE_DONE) { if (ofp) fclose(ofp); ofp = NULL; return state; } if (state == DECODE_START) { char* filename = decode_fix_fname(mime_section->filename); ofp = fopen(filename, FOPEN_WB); if (!ofp) return DECODE_ERROR; erase_line(0); printf("Decoding %s", filename); if (nowait_fork) fflush(stdout); else newline(); } while ((c1 = mime_getc(ifp)) != EOF) { check_c1: if (c1 == '=') { c1 = mime_getc(ifp); if (c1 == '\n') continue; if (index_hex[(Uchar)c1] == XX) { putc('=', ofp); goto check_c1; } c2 = mime_getc(ifp); if (index_hex[(Uchar)c2] == XX) { putc('=', ofp); putc(c1, ofp); c1 = c2; goto check_c1; } c1 = (index_hex[(Uchar)c1] << 4) | index_hex[(Uchar)c2]; if (c1 != '\r') putc(c1, ofp); } else putc(c1, ofp); } return DECODE_MAYBEDONE; } static Uchar index_b64[256] = { XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63, 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, }; int b64_decodestring(t, f) char* t; char* f; { char* save_t = t; Uchar ch1, ch2; while (*f && *f != '=') { ch1 = index_b64[(Uchar)*f++]; if (ch1 == XX) continue; do { if (!*f || *f == '=') goto dbl_break; ch2 = index_b64[(Uchar)*f++]; } while (ch2 == XX); *t++ = (ch1 << 2) | (ch2 >> 4); do { if (!*f || *f == '=') goto dbl_break; ch1 = index_b64[(Uchar)*f++]; } while (ch1 == XX); *t++ = ((ch2 & 0x0f) << 4) | (ch1 >> 2); do { if (!*f || *f == '=') goto dbl_break; ch2 = index_b64[(Uchar)*f++]; } while (ch2 == XX); *t++ = ((ch1 & 0x03) << 6) | ch2; } dbl_break: *t = '\0'; return t - save_t; } int b64_decode(ifp, state) FILE* ifp; int state; { static FILE* ofp = NULL; int c1, c2, c3, c4; if (state == DECODE_DONE) { all_done: if (ofp) fclose(ofp); ofp = NULL; return state; } if (state == DECODE_START) { char* filename = decode_fix_fname(mime_section->filename); ofp = fopen(filename, FOPEN_WB); if (!ofp) return DECODE_ERROR; printf("Decoding %s", filename); if (nowait_fork) fflush(stdout); else newline(); state = DECODE_ACTIVE; } while ((c1 = mime_getc(ifp)) != EOF) { if (c1 != '=' && index_b64[c1] == XX) continue; do { c2 = mime_getc(ifp); if (c2 == EOF) return state; } while (c2 != '=' && index_b64[c2] == XX); do { c3 = mime_getc(ifp); if (c3 == EOF) return state; } while (c3 != '=' && index_b64[c3] == XX); do { c4 = mime_getc(ifp); if (c4 == EOF) return state; } while (c4 != '=' && index_b64[c4] == XX); if (c1 == '=' || c2 == '=') { state = DECODE_DONE; break; } c1 = index_b64[c1]; c2 = index_b64[c2]; c1 = (c1 << 2) | (c2 >> 4); putc(c1, ofp); if (c3 == '=') { state = DECODE_DONE; break; } c3 = index_b64[c3]; c2 = ((c2 & 0x0f) << 4) | (c3 >> 2); putc(c2, ofp); if (c4 == '=') { state = DECODE_DONE; break; } c4 = index_b64[c4]; c3 = ((c3 & 0x03) << 6) | c4; putc(c3, ofp); } if (state == DECODE_DONE) goto all_done; return DECODE_MAYBEDONE; } #undef XX static int mime_getc(fp) FILE* fp; { if (fp) return fgetc(fp); if (!mime_getc_line || !*mime_getc_line) { mime_getc_line = readart(art_line,sizeof art_line); if (mime_EndOfSection(art_line)) return EOF; if (!mime_getc_line) return EOF; } return *mime_getc_line++; } int cat_decode(ifp, state) FILE* ifp; int state; { static FILE* ofp = NULL; if (state == DECODE_DONE) { if (ofp) fclose(ofp); ofp = NULL; return state; } if (state == DECODE_START) { char* filename = decode_fix_fname(mime_section->filename); ofp = fopen(filename, FOPEN_WB); if (!ofp) return DECODE_ERROR; printf("Decoding %s", filename); if (nowait_fork) fflush(stdout); else newline(); } if (ifp) { while (fgets(buf, sizeof buf, ifp)) fputs(buf, ofp); } else { while (readart(buf, sizeof buf)) { if (mime_EndOfSection(buf)) break; fputs(buf, ofp); } } return DECODE_MAYBEDONE; } static int word_wrap_in_pre, normal_word_wrap, word_wrap; int filter_html(t, f) char* t; char* f; { static char tagword[32]; static int tagword_len; char* bp; char* cp; if (word_wrap_offset < 0) { normal_word_wrap = COLS - 8; word_wrap_in_pre = 0; } else word_wrap_in_pre = normal_word_wrap = COLS - word_wrap_offset; if (normal_word_wrap <= 20) normal_word_wrap = 0; if (word_wrap_in_pre <= 20) word_wrap_in_pre = 0; word_wrap = (mime_section->html & HF_IN_PRE)? word_wrap_in_pre : normal_word_wrap; if (!mime_section->html_line_start) mime_section->html_line_start = t - artbuf; if (!mime_section->html_blks) { mime_section->html_blks = (HBLK*)safemalloc(HTML_MAX_BLOCKS * sizeof (HBLK)); } for (bp = t; *f; f++) { if (mime_section->html & HF_IN_DQUOTE) { if (*f == '"') mime_section->html &= ~HF_IN_DQUOTE; else if (tagword_len < (sizeof tagword) - 1) tagword[tagword_len++] = *f; } else if (mime_section->html & HF_IN_SQUOTE) { if (*f == '\'') mime_section->html &= ~HF_IN_SQUOTE; else if (tagword_len < (sizeof tagword) - 1) tagword[tagword_len++] = *f; } else if (mime_section->html & HF_IN_COMMENT) { if (*f == '-' && f[1] == '-') { f++; mime_section->html &= ~HF_IN_COMMENT; } } else if (mime_section->html & HF_IN_TAG) { if (*f == '>') { mime_section->html &= ~HF_IN_TAG; tagword[tagword_len] = '\0'; if (*tagword == '/') t = tag_action(t, tagword+1, CLOSING_TAG); else t = tag_action(t, tagword, OPENING_TAG); } else if (*f == '-' && f[1] == '-') { f++; mime_section->html |= HF_IN_COMMENT; } else if (*f == '"') mime_section->html |= HF_IN_DQUOTE; else if (*f == '\'') mime_section->html |= HF_IN_SQUOTE; else if (tagword_len < (sizeof tagword) - 1) { tagword[tagword_len++] = AT_GREY_SPACE(f)? ' ' : *f; } } else if (*f == '<') { tagword_len = 0; mime_section->html |= HF_IN_TAG; } else if (mime_section->html & HF_IN_HIDING) ; else if (*f == '&') { t = output_prep(t); if (strncaseEQ(f+1,"lt;",3)) { *t++ = '<'; f += 3; } else if (strncaseEQ(f+1,"gt;",3)) { *t++ = '>'; f += 3; } else if (strncaseEQ(f+1,"amp;",4)) { *t++ = '&'; f += 4; } else if (strncaseEQ(f+1,"nbsp;",5)) { *t++ = ' '; f += 5; } else if (strncaseEQ(f+1,"quot;",5)) { *t++ = '"'; f += 5; } else *t++ = *f; mime_section->html |= HF_NL_OK|HF_P_OK|HF_SPACE_OK; } else if (AT_GREY_SPACE(f) && !(mime_section->html & HF_IN_PRE)) { /* We don't want to call output_prep() here. */ if (mime_section->html & HF_SPACE_OK) { mime_section->html &= ~HF_SPACE_OK; *t++ = ' '; } } else if (*f == '\n') { /* Handle the HF_IN_PRE case */ t = output_prep(t); mime_section->html |= HF_NL_OK; t = do_newline(t, HF_NL_OK); } else { t = output_prep(t); *t++ = *f; mime_section->html |= HF_NL_OK|HF_P_OK|HF_SPACE_OK; } if (word_wrap && t - artbuf - mime_section->html_line_start > COLS) { char* line_start = mime_section->html_line_start + artbuf; for (cp = line_start + word_wrap; cp > line_start && *cp != ' ' && *cp != '\t'; cp--) ; if (cp == line_start) { for (cp = line_start + word_wrap; cp - line_start <= COLS && *cp != ' ' && *cp != '\t'; cp++) ; if (cp - line_start > COLS) { mime_section->html_line_start += COLS; cp = NULL; } } if (cp) { int flag_save = mime_section->html; int fudge; char* s; mime_section->html |= HF_NL_OK; cp = line_start = do_newline(cp, HF_NL_OK); fudge = do_indent((char*)NULL); while (*cp == ' ' || *cp == '\t') cp++; if ((fudge -= cp - line_start) != 0) { if (fudge < 0) bcopy(cp, cp + fudge, t - cp); else for (s = t; s-- != cp; ) s[fudge] = *s; (void) do_indent(line_start); t += fudge; } mime_section->html = flag_save; } } } *t = '\0'; return t - bp; } static char bullets[3] = {'*', 'o', '+'}; static char letters[2] = {'a', 'A'}; static char roman_letters[] = { 'M', 'D', 'C', 'L', 'X', 'V', 'I'}; static int roman_values[] = {1000, 500, 100, 50, 10, 5, 1 }; static char* tag_action(t, word, opening_tag) char* t; char* word; bool_int opening_tag; { char* cp; int i, j, tnum, len, itype, ch, cnt, num; bool match = 0; HBLK* blks = mime_section->html_blks; for (cp = word; *cp && *cp != ' '; cp++) ; len = cp - word; if (!isalpha(*word)) return t; ch = isupper(*word)? tolower(*word) : *word; for (tnum = 0; tnum < LAST_TAG && *tagattr[tnum].name != ch; tnum++) ; for ( ; tnum < LAST_TAG && *tagattr[tnum].name == ch; tnum++) { if (len == tagattr[tnum].length && strncaseEQ(word, tagattr[tnum].name, len)) { match = 1; break; } } if (!match) return t; if (!opening_tag && !(tagattr[tnum].flags & (TF_BLOCK|TF_HAS_CLOSE))) return t; if ((mime_section->html & HF_IN_HIDING) && (opening_tag || tnum != blks[mime_section->html_blkcnt-1].tnum)) return t; if (tagattr[tnum].flags & TF_BR) mime_section->html |= HF_NL_OK; if (opening_tag) { if (tagattr[tnum].flags & TF_NL) { t = output_prep(t); t = do_newline(t, HF_NL_OK); } if ((num = tagattr[tnum].flags & (TF_P|TF_LIST)) == TF_P || (num == (TF_P|TF_LIST) && !(mime_section->html & HF_COMPACT))) { t = output_prep(t); t = do_newline(t, HF_P_OK); } if (tagattr[tnum].flags & TF_SPACE) { if (mime_section->html & HF_SPACE_OK) { mime_section->html &= ~HF_SPACE_OK; *t++ = ' '; } } if (tagattr[tnum].flags & TF_TAB) { if (mime_section->html & HF_NL_OK) { mime_section->html &= ~HF_SPACE_OK; *t++ = '\t'; } } if ((tagattr[tnum].flags & TF_BLOCK) && mime_section->html_blkcnt < HTML_MAX_BLOCKS) { j = mime_section->html_blkcnt++; blks[j].tnum = tnum; blks[j].indent = 0; blks[j].cnt = 0; if (tagattr[tnum].flags & TF_LIST) mime_section->html |= HF_COMPACT; else mime_section->html &= ~HF_COMPACT; } else j = mime_section->html_blkcnt - 1; if ((tagattr[tnum].flags & (TF_BLOCK|TF_HIDE)) == (TF_BLOCK|TF_HIDE)) mime_section->html |= HF_IN_HIDING; switch (tnum) { case TAG_BLOCKQUOTE: if (((cp = find_attr(word, "type")) != NULL && strncaseEQ(cp, "cite", 4)) || ((cp = find_attr(word, "style")) != NULL && strncaseEQ(cp, "border-left:", 12))) blks[j].indent = '>'; else blks[j].indent = ' '; break; case TAG_HR: t = output_prep(t); *t++ = '-'; *t++ = '-'; mime_section->html |= HF_NL_OK; t = do_newline(t, HF_NL_OK); break; case TAG_IMG: t = output_prep(t); if (mime_section->html & HF_SPACE_OK) *t++ = ' '; strcpy(t, "[Image] "); t += 8; mime_section->html &= ~HF_SPACE_OK; break; case TAG_OL: itype = 4; if ((cp = find_attr(word, "type")) != NULL) { switch (*cp) { case '1': itype = 4; break; case 'a': itype = 5; break; case 'A': itype = 6; break; case 'i': itype = 7; break; case 'I': itype = 8; break; } } blks[j].indent = itype; break; case TAG_UL: itype = 1; if ((cp = find_attr(word, "type")) != NULL) { switch (*cp) { case 'd': case 'D': itype = 1; break; case 'c': case 'C': itype = 2; break; case 's': case 'S': itype = 3; break; } } else { for (i = 0; i < mime_section->html_blkcnt; i++) { if (blks[i].indent && blks[i].indent < ' ') { if (++itype == 3) break; } } } blks[j].indent = itype; break; case TAG_LI: t = output_prep(t); ch = j < 0? ' ' : blks[j].indent; switch (ch) { case 1: case 2: case 3: t[-2] = bullets[ch-1]; break; case 4: sprintf(t-4, "%2d. ", ++blks[j].cnt); if (*t) t += strlen(t); break; case 5: case 6: cnt = blks[j].cnt++; if (cnt >= 26*26) cnt = blks[j].cnt = 0; if (cnt >= 26) t[-4] = letters[ch-5] + (cnt / 26) - 1; t[-3] = letters[ch-5] + (cnt % 26); t[-2] = '.'; break; case 7: for (i = 0; i < 7; i++) { if (isupper(roman_letters[i])) roman_letters[i] = tolower(roman_letters[i]); } goto roman_numerals; case 8: for (i = 0; i < 7; i++) { if (islower(roman_letters[i])) roman_letters[i] = toupper(roman_letters[i]); } roman_numerals: cp = t - 6; cnt = ++blks[j].cnt; for (i = 0; cnt && i < 7; i++) { num = roman_values[i]; while (cnt >= num) { *cp++ = roman_letters[i]; cnt -= num; } j = (i | 1) + 1; if (j < 7) { num -= roman_values[j]; if (cnt >= num) { *cp++ = roman_letters[j]; *cp++ = roman_letters[i]; cnt -= num; } } } if (cp < t - 2) { t -= 2; for (cnt = t - cp; cp-- != t - 4; ) cp[cnt] = *cp; while (cnt--) *++cp = ' '; } else t = cp; *t++ = '.'; *t++ = ' '; break; default: *t++ = '*'; *t++ = ' '; break; } mime_section->html |= HF_NL_OK|HF_P_OK; break; case TAG_PRE: mime_section->html |= HF_IN_PRE; word_wrap = word_wrap_in_pre; break; } } else { if ((tagattr[tnum].flags & TF_BLOCK)) { for (j = mime_section->html_blkcnt; j--; ) { if (blks[j].tnum == tnum) { for (i = mime_section->html_blkcnt; --i > j; ) { t = tag_action(t, tagattr[blks[i].tnum].name, CLOSING_TAG); } mime_section->html_blkcnt = j; break; } } mime_section->html &= ~HF_IN_HIDING; while (j-- > 0) { if (tagattr[blks[j].tnum].flags & TF_HIDE) { mime_section->html |= HF_IN_HIDING; break; } } } j = mime_section->html_blkcnt - 1; if (j >= 0 && (tagattr[blks[j].tnum].flags & TF_LIST)) mime_section->html |= HF_COMPACT; else mime_section->html &= ~HF_COMPACT; if ((tagattr[tnum].flags & TF_NL) && (mime_section->html & HF_NL_OK)) { mime_section->html |= HF_QUEUED_NL; mime_section->html &= ~HF_SPACE_OK; } if ((num = tagattr[tnum].flags & (TF_P|TF_LIST)) == TF_P || (num == (TF_P|TF_LIST) && !(mime_section->html & HF_COMPACT))) { if (mime_section->html & HF_P_OK) { mime_section->html |= HF_QUEUED_P; mime_section->html &= ~HF_SPACE_OK; } } switch (tnum) { case TAG_PRE: mime_section->html &= ~HF_IN_PRE; word_wrap = normal_word_wrap; break; } } return t; } static char* output_prep(t) char* t; { if (mime_section->html & HF_QUEUED_P) { mime_section->html &= ~HF_QUEUED_P; t = do_newline(t, HF_P_OK); } if (mime_section->html & HF_QUEUED_NL) { mime_section->html &= ~HF_QUEUED_NL; t = do_newline(t, HF_NL_OK); } return t + do_indent(t); } static char* do_newline(t, flag) char* t; int flag; { if (mime_section->html & flag) { mime_section->html &= ~(flag|HF_SPACE_OK); t += do_indent(t); *t++ = '\n'; mime_section->html_line_start = t - artbuf; mime_section->html |= HF_NEED_INDENT; } return t; } static int do_indent(t) char* t; { HBLK* blks; int j, ch, spaces, len = 0; if (!(mime_section->html & HF_NEED_INDENT)) return len; if (t) mime_section->html &= ~HF_NEED_INDENT; if ((blks = mime_section->html_blks) != NULL) { for (j = 0; j < mime_section->html_blkcnt; j++) { if ((ch = blks[j].indent) != 0) { switch (ch) { case '>': spaces = 1; break; case ' ': spaces = 3; break; case 7: case 8: ch = ' '; spaces = 5; break; default: ch = ' '; spaces = 3; break; } len += spaces + 1; if (len > 64) { len -= spaces + 1; break; } if (t) { *t++ = ch; while (spaces--) *t++ = ' '; } } } } return len; } static char* find_attr(str, attr) char* str; char* attr; { int len = strlen(attr); char* cp = str; char* s; while ((cp = index(cp+1, '=')) != NULL) { for (s = cp; s[-1] == ' '; s--) ; while (cp[1] == ' ') cp++; if (s - str > len && s[-len-1] == ' ' && strncaseEQ(s-len,attr,len)) return cp+1; } return NULL; }