1 /* mime.c
2  */
3 
4 #include "EXTERN.h"
5 #include "common.h"
6 #include "list.h"
7 #include "hash.h"
8 #include "cache.h"
9 #include "head.h"
10 #include "search.h"
11 #include "art.h"
12 #include "artio.h"
13 #include "artstate.h"
14 #include "ng.h"
15 #include "term.h"
16 #include "decode.h"
17 #include "respond.h"
18 #include "env.h"
19 #include "color.h"
20 #include "util.h"
21 #include "util2.h"
22 #include "backpage.h"
23 #include "charsubst.h"
24 #include "INTERN.h"
25 #include "mime.h"
26 #include "mime.ih"
27 
28 static char text_plain[] = "text/plain";
29 
30 void
mime_init()31 mime_init()
32 {
33     char* s;
34     char* t;
35     char* mcname;
36 
37     mimecap_list = new_list(0,-1,sizeof(MIMECAP_ENTRY),40,LF_ZERO_MEM,NULL);
38 
39     if ((mcname = getenv("MIMECAPS")) == NULL)
40 	mcname = getval("MAILCAPS", MIMECAP);
41     mcname = s = savestr(mcname);
42     do {
43 	if ((t = index(s, ':')) != NULL)
44 	    *t++ = '\0';
45 	if (*s)
46 	    mime_ReadMimecap(s);
47 	s = t;
48     } while (s && *s);
49     free(mcname);
50 }
51 
52 void
mime_ReadMimecap(mcname)53 mime_ReadMimecap(mcname)
54 char* mcname;
55 {
56     FILE* fp;
57     char* bp;
58     char* s;
59     char* t;
60     char* arg;
61     int buflen = 2048;
62     int linelen;
63     MIMECAP_ENTRY* mcp;
64     int i;
65 
66     if ((fp = fopen(filexp(mcname), "r")) == NULL)
67 	return;
68     bp = safemalloc(buflen);
69     for (i = mimecap_list->high; !feof(fp); ) {
70 	*(s = bp) = '\0';
71 	linelen = 0;
72 	while (fgets(s, buflen - linelen, fp)) {
73 	    if (*s == '#')
74 		continue;
75 	    linelen += strlen(s);
76 	    if (linelen == 0)
77 		continue;
78 	    if (bp[linelen-1] == '\n') {
79 		if (--linelen == 0)
80 		    continue;
81 		if (bp[linelen-1] != '\\') {
82 		    bp[linelen] = '\0';
83 		    break;
84 		}
85 		bp[--linelen] = '\0';
86 	    }
87 
88 	    if (linelen+1024 > buflen) {
89 		buflen *= 2;
90 		bp = saferealloc(bp, buflen);
91 	    }
92 
93 	    s = bp + linelen;
94 	}
95 	for (s = bp; isspace(*s); s++) ;
96 	if (!*s)
97 	    continue;
98 	t = mime_ParseEntryArg(&s);
99 	if (!s) {
100 	    fprintf(stderr, "trn: Ignoring invalid mimecap entry: %s\n", bp);
101 	    continue;
102 	}
103 	mcp = mimecap_ptr(++i);
104 	mcp->contenttype = savestr(t);
105 	mcp->command = savestr(mime_ParseEntryArg(&s));
106 	while (s) {
107 	    t = mime_ParseEntryArg(&s);
108 	    if ((arg = index(t, '=')) != NULL) {
109 		char* f = arg+1;
110 		while (arg != t && isspace(arg[-1])) arg--;
111 		*arg++ = '\0';
112 		while (isspace(*f)) f++;
113 		if (*f == '"')
114 		    f = cpytill(arg,f+1,'"');
115 		else
116 		    arg = f;
117 	    }
118 	    if (*t) {
119 		if (strcaseEQ(t, "needsterminal"))
120 		    mcp->flags |= MCF_NEEDSTERMINAL;
121 		else if (strcaseEQ(t, "copiousoutput"))
122 		    mcp->flags |= MCF_COPIOUSOUTPUT;
123 		else if (arg && strcaseEQ(t, "test"))
124 		    mcp->testcommand = savestr(arg);
125 		else if (arg && strcaseEQ(t, "description"))
126 		    mcp->label = savestr(arg);
127 		else if (arg && strcaseEQ(t, "label"))
128 		    mcp->label = savestr(arg); /* bogus old name for description */
129 	    }
130 	}
131     }
132     mimecap_list->high = i;
133     free(bp);
134     fclose(fp);
135 }
136 
137 static char*
mime_ParseEntryArg(cpp)138 mime_ParseEntryArg(cpp)
139 char** cpp;
140 {
141     char* s = *cpp;
142     char* f;
143     char* t;
144 
145     while (isspace(*s)) s++;
146 
147     for (f = t = s; *f && *f != ';'; ) {
148 	if (*f == '\\') {
149 	    if (*++f == '%')
150 		*t++ = '%';
151 	    else if (!*f)
152 		break;
153 	}
154 	*t++ = *f++;
155     }
156     while (isspace(*f) || *f == ';') f++;
157     if (!*f)
158 	f = NULL;
159     while (t != s && isspace(t[-1])) t--;
160     *t = '\0';
161     *cpp = f;
162     return s;
163 }
164 
165 MIMECAP_ENTRY*
mime_FindMimecapEntry(contenttype,skip_flags)166 mime_FindMimecapEntry(contenttype, skip_flags)
167 char* contenttype;
168 int skip_flags;
169 {
170     MIMECAP_ENTRY* mcp;
171     int i;
172 
173     for (i = 0; i <= mimecap_list->high; i++) {
174 	mcp = mimecap_ptr(i);
175 	if (!(mcp->flags & skip_flags)
176 	 && mime_TypesMatch(contenttype, mcp->contenttype)) {
177 	    if (!mcp->testcommand)
178 		return mcp;
179 	    if (mime_Exec(mcp->testcommand) == 0)
180 		return mcp;
181 	}
182     }
183     return NULL;
184 }
185 
186 bool
mime_TypesMatch(ct,pat)187 mime_TypesMatch(ct,pat)
188 char* ct;
189 char* pat;
190 {
191     char* s = index(pat,'/');
192     int len = (s? s - pat : strlen(pat));
193     bool iswild = (!s || strEQ(s+1,"*"));
194 
195     return strcaseEQ(ct,pat)
196 	|| (iswild && strncaseEQ(ct,pat,len) && ct[len] == '/');
197 }
198 
199 int
mime_Exec(cmd)200 mime_Exec(cmd)
201 char* cmd;
202 {
203     char* f;
204     char* t;
205 
206     for (f = cmd, t = cmd_buf; *f && t-cmd_buf < CBUFLEN-2; f++) {
207 	if (*f == '%') {
208 	    switch (*++f) {
209 	      case 's':
210 		safecpy(t, decode_filename, CBUFLEN-(t-cmd_buf));
211 		t += strlen(t);
212 		break;
213 	      case 't':
214 		*t++ = '\'';
215 		safecpy(t, mime_section->type_name, CBUFLEN-(t-cmd_buf)-1);
216 		t += strlen(t);
217 		*t++ = '\'';
218 		break;
219 	      case '{': {
220 		char* s = index(f, '}');
221 		char* p;
222                 if (!s)
223 		    return -1;
224 		f++;
225 		*s = '\0';
226 		p = mime_FindParam(mime_section->type_params, f);
227 		*s = '}'; /* restore */
228 		f = s;
229 		*t++ = '\'';
230 		safecpy(t, p, CBUFLEN-(t-cmd_buf)-1);
231 		t += strlen(t);
232 		*t++ = '\'';
233 		break;
234 	      }
235 	      case '%':
236 		*t++ = '%';
237 		break;
238 	      case 'n':
239 	      case 'F':
240 		return -1;
241 	    }
242         }
243 	else
244             *t++ = *f;
245     }
246     *t = '\0';
247 
248     return doshell(sh, cmd_buf);
249 }
250 
251 void
mime_InitSections()252 mime_InitSections()
253 {
254     while (mime_PopSection()) ;
255     mime_ClearStruct(mime_section);
256     mime_state = NOT_MIME;
257 }
258 
259 void
mime_PushSection()260 mime_PushSection()
261 {
262     MIME_SECT* mp = (MIME_SECT*)safemalloc(sizeof (MIME_SECT));
263     bzero((char*)mp, sizeof (MIME_SECT));
264     mp->prev = mime_section;
265     mime_section = mp;
266 }
267 
268 bool
mime_PopSection()269 mime_PopSection()
270 {
271     MIME_SECT* mp = mime_section->prev;
272     if (mp) {
273 	mime_ClearStruct(mime_section);
274 	free((char*)mime_section);
275 	mime_section = mp;
276 	mime_state = mp->type;
277 	return TRUE;
278     }
279     mime_state = mime_article.type;
280     return FALSE;
281 }
282 
283 /* Free up this mime structure's resources */
284 void
mime_ClearStruct(mp)285 mime_ClearStruct(mp)
286 MIME_SECT* mp;
287 {
288     safefree0(mp->filename);
289     safefree0(mp->type_name);
290     safefree0(mp->type_params);
291     safefree0(mp->boundary);
292     safefree0(mp->html_blks);
293     mp->type = NOT_MIME;
294     mp->encoding = MENCODE_NONE;
295     mp->part = mp->total = mp->boundary_len = mp->flags = mp->html
296 	     = mp->html_blkcnt = 0;
297     mp->html_line_start = 0;
298 }
299 
300 /* Setup mime_article structure based on article's headers */
301 void
mime_SetArticle()302 mime_SetArticle()
303 {
304     char* s;
305 
306     mime_InitSections();
307     /*$$ Check mime version #? */
308     multimedia_mime = FALSE;
309     is_mime = (htype[MIMEVER_LINE].flags & HT_MAGIC)
310 	    && htype[MIMEVER_LINE].minpos >= 0;
311     if (is_mime) {
312 	s = fetchlines(art,CONTXFER_LINE);
313 	mime_ParseEncoding(mime_section,s);
314 	free(s);
315 
316 	s = fetchlines(art,CONTTYPE_LINE);
317 	mime_ParseType(mime_section,s);
318 	free(s);
319 
320 	s = fetchlines(art,CONTDISP_LINE);
321 	mime_ParseDisposition(mime_section,s);
322 	free(s);
323 
324 	mime_state = mime_section->type;
325 	if (mime_state == NOT_MIME
326 	 || (mime_state == TEXT_MIME && mime_section->encoding == MENCODE_NONE))
327 	    is_mime = FALSE;
328 	else if (!mime_section->type_name)
329 	    mime_section->type_name = savestr(text_plain);
330     }
331 }
332 
333 /* Use the Content-Type to set values in the mime structure */
334 void
mime_ParseType(mp,s)335 mime_ParseType(mp, s)
336 MIME_SECT* mp;
337 char* s;
338 {
339     char* t;
340 
341     safefree0(mp->type_name);
342     safefree0(mp->type_params);
343 
344     mp->type_params = mime_ParseParams(s);
345     if (!*s) {
346 	mp->type = NOT_MIME;
347 	return;
348     }
349     mp->type_name = savestr(s);
350     t = mime_FindParam(mp->type_params,"name");
351     if (t) {
352 	safefree(mp->filename);
353 	mp->filename = savestr(t);
354     }
355 
356     if (strncaseEQ(s, "text", 4)) {
357 	mp->type = TEXT_MIME;
358 	s += 4;
359 	if (*s++ != '/')
360 	    return;
361 #if 0
362 	t = mime_FindParam(mp->type_params,"charset");
363 	if (t && strncaseNE(t, "us-ascii", 8))
364 	    mp->type = ISOTEXT_MIME;
365 #endif
366 	if (strncaseEQ(s, "html", 4))
367 	    mp->type = HTMLTEXT_MIME;
368 	else if (strncaseEQ(s, "x-vcard", 7))
369 	    mp->type = UNHANDLED_MIME;
370 	return;
371     }
372 
373     if (strncaseEQ(s, "message/", 8)) {
374 	s += 8;
375 	mp->type = MESSAGE_MIME;
376 	if (strcaseEQ(s, "partial")) {
377 	    t = mime_FindParam(mp->type_params,"id");
378 	    if (!t)
379 		return;
380 	    safefree(mp->filename);
381 	    mp->filename = savestr(t);
382 	    t = mime_FindParam(mp->type_params,"number");
383 	    if (t)
384 		mp->part = (short)atoi(t);
385 	    t = mime_FindParam(mp->type_params,"total");
386 	    if (t)
387 		mp->total = (short)atoi(t);
388 	    if (!mp->total) {
389 		mp->part = 0;
390 		return;
391 	    }
392 	    return;
393 	}
394 	return;
395     }
396 
397     if (strncaseEQ(s, "multipart/", 10)) {
398 	s += 10;
399 	t = mime_FindParam(mp->type_params,"boundary");
400 	if (!t) {
401 	    mp->type = UNHANDLED_MIME;
402 	    return;
403 	}
404 	if (strncaseEQ(s, "alternative", 11))
405 	    mp->flags |= MSF_ALTERNATIVE;
406 	safefree(mp->boundary);
407 	mp->boundary = savestr(t);
408 	mp->boundary_len = (short)strlen(t);
409 	mp->type = MULTIPART_MIME;
410 	return;
411     }
412 
413     if (strncaseEQ(s, "image/", 6)) {
414 	mp->type = IMAGE_MIME;
415 	return;
416     }
417 
418     if (strncaseEQ(s, "audio/", 6)) {
419 	mp->type = AUDIO_MIME;
420 	return;
421     }
422 
423     mp->type = UNHANDLED_MIME;
424 }
425 
426 /* Use the Content-Disposition to set values in the mime structure */
427 void
mime_ParseDisposition(mp,s)428 mime_ParseDisposition(mp, s)
429 MIME_SECT* mp;
430 char* s;
431 {
432     char* params;
433 
434     params = mime_ParseParams(s);
435     if (strcaseEQ(s,"inline"))
436 	mp->flags |= MSF_INLINE;
437 
438     s = mime_FindParam(params,"filename");
439     if (s) {
440 	safefree(mp->filename);
441 	mp->filename = savestr(s);
442     }
443     safefree(params);
444 }
445 
446 /* Use the Content-Transfer-Encoding to set values in the mime structure */
447 void
mime_ParseEncoding(mp,s)448 mime_ParseEncoding(mp, s)
449 MIME_SECT* mp;
450 char* s;
451 {
452     s = mime_SkipWhitespace(s);
453     if (!*s) {
454 	mp->encoding = MENCODE_NONE;
455 	return;
456     }
457     if (*s == '7' || *s == '8') {
458 	if (strncaseEQ(s+1, "bit", 3)) {
459 	    s += 4;
460 	    mp->encoding = MENCODE_NONE;
461 	}
462     }
463     else if (strncaseEQ(s, "quoted-printable", 16)) {
464 	s += 16;
465 	mp->encoding = MENCODE_QPRINT;
466     }
467     else if (strncaseEQ(s, "binary", 6)) {
468 	s += 6;
469 	mp->encoding = MENCODE_NONE;
470     }
471     else if (strncaseEQ(s, "base64", 6)) {
472 	s += 6;
473 	mp->encoding = MENCODE_BASE64;
474     }
475     else if (strncaseEQ(s, "x-uue", 5)) {
476 	s += 5;
477 	mp->encoding = MENCODE_UUE;
478 	if (strncaseEQ(s, "ncode", 5))
479 	    s += 5;
480     }
481     else {
482 	mp->encoding = MENCODE_UNHANDLED;
483 	return;
484     }
485     if (*s != '\0' && !isspace(*s) && *s != ';' && *s != '(')
486 	mp->encoding = MENCODE_UNHANDLED;
487 }
488 
489 /* Parse a multipart mime header and affect the *mime_section structure */
490 
491 void
mime_ParseSubheader(ifp,next_line)492 mime_ParseSubheader(ifp, next_line)
493 FILE* ifp;
494 char* next_line;
495 {
496     static char* line = NULL;
497     static int line_size = 0;
498     char* s;
499     int pos, linetype, len;
500     mime_ClearStruct(mime_section);
501     mime_section->type = TEXT_MIME;
502     for (;;) {
503 	for (pos = 0; ; pos += strlen(line+pos)) {
504 	    len = pos + (next_line? strlen(next_line) : 0) + LBUFLEN;
505 	    if (line_size < len) {
506 		line_size = len + LBUFLEN;
507 		line = saferealloc(line, line_size);
508 	    }
509 	    if (next_line) {
510 		safecpy(line+pos, next_line, line_size - pos);
511 		next_line = NULL;
512 	    }
513 	    else if (ifp) {
514 		if (!fgets(line + pos, LBUFLEN, ifp))
515 		    break;
516 	    }
517 	    else if (!readart(line + pos, LBUFLEN))
518 		break;
519 	    if (line[0] == '\n')
520 		break;
521 	    if (pos && line[pos] != ' ' && line[pos] != '\t') {
522 		next_line = line + pos;
523 		line[pos-1] = '\0';
524 		break;
525 	    }
526 	}
527 	s = index(line,':');
528 	if (s == NULL)
529 	    break;
530 
531 	linetype = set_line_type(line,s);
532 	switch (linetype) {
533 	  case CONTTYPE_LINE:
534 	    mime_ParseType(mime_section,s+1);
535 	    break;
536 	  case CONTXFER_LINE:
537 	    mime_ParseEncoding(mime_section,s+1);
538 	    break;
539 	  case CONTDISP_LINE:
540 	    mime_ParseDisposition(mime_section,s+1);
541 	    break;
542 	  case CONTNAME_LINE:
543 	    safefree(mime_section->filename);
544 	    s = mime_SkipWhitespace(s+1);
545 	    mime_section->filename = savestr(s);
546 	    break;
547 #if 0
548 	  case CONTLEN_LINE:
549 	    mime_section->content_len = atol(s+1);
550 	    break;
551 #endif
552 	}
553     }
554     mime_state = mime_section->type;
555     if (!mime_section->type_name)
556 	mime_section->type_name = savestr(text_plain);
557 }
558 
559 void
mime_SetState(bp)560 mime_SetState(bp)
561 char* bp;
562 {
563     int ret;
564 
565     if (mime_state == BETWEEN_MIME) {
566 	mime_ParseSubheader((FILE*)NULL,bp);
567 	*bp = '\0';
568 	if (mime_section->prev->flags & MSF_ALTERNADONE)
569 	    mime_state = ALTERNATE_MIME;
570 	else if (mime_section->prev->flags & MSF_ALTERNATIVE)
571 	    mime_section->prev->flags |= MSF_ALTERNADONE;
572     }
573 
574     while (mime_state == MESSAGE_MIME) {
575 	mime_PushSection();
576 	mime_ParseSubheader((FILE*)NULL,*bp? bp : (char*)NULL);
577 	*bp = '\0';
578     }
579 
580     if (mime_state == MULTIPART_MIME) {
581 	mime_PushSection();
582 	mime_state = SKIP_MIME;		/* Skip anything before 1st part */
583     }
584 
585     ret = mime_EndOfSection(bp);
586     switch (ret) {
587       case 0:
588 	break;
589       case 1:
590 	while (!mime_section->prev->boundary_len)
591 	    mime_PopSection();
592 	mime_state = BETWEEN_MIME;
593 	break;
594       case 2:
595 	while (!mime_section->prev->boundary_len)
596 	    mime_PopSection();
597 	mime_PopSection();
598 	mime_state = END_OF_MIME;
599 	break;
600     }
601 }
602 
603 int
mime_EndOfSection(bp)604 mime_EndOfSection(bp)
605 char* bp;
606 {
607     MIME_SECT* mp = mime_section->prev;
608     while (mp && !mp->boundary_len)
609 	mp = mp->prev;
610     if (mp) {
611 	/* have we read all the data in this part? */
612 	if (bp[0] == '-' && bp[1] == '-'
613 	 && strnEQ(bp+2,mp->boundary,mp->boundary_len)) {
614 	    int len = 2 + mp->boundary_len;
615 	    /* have we found the last boundary? */
616 	    if (bp[len] == '-' && bp[len+1] == '-'
617 	     && (bp[len+2] == '\n' || bp[len+2] == '\0'))
618 		return 2;
619 	    return bp[len] == '\n' || bp[len] == '\0';
620 	}
621     }
622     return 0;
623 }
624 
625 /* Return a saved string of all the extra parameters on this mime
626  * header line.  The passed-in string is transformed into just the
627  * first word on the line.
628  */
629 char*
mime_ParseParams(str)630 mime_ParseParams(str)
631 char* str;
632 {
633     char* s;
634     char* t;
635     char* e;
636     s = e = mime_SkipWhitespace(str);
637     while (*e && *e != ';' && !isspace(*e) && *e != '(') e++;
638     t = savestr(mime_SkipWhitespace(e));
639     *e = '\0';
640     if (s != str)
641 	safecpy(str, s, e - s + 1);
642     str = s = t;
643     while (*s == ';') {
644 	s = mime_SkipWhitespace(s+1);
645 	while (*s && *s != ';' && *s != '(' && *s != '=' && !isspace(*s))
646 	    *t++ = *s++;
647 	s = mime_SkipWhitespace(s);
648 	if (*s == '=') {
649 	    *t++ = *s;
650 	    s = mime_SkipWhitespace(s+1);
651 	    if (*s == '"') {
652 		s = cpytill(t,s+1,'"');
653 		if (*s == '"')
654 		    s++;
655 		t += strlen(t);
656 	    }
657 	    else
658 		while (*s && *s != ';' && !isspace(*s) && *s != '(')
659 		    *t++ = *s++;
660 	}
661 	*t++ = '\0';
662     }
663     *t = '\0';
664     return str;
665 }
666 
667 char*
mime_FindParam(s,param)668 mime_FindParam(s, param)
669 char* s;
670 char* param;
671 {
672     int param_len = strlen(param);
673     while (s && *s) {
674 	if (strncaseEQ(s, param, param_len) && s[param_len] == '=')
675 	    return s + param_len + 1;
676 	s += strlen(s) + 1;
677     }
678     return NULL;
679 }
680 
681 /* Skip whitespace and RFC-822 comments. */
682 
683 char*
mime_SkipWhitespace(s)684 mime_SkipWhitespace(s)
685 char* s;
686 {
687     int comment_level = 0;
688 
689     while (*s) {
690 	if (*s == '(') {
691 	    s++;
692 	    comment_level++;
693 	    while (comment_level) {
694 		switch (*s++) {
695 		  case '\0':
696 		    return s-1;
697 		  case '\\':
698 		    s++;
699 		    break;
700 		  case '(':
701 		    comment_level++;
702 		    break;
703 		  case ')':
704 		    comment_level--;
705 		    break;
706 		}
707 	    }
708 	}
709 	else if (!isspace(*s))
710 	    break;
711 	else
712 	    s++;
713     }
714     return s;
715 }
716 
717 void
mime_DecodeArticle(view)718 mime_DecodeArticle(view)
719 bool_int view;
720 {
721     MIMECAP_ENTRY* mcp = NULL;
722 
723     seekart(savefrom);
724     *art_line = '\0';
725 
726     while (1) {
727 	if (mime_state != MESSAGE_MIME || !mime_section->total) {
728 	    if (!readart(art_line,sizeof art_line))
729 		break;
730 	    mime_SetState(art_line);
731 	}
732 	switch (mime_state) {
733 	  case BETWEEN_MIME:
734 	  case END_OF_MIME:
735 	    break;
736 	  case TEXT_MIME:
737 	  case HTMLTEXT_MIME:
738 	  case ISOTEXT_MIME:
739 	  case MESSAGE_MIME:
740 	    /* $$ Check for uuencoded file here? */
741 	    mime_state = SKIP_MIME;
742 	    /* FALL THROUGH */
743 	  case SKIP_MIME: {
744 	    MIME_SECT* mp = mime_section;
745 	    while ((mp = mp->prev) != NULL && !mp->boundary_len) ;
746 	    if (!mp)
747 		return;
748 	    break;
749 	  }
750 	  default:
751 	    if (view) {
752 		mcp = mime_FindMimecapEntry(mime_section->type_name,0);
753 		if (!mcp) {
754 		    printf("No view method for %s -- skipping.\n",
755 			   mime_section->type_name);
756 		    mime_state = SKIP_MIME;
757 		    break;
758 		}
759 	    }
760 	    mime_state = DECODE_MIME;
761 	    if (decode_piece(mcp, *art_line == '\n'? NULL : art_line) != 0) {
762 		mime_SetState(art_line);
763 		if (mime_state == DECODE_MIME)
764 		    mime_state = SKIP_MIME;
765 	    }
766 	    else {
767 		if (*msg) {
768 		    newline();
769 		    fputs(msg,stdout);
770 		}
771 		mime_state = SKIP_MIME;
772 	    }
773 	    newline();
774 	    break;
775 	}
776     }
777 }
778 
779 void
mime_Description(mp,s,limit)780 mime_Description(mp, s, limit)
781 MIME_SECT* mp;
782 char* s;
783 int limit;
784 {
785     char* fn = decode_fix_fname(mp->filename);
786     int len, flen = strlen(fn);
787 
788     limit -= 2;  /* leave room for the trailing ']' and '\n' */
789     sprintf(s, "[Attachment type=%s, name=", mp->type_name);
790     len = strlen(s);
791     if (len + flen <= limit)
792 	sprintf(s+len, "%s]\n", fn);
793     else if (len+3 >= limit)
794 	strcpy(s+limit-3, "...]\n");
795     else {
796 #if 0
797 	sprintf(s+len, "...%s]\n", fn + flen - (limit-(len+3)));
798 #else
799 	safecpy(s+len, fn, limit - (len+3));
800 	strcat(s, "...]\n");
801 #endif
802     }
803 }
804 
805 #define XX 255
806 static Uchar index_hex[256] = {
807     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
808     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
809     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
810      0, 1, 2, 3,  4, 5, 6, 7,  8, 9,XX,XX, XX,XX,XX,XX,
811     XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
812     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
813     XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
814     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
815     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
816     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
817     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
818     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
819     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
820     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
821     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
822     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
823 };
824 
825 int
qp_decodestring(t,f,in_header)826 qp_decodestring(t, f, in_header)
827 char* t;
828 char* f;
829 bool_int in_header;
830 {
831     char* save_t = t;
832     while (*f) {
833 	switch (*f) {
834 	  case '_':
835 	    if (in_header) {
836 		*t++ = ' ';
837 		f++;
838 	    }
839 	    else
840 		*t++ = *f++;
841 	    break;
842 	  case '=':	/* decode a hex-value */
843 	    if (f[1] == '\n') {
844 		f += 2;
845 		break;
846 	    }
847 	    if (index_hex[(Uchar)f[1]] != XX && index_hex[(Uchar)f[2]] != XX) {
848 		*t = (index_hex[(Uchar)f[1]] << 4) + index_hex[(Uchar)f[2]];
849 		f += 3;
850 		if (*t != '\r')
851 		    t++;
852 		break;
853 	    }
854 	    /* FALL THROUGH */
855 	  default:
856 	    *t++ = *f++;
857 	    break;
858 	}
859     }
860     *t = '\0';
861     return t - save_t;
862 }
863 
864 int
qp_decode(ifp,state)865 qp_decode(ifp,state)
866 FILE* ifp;
867 int state;
868 {
869     static FILE* ofp = NULL;
870     int c1, c2;
871 
872     if (state == DECODE_DONE) {
873 	if (ofp)
874 	    fclose(ofp);
875 	ofp = NULL;
876 	return state;
877     }
878 
879     if (state == DECODE_START) {
880 	char* filename = decode_fix_fname(mime_section->filename);
881 	ofp = fopen(filename, FOPEN_WB);
882 	if (!ofp)
883 	    return DECODE_ERROR;
884 	erase_line(0);
885 	printf("Decoding %s", filename);
886 	if (nowait_fork)
887 	    fflush(stdout);
888 	else
889 	    newline();
890     }
891 
892     while ((c1 = mime_getc(ifp)) != EOF) {
893       check_c1:
894 	if (c1 == '=') {
895 	    c1 = mime_getc(ifp);
896 	    if (c1 == '\n')
897 		continue;
898 	    if (index_hex[(Uchar)c1] == XX) {
899 		putc('=', ofp);
900 		goto check_c1;
901 	    }
902 	    c2 = mime_getc(ifp);
903 	    if (index_hex[(Uchar)c2] == XX) {
904 		putc('=', ofp);
905 		putc(c1, ofp);
906 		c1 = c2;
907 		goto check_c1;
908 	    }
909 	    c1 = (index_hex[(Uchar)c1] << 4) | index_hex[(Uchar)c2];
910 	    if (c1 != '\r')
911 		putc(c1, ofp);
912 	}
913 	else
914 	    putc(c1, ofp);
915     }
916 
917     return DECODE_MAYBEDONE;
918 }
919 
920 static Uchar index_b64[256] = {
921     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
922     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
923     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
924     52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
925     XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
926     15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
927     XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
928     41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
929     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
930     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
931     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
932     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
933     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
934     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
935     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
936     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
937 };
938 
939 int
b64_decodestring(t,f)940 b64_decodestring(t, f)
941 char* t;
942 char* f;
943 {
944     char* save_t = t;
945     Uchar ch1, ch2;
946 
947     while (*f && *f != '=') {
948 	ch1 = index_b64[(Uchar)*f++];
949 	if (ch1 == XX)
950 	    continue;
951 	do {
952 	    if (!*f || *f == '=')
953 		goto dbl_break;
954 	    ch2 = index_b64[(Uchar)*f++];
955 	} while (ch2 == XX);
956 	*t++ = (ch1 << 2) | (ch2 >> 4);
957 	do {
958 	    if (!*f || *f == '=')
959 		goto dbl_break;
960 	    ch1 = index_b64[(Uchar)*f++];
961 	} while (ch1 == XX);
962 	*t++ = ((ch2 & 0x0f) << 4) | (ch1 >> 2);
963 	do {
964 	    if (!*f || *f == '=')
965 		goto dbl_break;
966 	    ch2 = index_b64[(Uchar)*f++];
967 	} while (ch2 == XX);
968 	*t++ = ((ch1 & 0x03) << 6) | ch2;
969     }
970   dbl_break:
971     *t = '\0';
972     return t - save_t;
973 }
974 
975 int
b64_decode(ifp,state)976 b64_decode(ifp, state)
977 FILE* ifp;
978 int state;
979 {
980     static FILE* ofp = NULL;
981     int c1, c2, c3, c4;
982 
983     if (state == DECODE_DONE) {
984       all_done:
985 	if (ofp)
986 	    fclose(ofp);
987 	ofp = NULL;
988 	return state;
989     }
990 
991     if (state == DECODE_START) {
992 	char* filename = decode_fix_fname(mime_section->filename);
993 	ofp = fopen(filename, FOPEN_WB);
994 	if (!ofp)
995 	    return DECODE_ERROR;
996 	printf("Decoding %s", filename);
997 	if (nowait_fork)
998 	    fflush(stdout);
999 	else
1000 	    newline();
1001 	state = DECODE_ACTIVE;
1002     }
1003 
1004     while ((c1 = mime_getc(ifp)) != EOF) {
1005 	if (c1 != '=' && index_b64[c1] == XX)
1006 	    continue;
1007 	do {
1008 	    c2 = mime_getc(ifp);
1009 	    if (c2 == EOF)
1010 		return state;
1011 	} while (c2 != '=' && index_b64[c2] == XX);
1012 	do {
1013 	    c3 = mime_getc(ifp);
1014 	    if (c3 == EOF)
1015 		return state;
1016 	} while (c3 != '=' && index_b64[c3] == XX);
1017 	do {
1018 	    c4 = mime_getc(ifp);
1019 	    if (c4 == EOF)
1020 		return state;
1021 	} while (c4 != '=' && index_b64[c4] == XX);
1022 	if (c1 == '=' || c2 == '=') {
1023 	    state = DECODE_DONE;
1024 	    break;
1025 	}
1026 	c1 = index_b64[c1];
1027 	c2 = index_b64[c2];
1028 	c1 = (c1 << 2) | (c2 >> 4);
1029 	putc(c1, ofp);
1030 	if (c3 == '=') {
1031 	    state = DECODE_DONE;
1032 	    break;
1033 	}
1034 	c3 = index_b64[c3];
1035 	c2 = ((c2 & 0x0f) << 4) | (c3 >> 2);
1036 	putc(c2, ofp);
1037 	if (c4 == '=') {
1038 	    state = DECODE_DONE;
1039 	    break;
1040 	}
1041 	c4 = index_b64[c4];
1042 	c3 = ((c3 & 0x03) << 6) | c4;
1043 	putc(c3, ofp);
1044     }
1045 
1046     if (state == DECODE_DONE)
1047 	goto all_done;
1048 
1049     return DECODE_MAYBEDONE;
1050 }
1051 #undef XX
1052 
1053 static int
mime_getc(fp)1054 mime_getc(fp)
1055 FILE* fp;
1056 {
1057     if (fp)
1058 	return fgetc(fp);
1059 
1060     if (!mime_getc_line || !*mime_getc_line) {
1061 	mime_getc_line = readart(art_line,sizeof art_line);
1062 	if (mime_EndOfSection(art_line))
1063 	    return EOF;
1064 	if (!mime_getc_line)
1065 	    return EOF;
1066     }
1067     return *mime_getc_line++;
1068 }
1069 
1070 int
cat_decode(ifp,state)1071 cat_decode(ifp, state)
1072 FILE* ifp;
1073 int state;
1074 {
1075     static FILE* ofp = NULL;
1076 
1077     if (state == DECODE_DONE) {
1078 	if (ofp)
1079 	    fclose(ofp);
1080 	ofp = NULL;
1081 	return state;
1082     }
1083 
1084     if (state == DECODE_START) {
1085 	char* filename = decode_fix_fname(mime_section->filename);
1086 	ofp = fopen(filename, FOPEN_WB);
1087 	if (!ofp)
1088 	    return DECODE_ERROR;
1089 	printf("Decoding %s", filename);
1090 	if (nowait_fork)
1091 	    fflush(stdout);
1092 	else
1093 	    newline();
1094     }
1095 
1096     if (ifp) {
1097 	while (fgets(buf, sizeof buf, ifp))
1098 	    fputs(buf, ofp);
1099     }
1100     else {
1101 	while (readart(buf, sizeof buf)) {
1102 	    if (mime_EndOfSection(buf))
1103 		break;
1104 	    fputs(buf, ofp);
1105 	}
1106     }
1107 
1108     return DECODE_MAYBEDONE;
1109 }
1110 
1111 static int word_wrap_in_pre, normal_word_wrap, word_wrap;
1112 
1113 int
filter_html(t,f)1114 filter_html(t, f)
1115 char* t;
1116 char* f;
1117 {
1118     static char tagword[32];
1119     static int tagword_len;
1120     char* bp;
1121     char* cp;
1122 
1123     if (word_wrap_offset < 0) {
1124 	normal_word_wrap = tc_COLS - 8;
1125 	word_wrap_in_pre = 0;
1126     }
1127     else
1128 	word_wrap_in_pre = normal_word_wrap = tc_COLS - word_wrap_offset;
1129 
1130     if (normal_word_wrap <= 20)
1131 	normal_word_wrap = 0;
1132     if (word_wrap_in_pre <= 20)
1133 	word_wrap_in_pre = 0;
1134     word_wrap = (mime_section->html & HF_IN_PRE)? word_wrap_in_pre
1135 						: normal_word_wrap;
1136     if (!mime_section->html_line_start)
1137 	mime_section->html_line_start = t - artbuf;
1138 
1139     if (!mime_section->html_blks) {
1140 	mime_section->html_blks = (HBLK*)safemalloc(HTML_MAX_BLOCKS
1141 						  * sizeof (HBLK));
1142     }
1143 
1144     for (bp = t; *f; f++) {
1145 	if (mime_section->html & HF_IN_DQUOTE) {
1146 	    if (*f == '"')
1147 		mime_section->html &= ~HF_IN_DQUOTE;
1148 	    else if (tagword_len < (sizeof tagword) - 1)
1149 		tagword[tagword_len++] = *f;
1150 	}
1151 	else if (mime_section->html & HF_IN_SQUOTE) {
1152 	    if (*f == '\'')
1153 		mime_section->html &= ~HF_IN_SQUOTE;
1154 	    else if (tagword_len < (sizeof tagword) - 1)
1155 		tagword[tagword_len++] = *f;
1156 	}
1157 	else if (mime_section->html & HF_IN_COMMENT) {
1158 	    if (*f == '-' && f[1] == '-') {
1159 		f++;
1160 		mime_section->html &= ~HF_IN_COMMENT;
1161 	    }
1162 	}
1163 	else if (mime_section->html & HF_IN_TAG) {
1164 	    if (*f == '>') {
1165 		mime_section->html &= ~HF_IN_TAG;
1166 		tagword[tagword_len] = '\0';
1167 		if (*tagword == '/')
1168 		    t = tag_action(t, tagword+1, CLOSING_TAG);
1169 		else
1170 		    t = tag_action(t, tagword, OPENING_TAG);
1171 	    }
1172 	    else if (*f == '-' && f[1] == '-') {
1173 		f++;
1174 		mime_section->html |= HF_IN_COMMENT;
1175 	    }
1176 	    else if (*f == '"')
1177 		mime_section->html |= HF_IN_DQUOTE;
1178 	    else if (*f == '\'')
1179 		mime_section->html |= HF_IN_SQUOTE;
1180 	    else if (tagword_len < (sizeof tagword) - 1) {
1181 		tagword[tagword_len++] = AT_GREY_SPACE(f)? ' ' : *f;
1182 	    }
1183 	}
1184 	else if (*f == '<') {
1185 	    tagword_len = 0;
1186 	    mime_section->html |= HF_IN_TAG;
1187 	}
1188 	else if (mime_section->html & HF_IN_HIDING)
1189 	    ;
1190 	else if (*f == '&') {
1191 	    t = output_prep(t);
1192 	    if (strncaseEQ(f+1,"lt;",3)) {
1193 		*t++ = '<';
1194 		f += 3;
1195 	    }
1196 	    else if (strncaseEQ(f+1,"gt;",3)) {
1197 		*t++ = '>';
1198 		f += 3;
1199 	    }
1200 	    else if (strncaseEQ(f+1,"amp;",4)) {
1201 		*t++ = '&';
1202 		f += 4;
1203 	    }
1204 	    else if (strncaseEQ(f+1,"nbsp;",5)) {
1205 		*t++ = ' ';
1206 		f += 5;
1207 	    }
1208 	    else if (strncaseEQ(f+1,"quot;",5)) {
1209 		*t++ = '"';
1210 		f += 5;
1211 	    }
1212 	    else
1213 		*t++ = *f;
1214 	    mime_section->html |= HF_NL_OK|HF_P_OK|HF_SPACE_OK;
1215 	}
1216 	else if (AT_GREY_SPACE(f) && !(mime_section->html & HF_IN_PRE)) {
1217 	    /* We don't want to call output_prep() here. */
1218 	    if (mime_section->html & HF_SPACE_OK) {
1219 		mime_section->html &= ~HF_SPACE_OK;
1220 		*t++ = ' ';
1221 	    }
1222 	}
1223 	else if (*f == '\n') { /* Handle the HF_IN_PRE case */
1224 	    t = output_prep(t);
1225 	    mime_section->html |= HF_NL_OK;
1226 	    t = do_newline(t, HF_NL_OK);
1227 	}
1228 	else {
1229 	    t = output_prep(t);
1230 	    *t++ = *f;
1231 	    mime_section->html |= HF_NL_OK|HF_P_OK|HF_SPACE_OK;
1232 	}
1233 
1234 	if (word_wrap && t - artbuf - mime_section->html_line_start > tc_COLS) {
1235 	    char* line_start = mime_section->html_line_start + artbuf;
1236 	    for (cp = line_start + word_wrap;
1237 		 cp > line_start && *cp != ' ' && *cp != '\t';
1238 		 cp--) ;
1239 	    if (cp == line_start) {
1240 		for (cp = line_start + word_wrap;
1241 		     cp - line_start <= tc_COLS && *cp != ' ' && *cp != '\t';
1242 		     cp++) ;
1243 		if (cp - line_start > tc_COLS) {
1244 		    mime_section->html_line_start += tc_COLS;
1245 		    cp = NULL;
1246 		}
1247 	    }
1248 	    if (cp) {
1249 		int flag_save = mime_section->html;
1250 		int fudge;
1251 		char* s;
1252 		mime_section->html |= HF_NL_OK;
1253 		cp = line_start = do_newline(cp, HF_NL_OK);
1254 		fudge = do_indent((char*)NULL);
1255 		while (*cp == ' ' || *cp == '\t') cp++;
1256 		if ((fudge -= cp - line_start) != 0) {
1257 		    if (fudge < 0)
1258 			bcopy(cp, cp + fudge, t - cp);
1259 		    else
1260 			for (s = t; s-- != cp; ) s[fudge] = *s;
1261 		    (void) do_indent(line_start);
1262 		    t += fudge;
1263 		}
1264 		mime_section->html = flag_save;
1265 	    }
1266 	}
1267     }
1268     *t = '\0';
1269 
1270     return t - bp;
1271 }
1272 
1273 static char bullets[3] = {'*', 'o', '+'};
1274 static char letters[2] = {'a', 'A'};
1275 static char roman_letters[] = { 'M', 'D', 'C', 'L', 'X', 'V', 'I'};
1276 static int  roman_values[]  = {1000, 500, 100,  50, 10,   5,   1 };
1277 
1278 static char*
tag_action(t,word,opening_tag)1279 tag_action(t, word, opening_tag)
1280 char* t;
1281 char* word;
1282 bool_int opening_tag;
1283 {
1284     char* cp;
1285     int i, j, tnum, len, itype, ch, cnt, num;
1286     bool match = 0;
1287     HBLK* blks = mime_section->html_blks;
1288 
1289     for (cp = word; *cp && *cp != ' '; cp++) ;
1290     len = cp - word;
1291 
1292     if (!isalpha(*word))
1293 	return t;
1294     ch = isupper(*word)? tolower(*word) : *word;
1295     for (tnum = 0; tnum < LAST_TAG && *tagattr[tnum].name != ch; tnum++) ;
1296     for ( ; tnum < LAST_TAG && *tagattr[tnum].name == ch; tnum++) {
1297 	if (len == tagattr[tnum].length
1298 	 && strncaseEQ(word, tagattr[tnum].name, len)) {
1299 	    match = 1;
1300 	    break;
1301 	}
1302     }
1303     if (!match)
1304 	return t;
1305 
1306     if (!opening_tag && !(tagattr[tnum].flags & (TF_BLOCK|TF_HAS_CLOSE)))
1307 	return t;
1308 
1309     if ((mime_section->html & HF_IN_HIDING)
1310      && (opening_tag || tnum != blks[mime_section->html_blkcnt-1].tnum))
1311 	return t;
1312 
1313     if (tagattr[tnum].flags & TF_BR)
1314 	mime_section->html |= HF_NL_OK;
1315 
1316     if (opening_tag) {
1317 	if (tagattr[tnum].flags & TF_NL) {
1318 	    t = output_prep(t);
1319 	    t = do_newline(t, HF_NL_OK);
1320 	}
1321 	if ((num = tagattr[tnum].flags & (TF_P|TF_LIST)) == TF_P
1322 	 || (num == (TF_P|TF_LIST) && !(mime_section->html & HF_COMPACT))) {
1323 	    t = output_prep(t);
1324 	    t = do_newline(t, HF_P_OK);
1325 	}
1326 	if (tagattr[tnum].flags & TF_SPACE) {
1327 	    if (mime_section->html & HF_SPACE_OK) {
1328 		mime_section->html &= ~HF_SPACE_OK;
1329 		*t++ = ' ';
1330 	    }
1331 	}
1332 	if (tagattr[tnum].flags & TF_TAB) {
1333 	    if (mime_section->html & HF_NL_OK) {
1334 		mime_section->html &= ~HF_SPACE_OK;
1335 		*t++ = '\t';
1336 	    }
1337 	}
1338 
1339 	if ((tagattr[tnum].flags & TF_BLOCK)
1340 	 && mime_section->html_blkcnt < HTML_MAX_BLOCKS) {
1341 	    j = mime_section->html_blkcnt++;
1342 	    blks[j].tnum = tnum;
1343 	    blks[j].indent = 0;
1344 	    blks[j].cnt = 0;
1345 
1346 	    if (tagattr[tnum].flags & TF_LIST)
1347 		mime_section->html |= HF_COMPACT;
1348 	    else
1349 		mime_section->html &= ~HF_COMPACT;
1350 	}
1351 	else
1352 	    j = mime_section->html_blkcnt - 1;
1353 
1354 	if ((tagattr[tnum].flags & (TF_BLOCK|TF_HIDE)) == (TF_BLOCK|TF_HIDE))
1355 	    mime_section->html |= HF_IN_HIDING;
1356 
1357 	switch (tnum) {
1358 	  case TAG_BLOCKQUOTE:
1359 	    if (((cp = find_attr(word, "type")) != NULL
1360 	      && strncaseEQ(cp, "cite", 4))
1361 	     || ((cp = find_attr(word, "style")) != NULL
1362 	      && strncaseEQ(cp, "border-left:", 12)))
1363 		blks[j].indent = '>';
1364 	    else
1365 		blks[j].indent = ' ';
1366 	    break;
1367 	  case TAG_HR:
1368 	    t = output_prep(t);
1369 	    *t++ = '-'; *t++ = '-';
1370 	    mime_section->html |= HF_NL_OK;
1371 	    t = do_newline(t, HF_NL_OK);
1372 	    break;
1373 	  case TAG_IMG:
1374 	    t = output_prep(t);
1375 	    if (mime_section->html & HF_SPACE_OK)
1376 		*t++ = ' ';
1377 	    strcpy(t, "[Image] ");
1378 	    t += 8;
1379 	    mime_section->html &= ~HF_SPACE_OK;
1380 	    break;
1381 	  case TAG_OL:
1382 	    itype = 4;
1383 	    if ((cp = find_attr(word, "type")) != NULL) {
1384 		switch (*cp) {
1385 		  case '1':  itype = 4;  break;
1386 		  case 'a':  itype = 5;  break;
1387 		  case 'A':  itype = 6;  break;
1388 		  case 'i':  itype = 7;  break;
1389 		  case 'I':  itype = 8;  break;
1390 		}
1391 	    }
1392 	    blks[j].indent = itype;
1393 	    break;
1394 	  case TAG_UL:
1395 	    itype = 1;
1396 	    if ((cp = find_attr(word, "type")) != NULL) {
1397 		switch (*cp) {
1398 		  case 'd': case 'D':  itype = 1;  break;
1399 		  case 'c': case 'C':  itype = 2;  break;
1400 		  case 's': case 'S':  itype = 3;  break;
1401 		}
1402 	    }
1403 	    else {
1404 		for (i = 0; i < mime_section->html_blkcnt; i++) {
1405 		    if (blks[i].indent && blks[i].indent < ' ') {
1406 			if (++itype == 3)
1407 			    break;
1408 		    }
1409 		}
1410 	    }
1411 	    blks[j].indent = itype;
1412 	    break;
1413 	  case TAG_LI:
1414 	    t = output_prep(t);
1415 	    ch = j < 0? ' ' : blks[j].indent;
1416 	    switch (ch) {
1417 	      case 1: case 2: case 3:
1418 		t[-2] = bullets[ch-1];
1419 		break;
1420 	      case 4:
1421 		sprintf(t-4, "%2d. ", ++blks[j].cnt);
1422 		if (*t)
1423 		    t += strlen(t);
1424 		break;
1425 	      case 5: case 6:
1426 		cnt = blks[j].cnt++;
1427 		if (cnt >= 26*26)
1428 		    cnt = blks[j].cnt = 0;
1429 		if (cnt >= 26)
1430 		    t[-4] = letters[ch-5] + (cnt / 26) - 1;
1431 		t[-3] =	letters[ch-5] + (cnt % 26);
1432 		t[-2] = '.';
1433 		break;
1434 	      case 7:
1435 		for (i = 0; i < 7; i++) {
1436 		    if (isupper(roman_letters[i]))
1437 			roman_letters[i] = tolower(roman_letters[i]);
1438 		}
1439 		goto roman_numerals;
1440 	      case 8:
1441 		for (i = 0; i < 7; i++) {
1442 		    if (islower(roman_letters[i]))
1443 			roman_letters[i] = toupper(roman_letters[i]);
1444 		}
1445 	      roman_numerals:
1446 		cp = t - 6;
1447 		cnt = ++blks[j].cnt;
1448 		for (i = 0; cnt && i < 7; i++) {
1449 		    num = roman_values[i];
1450 		    while (cnt >= num) {
1451 			*cp++ = roman_letters[i];
1452 			cnt -= num;
1453 		    }
1454 		    j = (i | 1) + 1;
1455 		    if (j < 7) {
1456 			num -= roman_values[j];
1457 			if (cnt >= num) {
1458 			    *cp++ = roman_letters[j];
1459 			    *cp++ = roman_letters[i];
1460 			    cnt -= num;
1461 			}
1462 		    }
1463 		}
1464 		if (cp < t - 2) {
1465 		    t -= 2;
1466 		    for (cnt = t - cp; cp-- != t - 4; ) cp[cnt] = *cp;
1467 		    while (cnt--) *++cp = ' ';
1468 		}
1469 		else
1470 		    t = cp;
1471 		*t++ = '.';
1472 		*t++ = ' ';
1473 		break;
1474 	      default:
1475 		*t++ = '*';
1476 		*t++ = ' ';
1477 		break;
1478 	    }
1479 	    mime_section->html |= HF_NL_OK|HF_P_OK;
1480 	    break;
1481 	  case TAG_PRE:
1482 	    mime_section->html |= HF_IN_PRE;
1483 	    word_wrap = word_wrap_in_pre;
1484 	    break;
1485 	}
1486     }
1487     else {
1488 	if ((tagattr[tnum].flags & TF_BLOCK)) {
1489 	    for (j = mime_section->html_blkcnt; j--; ) {
1490 		if (blks[j].tnum == tnum) {
1491 		    for (i = mime_section->html_blkcnt; --i > j; ) {
1492 			t = tag_action(t, tagattr[blks[i].tnum].name,
1493 				       CLOSING_TAG);
1494 		    }
1495 		    mime_section->html_blkcnt = j;
1496 		    break;
1497 		}
1498 	    }
1499 	    mime_section->html &= ~HF_IN_HIDING;
1500 	    while (j-- > 0) {
1501 		if (tagattr[blks[j].tnum].flags & TF_HIDE) {
1502 		    mime_section->html |= HF_IN_HIDING;
1503 		    break;
1504 		}
1505 	    }
1506 	}
1507 
1508 	j = mime_section->html_blkcnt - 1;
1509 	if (j >= 0 && (tagattr[blks[j].tnum].flags & TF_LIST))
1510 	    mime_section->html |= HF_COMPACT;
1511 	else
1512 	    mime_section->html &= ~HF_COMPACT;
1513 
1514 	if ((tagattr[tnum].flags & TF_NL) && (mime_section->html & HF_NL_OK)) {
1515 	    mime_section->html |= HF_QUEUED_NL;
1516 	    mime_section->html &= ~HF_SPACE_OK;
1517 	}
1518 	if ((num = tagattr[tnum].flags & (TF_P|TF_LIST)) == TF_P
1519 	 || (num == (TF_P|TF_LIST) && !(mime_section->html & HF_COMPACT))) {
1520 	    if (mime_section->html & HF_P_OK) {
1521 		mime_section->html |= HF_QUEUED_P;
1522 		mime_section->html &= ~HF_SPACE_OK;
1523 	    }
1524 	}
1525 
1526 	switch (tnum) {
1527 	  case TAG_PRE:
1528 	    mime_section->html &= ~HF_IN_PRE;
1529 	    word_wrap = normal_word_wrap;
1530 	    break;
1531 	}
1532     }
1533 
1534     return t;
1535 }
1536 
1537 static char*
output_prep(t)1538 output_prep(t)
1539 char* t;
1540 {
1541     if (mime_section->html & HF_QUEUED_P) {
1542 	mime_section->html &= ~HF_QUEUED_P;
1543 	t = do_newline(t, HF_P_OK);
1544     }
1545     if (mime_section->html & HF_QUEUED_NL) {
1546 	mime_section->html &= ~HF_QUEUED_NL;
1547 	t = do_newline(t, HF_NL_OK);
1548     }
1549     return t + do_indent(t);
1550 }
1551 
1552 static char*
do_newline(t,flag)1553 do_newline(t, flag)
1554 char* t;
1555 int flag;
1556 {
1557     if (mime_section->html & flag) {
1558 	mime_section->html &= ~(flag|HF_SPACE_OK);
1559 	t += do_indent(t);
1560 	*t++ = '\n';
1561 	mime_section->html_line_start = t - artbuf;
1562 	mime_section->html |= HF_NEED_INDENT;
1563     }
1564     return t;
1565 }
1566 
1567 static int
do_indent(t)1568 do_indent(t)
1569 char* t;
1570 {
1571     HBLK* blks;
1572     int j, ch, spaces, len = 0;
1573 
1574     if (!(mime_section->html & HF_NEED_INDENT))
1575 	return len;
1576 
1577     if (t)
1578 	mime_section->html &= ~HF_NEED_INDENT;
1579 
1580     if ((blks = mime_section->html_blks) != NULL) {
1581 	for (j = 0; j < mime_section->html_blkcnt; j++) {
1582 	    if ((ch = blks[j].indent) != 0) {
1583 		switch (ch) {
1584 		  case '>':
1585 		    spaces = 1;
1586 		    break;
1587 		  case ' ':
1588 		    spaces = 3;
1589 		    break;
1590 		  case 7:  case 8:
1591 		    ch = ' ';
1592 		    spaces = 5;
1593 		    break;
1594 		  default:
1595 		    ch = ' ';
1596 		    spaces = 3;
1597 		    break;
1598 		}
1599 		len += spaces + 1;
1600 		if (len > 64) {
1601 		    len -= spaces + 1;
1602 		    break;
1603 		}
1604 		if (t) {
1605 		    *t++ = ch;
1606 		    while (spaces--)
1607 			*t++ = ' ';
1608 		}
1609 	    }
1610 	}
1611     }
1612 
1613     return len;
1614 }
1615 
1616 static char*
find_attr(str,attr)1617 find_attr(str, attr)
1618 char* str;
1619 char* attr;
1620 {
1621     int len = strlen(attr);
1622     char* cp = str;
1623     char* s;
1624 
1625     while ((cp = index(cp+1, '=')) != NULL) {
1626 	for (s = cp; s[-1] == ' '; s--) ;
1627 	while (cp[1] == ' ') cp++;
1628 	if (s - str > len && s[-len-1] == ' ' && strncaseEQ(s-len,attr,len))
1629 	    return cp+1;
1630     }
1631     return NULL;
1632 }
1633