3a9dd146a4f2913a95e4b01ffda732d1e240245a
[apps/madmutt.git] / handler.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <ctype.h>
18 #include <sys/wait.h>
19 #include <sys/stat.h>
20
21 #include <lib-lib/mem.h>
22 #include <lib-lib/ascii.h>
23 #include <lib-lib/str.h>
24 #include <lib-lib/macros.h>
25 #include <lib-lib/file.h>
26
27 #include <lib-mime/mime.h>
28
29 #include "mutt.h"
30 #include "recvattach.h"
31 #include "handler.h"
32 #include "mutt_curses.h"
33 #include "rfc1524.h"
34 #include "rfc3676.h"
35 #include "keymap.h"
36 #include "copy.h"
37 #include "charset.h"
38 #include "mutt_crypt.h"
39 #include "state.h"
40 #include "attach.h"
41 #include "lib.h"
42
43 #include "lib/debug.h"
44
45 typedef int handler_f (BODY *, STATE *);
46 typedef handler_f *handler_t;
47
48 static void mutt_decode_xbit (STATE * s, long len, int istext, iconv_t cd)
49 {
50   int c, ch;
51   char bufi[BUFI_SIZE];
52   size_t l = 0;
53
54   if (istext) {
55     state_set_prefix (s);
56
57     while ((c = fgetc (s->fpin)) != EOF && len--) {
58       if (c == '\r' && len) {
59         if ((ch = fgetc (s->fpin)) == '\n') {
60           c = ch;
61           len--;
62         }
63         else
64           ungetc (ch, s->fpin);
65       }
66
67       bufi[l++] = c;
68       if (l == sizeof (bufi))
69         mutt_convert_to_state (cd, bufi, &l, s);
70     }
71
72     mutt_convert_to_state (cd, bufi, &l, s);
73     mutt_convert_to_state (cd, 0, 0, s);
74
75     state_reset_prefix (s);
76   }
77   else
78     mutt_copy_bytes (s->fpin, s->fpout, len);
79 }
80
81 static int qp_decode_triple (char *s, char *d)
82 {
83   /* soft line break */
84   if (*s == '=' && !(*(s + 1)))
85     return 1;
86
87   /* quoted-printable triple */
88   if (*s == '=' &&
89       isxdigit ((unsigned char) *(s + 1)) &&
90       isxdigit ((unsigned char) *(s + 2))) {
91     *d = (hexval (*(s + 1)) << 4) | hexval (*(s + 2));
92     return 0;
93   }
94
95   /* something else */
96   return -1;
97 }
98
99 static void qp_decode_line (char *dest, char *src, size_t * l, int last)
100 {
101   char *d, *s;
102   char c;
103
104   int kind;
105   int soft = 0;
106
107   /* decode the line */
108
109   for (d = dest, s = src; *s;) {
110     switch ((kind = qp_decode_triple (s, &c))) {
111     case 0:
112       *d++ = c;
113       s += 3;
114       break;                    /* qp triple */
115     case -1:
116       *d++ = *s++;
117       break;                    /* single character */
118     case 1:
119       soft = 1;
120       s++;
121       break;                    /* soft line break */
122     }
123   }
124
125   if (!soft && last == '\n')
126     *d++ = '\n';
127
128   *d = '\0';
129   *l = d - dest;
130 }
131
132 /* 
133  * Decode an attachment encoded with quoted-printable.
134  * 
135  * Why doesn't this overflow any buffers?  First, it's guaranteed
136  * that the length of a line grows when you _en_-code it to
137  * quoted-printable.  That means that we always can store the
138  * result in a buffer of at most the _same_ size.
139  * 
140  * Now, we don't special-case if the line we read with fgets()
141  * isn't terminated.  We don't care about this, since STRING > 78,
142  * so corrupted input will just be corrupted a bit more.  That
143  * implies that STRING+1 bytes are always sufficient to store the
144  * result of qp_decode_line.
145  * 
146  * Finally, at soft line breaks, some part of a multibyte character
147  * may have been left over by mutt_convert_to_state().  This shouldn't
148  * be more than 6 characters, so STRING + 7 should be sufficient
149  * memory to store the decoded data.
150  * 
151  * Just to make sure that I didn't make some off-by-one error
152  * above, we just use STRING*2 for the target buffer's size.
153  * 
154  */
155
156 void mutt_decode_quoted (STATE * s, long len, int istext, iconv_t cd)
157 {
158   char line[STRING];
159   char decline[2 * STRING];
160   size_t l = 0;
161   size_t linelen;               /* number of input bytes in `line' */
162   size_t l3;
163
164   int last;                     /* store the last character in the input line */
165
166   if (istext)
167     state_set_prefix (s);
168
169   while (len > 0) {
170     last = 0;
171
172     /*
173      * It's ok to use a fixed size buffer for input, even if the line turns
174      * out to be longer than this.  Just process the line in chunks.  This
175      * really shouldn't happen according the MIME spec, since Q-P encoded
176      * lines are at most 76 characters, but we should be liberal about what
177      * we accept.
178      */
179     if (fgets (line, MIN ((ssize_t) sizeof (line), len + 1), s->fpin) == NULL)
180       break;
181
182     linelen = m_strlen(line);
183     len -= linelen;
184
185     /*
186      * inspect the last character we read so we can tell if we got the
187      * entire line.
188      */
189     last = linelen ? line[linelen - 1] : 0;
190
191     /* chop trailing whitespace if we got the full line */
192     if (last == '\n') {
193       while (linelen > 0 && ISSPACE (line[linelen - 1]))
194         linelen--;
195       line[linelen] = 0;
196     }
197
198     /* decode and do character set conversion */
199     qp_decode_line (decline + l, line, &l3, last);
200     l += l3;
201     mutt_convert_to_state (cd, decline, &l, s);
202   }
203
204   mutt_convert_to_state (cd, 0, 0, s);
205   state_reset_prefix (s);
206 }
207
208 void mutt_decode_base64 (STATE * s, long len, int istext, iconv_t cd)
209 {
210   char buf[5];
211   int c1, c2, c3, c4, ch, cr = 0, i;
212   char bufi[BUFI_SIZE];
213   size_t l = 0;
214
215   buf[4] = 0;
216
217   if (istext)
218     state_set_prefix (s);
219
220   while (len > 0) {
221     for (i = 0; i < 4 && len > 0; len--) {
222       if ((ch = fgetc (s->fpin)) == EOF)
223         break;
224       if (base64val(ch) >= 0 || ch == '=')
225         buf[i++] = ch;
226     }
227     if (i != 4) {
228       debug_print (2, ("didn't get a multiple of 4 chars.\n"));
229       break;
230     }
231
232     c1 = base64val(buf[0]);
233     c2 = base64val(buf[1]);
234     ch = (c1 << 2) | (c2 >> 4);
235
236     if (cr && ch != '\n')
237       bufi[l++] = '\r';
238
239     cr = 0;
240
241     if (istext && ch == '\r')
242       cr = 1;
243     else
244       bufi[l++] = ch;
245
246     if (buf[2] == '=')
247       break;
248     c3 = base64val(buf[2]);
249     ch = ((c2 & 0xf) << 4) | (c3 >> 2);
250
251     if (cr && ch != '\n')
252       bufi[l++] = '\r';
253
254     cr = 0;
255
256     if (istext && ch == '\r')
257       cr = 1;
258     else
259       bufi[l++] = ch;
260
261     if (buf[3] == '=')
262       break;
263     c4 = base64val(buf[3]);
264     ch = ((c3 & 0x3) << 6) | c4;
265
266     if (cr && ch != '\n')
267       bufi[l++] = '\r';
268     cr = 0;
269
270     if (istext && ch == '\r')
271       cr = 1;
272     else
273       bufi[l++] = ch;
274
275     if (l + 8 >= sizeof (bufi))
276       mutt_convert_to_state (cd, bufi, &l, s);
277   }
278
279   if (cr)
280     bufi[l++] = '\r';
281
282   mutt_convert_to_state (cd, bufi, &l, s);
283   mutt_convert_to_state (cd, 0, 0, s);
284
285   state_reset_prefix (s);
286 }
287
288 unsigned char decode_byte (char ch)
289 {
290   if (ch == 96)
291     return 0;
292   return ch - 32;
293 }
294
295 void mutt_decode_uuencoded (STATE * s, long len, int istext, iconv_t cd)
296 {
297   char tmps[SHORT_STRING];
298   char linelen, c, l, out;
299   char *pt;
300   char bufi[BUFI_SIZE];
301   size_t k = 0;
302
303   if (istext)
304     state_set_prefix (s);
305
306   while (len > 0) {
307     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
308       return;
309     len -= m_strlen(tmps);
310     if ((!m_strncmp(tmps, "begin", 5)) && ISSPACE (tmps[5]))
311       break;
312   }
313   while (len > 0) {
314     if ((fgets (tmps, sizeof (tmps), s->fpin)) == NULL)
315       return;
316     len -= m_strlen(tmps);
317     if (!m_strncmp(tmps, "end", 3))
318       break;
319     pt = tmps;
320     linelen = decode_byte (*pt);
321     pt++;
322     for (c = 0; c < linelen;) {
323       for (l = 2; l <= 6; l += 2) {
324         out = decode_byte (*pt) << l;
325         pt++;
326         out |= (decode_byte (*pt) >> (6 - l));
327         bufi[k++] = out;
328         c++;
329         if (c == linelen)
330           break;
331       }
332       mutt_convert_to_state (cd, bufi, &k, s);
333       pt++;
334     }
335   }
336
337   mutt_convert_to_state (cd, bufi, &k, s);
338   mutt_convert_to_state (cd, 0, 0, s);
339
340   state_reset_prefix (s);
341 }
342
343 /* ----------------------------------------------------------------------------
344  * A (not so) minimal implementation of RFC1563.
345  */
346
347 #define IndentSize (4)
348
349 enum { RICH_PARAM = 0, RICH_BOLD, RICH_UNDERLINE, RICH_ITALIC, RICH_NOFILL,
350   RICH_INDENT, RICH_INDENT_RIGHT, RICH_EXCERPT, RICH_CENTER, RICH_FLUSHLEFT,
351   RICH_FLUSHRIGHT, RICH_COLOR, RICH_LAST_TAG
352 };
353
354 static struct {
355   const char *tag_name;
356   int index;
357 } EnrichedTags[] = {
358   {
359   "param", RICH_PARAM}, {
360   "bold", RICH_BOLD}, {
361   "italic", RICH_ITALIC}, {
362   "underline", RICH_UNDERLINE}, {
363   "nofill", RICH_NOFILL}, {
364   "excerpt", RICH_EXCERPT}, {
365   "indent", RICH_INDENT}, {
366   "indentright", RICH_INDENT_RIGHT}, {
367   "center", RICH_CENTER}, {
368   "flushleft", RICH_FLUSHLEFT}, {
369   "flushright", RICH_FLUSHRIGHT}, {
370   "flushboth", RICH_FLUSHLEFT}, {
371   "color", RICH_COLOR}, {
372   "x-color", RICH_COLOR}, {
373   NULL, -1}
374 };
375
376 struct enriched_state {
377   char *buffer;
378   char *line;
379   char *param;
380   size_t buff_len;
381   size_t line_len;
382   size_t line_used;
383   size_t line_max;
384   size_t indent_len;
385   size_t word_len;
386   size_t buff_used;
387   size_t param_used;
388   size_t param_len;
389   int tag_level[RICH_LAST_TAG];
390   int WrapMargin;
391   STATE *s;
392 };
393
394 static void enriched_wrap (struct enriched_state *stte)
395 {
396   int x;
397   int extra;
398
399   if (stte->line_len) {
400     if (stte->tag_level[RICH_CENTER] || stte->tag_level[RICH_FLUSHRIGHT]) {
401       /* Strip trailing white space */
402       size_t y = stte->line_used - 1;
403
404       while (y && ISSPACE (stte->line[y])) {
405         stte->line[y] = '\0';
406         y--;
407         stte->line_used--;
408         stte->line_len--;
409       }
410       if (stte->tag_level[RICH_CENTER]) {
411         /* Strip leading whitespace */
412         y = 0;
413
414         while (stte->line[y] && ISSPACE (stte->line[y]))
415           y++;
416         if (y) {
417           size_t z;
418
419           for (z = y; z <= stte->line_used; z++) {
420             stte->line[z - y] = stte->line[z];
421           }
422
423           stte->line_len -= y;
424           stte->line_used -= y;
425         }
426       }
427     }
428
429     extra = stte->WrapMargin - stte->line_len - stte->indent_len -
430       (stte->tag_level[RICH_INDENT_RIGHT] * IndentSize);
431     if (extra > 0) {
432       if (stte->tag_level[RICH_CENTER]) {
433         x = extra / 2;
434         while (x) {
435           state_putc (' ', stte->s);
436           x--;
437         }
438       }
439       else if (stte->tag_level[RICH_FLUSHRIGHT]) {
440         x = extra - 1;
441         while (x) {
442           state_putc (' ', stte->s);
443           x--;
444         }
445       }
446     }
447     state_puts (stte->line, stte->s);
448   }
449
450   state_putc ('\n', stte->s);
451   stte->line[0] = '\0';
452   stte->line_len = 0;
453   stte->line_used = 0;
454   stte->indent_len = 0;
455   if (stte->s->prefix) {
456     state_puts (stte->s->prefix, stte->s);
457     stte->indent_len += m_strlen(stte->s->prefix);
458   }
459
460   if (stte->tag_level[RICH_EXCERPT]) {
461     x = stte->tag_level[RICH_EXCERPT];
462     while (x) {
463       if (stte->s->prefix) {
464         state_puts (stte->s->prefix, stte->s);
465         stte->indent_len += m_strlen(stte->s->prefix);
466       }
467       else {
468         state_puts ("> ", stte->s);
469         stte->indent_len += m_strlen("> ");
470       }
471       x--;
472     }
473   }
474   else
475     stte->indent_len = 0;
476   if (stte->tag_level[RICH_INDENT]) {
477     x = stte->tag_level[RICH_INDENT] * IndentSize;
478     stte->indent_len += x;
479     while (x) {
480       state_putc (' ', stte->s);
481       x--;
482     }
483   }
484 }
485
486 static void enriched_flush (struct enriched_state *stte, int wrap)
487 {
488   if (!stte->tag_level[RICH_NOFILL] && (stte->line_len + stte->word_len >
489                                         (stte->WrapMargin -
490                                          (stte->tag_level[RICH_INDENT_RIGHT] *
491                                           IndentSize) - stte->indent_len)))
492     enriched_wrap (stte);
493
494   if (stte->buff_used) {
495     stte->buffer[stte->buff_used] = '\0';
496     stte->line_used += stte->buff_used;
497     if (stte->line_used > stte->line_max) {
498       stte->line_max = stte->line_used;
499       p_realloc(&stte->line, stte->line_max + 1);
500     }
501     strcat (stte->line, stte->buffer);  /* __STRCAT_CHECKED__ */
502     stte->line_len += stte->word_len;
503     stte->word_len = 0;
504     stte->buff_used = 0;
505   }
506   if (wrap)
507     enriched_wrap (stte);
508 }
509
510
511 static void enriched_putc (int c, struct enriched_state *stte)
512 {
513   if (stte->tag_level[RICH_PARAM]) {
514     if (stte->tag_level[RICH_COLOR]) {
515       if (stte->param_used + 1 >= stte->param_len)
516         p_realloc(&stte->param, (stte->param_len += STRING));
517
518       stte->param[stte->param_used++] = c;
519     }
520     return;                     /* nothing to do */
521   }
522
523   /* see if more space is needed (plus extra for possible rich characters) */
524   if (stte->buff_len < stte->buff_used + 3) {
525     stte->buff_len += LONG_STRING;
526     p_realloc(&stte->buffer, stte->buff_len + 1);
527   }
528
529   if ((!stte->tag_level[RICH_NOFILL] && ISSPACE (c)) || c == '\0') {
530     if (c == '\t')
531       stte->word_len += 8 - (stte->line_len + stte->word_len) % 8;
532     else
533       stte->word_len++;
534
535     stte->buffer[stte->buff_used++] = c;
536     enriched_flush (stte, 0);
537   }
538   else {
539     if (stte->s->flags & M_DISPLAY) {
540       if (stte->tag_level[RICH_BOLD]) {
541         stte->buffer[stte->buff_used++] = c;
542         stte->buffer[stte->buff_used++] = '\010';
543         stte->buffer[stte->buff_used++] = c;
544       }
545       else if (stte->tag_level[RICH_UNDERLINE]) {
546
547         stte->buffer[stte->buff_used++] = '_';
548         stte->buffer[stte->buff_used++] = '\010';
549         stte->buffer[stte->buff_used++] = c;
550       }
551       else if (stte->tag_level[RICH_ITALIC]) {
552         stte->buffer[stte->buff_used++] = c;
553         stte->buffer[stte->buff_used++] = '\010';
554         stte->buffer[stte->buff_used++] = '_';
555       }
556       else {
557         stte->buffer[stte->buff_used++] = c;
558       }
559     }
560     else {
561       stte->buffer[stte->buff_used++] = c;
562     }
563     stte->word_len++;
564   }
565 }
566
567 static void enriched_puts (const char *s, struct enriched_state *stte)
568 {
569   const char *p;
570
571   if (stte->buff_len < stte->buff_used + m_strlen(s)) {
572     stte->buff_len += LONG_STRING;
573     p_realloc(&stte->buffer, stte->buff_len + 1);
574   }
575
576   p = s;
577   while (*p) {
578     stte->buffer[stte->buff_used++] = *p++;
579   }
580 }
581
582 static void enriched_set_flags (const char *tag, struct enriched_state *stte)
583 {
584   const char *tagptr = tag;
585   int i, j;
586
587   if (*tagptr == '/')
588     tagptr++;
589
590   for (i = 0, j = -1; EnrichedTags[i].tag_name; i++)
591     if (ascii_strcasecmp (EnrichedTags[i].tag_name, tagptr) == 0) {
592       j = EnrichedTags[i].index;
593       break;
594     }
595
596   if (j != -1) {
597     if (j == RICH_CENTER || j == RICH_FLUSHLEFT || j == RICH_FLUSHRIGHT)
598       enriched_flush (stte, 1);
599
600     if (*tag == '/') {
601       if (stte->tag_level[j])   /* make sure not to go negative */
602         stte->tag_level[j]--;
603       if ((stte->s->flags & M_DISPLAY) && j == RICH_PARAM
604           && stte->tag_level[RICH_COLOR]) {
605         stte->param[stte->param_used] = '\0';
606         if (!ascii_strcasecmp (stte->param, "black")) {
607           enriched_puts ("\033[30m", stte);
608         }
609         else if (!ascii_strcasecmp (stte->param, "red")) {
610           enriched_puts ("\033[31m", stte);
611         }
612         else if (!ascii_strcasecmp (stte->param, "green")) {
613           enriched_puts ("\033[32m", stte);
614         }
615         else if (!ascii_strcasecmp (stte->param, "yellow")) {
616           enriched_puts ("\033[33m", stte);
617         }
618         else if (!ascii_strcasecmp (stte->param, "blue")) {
619           enriched_puts ("\033[34m", stte);
620         }
621         else if (!ascii_strcasecmp (stte->param, "magenta")) {
622           enriched_puts ("\033[35m", stte);
623         }
624         else if (!ascii_strcasecmp (stte->param, "cyan")) {
625           enriched_puts ("\033[36m", stte);
626         }
627         else if (!ascii_strcasecmp (stte->param, "white")) {
628           enriched_puts ("\033[37m", stte);
629         }
630       }
631       if ((stte->s->flags & M_DISPLAY) && j == RICH_COLOR) {
632         enriched_puts ("\033[0m", stte);
633       }
634
635       /* flush parameter buffer when closing the tag */
636       if (j == RICH_PARAM) {
637         stte->param_used = 0;
638         stte->param[0] = '\0';
639       }
640     }
641     else
642       stte->tag_level[j]++;
643
644     if (j == RICH_EXCERPT)
645       enriched_flush (stte, 1);
646   }
647 }
648
649 int text_enriched_handler (BODY * a, STATE * s)
650 {
651   enum {
652     TEXT, LANGLE, TAG, BOGUS_TAG, NEWLINE, ST_EOF, DONE
653   } state = TEXT;
654
655   long bytes = a->length;
656   struct enriched_state stte;
657   int c = 0;
658   int tag_len = 0;
659   char tag[LONG_STRING + 1];
660
661   p_clear(&stte, 1);
662   stte.s = s;
663   stte.WrapMargin =
664     ((s->flags & M_DISPLAY) ? (COLS - 4) : ((COLS - 4) <
665                                             72) ? (COLS - 4) : 72);
666   stte.line_max = stte.WrapMargin * 4;
667   stte.line = p_new(char, stte.line_max + 1);
668   stte.param = p_new(char, STRING);
669
670   stte.param_len = STRING;
671   stte.param_used = 0;
672
673   if (s->prefix) {
674     state_puts (s->prefix, s);
675     stte.indent_len += m_strlen(s->prefix);
676   }
677
678   while (state != DONE) {
679     if (state != ST_EOF) {
680       if (!bytes || (c = fgetc (s->fpin)) == EOF)
681         state = ST_EOF;
682       else
683         bytes--;
684     }
685
686     switch (state) {
687     case TEXT:
688       switch (c) {
689       case '<':
690         state = LANGLE;
691         break;
692
693       case '\n':
694         if (stte.tag_level[RICH_NOFILL]) {
695           enriched_flush (&stte, 1);
696         }
697         else {
698           enriched_putc (' ', &stte);
699           state = NEWLINE;
700         }
701         break;
702
703       default:
704         enriched_putc (c, &stte);
705       }
706       break;
707
708     case LANGLE:
709       if (c == '<') {
710         enriched_putc (c, &stte);
711         state = TEXT;
712         break;
713       }
714       else {
715         tag_len = 0;
716         state = TAG;
717       }
718       /* Yes, fall through (it wasn't a <<, so this char is first in TAG) */
719     case TAG:
720       if (c == '>') {
721         tag[tag_len] = '\0';
722         enriched_set_flags (tag, &stte);
723         state = TEXT;
724       }
725       else if (tag_len < LONG_STRING)   /* ignore overly long tags */
726         tag[tag_len++] = c;
727       else
728         state = BOGUS_TAG;
729       break;
730
731     case BOGUS_TAG:
732       if (c == '>')
733         state = TEXT;
734       break;
735
736     case NEWLINE:
737       if (c == '\n')
738         enriched_flush (&stte, 1);
739       else {
740         ungetc (c, s->fpin);
741         bytes++;
742         state = TEXT;
743       }
744       break;
745
746     case ST_EOF:
747       enriched_putc ('\0', &stte);
748       enriched_flush (&stte, 1);
749       state = DONE;
750       break;
751
752     case DONE:                 /* not reached, but gcc complains if this is absent */
753       break;
754     }
755   }
756
757   state_putc ('\n', s);         /* add a final newline */
758
759   p_delete(&(stte.buffer));
760   p_delete(&(stte.line));
761   p_delete(&(stte.param));
762
763   return (0);
764 }
765
766 #define TXTHTML     1
767 #define TXTPLAIN    2
768 #define TXTENRICHED 3
769
770 static int alternative_handler (BODY * a, STATE * s)
771 {
772   BODY *choice = NULL;
773   BODY *b;
774   LIST *t;
775   char buf[STRING];
776   int type = 0;
777   int mustfree = 0;
778   int rc = 0;
779
780   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
781       a->encoding == ENCUUENCODED) {
782     struct stat st;
783
784     mustfree = 1;
785     fstat (fileno (s->fpin), &st);
786     b = mutt_new_body ();
787     b->length = (long) st.st_size;
788     b->parts = mutt_parse_multipart (s->fpin,
789                                      mutt_get_parameter ("boundary",
790                                                          a->parameter),
791                                      (long) st.st_size,
792                                      ascii_strcasecmp ("digest",
793                                                        a->subtype) == 0);
794   }
795   else
796     b = a;
797
798   a = b;
799
800   /* First, search list of prefered types */
801   t = AlternativeOrderList;
802   while (t && !choice) {
803     char *c;
804     int btlen;                  /* length of basetype */
805     int wild;                   /* do we have a wildcard to match all subtypes? */
806
807     c = strchr (t->data, '/');
808     if (c) {
809       wild = (c[1] == '*' && c[2] == 0);
810       btlen = c - t->data;
811     }
812     else {
813       wild = 1;
814       btlen = m_strlen(t->data);
815     }
816
817     if (a && a->parts)
818       b = a->parts;
819     else
820       b = a;
821     while (b) {
822       const char *bt = TYPE (b);
823
824       if (!ascii_strncasecmp (bt, t->data, btlen) && bt[btlen] == 0) {
825         /* the basetype matches */
826         if (wild || !ascii_strcasecmp (t->data + btlen + 1, b->subtype)) {
827           choice = b;
828         }
829       }
830       b = b->next;
831     }
832     t = t->next;
833   }
834
835   /* Next, look for an autoviewable type */
836   if (!choice) {
837     if (a && a->parts)
838       b = a->parts;
839     else
840       b = a;
841     while (b) {
842       snprintf (buf, sizeof (buf), "%s/%s", TYPE (b), b->subtype);
843       if (mutt_is_autoview (b, buf)) {
844         rfc1524_entry *entry = rfc1524_new_entry ();
845
846         if (rfc1524_mailcap_lookup (b, buf, entry, M_AUTOVIEW)) {
847           choice = b;
848         }
849         rfc1524_free_entry (&entry);
850       }
851       b = b->next;
852     }
853   }
854
855   /* Then, look for a text entry */
856   if (!choice) {
857     if (a && a->parts)
858       b = a->parts;
859     else
860       b = a;
861     while (b) {
862       if (b->type == TYPETEXT) {
863         if (!ascii_strcasecmp ("plain", b->subtype) && type <= TXTPLAIN) {
864           choice = b;
865           type = TXTPLAIN;
866         }
867         else if (!ascii_strcasecmp ("enriched", b->subtype)
868                  && type <= TXTENRICHED) {
869           choice = b;
870           type = TXTENRICHED;
871         }
872         else if (!ascii_strcasecmp ("html", b->subtype) && type <= TXTHTML) {
873           choice = b;
874           type = TXTHTML;
875         }
876       }
877       b = b->next;
878     }
879   }
880
881   /* Finally, look for other possibilities */
882   if (!choice) {
883     if (a && a->parts)
884       b = a->parts;
885     else
886       b = a;
887     while (b) {
888       if (mutt_can_decode (b))
889         choice = b;
890       b = b->next;
891     }
892   }
893
894   if (choice) {
895     if (s->flags & M_DISPLAY && !option (OPTWEED)) {
896       fseeko (s->fpin, choice->hdr_offset, 0);
897       mutt_copy_bytes (s->fpin, s->fpout,
898                        choice->offset - choice->hdr_offset);
899     }
900     mutt_body_handler (choice, s);
901   }
902   else if (s->flags & M_DISPLAY) {
903     /* didn't find anything that we could display! */
904     state_mark_attach (s);
905     state_puts (_("[-- Error:  Could not display any parts of Multipart/Alternative! --]\n"), s);
906     rc = -1;
907   }
908
909   if (mustfree)
910     mutt_free_body (&a);
911
912   return (rc);
913 }
914
915 /* handles message/rfc822 body parts */
916 static int message_handler (BODY * a, STATE * s)
917 {
918   struct stat st;
919   BODY *b;
920   off_t off_start;
921   int rc = 0;
922
923   off_start = ftello (s->fpin);
924   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
925       a->encoding == ENCUUENCODED) {
926     fstat (fileno (s->fpin), &st);
927     b = mutt_new_body ();
928     b->length = (long) st.st_size;
929     b->parts = mutt_parse_messageRFC822 (s->fpin, b);
930   }
931   else
932     b = a;
933
934   if (b->parts) {
935     mutt_copy_hdr (s->fpin, s->fpout, off_start, b->parts->offset,
936                    (((s->flags & M_WEED)
937                      || ((s->flags & (M_DISPLAY | M_PRINTING))
938                          && option (OPTWEED))) ? (CH_WEED | CH_REORDER) : 0) |
939                    (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM,
940                    s->prefix);
941
942     if (s->prefix)
943       state_puts (s->prefix, s);
944     state_putc ('\n', s);
945
946     rc = mutt_body_handler (b->parts, s);
947   }
948
949   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
950       a->encoding == ENCUUENCODED)
951     mutt_free_body (&b);
952
953   return (rc);
954 }
955
956 /* returns 1 if decoding the attachment will produce output */
957 int mutt_can_decode (BODY * a)
958 {
959   char type[STRING];
960
961   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
962   if (mutt_is_autoview (a, type))
963     return (rfc1524_mailcap_lookup (a, type, NULL, M_AUTOVIEW));
964   else if (a->type == TYPETEXT)
965     return (1);
966   else if (a->type == TYPEMESSAGE)
967     return (1);
968   else if (a->type == TYPEMULTIPART) {
969     BODY *p;
970
971     if (WithCrypto) {
972       if (ascii_strcasecmp (a->subtype, "signed") == 0 ||
973           ascii_strcasecmp (a->subtype, "encrypted") == 0)
974         return (1);
975     }
976
977     for (p = a->parts; p; p = p->next) {
978       if (mutt_can_decode (p))
979         return (1);
980     }
981
982   }
983   else if (WithCrypto && a->type == TYPEAPPLICATION) {
984     if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (a))
985       return (1);
986     if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime (a))
987       return (1);
988   }
989
990   return (0);
991 }
992
993 static int multipart_handler (BODY * a, STATE * s)
994 {
995   BODY *b, *p;
996   char length[5];
997   struct stat st;
998   int count;
999   int rc = 0;
1000
1001   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1002       a->encoding == ENCUUENCODED) {
1003     fstat (fileno (s->fpin), &st);
1004     b = mutt_new_body ();
1005     b->length = (long) st.st_size;
1006     b->parts = mutt_parse_multipart (s->fpin,
1007                                      mutt_get_parameter ("boundary",
1008                                                          a->parameter),
1009                                      (long) st.st_size,
1010                                      ascii_strcasecmp ("digest",
1011                                                        a->subtype) == 0);
1012   }
1013   else
1014     b = a;
1015
1016   for (p = b->parts, count = 1; p; p = p->next, count++) {
1017     if (s->flags & M_DISPLAY) {
1018       state_mark_attach (s);
1019       state_printf (s, _("[-- Attachment #%d"), count);
1020       if (p->description || p->filename || p->form_name) {
1021         state_puts (": ", s);
1022         state_puts (p->description ? p->description :
1023                     p->filename ? p->filename : p->form_name, s);
1024       }
1025       state_puts (" --]\n", s);
1026
1027       mutt_pretty_size (length, sizeof (length), p->length);
1028
1029       state_mark_attach (s);
1030       state_printf (s, _("[-- Type: %s/%s, Encoding: %s, Size: %s --]\n"),
1031                     TYPE (p), p->subtype, ENCODING (p->encoding), length);
1032       if (!option (OPTWEED)) {
1033         fseeko (s->fpin, p->hdr_offset, 0);
1034         mutt_copy_bytes (s->fpin, s->fpout, p->offset - p->hdr_offset);
1035       }
1036       else
1037         state_putc ('\n', s);
1038     }
1039     else {
1040       if (p->description && mutt_can_decode (p))
1041         state_printf (s, "Content-Description: %s\n", p->description);
1042
1043       if (p->form_name)
1044         state_printf (s, "%s: \n", p->form_name);
1045
1046     }
1047     rc = mutt_body_handler (p, s);
1048     state_putc ('\n', s);
1049     if (rc || ((s->flags & M_REPLYING)
1050         && (option (OPTINCLUDEONLYFIRST)) && (s->flags & M_FIRSTDONE)))
1051       break;
1052   }
1053
1054   if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
1055       a->encoding == ENCUUENCODED)
1056     mutt_free_body (&b);
1057
1058   return (rc);
1059 }
1060
1061 static int autoview_handler (BODY * a, STATE * s)
1062 {
1063   rfc1524_entry *entry = rfc1524_new_entry ();
1064   char buffer[LONG_STRING];
1065   char type[STRING];
1066   char command[LONG_STRING];
1067   char tempfile[_POSIX_PATH_MAX] = "";
1068   char *fname;
1069   FILE *fpin = NULL;
1070   FILE *fpout = NULL;
1071   FILE *fperr = NULL;
1072   int piped = FALSE;
1073   pid_t thepid;
1074   int rc = 0;
1075
1076   snprintf (type, sizeof (type), "%s/%s", TYPE (a), a->subtype);
1077   rfc1524_mailcap_lookup (a, type, entry, M_AUTOVIEW);
1078
1079   fname = m_strdup(a->filename);
1080   mutt_sanitize_filename (fname, 1);
1081   rfc1524_expand_filename (entry->nametemplate, fname, tempfile,
1082                            sizeof (tempfile));
1083   p_delete(&fname);
1084
1085   if (entry->command) {
1086     m_strcpy(command, sizeof(command), entry->command);
1087
1088     /* rfc1524_expand_command returns 0 if the file is required */
1089     piped =
1090       rfc1524_expand_command (a, tempfile, type, command, sizeof (command));
1091
1092     if (s->flags & M_DISPLAY) {
1093       state_mark_attach (s);
1094       state_printf (s, _("[-- Autoview using %s --]\n"), command);
1095       mutt_message (_("Invoking autoview command: %s"), command);
1096     }
1097
1098     if ((fpin = safe_fopen (tempfile, "w+")) == NULL) {
1099       mutt_perror ("fopen");
1100       rfc1524_free_entry (&entry);
1101       return (-1);
1102     }
1103
1104     mutt_copy_bytes (s->fpin, fpin, a->length);
1105
1106     if (!piped) {
1107       safe_fclose (&fpin);
1108       thepid = mutt_create_filter (command, NULL, &fpout, &fperr);
1109     }
1110     else {
1111       unlink (tempfile);
1112       fflush (fpin);
1113       rewind (fpin);
1114       thepid = mutt_create_filter_fd (command, NULL, &fpout, &fperr,
1115                                       fileno (fpin), -1, -1);
1116     }
1117
1118     if (thepid < 0) {
1119       mutt_perror (_("Can't create filter"));
1120
1121       if (s->flags & M_DISPLAY) {
1122         state_mark_attach (s);
1123         state_printf (s, _("[-- Can't run %s. --]\n"), command);
1124       }
1125       rc = -1;
1126       goto bail;
1127     }
1128
1129     if (s->prefix) {
1130       while (fgets (buffer, sizeof (buffer), fpout) != NULL) {
1131         state_puts (s->prefix, s);
1132         state_puts (buffer, s);
1133       }
1134       /* check for data on stderr */
1135       if (fgets (buffer, sizeof (buffer), fperr)) {
1136         if (s->flags & M_DISPLAY) {
1137           state_mark_attach (s);
1138           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1139         }
1140
1141         state_puts (s->prefix, s);
1142         state_puts (buffer, s);
1143         while (fgets (buffer, sizeof (buffer), fperr) != NULL) {
1144           state_puts (s->prefix, s);
1145           state_puts (buffer, s);
1146         }
1147       }
1148     }
1149     else {
1150       mutt_copy_stream (fpout, s->fpout);
1151       /* Check for stderr messages */
1152       if (fgets (buffer, sizeof (buffer), fperr)) {
1153         if (s->flags & M_DISPLAY) {
1154           state_mark_attach (s);
1155           state_printf (s, _("[-- Autoview stderr of %s --]\n"), command);
1156         }
1157
1158         state_puts (buffer, s);
1159         mutt_copy_stream (fperr, s->fpout);
1160       }
1161     }
1162
1163   bail:
1164     safe_fclose (&fpout);
1165     safe_fclose (&fperr);
1166
1167     mutt_wait_filter (thepid);
1168     if (piped)
1169       safe_fclose (&fpin);
1170     else
1171       mutt_unlink (tempfile);
1172
1173     if (s->flags & M_DISPLAY)
1174       mutt_clear_error ();
1175   }
1176   rfc1524_free_entry (&entry);
1177   return (rc);
1178 }
1179
1180 static int external_body_handler (BODY * b, STATE * s)
1181 {
1182   const char *access_type;
1183   const char *expiration;
1184   time_t expire;
1185
1186   access_type = mutt_get_parameter ("access-type", b->parameter);
1187   if (!access_type) {
1188     if (s->flags & M_DISPLAY) {
1189       state_mark_attach (s);
1190       state_puts (_("[-- Error: message/external-body has no access-type parameter --]\n"), s);
1191     }
1192     return (-1);
1193   }
1194
1195   expiration = mutt_get_parameter ("expiration", b->parameter);
1196   if (expiration)
1197     expire = mutt_parse_date (expiration, NULL);
1198   else
1199     expire = -1;
1200
1201   if (!ascii_strcasecmp (access_type, "x-mutt-deleted")) {
1202     if (s->flags & (M_DISPLAY | M_PRINTING)) {
1203       char *length;
1204       char pretty_size[10];
1205
1206       state_mark_attach (s);
1207       state_printf (s, _("[-- This %s/%s attachment "),
1208                     TYPE (b->parts), b->parts->subtype);
1209       length = mutt_get_parameter ("length", b->parameter);
1210       if (length) {
1211         mutt_pretty_size (pretty_size, sizeof (pretty_size),
1212                           strtol (length, NULL, 10));
1213         state_printf (s, _("(size %s bytes) "), pretty_size);
1214       }
1215       state_puts (_("has been deleted --]\n"), s);
1216
1217       if (expire != -1) {
1218         state_mark_attach (s);
1219         state_printf (s, _("[-- on %s --]\n"), expiration);
1220       }
1221       if (b->parts->filename) {
1222         state_mark_attach (s);
1223         state_printf (s, _("[-- name: %s --]\n"), b->parts->filename);
1224       }
1225
1226       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1227                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1228                      CH_DECODE, NULL);
1229     }
1230   }
1231   else if (expiration && expire < time (NULL)) {
1232     if (s->flags & M_DISPLAY) {
1233       state_mark_attach (s);
1234       state_printf (s, _("[-- This %s/%s attachment is not included, --]\n"),
1235                     TYPE (b->parts), b->parts->subtype);
1236       state_attach_puts (_("[-- and the indicated external source has --]\n"
1237                            "[-- expired. --]\n"), s);
1238
1239       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1240                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1241                      CH_DECODE, NULL);
1242     }
1243   }
1244   else {
1245     if (s->flags & M_DISPLAY) {
1246       state_mark_attach (s);
1247       state_printf (s,
1248                     _("[-- This %s/%s attachment is not included, --]\n"),
1249                     TYPE (b->parts), b->parts->subtype);
1250       state_mark_attach (s);
1251       state_printf (s,
1252                     _
1253                     ("[-- and the indicated access-type %s is unsupported --]\n"),
1254                     access_type);
1255       mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset,
1256                      (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) |
1257                      CH_DECODE, NULL);
1258     }
1259   }
1260   return (0);
1261 }
1262
1263 void mutt_decode_attachment (BODY * b, STATE * s)
1264 {
1265   int istext = mutt_is_text_part (b);
1266   iconv_t cd = (iconv_t) (-1);
1267
1268   Quotebuf[0] = '\0';
1269
1270   if (istext) {
1271     if (s->flags & M_CHARCONV) {
1272       const char *charset = mutt_get_parameter ("charset", b->parameter);
1273
1274       if (!option (OPTSTRICTMIME) && !charset)
1275         charset = mutt_get_first_charset (AssumedCharset);
1276       if (charset && Charset)
1277         cd = mutt_iconv_open (Charset, charset, M_ICONV_HOOK_FROM);
1278     }
1279     else {
1280       if (b->file_charset)
1281         cd = mutt_iconv_open (Charset, b->file_charset, M_ICONV_HOOK_FROM);
1282     }
1283   }
1284
1285   fseeko (s->fpin, b->offset, 0);
1286   switch (b->encoding) {
1287   case ENCQUOTEDPRINTABLE:
1288     mutt_decode_quoted (s, b->length, istext ||
1289                         ((WithCrypto & APPLICATION_PGP) &&
1290                          mutt_is_application_pgp (b)), cd);
1291     break;
1292   case ENCBASE64:
1293     mutt_decode_base64 (s, b->length, istext ||
1294                         ((WithCrypto & APPLICATION_PGP) &&
1295                          mutt_is_application_pgp (b)), cd);
1296     break;
1297   case ENCUUENCODED:
1298     mutt_decode_uuencoded (s, b->length, istext
1299                            || ((WithCrypto & APPLICATION_PGP) &&
1300                                mutt_is_application_pgp (b)), cd);
1301     break;
1302   default:
1303     mutt_decode_xbit (s, b->length, istext
1304                       || ((WithCrypto & APPLICATION_PGP) &&
1305                           mutt_is_application_pgp (b)), cd);
1306     break;
1307   }
1308
1309   if (cd != (iconv_t) (-1))
1310     iconv_close (cd);
1311 }
1312
1313 int mutt_body_handler (BODY * b, STATE * s)
1314 {
1315   int decode = 0;
1316   int plaintext = 0;
1317   FILE *fp = NULL;
1318   char tempfile[_POSIX_PATH_MAX];
1319   handler_t handler = NULL;
1320   long tmpoffset = 0;
1321   size_t tmplength = 0;
1322   char type[STRING];
1323   int rc = 0;
1324
1325   int oflags = s->flags;
1326
1327   /* first determine which handler to use to process this part */
1328
1329   snprintf (type, sizeof (type), "%s/%s", TYPE (b), b->subtype);
1330   if (mutt_is_autoview (b, type)) {
1331     rfc1524_entry *entry = rfc1524_new_entry ();
1332
1333     if (rfc1524_mailcap_lookup (b, type, entry, M_AUTOVIEW)) {
1334       handler = autoview_handler;
1335       s->flags &= ~M_CHARCONV;
1336     }
1337     rfc1524_free_entry (&entry);
1338   }
1339   else if (b->type == TYPETEXT) {
1340     if (ascii_strcasecmp ("plain", b->subtype) == 0) {
1341       /* avoid copying this part twice since removing the transfer-encoding is
1342        * the only operation needed.
1343        */
1344       if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
1345         handler = crypt_pgp_application_pgp_handler;
1346       else
1347         if (ascii_strcasecmp
1348             ("flowed", mutt_get_parameter ("format", b->parameter)) == 0)
1349         handler = rfc3676_handler;
1350       else
1351         plaintext = 1;
1352     }
1353     else if (ascii_strcasecmp ("enriched", b->subtype) == 0)
1354       handler = text_enriched_handler;
1355     else                        /* text body type without a handler */
1356       plaintext = 1;
1357   }
1358   else if (b->type == TYPEMESSAGE) {
1359     if (mutt_is_message_type (b->type, b->subtype))
1360       handler = message_handler;
1361     else if (!ascii_strcasecmp ("delivery-status", b->subtype))
1362       plaintext = 1;
1363     else if (!ascii_strcasecmp ("external-body", b->subtype))
1364       handler = external_body_handler;
1365   }
1366   else if (b->type == TYPEMULTIPART) {
1367     char *p;
1368
1369     if (ascii_strcasecmp ("alternative", b->subtype) == 0)
1370       handler = alternative_handler;
1371     else if (WithCrypto && ascii_strcasecmp ("signed", b->subtype) == 0) {
1372       p = mutt_get_parameter ("protocol", b->parameter);
1373
1374       if (!p)
1375         mutt_error (_("Error: multipart/signed has no protocol."));
1376
1377       else if (s->flags & M_VERIFY)
1378         handler = mutt_signed_handler;
1379     }
1380     else if ((WithCrypto & APPLICATION_PGP)
1381              && m_strcasecmp("encrypted", b->subtype) == 0) {
1382       p = mutt_get_parameter ("protocol", b->parameter);
1383
1384       if (!p)
1385         mutt_error (_
1386                     ("Error: multipart/encrypted has no protocol parameter!"));
1387
1388       else if (ascii_strcasecmp ("application/pgp-encrypted", p) == 0)
1389         handler = crypt_pgp_encrypted_handler;
1390     }
1391
1392     if (!handler)
1393       handler = multipart_handler;
1394   }
1395   else if (WithCrypto && b->type == TYPEAPPLICATION) {
1396     if ((WithCrypto & APPLICATION_PGP) && mutt_is_application_pgp (b))
1397       handler = crypt_pgp_application_pgp_handler;
1398     if ((WithCrypto & APPLICATION_SMIME) && mutt_is_application_smime (b))
1399       handler = crypt_smime_application_smime_handler;
1400   }
1401
1402
1403   if (plaintext || handler) {
1404     fseeko (s->fpin, b->offset, 0);
1405
1406     /* see if we need to decode this part before processing it */
1407     if (b->encoding == ENCBASE64 || b->encoding == ENCQUOTEDPRINTABLE || b->encoding == ENCUUENCODED || plaintext || mutt_is_text_part (b)) {   /* text subtypes may
1408                                                                                                                                                  * require character
1409                                                                                                                                                  * set conversion even
1410                                                                                                                                                  * with 8bit encoding.
1411                                                                                                                                                  */
1412       int origType = b->type;
1413       char *savePrefix = NULL;
1414
1415       if (!plaintext) {
1416         /* decode to a tempfile, saving the original destination */
1417         fp = s->fpout;
1418         mutt_mktemp (tempfile);
1419         if ((s->fpout = safe_fopen (tempfile, "w")) == NULL) {
1420           mutt_error _("Unable to open temporary file!");
1421
1422           goto bail;
1423         }
1424         /* decoding the attachment changes the size and offset, so save a copy
1425          * of the "real" values now, and restore them after processing
1426          */
1427         tmplength = b->length;
1428         tmpoffset = b->offset;
1429
1430         /* if we are decoding binary bodies, we don't want to prefix each
1431          * line with the prefix or else the data will get corrupted.
1432          */
1433         savePrefix = s->prefix;
1434         s->prefix = NULL;
1435
1436         decode = 1;
1437       }
1438       else
1439         b->type = TYPETEXT;
1440
1441       mutt_decode_attachment (b, s);
1442
1443       if (decode) {
1444         b->length = ftello (s->fpout);
1445         b->offset = 0;
1446         fclose (s->fpout);
1447
1448         /* restore final destination and substitute the tempfile for input */
1449         s->fpout = fp;
1450         fp = s->fpin;
1451         s->fpin = safe_fopen (tempfile, "r");
1452         unlink (tempfile);
1453
1454         /* restore the prefix */
1455         s->prefix = savePrefix;
1456       }
1457
1458       b->type = origType;
1459     }
1460
1461     /* process the (decoded) body part */
1462     if (handler) {
1463       rc = handler (b, s);
1464
1465       if (decode) {
1466         b->length = tmplength;
1467         b->offset = tmpoffset;
1468
1469         /* restore the original source stream */
1470         fclose (s->fpin);
1471         s->fpin = fp;
1472       }
1473     }
1474     s->flags |= M_FIRSTDONE;
1475   }
1476   else if (s->flags & M_DISPLAY) {
1477     state_mark_attach (s);
1478     state_printf (s, _("[-- %s/%s is unsupported "), TYPE (b), b->subtype);
1479     if (!option (OPTVIEWATTACH)) {
1480       if (km_expand_key
1481           (type, sizeof (type),
1482            km_find_func (MENU_PAGER, OP_VIEW_ATTACHMENTS)))
1483         fprintf (s->fpout, _("(use '%s' to view this part)"), type);
1484       else
1485         fputs (_("(need 'view-attachments' bound to key!)"), s->fpout);
1486     }
1487     fputs (" --]\n", s->fpout);
1488   }
1489
1490 bail:
1491   s->flags = oflags | (s->flags & M_FIRSTDONE);
1492
1493   return (rc);
1494 }