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