This commit was manufactured by cvs2svn to create branch 'gtk2'.
[claws.git] / src / procmime.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto & The Sylpheed-Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <stdio.h>
27 #include <glib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <locale.h>
31 #include <ctype.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35
36 #include "intl.h"
37 #include "procmime.h"
38 #include "procheader.h"
39 #include "base64.h"
40 #include "quoted-printable.h"
41 #include "uuencode.h"
42 #include "unmime.h"
43 #include "html.h"
44 #include "enriched.h"
45 #include "codeconv.h"
46 #include "utils.h"
47 #include "prefs_common.h"
48 #include "prefs_gtk.h"
49
50 static GHashTable *procmime_get_mime_type_table (void);
51
52 MimeInfo *procmime_mimeinfo_new(void)
53 {
54         MimeInfo *mimeinfo;
55
56         mimeinfo = g_new0(MimeInfo, 1);
57         mimeinfo->content        = MIMECONTENT_EMPTY;
58         mimeinfo->data.filename  = NULL;
59
60         mimeinfo->type           = MIMETYPE_UNKNOWN;
61         mimeinfo->encoding_type  = ENC_UNKNOWN;
62         mimeinfo->typeparameters = g_hash_table_new(g_str_hash, g_str_equal);
63
64         mimeinfo->disposition    = DISPOSITIONTYPE_UNKNOWN;
65         mimeinfo->dispositionparameters 
66                                  = g_hash_table_new(g_str_hash, g_str_equal);
67
68         mimeinfo->node           = g_node_new(mimeinfo);
69         
70         return mimeinfo;
71 }
72
73 static gboolean procmime_mimeinfo_parameters_destroy(gpointer key, gpointer value, gpointer user_data)
74 {
75         g_free(key);
76         g_free(value);
77         
78         return TRUE;
79 }
80
81 static gchar *forced_charset = NULL;
82
83 void procmime_force_charset(const gchar *str)
84 {
85         g_free(forced_charset);
86         forced_charset = NULL;
87         if (str)
88                 forced_charset = g_strdup(str);
89 }
90
91 static EncodingType forced_encoding = 0;
92
93 void procmime_force_encoding(EncodingType encoding)
94 {
95         forced_encoding = encoding;
96 }
97
98 static gboolean free_func(GNode *node, gpointer data)
99 {
100         MimeInfo *mimeinfo = (MimeInfo *) node->data;
101
102         switch (mimeinfo->content) {
103         case MIMECONTENT_FILE:
104                 if (mimeinfo->tmp)
105                         unlink(mimeinfo->data.filename);
106                 g_free(mimeinfo->data.filename);
107                 break;
108
109         case MIMECONTENT_MEM:
110                 if (mimeinfo->tmp)
111                         g_free(mimeinfo->data.mem);
112         default:
113                 break;
114         }
115
116         g_free(mimeinfo->subtype);
117         g_free(mimeinfo->description);
118         g_free(mimeinfo->id);
119
120         g_hash_table_foreach_remove(mimeinfo->typeparameters,
121                 procmime_mimeinfo_parameters_destroy, NULL);
122         g_hash_table_destroy(mimeinfo->typeparameters);
123         g_hash_table_foreach_remove(mimeinfo->dispositionparameters,
124                 procmime_mimeinfo_parameters_destroy, NULL);
125         g_hash_table_destroy(mimeinfo->dispositionparameters);
126
127         if (mimeinfo->privacy)
128                 privacy_free_privacydata(mimeinfo->privacy);
129
130         g_free(mimeinfo);
131
132         return FALSE;
133 }
134
135 void procmime_mimeinfo_free_all(MimeInfo *mimeinfo)
136 {
137         GNode *node;
138
139         if (!mimeinfo)
140                 return;
141
142         node = mimeinfo->node;
143         g_node_traverse(node, G_IN_ORDER, G_TRAVERSE_ALL, -1, free_func, NULL);
144
145         g_node_destroy(node);
146 }
147
148 #if 0 /* UNUSED */
149 MimeInfo *procmime_mimeinfo_insert(MimeInfo *parent, MimeInfo *mimeinfo)
150 {
151         MimeInfo *child = parent->children;
152
153         if (!child)
154                 parent->children = mimeinfo;
155         else {
156                 while (child->next != NULL)
157                         child = child->next;
158
159                 child->next = mimeinfo;
160         }
161
162         mimeinfo->parent = parent;
163         mimeinfo->level = parent->level + 1;
164
165         return mimeinfo;
166 }
167
168 void procmime_mimeinfo_replace(MimeInfo *old, MimeInfo *new)
169 {
170         MimeInfo *parent = old->parent;
171         MimeInfo *child;
172
173         g_return_if_fail(parent != NULL);
174         g_return_if_fail(new->next == NULL);
175
176         for (child = parent->children; child && child != old;
177              child = child->next)
178                 ;
179         if (!child) {
180                 g_warning("oops: parent can't find it's own child");
181                 return;
182         }
183         procmime_mimeinfo_free_all(old);
184
185         if (child == parent->children) {
186                 new->next = parent->children->next;
187                 parent->children = new;
188         } else {
189                 new->next = child->next;
190                 child = new;
191         }
192 }
193 #endif
194
195 MimeInfo *procmime_mimeinfo_parent(MimeInfo *mimeinfo)
196 {
197         g_return_val_if_fail(mimeinfo != NULL, NULL);
198         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
199
200         if (mimeinfo->node->parent == NULL)
201                 return NULL;
202         return (MimeInfo *) mimeinfo->node->parent->data;
203 }
204
205 MimeInfo *procmime_mimeinfo_next(MimeInfo *mimeinfo)
206 {
207         g_return_val_if_fail(mimeinfo != NULL, NULL);
208         g_return_val_if_fail(mimeinfo->node != NULL, NULL);
209
210         if (mimeinfo->node->children)
211                 return (MimeInfo *) mimeinfo->node->children->data;
212         if (mimeinfo->node->next)
213                 return (MimeInfo *) mimeinfo->node->next->data;
214
215         if (mimeinfo->node->parent == NULL)
216                 return NULL;
217
218         while (mimeinfo->node->parent != NULL) {
219                 mimeinfo = (MimeInfo *) mimeinfo->node->parent->data;
220                 if (mimeinfo->node->next)
221                         return (MimeInfo *) mimeinfo->node->next->data;
222         }
223
224         return NULL;
225 }
226
227 MimeInfo *procmime_scan_message(MsgInfo *msginfo)
228 {
229         gchar *filename;
230         MimeInfo *mimeinfo;
231
232         filename = procmsg_get_message_file(msginfo);
233         if (!filename)
234                 return NULL;
235         if (msginfo->folder->stype != F_QUEUE && 
236             msginfo->folder->stype != F_DRAFT)
237                 mimeinfo = procmime_scan_file(filename);
238         else
239                 mimeinfo = procmime_scan_queue_file(filename);
240         g_free(filename);
241
242         return mimeinfo;
243 }
244
245 enum
246 {
247         H_CONTENT_TRANSFER_ENCODING = 0,
248         H_CONTENT_TYPE              = 1,
249         H_CONTENT_DISPOSITION       = 2,
250         H_CONTENT_DESCRIPTION       = 3,
251         H_SUBJECT                   = 4
252 };
253
254 const gchar *procmime_mimeinfo_get_parameter(MimeInfo *mimeinfo, const gchar *name)
255 {
256         const gchar *value;
257
258         g_return_val_if_fail(mimeinfo != NULL, NULL);
259         g_return_val_if_fail(name != NULL, NULL);
260
261         value = g_hash_table_lookup(mimeinfo->dispositionparameters, name);
262         if (value == NULL)
263                 value = g_hash_table_lookup(mimeinfo->typeparameters, name);
264         
265         return value;
266 }
267
268 gboolean procmime_decode_content(MimeInfo *mimeinfo)
269 {
270         gchar buf[BUFFSIZE];
271         gint readend;
272         gchar *tmpfilename;
273         FILE *outfp, *infp;
274         struct stat statbuf;
275
276         EncodingType encoding = forced_encoding 
277                                 ? forced_encoding
278                                 : mimeinfo->encoding_type;
279                    
280         g_return_val_if_fail(mimeinfo != NULL, FALSE);
281
282         if (encoding == ENC_UNKNOWN ||
283             encoding == ENC_BINARY)
284                 return TRUE;
285
286         infp = fopen(mimeinfo->data.filename, "rb");
287         if (!infp) {
288                 perror("fopen");
289                 return FALSE;
290         }
291         fseek(infp, mimeinfo->offset, SEEK_SET);
292
293         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
294         if (!outfp) {
295                 perror("tmpfile");
296                 return FALSE;
297         }
298
299         readend = mimeinfo->offset + mimeinfo->length;
300
301         if (encoding == ENC_QUOTED_PRINTABLE) {
302                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
303                         gint len;
304                         len = qp_decode_line(buf);
305                         fwrite(buf, len, 1, outfp);
306                 }
307         } else if (encoding == ENC_BASE64) {
308                 gchar outbuf[BUFFSIZE];
309                 gint len;
310                 Base64Decoder *decoder;
311
312                 decoder = base64_decoder_new();
313                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
314                         len = base64_decoder_decode(decoder, buf, outbuf);
315                         if (len < 0) {
316                                 g_warning("Bad BASE64 content\n");
317                                 break;
318                         }
319                         fwrite(outbuf, sizeof(gchar), len, outfp);
320                 }
321                 base64_decoder_free(decoder);
322         } else if (encoding == ENC_X_UUENCODE) {
323                 gchar outbuf[BUFFSIZE];
324                 gint len;
325                 gboolean flag = FALSE;
326
327                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
328                         if (!flag && strncmp(buf,"begin ", 6)) continue;
329
330                         if (flag) {
331                                 len = fromuutobits(outbuf, buf);
332                                 if (len <= 0) {
333                                         if (len < 0) 
334                                                 g_warning("Bad UUENCODE content(%d)\n", len);
335                                         break;
336                                 }
337                                 fwrite(outbuf, sizeof(gchar), len, outfp);
338                         } else
339                                 flag = TRUE;
340                 }
341         } else {
342                 while ((ftell(infp) < readend) && (fgets(buf, sizeof(buf), infp) != NULL)) {
343                         fputs(buf, outfp);
344                 }
345         }
346
347         fclose(outfp);
348         fclose(infp);
349
350         stat(tmpfilename, &statbuf);
351         if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
352                 unlink(mimeinfo->data.filename);
353         if (mimeinfo->data.filename != NULL)
354                 g_free(mimeinfo->data.filename);
355         mimeinfo->data.filename = tmpfilename;
356         mimeinfo->tmp = TRUE;
357         mimeinfo->offset = 0;
358         mimeinfo->length = statbuf.st_size;
359         mimeinfo->encoding_type = ENC_BINARY;
360
361         return TRUE;
362 }
363
364 #define B64_LINE_SIZE           57
365 #define B64_BUFFSIZE            77
366
367 gboolean procmime_encode_content(MimeInfo *mimeinfo, EncodingType encoding)
368 {
369         FILE *infp, *outfp;
370         gint len;
371         gchar *tmpfilename;
372         struct stat statbuf;
373
374         if (mimeinfo->encoding_type != ENC_UNKNOWN &&
375             mimeinfo->encoding_type != ENC_BINARY &&
376             mimeinfo->encoding_type != ENC_7BIT &&
377             mimeinfo->encoding_type != ENC_8BIT)
378                 if(!procmime_decode_content(mimeinfo))
379                         return FALSE;
380
381         outfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmpfilename);
382         if (!outfp) {
383                 perror("tmpfile");
384                 return FALSE;
385         }
386
387         if (mimeinfo->content == MIMECONTENT_FILE) {
388                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
389                         g_warning("Can't open file %s\n", mimeinfo->data.filename);
390                         return FALSE;
391                 }
392         } else if (mimeinfo->content == MIMECONTENT_MEM) {
393                 infp = str_open_as_stream(mimeinfo->data.mem);
394                 if (infp == NULL)
395                         return FALSE;
396         }
397
398         if (encoding == ENC_BASE64) {
399                 gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE];
400
401                 while ((len = fread(inbuf, sizeof(gchar),
402                                     B64_LINE_SIZE, infp))
403                        == B64_LINE_SIZE) {
404                         base64_encode(outbuf, inbuf, B64_LINE_SIZE);
405                         fputs(outbuf, outfp);
406                         fputc('\n', outfp);
407                 }
408                 if (len > 0 && feof(infp)) {
409                         base64_encode(outbuf, inbuf, len);
410                         fputs(outbuf, outfp);
411                         fputc('\n', outfp);
412                 }
413         } else if (encoding == ENC_QUOTED_PRINTABLE) {
414                 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
415
416                 while (fgets(inbuf, sizeof(inbuf), infp) != NULL) {
417                         qp_encode_line(outbuf, inbuf);
418                         fputs(outbuf, outfp);
419                 }
420         } else {
421                 gchar buf[BUFFSIZE];
422
423                 while (fgets(buf, sizeof(buf), infp) != NULL) {
424                         strcrchomp(buf);
425                         fputs(buf, outfp);
426                 }
427         }
428
429         fclose(outfp);
430         fclose(infp);
431
432         if (mimeinfo->content == MIMECONTENT_FILE) {
433                 if (mimeinfo->tmp && (mimeinfo->data.filename != NULL))
434                         unlink(mimeinfo->data.filename);
435                 g_free(mimeinfo->data.filename);
436         } else if (mimeinfo->content == MIMECONTENT_MEM) {
437                 if (mimeinfo->tmp && (mimeinfo->data.mem != NULL))
438                         g_free(mimeinfo->data.mem);
439         }
440
441         stat(tmpfilename, &statbuf);
442         mimeinfo->content = MIMECONTENT_FILE;
443         mimeinfo->data.filename = tmpfilename;
444         mimeinfo->tmp = TRUE;
445         mimeinfo->offset = 0;
446         mimeinfo->length = statbuf.st_size;
447         mimeinfo->encoding_type = encoding;
448
449         return TRUE;
450 }
451
452 gint procmime_get_part(const gchar *outfile, MimeInfo *mimeinfo)
453 {
454         FILE *infp, *outfp;
455         gchar buf[BUFFSIZE];
456         gint restlength, readlength;
457
458         g_return_val_if_fail(outfile != NULL, -1);
459         g_return_val_if_fail(mimeinfo != NULL, -1);
460
461         if (mimeinfo->encoding_type != ENC_BINARY && !procmime_decode_content(mimeinfo))
462                 return -1;
463
464         if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
465                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
466                 return -1;
467         }
468         if (fseek(infp, mimeinfo->offset, SEEK_SET) < 0) {
469                 FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
470                 fclose(infp);
471                 return -1;
472         }
473         if ((outfp = fopen(outfile, "wb")) == NULL) {
474                 FILE_OP_ERROR(outfile, "fopen");
475                 fclose(infp);
476                 return -1;
477         }
478
479         restlength = mimeinfo->length;
480
481         while ((restlength > 0) && ((readlength = fread(buf, 1, restlength > BUFFSIZE ? BUFFSIZE : restlength, infp)) > 0)) {
482                 fwrite(buf, 1, readlength, outfp);
483                 restlength -= readlength;
484         }
485
486         fclose(infp);
487         if (fclose(outfp) == EOF) {
488                 FILE_OP_ERROR(outfile, "fclose");
489                 unlink(outfile);
490                 return -1;
491         }
492
493         return 0;
494 }
495
496 struct ContentRenderer {
497         char * content_type;
498         char * renderer;
499 };
500
501 static GList * renderer_list = NULL;
502
503 static struct ContentRenderer *
504 content_renderer_new(char * content_type, char * renderer)
505 {
506         struct ContentRenderer * cr;
507
508         cr = g_new(struct ContentRenderer, 1);
509         if (cr == NULL)
510                 return NULL;
511
512         cr->content_type = g_strdup(content_type);
513         cr->renderer = g_strdup(renderer);
514
515         return cr;
516 }
517
518 static void content_renderer_free(struct ContentRenderer * cr)
519 {
520         g_free(cr->content_type);
521         g_free(cr->renderer);
522         g_free(cr);
523 }
524
525 void renderer_read_config(void)
526 {
527         gchar buf[BUFFSIZE];
528         FILE * f;
529         gchar * rcpath;
530
531         g_list_foreach(renderer_list, (GFunc) content_renderer_free, NULL);
532         renderer_list = NULL;
533
534         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
535         f = fopen(rcpath, "rb");
536         g_free(rcpath);
537         
538         if (f == NULL)
539                 return;
540
541         while (fgets(buf, BUFFSIZE, f)) {
542                 char * p;
543                 struct ContentRenderer * cr;
544
545                 strretchomp(buf);
546                 p = strchr(buf, ' ');
547                 if (p == NULL)
548                         continue;
549                 * p = 0;
550
551                 cr = content_renderer_new(buf, p + 1);
552                 if (cr == NULL)
553                         continue;
554
555                 renderer_list = g_list_append(renderer_list, cr);
556         }
557
558         fclose(f);
559 }
560
561 void renderer_write_config(void)
562 {
563         gchar * rcpath;
564         PrefFile *pfile;
565         GList * cur;
566
567         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RENDERER_RC, NULL);
568         
569         if ((pfile = prefs_write_open(rcpath)) == NULL) {
570                 g_warning("failed to write configuration to file\n");
571                 g_free(rcpath);
572                 return;
573         }
574
575         g_free(rcpath);
576
577         for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
578                 struct ContentRenderer * renderer;
579                 renderer = cur->data;
580                 fprintf(pfile->fp, "%s %s\n", renderer->content_type,
581                         renderer->renderer);
582         }
583
584         if (prefs_file_close(pfile) < 0) {
585                 g_warning("failed to write configuration to file\n");
586                 return;
587         }
588 }
589
590 FILE *procmime_get_text_content(MimeInfo *mimeinfo)
591 {
592         FILE *tmpfp, *outfp;
593         const gchar *src_codeset;
594         gboolean conv_fail = FALSE;
595         gchar buf[BUFFSIZE];
596         gchar *str;
597         struct ContentRenderer * renderer;
598         GList * cur;
599         gchar *tmpfile, *content_type;
600     
601         g_return_val_if_fail(mimeinfo != NULL, NULL);
602
603         if (!procmime_decode_content(mimeinfo))
604                 return NULL;
605
606         tmpfile = procmime_get_tmp_file_name(mimeinfo);
607         if (tmpfile == NULL)
608                 return NULL;
609
610         if (procmime_get_part(tmpfile, mimeinfo) < 0) {
611                 g_free(tmpfile);
612                 return NULL;
613         }
614
615         tmpfp = fopen(tmpfile, "rb");
616         if (tmpfp == NULL) {
617                 g_free(tmpfile);
618                 return NULL;
619         }
620
621         if ((outfp = my_tmpfile()) == NULL) {
622                 perror("tmpfile");
623                 fclose(tmpfp);
624                 g_free(tmpfile);
625                 return NULL;
626         }
627
628         src_codeset = forced_charset
629                       ? forced_charset : 
630                       procmime_mimeinfo_get_parameter(mimeinfo, "charset");
631
632         renderer = NULL;
633
634         content_type = procmime_get_content_type_str(mimeinfo->type,
635                                                      mimeinfo->subtype);
636         for (cur = renderer_list ; cur != NULL ; cur = cur->next) {
637                 struct ContentRenderer * cr;
638
639                 cr = cur->data;
640                 if (g_ascii_strcasecmp(cr->content_type, content_type) == 0) {
641                         renderer = cr;
642                         break;
643                 }
644         }
645         g_free(content_type);
646
647         if (renderer != NULL) {
648                 FILE * p;
649                 int oldout;
650                 
651                 oldout = dup(1);
652                 
653                 dup2(fileno(outfp), 1);
654                 
655                 p = popen(renderer->renderer, "w");
656                 if (p != NULL) {
657                         size_t count;
658                         
659                         while ((count =
660                                 fread(buf, sizeof(char), sizeof(buf),
661                                       tmpfp)) > 0)
662                                 fwrite(buf, sizeof(char), count, p);
663                         pclose(p);
664                 }
665                 
666                 dup2(oldout, 1);
667 #warning FIXME_GTK2 HTML/RTF not yet utf8
668 /* CodeConverter seems to have no effect here */
669         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
670                 HTMLParser *parser;
671                 CodeConverter *conv;
672
673                 conv = conv_code_converter_new(src_codeset);
674                 parser = html_parser_new(tmpfp, conv);
675                 while ((str = html_parse(parser)) != NULL) {
676                         fputs(str, outfp);
677                 }
678                 html_parser_destroy(parser);
679                 conv_code_converter_destroy(conv);
680         } else if (mimeinfo->type == MIMETYPE_TEXT && !g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
681                 ERTFParser *parser;
682                 CodeConverter *conv;
683
684                 conv = conv_code_converter_new(src_codeset);
685                 parser = ertf_parser_new(tmpfp, conv);
686                 while ((str = ertf_parse(parser)) != NULL) {
687                         fputs(str, outfp);
688                 }
689                 ertf_parser_destroy(parser);
690                 conv_code_converter_destroy(conv);
691         } else if (mimeinfo->type == MIMETYPE_TEXT) {
692                 while (fgets(buf, sizeof(buf), tmpfp) != NULL) {
693                         str = conv_codeset_strdup(buf, src_codeset, CS_UTF_8);
694                         if (str) {
695                                 fputs(str, outfp);
696                                 g_free(str);
697                         } else {
698                                 conv_fail = TRUE;
699                                 fputs(buf, outfp);
700                         }
701                 }
702         }
703
704         if (conv_fail)
705                 g_warning("procmime_get_text_content(): Code conversion failed.\n");
706
707         fclose(tmpfp);
708         rewind(outfp);
709         unlink(tmpfile);
710         g_free(tmpfile);
711
712         return outfp;
713 }
714
715 /* search the first text part of (multipart) MIME message,
716    decode, convert it and output to outfp. */
717 FILE *procmime_get_first_text_content(MsgInfo *msginfo)
718 {
719         FILE *outfp = NULL;
720         MimeInfo *mimeinfo, *partinfo;
721
722         g_return_val_if_fail(msginfo != NULL, NULL);
723
724         mimeinfo = procmime_scan_message(msginfo);
725         if (!mimeinfo) return NULL;
726
727         partinfo = mimeinfo;
728         while (partinfo && partinfo->type != MIMETYPE_TEXT)
729                 partinfo = procmime_mimeinfo_next(partinfo);
730
731         if (partinfo)
732                 outfp = procmime_get_text_content(partinfo);
733
734         procmime_mimeinfo_free_all(mimeinfo);
735
736         return outfp;
737 }
738
739 gboolean procmime_find_string_part(MimeInfo *mimeinfo, const gchar *filename,
740                                    const gchar *str, StrFindFunc find_func)
741 {
742         FILE *outfp;
743         gchar buf[BUFFSIZE];
744
745         g_return_val_if_fail(mimeinfo != NULL, FALSE);
746         g_return_val_if_fail(mimeinfo->type == MIMETYPE_TEXT, FALSE);
747         g_return_val_if_fail(str != NULL, FALSE);
748         g_return_val_if_fail(find_func != NULL, FALSE);
749
750         outfp = procmime_get_text_content(mimeinfo);
751
752         if (!outfp)
753                 return FALSE;
754
755         while (fgets(buf, sizeof(buf), outfp) != NULL) {
756                 strretchomp(buf);
757                 if (find_func(buf, str)) {
758                         fclose(outfp);
759                         return TRUE;
760                 }
761         }
762
763         fclose(outfp);
764
765         return FALSE;
766 }
767
768 gboolean procmime_find_string(MsgInfo *msginfo, const gchar *str,
769                               StrFindFunc find_func)
770 {
771         MimeInfo *mimeinfo;
772         MimeInfo *partinfo;
773         gchar *filename;
774         gboolean found = FALSE;
775
776         g_return_val_if_fail(msginfo != NULL, FALSE);
777         g_return_val_if_fail(str != NULL, FALSE);
778         g_return_val_if_fail(find_func != NULL, FALSE);
779
780         filename = procmsg_get_message_file(msginfo);
781         if (!filename) return FALSE;
782         mimeinfo = procmime_scan_message(msginfo);
783
784         for (partinfo = mimeinfo; partinfo != NULL;
785              partinfo = procmime_mimeinfo_next(partinfo)) {
786                 if (partinfo->type == MIMETYPE_TEXT) {
787                         if (procmime_find_string_part
788                                 (partinfo, filename, str, find_func) == TRUE) {
789                                 found = TRUE;
790                                 break;
791                         }
792                 }
793         }
794
795         procmime_mimeinfo_free_all(mimeinfo);
796         g_free(filename);
797
798         return found;
799 }
800
801 gchar *procmime_get_tmp_file_name(MimeInfo *mimeinfo)
802 {
803         static guint32 id = 0;
804         gchar *base;
805         gchar *filename;
806         gchar f_prefix[10];
807
808         g_return_val_if_fail(mimeinfo != NULL, NULL);
809
810         g_snprintf(f_prefix, sizeof(f_prefix), "%08x.", id++);
811
812         if ((mimeinfo->type == MIMETYPE_TEXT) && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
813                 base = "mimetmp.html";
814         else {
815                 const gchar *basetmp;
816                 gchar *basename;
817
818                 basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
819                 if (basetmp == NULL)
820                         basetmp = procmime_mimeinfo_get_parameter(mimeinfo, "name");
821                 if (basetmp == NULL)
822                         basetmp = "mimetmp";
823                 basename = g_path_get_basename(basetmp);
824                 if (*base == '\0') base = "mimetmp";
825                 Xstrdup_a(base, basename, {g_free(basename); return NULL;});
826                 subst_for_shellsafe_filename(base);
827                 g_free(basename);
828         }
829
830         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
831                                f_prefix, base, NULL);
832
833         return filename;
834 }
835
836 static GList *mime_type_list = NULL;
837
838 gchar *procmime_get_mime_type(const gchar *filename)
839 {
840         static GHashTable *mime_type_table = NULL;
841         MimeType *mime_type;
842         const gchar *p;
843         gchar *ext;
844         gchar *base;
845
846         if (!mime_type_table) {
847                 mime_type_table = procmime_get_mime_type_table();
848                 if (!mime_type_table) return NULL;
849         }
850
851         base = g_path_get_basename(filename);
852         p = strrchr(base, '.');
853         g_free(base);
854         if (!p) return NULL;
855
856         Xstrdup_a(ext, p + 1, return NULL);
857         g_strdown(ext);
858         mime_type = g_hash_table_lookup(mime_type_table, ext);
859         if (mime_type) {
860                 gchar *str;
861
862                 str = g_strconcat(mime_type->type, "/", mime_type->sub_type,
863                                   NULL);
864                 return str;
865         }
866
867         return NULL;
868 }
869
870 static guint procmime_str_hash(gconstpointer gptr)
871 {
872         guint hash_result = 0;
873         const char *str;
874
875         for (str = gptr; str && *str; str++) {
876                 if (isupper(*str)) hash_result += (*str + ' ');
877                 else hash_result += *str;
878         }
879
880         return hash_result;
881 }
882
883 static gint procmime_str_equal(gconstpointer gptr1, gconstpointer gptr2)
884 {
885         const char *str1 = gptr1;
886         const char *str2 = gptr2;
887
888         return !g_utf8_collate(str1, str2);
889 }
890
891 static GHashTable *procmime_get_mime_type_table(void)
892 {
893         GHashTable *table = NULL;
894         GList *cur;
895         MimeType *mime_type;
896         gchar **exts;
897
898         if (!mime_type_list) {
899                 mime_type_list = procmime_get_mime_type_list();
900                 if (!mime_type_list) return NULL;
901         }
902
903         table = g_hash_table_new(procmime_str_hash, procmime_str_equal);
904
905         for (cur = mime_type_list; cur != NULL; cur = cur->next) {
906                 gint i;
907                 gchar *key;
908
909                 mime_type = (MimeType *)cur->data;
910
911                 if (!mime_type->extension) continue;
912
913                 exts = g_strsplit(mime_type->extension, " ", 16);
914                 for (i = 0; exts[i] != NULL; i++) {
915                         /* make the key case insensitive */
916                         g_strdown(exts[i]);
917                         /* use previously dup'd key on overwriting */
918                         if (g_hash_table_lookup(table, exts[i]))
919                                 key = exts[i];
920                         else
921                                 key = g_strdup(exts[i]);
922                         g_hash_table_insert(table, key, mime_type);
923                 }
924                 g_strfreev(exts);
925         }
926
927         return table;
928 }
929
930 GList *procmime_get_mime_type_list(void)
931 {
932         GList *list = NULL;
933         FILE *fp;
934         gchar buf[BUFFSIZE];
935         guchar *p;
936         gchar *delim;
937         MimeType *mime_type;
938
939         if (mime_type_list) 
940                 return mime_type_list;
941
942         if ((fp = fopen("/etc/mime.types", "rb")) == NULL) {
943                 if ((fp = fopen(SYSCONFDIR "/mime.types", "rb")) == NULL) {
944                         FILE_OP_ERROR(SYSCONFDIR "/mime.types", "fopen");
945                         return NULL;
946                 }
947         }
948
949         while (fgets(buf, sizeof(buf), fp) != NULL) {
950                 p = strchr(buf, '#');
951                 if (p) *p = '\0';
952                 g_strstrip(buf);
953
954                 p = buf;
955                 while (*p && !isspace(*p)) p++;
956                 if (*p) {
957                         *p = '\0';
958                         p++;
959                 }
960                 delim = strchr(buf, '/');
961                 if (delim == NULL) continue;
962                 *delim = '\0';
963
964                 mime_type = g_new(MimeType, 1);
965                 mime_type->type = g_strdup(buf);
966                 mime_type->sub_type = g_strdup(delim + 1);
967
968                 while (*p && isspace(*p)) p++;
969                 if (*p)
970                         mime_type->extension = g_strdup(p);
971                 else
972                         mime_type->extension = NULL;
973
974                 list = g_list_append(list, mime_type);
975         }
976
977         fclose(fp);
978
979         if (!list)
980                 g_warning("Can't read mime.types\n");
981
982         return list;
983 }
984
985 EncodingType procmime_get_encoding_for_charset(const gchar *charset)
986 {
987         if (!charset)
988                 return ENC_8BIT;
989         else if (!g_ascii_strncasecmp(charset, "ISO-2022-", 9) ||
990                  !g_ascii_strcasecmp(charset, "US-ASCII"))
991                 return ENC_7BIT;
992         else if (!g_ascii_strcasecmp(charset, "ISO-8859-5") ||
993                  !g_ascii_strncasecmp(charset, "KOI8-", 5) ||
994                  !g_ascii_strcasecmp(charset, "Windows-1251"))
995                 return ENC_8BIT;
996         else if (!g_ascii_strncasecmp(charset, "ISO-8859-", 9))
997                 return ENC_QUOTED_PRINTABLE;
998         else
999                 return ENC_8BIT;
1000 }
1001
1002 EncodingType procmime_get_encoding_for_text_file(const gchar *file)
1003 {
1004         FILE *fp;
1005         guchar buf[BUFFSIZE];
1006         size_t len;
1007         size_t octet_chars = 0;
1008         size_t total_len = 0;
1009         gfloat octet_percentage;
1010
1011         if ((fp = fopen(file, "rb")) == NULL) {
1012                 FILE_OP_ERROR(file, "fopen");
1013                 return ENC_UNKNOWN;
1014         }
1015
1016         while ((len = fread(buf, sizeof(guchar), sizeof(buf), fp)) > 0) {
1017                 guchar *p;
1018                 gint i;
1019
1020                 for (p = buf, i = 0; i < len; ++p, ++i) {
1021                         if (*p & 0x80)
1022                                 ++octet_chars;
1023                 }
1024                 total_len += len;
1025         }
1026
1027         fclose(fp);
1028         
1029         if (total_len > 0)
1030                 octet_percentage = (gfloat)octet_chars / (gfloat)total_len;
1031         else
1032                 octet_percentage = 0.0;
1033
1034         debug_print("procmime_get_encoding_for_text_file(): "
1035                     "8bit chars: %d / %d (%f%%)\n", octet_chars, total_len,
1036                     100.0 * octet_percentage);
1037
1038         if (octet_percentage > 0.20) {
1039                 debug_print("using BASE64\n");
1040                 return ENC_BASE64;
1041         } else if (octet_chars > 0) {
1042                 debug_print("using quoted-printable\n");
1043                 return ENC_QUOTED_PRINTABLE;
1044         } else {
1045                 debug_print("using 7bit\n");
1046                 return ENC_7BIT;
1047         }
1048 }
1049
1050 struct EncodingTable 
1051 {
1052         gchar *str;
1053         EncodingType enc_type;
1054 };
1055
1056 struct EncodingTable encoding_table[] = {
1057         {"7bit", ENC_7BIT},
1058         {"8bit", ENC_8BIT},
1059         {"binary", ENC_BINARY},
1060         {"quoted-printable", ENC_QUOTED_PRINTABLE},
1061         {"base64", ENC_BASE64},
1062         {"x-uuencode", ENC_UNKNOWN},
1063         {NULL, ENC_UNKNOWN},
1064 };
1065
1066 const gchar *procmime_get_encoding_str(EncodingType encoding)
1067 {
1068         struct EncodingTable *enc_table;
1069         
1070         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1071                 if (enc_table->enc_type == encoding)
1072                         return enc_table->str;
1073         }
1074         return NULL;
1075 }
1076
1077 /* --- NEW MIME STUFF --- */
1078 struct TypeTable
1079 {
1080         gchar *str;
1081         MimeMediaType type;
1082 };
1083
1084 static struct TypeTable mime_type_table[] = {
1085         {"text", MIMETYPE_TEXT},
1086         {"image", MIMETYPE_IMAGE},
1087         {"audio", MIMETYPE_AUDIO},
1088         {"video", MIMETYPE_VIDEO},
1089         {"application", MIMETYPE_APPLICATION},
1090         {"message", MIMETYPE_MESSAGE},
1091         {"multipart", MIMETYPE_MULTIPART},
1092         {NULL, 0},
1093 };
1094
1095 const gchar *procmime_get_media_type_str(MimeMediaType type)
1096 {
1097         struct TypeTable *type_table;
1098         
1099         for (type_table = mime_type_table; type_table->str != NULL; type_table++) {
1100                 if (type_table->type == type)
1101                         return type_table->str;
1102         }
1103         return NULL;
1104 }
1105
1106 MimeMediaType procmime_get_media_type(const gchar *str)
1107 {
1108         struct TypeTable *typetablearray;
1109
1110         for (typetablearray = mime_type_table; typetablearray->str != NULL; typetablearray++)
1111                 if (g_ascii_strncasecmp(str, typetablearray->str, strlen(typetablearray->str)) == 0)
1112                         return typetablearray->type;
1113
1114         return MIMETYPE_UNKNOWN;
1115 }
1116
1117 /*!
1118  *\brief        Safe wrapper for content type string.
1119  *
1120  *\return       const gchar * Pointer to content type string. 
1121  */
1122 gchar *procmime_get_content_type_str(MimeMediaType type,
1123                                            const char *subtype)
1124 {
1125         const gchar *type_str = NULL;
1126
1127         if (subtype == NULL || !(type_str = procmime_get_media_type_str(type)))
1128                 return g_strdup("unknown");
1129         return g_strdup_printf("%s/%s", type_str, subtype);
1130 }
1131
1132 void procmime_parse_mimepart(MimeInfo *parent,
1133                              gchar *content_type,
1134                              gchar *content_encoding,
1135                              gchar *content_description,
1136                              gchar *content_id,
1137                              gchar *content_disposition,
1138                              const gchar *filename,
1139                              guint offset,
1140                              guint length);
1141
1142 void procmime_parse_message_rfc822(MimeInfo *mimeinfo)
1143 {
1144         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1145                                 {"Content-Transfer-Encoding:",
1146                                                    NULL, FALSE},
1147                                 {"Content-Description:",
1148                                                    NULL, TRUE},
1149                                 {"Content-ID:",
1150                                                    NULL, TRUE},
1151                                 {"Content-Disposition:",
1152                                                    NULL, TRUE},
1153                                 {"MIME-Version:",
1154                                                    NULL, TRUE},
1155                                 {NULL,             NULL, FALSE}};
1156         guint content_start, i;
1157         FILE *fp;
1158         gint mime_major, mime_minor;
1159
1160         procmime_decode_content(mimeinfo);
1161
1162         fp = fopen(mimeinfo->data.filename, "rb");
1163         if (fp == NULL) {
1164                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1165                 return;
1166         }
1167         fseek(fp, mimeinfo->offset, SEEK_SET);
1168         procheader_get_header_fields(fp, hentry);
1169         if (hentry[0].body != NULL)
1170                 conv_unmime_header_overwrite(hentry[0].body);
1171         if (hentry[2].body != NULL)
1172                 conv_unmime_header_overwrite(hentry[2].body);
1173         if (hentry[4].body != NULL)
1174                 conv_unmime_header_overwrite(hentry[4].body);
1175         content_start = ftell(fp);
1176         fclose(fp);
1177
1178         if ((hentry[5].body != NULL) &&
1179             (sscanf(hentry[5].body, "%d.%d", &mime_major, &mime_minor) == 2) &&
1180             (mime_major == 1) && (mime_minor == 0)) {
1181                 procmime_parse_mimepart(mimeinfo,
1182                                         hentry[0].body, hentry[1].body,
1183                                         hentry[2].body, hentry[3].body, 
1184                                         hentry[4].body, 
1185                                         mimeinfo->data.filename, content_start,
1186                                         mimeinfo->length - (content_start - mimeinfo->offset));
1187         } else {
1188                 MimeInfo *subinfo;
1189
1190                 subinfo = procmime_mimeinfo_new();
1191                 subinfo->encoding_type = ENC_UNKNOWN;
1192                 subinfo->type = MIMETYPE_TEXT;
1193                 subinfo->subtype = g_strdup("plain");
1194                 subinfo->data.filename = g_strdup(mimeinfo->data.filename);
1195                 subinfo->offset = content_start;
1196                 subinfo->length = mimeinfo->length - (content_start - mimeinfo->offset);
1197
1198                 g_node_append(mimeinfo->node, subinfo->node);
1199         }
1200         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1201                 g_free(hentry[i].body);
1202                 hentry[i].body = NULL;
1203         }
1204 }
1205
1206 void procmime_parse_multipart(MimeInfo *mimeinfo)
1207 {
1208         HeaderEntry hentry[] = {{"Content-Type:",  NULL, TRUE},
1209                                 {"Content-Transfer-Encoding:",
1210                                                    NULL, FALSE},
1211                                 {"Content-Description:",
1212                                                    NULL, TRUE},
1213                                 {"Content-ID:",
1214                                                    NULL, TRUE},
1215                                 {"Content-Disposition:",
1216                                                    NULL, TRUE},
1217                                 {NULL,             NULL, FALSE}};
1218         gchar *p;
1219         gchar *boundary;
1220         gint boundary_len = 0, lastoffset = -1, i;
1221         gchar buf[BUFFSIZE];
1222         FILE *fp;
1223
1224         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1225         if (!boundary)
1226                 return;
1227         boundary_len = strlen(boundary);
1228
1229         procmime_decode_content(mimeinfo);
1230
1231         fp = fopen(mimeinfo->data.filename, "rb");
1232         if (fp == NULL) {
1233                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1234                 return;
1235         }
1236         fseek(fp, mimeinfo->offset, SEEK_SET);
1237         while ((p = fgets(buf, sizeof(buf), fp)) != NULL) {
1238                 if (ftell(fp) > (mimeinfo->offset + mimeinfo->length))
1239                         break;
1240
1241                 if (IS_BOUNDARY(buf, boundary, boundary_len)) {
1242                         if (lastoffset != -1) {
1243                                 procmime_parse_mimepart(mimeinfo,
1244                                                         hentry[0].body, hentry[1].body,
1245                                                         hentry[2].body, hentry[3].body, 
1246                                                         hentry[4].body, 
1247                                                         mimeinfo->data.filename, lastoffset,
1248                                                         (ftell(fp) - strlen(buf)) - lastoffset - 1);
1249                         }
1250                         
1251                         if (buf[2 + boundary_len]     == '-' &&
1252                             buf[2 + boundary_len + 1] == '-')
1253                                 break;
1254
1255                         for (i = 0; i < (sizeof hentry / sizeof hentry[0]) ; i++) {
1256                                 g_free(hentry[i].body);
1257                                 hentry[i].body = NULL;
1258                         }
1259                         procheader_get_header_fields(fp, hentry);
1260                         if (hentry[0].body != NULL)
1261                                 conv_unmime_header_overwrite(hentry[0].body);
1262                         if (hentry[2].body != NULL)
1263                                 conv_unmime_header_overwrite(hentry[2].body);
1264                         if (hentry[4].body != NULL)
1265                                 conv_unmime_header_overwrite(hentry[4].body);
1266                         lastoffset = ftell(fp);
1267                 }
1268         }
1269         for (i = 0; i < (sizeof hentry / sizeof hentry[0]); i++) {
1270                 g_free(hentry[i].body);
1271                 hentry[i].body = NULL;
1272         }
1273         fclose(fp);
1274 }
1275
1276 static void parse_parameters(const gchar *parameters, GHashTable *table)
1277 {
1278         gchar *params, *param, *next;
1279         GSList *convlist = NULL, *concatlist = NULL, *cur;
1280
1281         params = g_strdup(parameters);
1282         param = params;
1283         next = params;
1284         for (; next != NULL; param = next) {
1285                 gchar *attribute, *value, *tmp;
1286                 gint len;
1287                 gboolean convert = FALSE;
1288
1289                 next = strchr_with_skip_quote(param, '"', ';');
1290                 if (next != NULL) {
1291                         next[0] = '\0';
1292                         next++;
1293                 }
1294
1295                 g_strstrip(param);
1296
1297                 attribute = param;
1298                 value = strchr(attribute, '=');
1299                 if (value == NULL)
1300                         continue;
1301
1302                 value[0] = '\0';
1303                 value++;
1304
1305                 g_strdown(attribute);
1306
1307                 len = strlen(attribute);
1308                 if (attribute[len - 1] == '*') {
1309                         gchar *srcpos, *dstpos, *endpos;
1310
1311                         convert = TRUE;
1312                         attribute[len - 1] = '\0';
1313
1314                         srcpos = value;
1315                         dstpos = value;
1316                         endpos = value + strlen(value);
1317                         while (srcpos < endpos) {
1318                                 if (*srcpos != '%')
1319                                         *dstpos = *srcpos;
1320                                 else {
1321                                         guchar dstvalue;
1322
1323                                         if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1324                                                 *dstpos = '?';
1325                                         else
1326                                                 *dstpos = dstvalue;
1327                                         srcpos += 2;
1328                                 }
1329                                 srcpos++;
1330                                 dstpos++;
1331                         }
1332                         *dstpos = '\0';
1333                 } else {
1334                         if (value[0] == '"')
1335                                 extract_quote(value, '"');
1336                         else if ((tmp = strchr(value, ' ')) != NULL)
1337                                 *tmp = '\0';
1338                 }
1339
1340                 if (strrchr(attribute, '*') != NULL) {
1341                         gchar *tmpattr;
1342
1343                         tmpattr = g_strdup(attribute);
1344                         tmp = strrchr(tmpattr, '*');
1345                         tmp[0] = '\0';
1346
1347                         if ((tmp[1] == '0') && (tmp[2] == '\0') && 
1348                             (g_slist_find_custom(concatlist, attribute, g_str_equal) == NULL))
1349                                 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1350
1351                         if (convert && (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL))
1352                                 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1353
1354                         g_free(tmpattr);
1355                 } else if (convert) {
1356                         if (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL)
1357                                 convlist = g_slist_prepend(convlist, g_strdup(attribute));
1358                 }
1359
1360                 if (g_hash_table_lookup(table, attribute) == NULL)
1361                         g_hash_table_insert(table, g_strdup(attribute), g_strdup(value));
1362         }
1363
1364         for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1365                 gchar *attribute, *attrwnum, *partvalue;
1366                 gint n = 0;
1367                 GString *value;
1368
1369                 attribute = (gchar *) cur->data;
1370                 value = g_string_sized_new(64);
1371
1372                 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1373                 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1374                         g_string_append(value, partvalue);
1375
1376                         g_free(attrwnum);
1377                         n++;
1378                         attrwnum = g_strdup_printf("%s*%d", attribute, n);
1379                 }
1380                 g_free(attrwnum);
1381
1382                 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1383                 g_string_free(value, TRUE);
1384         }
1385         slist_free_strings(concatlist);
1386         g_slist_free(concatlist);
1387
1388         for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1389                 gchar *attribute, *key, *value;
1390                 gchar *charset, *lang, *oldvalue, *newvalue;
1391
1392                 attribute = (gchar *) cur->data;
1393                 if (!g_hash_table_lookup_extended(table, attribute, (gpointer *) &key, (gpointer *) &value))
1394                         continue;
1395
1396                 charset = value;
1397                 lang = strchr(charset, '\'');
1398                 if (lang == NULL)
1399                         continue;
1400                 lang[0] = '\0';
1401                 lang++;
1402                 oldvalue = strchr(lang, '\'');
1403                 if (oldvalue == NULL)
1404                         continue;
1405                 oldvalue[0] = '\0';
1406                 oldvalue++;
1407
1408                 newvalue = conv_codeset_strdup(oldvalue, charset, CS_UTF_8);
1409
1410                 g_hash_table_remove(table, attribute);
1411                 g_free(key);
1412                 g_free(value);
1413
1414                 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1415         }
1416         slist_free_strings(convlist);
1417         g_slist_free(convlist);
1418
1419         g_free(params);
1420 }       
1421
1422 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1423 {
1424         g_return_if_fail(content_type != NULL);
1425         g_return_if_fail(mimeinfo != NULL);
1426
1427         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1428          * if it's not available we use the default Content-Type */
1429         if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1430                 mimeinfo->type = MIMETYPE_TEXT;
1431                 mimeinfo->subtype = g_strdup("plain");
1432                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1433                                        "charset") == NULL)
1434                         g_hash_table_insert(mimeinfo->typeparameters,
1435                                             g_strdup("charset"),
1436                                             g_strdup("us-ascii"));
1437         } else {
1438                 gchar *type, *subtype, *params;
1439
1440                 type = g_strdup(content_type);
1441                 subtype = strchr(type, '/') + 1;
1442                 *(subtype - 1) = '\0';
1443                 if ((params = strchr(subtype, ';')) != NULL) {
1444                         params[0] = '\0';
1445                         params++;
1446                 }
1447
1448                 mimeinfo->type = procmime_get_media_type(type);
1449                 mimeinfo->subtype = g_strdup(subtype);
1450
1451                 /* Get mimeinfo->typeparameters */
1452                 if (params != NULL)
1453                         parse_parameters(params, mimeinfo->typeparameters);
1454
1455                 g_free(type);
1456         }
1457 }
1458
1459 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1460 {
1461         gchar *tmp, *params;
1462
1463         g_return_if_fail(content_disposition != NULL);
1464         g_return_if_fail(mimeinfo != NULL);
1465
1466         tmp = g_strdup(content_disposition);
1467         if ((params = strchr(tmp, ';')) != NULL) {
1468                 params[0] = '\0';
1469                 params++;
1470         }       
1471         g_strstrip(tmp);
1472
1473         if (!g_ascii_strcasecmp(tmp, "inline")) 
1474                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1475         else if (!g_ascii_strcasecmp(tmp, "attachment"))
1476                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1477         else
1478                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1479         
1480         if (params != NULL)
1481                 parse_parameters(params, mimeinfo->dispositionparameters);
1482
1483         g_free(tmp);
1484 }
1485
1486
1487 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1488 {
1489         struct EncodingTable *enc_table;
1490         
1491         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1492                 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1493                         mimeinfo->encoding_type = enc_table->enc_type;
1494                         return;
1495                 }
1496         }
1497         mimeinfo->encoding_type = ENC_UNKNOWN;
1498         return;
1499 }
1500
1501 void procmime_parse_mimepart(MimeInfo *parent,
1502                              gchar *content_type,
1503                              gchar *content_encoding,
1504                              gchar *content_description,
1505                              gchar *content_id,
1506                              gchar *content_disposition,
1507                              const gchar *filename,
1508                              guint offset,
1509                              guint length)
1510 {
1511         MimeInfo *mimeinfo;
1512
1513         /* Create MimeInfo */
1514         mimeinfo = procmime_mimeinfo_new();
1515         mimeinfo->content = MIMECONTENT_FILE;
1516         if (parent != NULL)
1517                 g_node_append(parent->node, mimeinfo->node);
1518         mimeinfo->data.filename = g_strdup(filename);
1519         mimeinfo->offset = offset;
1520         mimeinfo->length = length;
1521
1522         if (content_type != NULL) {
1523                 procmime_parse_content_type(content_type, mimeinfo);
1524         } else {
1525                 mimeinfo->type = MIMETYPE_TEXT;
1526                 mimeinfo->subtype = g_strdup("plain");
1527                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1528                                        "charset") == NULL)
1529                         g_hash_table_insert(mimeinfo->typeparameters, g_strdup("charset"), g_strdup("us-ascii"));
1530         }
1531
1532         if (content_encoding != NULL) {
1533                 procmime_parse_content_encoding(content_encoding, mimeinfo);
1534         } else {
1535                 mimeinfo->encoding_type = ENC_UNKNOWN;
1536         }
1537
1538         if (content_description != NULL)
1539                 mimeinfo->description = g_strdup(content_description);
1540         else
1541                 mimeinfo->description = NULL;
1542
1543         if (content_id != NULL)
1544                 mimeinfo->id = g_strdup(content_id);
1545         else
1546                 mimeinfo->id = NULL;
1547
1548         if (content_disposition != NULL) 
1549                 procmime_parse_content_disposition(content_disposition, mimeinfo);
1550         else
1551                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1552
1553         /* Call parser for mime type */
1554         switch (mimeinfo->type) {
1555                 case MIMETYPE_MESSAGE:
1556                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1557                                 procmime_parse_message_rfc822(mimeinfo);
1558                         }
1559                         break;
1560                         
1561                 case MIMETYPE_MULTIPART:
1562                         procmime_parse_multipart(mimeinfo);
1563                         break;
1564                         
1565                 default:
1566                         break;
1567         }
1568 }
1569
1570 static gchar *typenames[] = {
1571     "text",
1572     "image",
1573     "audio",
1574     "video",
1575     "application",
1576     "message",
1577     "multipart",
1578     "unknown",
1579 };
1580
1581 static gboolean output_func(GNode *node, gpointer data)
1582 {
1583         guint i, depth;
1584         MimeInfo *mimeinfo = (MimeInfo *) node->data;
1585
1586         depth = g_node_depth(node);
1587         for (i = 0; i < depth; i++)
1588                 printf("    ");
1589         printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1590
1591         return FALSE;
1592 }
1593
1594 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1595 {
1596         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1597 }
1598
1599 MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1600 {
1601         MimeInfo *mimeinfo;
1602         struct stat buf;
1603
1604         stat(filename, &buf);
1605
1606         mimeinfo = procmime_mimeinfo_new();
1607         mimeinfo->content = MIMECONTENT_FILE;
1608         mimeinfo->encoding_type = ENC_UNKNOWN;
1609         mimeinfo->type = MIMETYPE_MESSAGE;
1610         mimeinfo->subtype = g_strdup("rfc822");
1611         mimeinfo->data.filename = g_strdup(filename);
1612         mimeinfo->offset = offset;
1613         mimeinfo->length = buf.st_size - offset;
1614
1615         procmime_parse_message_rfc822(mimeinfo);
1616         if (debug_get_mode())
1617                 output_mime_structure(mimeinfo, 0);
1618
1619         return mimeinfo;
1620 }
1621
1622 MimeInfo *procmime_scan_file(const gchar *filename)
1623 {
1624         MimeInfo *mimeinfo;
1625
1626         g_return_val_if_fail(filename != NULL, NULL);
1627
1628         mimeinfo = procmime_scan_file_with_offset(filename, 0);
1629
1630         return mimeinfo;
1631 }
1632
1633 MimeInfo *procmime_scan_queue_file(const gchar *filename)
1634 {
1635         FILE *fp;
1636         MimeInfo *mimeinfo;
1637         gchar buf[BUFFSIZE];
1638         gint offset = 0;
1639
1640         g_return_val_if_fail(filename != NULL, NULL);
1641
1642         /* Open file */
1643         if ((fp = fopen(filename, "rb")) == NULL)
1644                 return NULL;
1645         /* Skip queue header */
1646         while (fgets(buf, sizeof(buf), fp) != NULL)
1647                 if (buf[0] == '\r' || buf[0] == '\n') break;
1648         offset = ftell(fp);
1649         fclose(fp);
1650
1651         mimeinfo = procmime_scan_file_with_offset(filename, offset);
1652
1653         return mimeinfo;
1654 }
1655
1656 typedef enum {
1657     ENC_AS_TOKEN,
1658     ENC_AS_QUOTED_STRING,
1659     ENC_AS_EXTENDED,
1660 } EncodeAs;
1661
1662 typedef struct _ParametersData {
1663         FILE *fp;
1664         guint len;
1665 } ParametersData;
1666
1667 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
1668 {
1669         gchar *param = key;
1670         gchar *val = value, *valpos;
1671         ParametersData *pdata = (ParametersData *)user_data;
1672         GString *buf = g_string_new("");
1673
1674         EncodeAs encas = ENC_AS_TOKEN;
1675
1676         for (valpos = val; *valpos != 0; valpos++) {
1677                 if (!IS_ASCII(*valpos)) {
1678                         encas = ENC_AS_EXTENDED;
1679                         break;
1680                 }
1681             
1682                 /* CTLs */
1683                 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
1684                         encas = ENC_AS_QUOTED_STRING;
1685                         continue;
1686                 }
1687
1688                 /* tspecials + SPACE */
1689                 switch (*valpos) {
1690                 case ' ':
1691                 case '(': 
1692                 case ')':
1693                 case '<':
1694                 case '>':
1695                 case '@':
1696                 case ',':
1697                 case ';':
1698                 case ':':
1699                 case '\\':
1700                 case '"':
1701                 case '/':
1702                 case '[':
1703                 case ']':
1704                 case '?':
1705                 case '=':
1706                         encas = ENC_AS_QUOTED_STRING;
1707                         continue;
1708                 }
1709         }
1710
1711         switch (encas) {
1712         case ENC_AS_TOKEN:
1713                 g_string_append_printf(buf, "%s=%s", param, val);
1714                 break;
1715
1716         case ENC_AS_QUOTED_STRING:
1717                 g_string_append_printf(buf, "%s=\"%s\"", param, val);
1718                 break;
1719
1720         case ENC_AS_EXTENDED:
1721                 g_string_append_printf(buf, "%s*=%s''", param,
1722                         conv_get_current_charset_str());
1723                 for (valpos = val; *valpos != '\0'; valpos++) {
1724                         if (IS_ASCII(*valpos) && isalnum(*valpos)) {
1725                                 g_string_append_printf(buf, "%c", *valpos);
1726                         } else {
1727                                 gchar hexstr[3] = "XX";
1728                                 get_hex_str(hexstr, *valpos);
1729                                 g_string_append_printf(buf, "%%%s", hexstr);
1730                         }
1731                 }
1732                 break;
1733         }
1734         
1735         if (buf->str && strlen(buf->str)) {
1736                 if (pdata->len + strlen(buf->str) + 2 > 76) {
1737                         fprintf(pdata->fp, ";\n %s", buf->str);
1738                         pdata->len = strlen(buf->str) + 1;
1739                 } else {
1740                         fprintf(pdata->fp, "; %s", buf->str);
1741                         pdata->len += strlen(buf->str) + 2;
1742                 }
1743         }
1744         g_string_free(buf, TRUE);
1745 }
1746
1747 void procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
1748 {
1749         struct TypeTable *type_table;
1750         ParametersData *pdata = g_new0(ParametersData, 1);
1751         debug_print("procmime_write_mime_header\n");
1752         
1753         pdata->fp = fp;
1754
1755         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
1756                 if (mimeinfo->type == type_table->type) {
1757                         gchar *buf = g_strdup_printf(
1758                                 "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
1759                         fprintf(fp, buf);
1760                         pdata->len = strlen(buf);
1761                         g_free(buf);
1762                         break;
1763                 }
1764         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, pdata);
1765         g_free(pdata);
1766
1767         fprintf(fp, "\n");
1768
1769         if (mimeinfo->encoding_type != ENC_UNKNOWN)
1770                 fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type));
1771
1772         if (mimeinfo->description != NULL)
1773                 fprintf(fp, "Content-Description: %s\n", mimeinfo->description);
1774
1775         if (mimeinfo->id != NULL)
1776                 fprintf(fp, "Content-ID: %s\n", mimeinfo->id);
1777
1778         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
1779                 ParametersData *pdata = g_new0(ParametersData, 1);
1780                 gchar *buf = NULL;
1781                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
1782                         buf = g_strdup("Content-Disposition: inline");
1783                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
1784                         buf = g_strdup("Content-Disposition: attachment");
1785                 else
1786                         buf = g_strdup("Content-Disposition: unknown");
1787
1788                 fprintf(fp, "%s", buf);
1789                 pdata->len = strlen(buf);
1790                 g_free(buf);
1791
1792                 pdata->fp = fp;
1793                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, pdata);
1794                 g_free(pdata);
1795                 fprintf(fp, "\n");
1796         }
1797
1798         fprintf(fp, "\n");
1799 }
1800
1801 gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
1802 {
1803         FILE *infp;
1804         GNode *childnode;
1805         MimeInfo *child;
1806         gchar buf[BUFFSIZE];
1807         gboolean skip = FALSE;;
1808
1809         debug_print("procmime_write_message_rfc822\n");
1810
1811         /* write header */
1812         switch (mimeinfo->content) {
1813         case MIMECONTENT_FILE:
1814                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1815                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1816                         return -1;
1817                 }
1818                 fseek(infp, mimeinfo->offset, SEEK_SET);
1819                 while (fgets(buf, sizeof(buf), infp) == buf) {
1820                         if (buf[0] == '\n' && buf[1] == '\0')
1821                                 break;
1822                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
1823                                 continue;
1824                         if (g_ascii_strncasecmp(buf, "Mime-Version:", 13) == 0 ||
1825                             g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
1826                             g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
1827                             g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
1828                             g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
1829                             g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
1830                                 skip = TRUE;
1831                                 continue;
1832                         }
1833                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
1834                         skip = FALSE;
1835                 }
1836                 fclose(infp);
1837                 break;
1838
1839         case MIMECONTENT_MEM:
1840                 fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1841                 break;
1842
1843         default:
1844                 break;
1845         }
1846
1847         childnode = mimeinfo->node->children;
1848         if (childnode == NULL)
1849                 return -1;
1850
1851         child = (MimeInfo *) childnode->data;
1852         fprintf(fp, "Mime-Version: 1.0\n");
1853         procmime_write_mime_header(child, fp);
1854         return procmime_write_mimeinfo(child, fp);
1855 }
1856
1857 gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
1858 {
1859         FILE *infp;
1860         GNode *childnode;
1861         gchar *boundary, *str, *str2;
1862         gchar buf[BUFFSIZE];
1863         gboolean firstboundary;
1864
1865         debug_print("procmime_write_multipart\n");
1866
1867         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1868
1869         switch (mimeinfo->content) {
1870         case MIMECONTENT_FILE:
1871                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1872                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1873                         return -1;
1874                 }
1875                 fseek(infp, mimeinfo->offset, SEEK_SET);
1876                 while (fgets(buf, sizeof(buf), infp) == buf) {
1877                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
1878                                 break;
1879                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
1880                 }
1881                 fclose(infp);
1882                 break;
1883
1884         case MIMECONTENT_MEM:
1885                 str = g_strdup(mimeinfo->data.mem);
1886                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
1887                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
1888                         *(str2 - 2) = '\0';
1889                 fwrite(str, strlen(str), sizeof(gchar), fp);
1890                 g_free(str);
1891                 break;
1892
1893         default:
1894                 break;
1895         }
1896
1897         childnode = mimeinfo->node->children;
1898         firstboundary = TRUE;
1899         while (childnode != NULL) {
1900                 MimeInfo *child = childnode->data;
1901
1902                 if (firstboundary)
1903                         firstboundary = FALSE;
1904                 else
1905                         fprintf(fp, "\n");
1906                 fprintf(fp, "--%s\n", boundary);
1907
1908                 procmime_write_mime_header(child, fp);
1909                 if (procmime_write_mimeinfo(child, fp) < 0)
1910                         return -1;
1911
1912                 childnode = g_node_next_sibling(childnode);
1913         }       
1914         fprintf(fp, "\n--%s--\n", boundary);
1915
1916         return 0;
1917 }
1918
1919 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
1920 {
1921         FILE *infp;
1922
1923         debug_print("procmime_write_mimeinfo\n");
1924
1925         if (G_NODE_IS_LEAF(mimeinfo->node)) {
1926                 switch (mimeinfo->content) {
1927                 case MIMECONTENT_FILE:
1928                         if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1929                                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1930                                 return -1;
1931                         }
1932                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
1933                         fclose(infp);
1934                         return 0;
1935
1936                 case MIMECONTENT_MEM:
1937                         fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1938                         return 0;
1939
1940                 default:
1941                         return 0;
1942                 }
1943         } else {
1944                 /* Call writer for mime type */
1945                 switch (mimeinfo->type) {
1946                 case MIMETYPE_MESSAGE:
1947                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0)
1948                                 return procmime_write_message_rfc822(mimeinfo, fp);
1949                         break;
1950                         
1951                 case MIMETYPE_MULTIPART:
1952                         return procmime_write_multipart(mimeinfo, fp);
1953                         
1954                 default:
1955                         break;
1956                 }
1957
1958                 return -1;
1959         }
1960
1961         return 0;
1962 }