2004-10-29 [paul] 0.9.12cvs135.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 parse_parameters(const gchar *parameters, GHashTable *table)
1243 {
1244         gchar *params, *param, *next;
1245         GSList *convlist = NULL, *concatlist = NULL, *cur;
1246
1247         params = g_strdup(parameters);
1248         param = params;
1249         next = params;
1250         for (; next != NULL; param = next) {
1251                 gchar *attribute, *value, *tmp;
1252                 gint len;
1253                 gboolean convert = FALSE;
1254
1255                 next = strchr_with_skip_quote(param, '"', ';');
1256                 if (next != NULL) {
1257                         next[0] = '\0';
1258                         next++;
1259                 }
1260
1261                 g_strstrip(param);
1262
1263                 attribute = param;
1264                 value = strchr(attribute, '=');
1265                 if (value == NULL)
1266                         continue;
1267
1268                 value[0] = '\0';
1269                 value++;
1270
1271                 g_strdown(attribute);
1272
1273                 len = strlen(attribute);
1274                 if (attribute[len - 1] == '*') {
1275                         gchar *srcpos, *dstpos, *endpos;
1276
1277                         convert = TRUE;
1278                         attribute[len - 1] = '\0';
1279
1280                         srcpos = value;
1281                         dstpos = value;
1282                         endpos = value + strlen(value);
1283                         while (srcpos < endpos) {
1284                                 if (*srcpos != '%')
1285                                         *dstpos = *srcpos;
1286                                 else {
1287                                         guchar dstvalue;
1288
1289                                         if (!get_hex_value(&dstvalue, srcpos[1], srcpos[2]))
1290                                                 *dstpos = '?';
1291                                         else
1292                                                 *dstpos = dstvalue;
1293                                         srcpos += 2;
1294                                 }
1295                                 srcpos++;
1296                                 dstpos++;
1297                         }
1298                         *dstpos = '\0';
1299                 } else {
1300                         if (value[0] == '"')
1301                                 extract_quote(value, '"');
1302                         else if ((tmp = strchr(value, ' ')) != NULL)
1303                                 *tmp = '\0';
1304                 }
1305
1306                 if (strrchr(attribute, '*') != NULL) {
1307                         gchar *tmpattr;
1308
1309                         tmpattr = g_strdup(attribute);
1310                         tmp = strrchr(tmpattr, '*');
1311                         tmp[0] = '\0';
1312
1313                         if ((tmp[1] == '0') && (tmp[2] == '\0') && 
1314                             (g_slist_find_custom(concatlist, attribute, g_str_equal) == NULL))
1315                                 concatlist = g_slist_prepend(concatlist, g_strdup(tmpattr));
1316
1317                         if (convert && (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL))
1318                                 convlist = g_slist_prepend(convlist, g_strdup(tmpattr));
1319
1320                         g_free(tmpattr);
1321                 } else if (convert) {
1322                         if (g_slist_find_custom(convlist, attribute, g_str_equal) == NULL)
1323                                 convlist = g_slist_prepend(convlist, g_strdup(attribute));
1324                 }
1325
1326                 if (g_hash_table_lookup(table, attribute) == NULL)
1327                         g_hash_table_insert(table, g_strdup(attribute), g_strdup(value));
1328         }
1329
1330         for (cur = concatlist; cur != NULL; cur = g_slist_next(cur)) {
1331                 gchar *attribute, *attrwnum, *partvalue;
1332                 gint n = 0;
1333                 GString *value;
1334
1335                 attribute = (gchar *) cur->data;
1336                 value = g_string_sized_new(64);
1337
1338                 attrwnum = g_strdup_printf("%s*%d", attribute, n);
1339                 while ((partvalue = g_hash_table_lookup(table, attrwnum)) != NULL) {
1340                         g_string_append(value, partvalue);
1341
1342                         g_free(attrwnum);
1343                         n++;
1344                         attrwnum = g_strdup_printf("%s*%d", attribute, n);
1345                 }
1346                 g_free(attrwnum);
1347
1348                 g_hash_table_insert(table, g_strdup(attribute), g_strdup(value->str));
1349                 g_string_free(value, TRUE);
1350         }
1351         slist_free_strings(concatlist);
1352         g_slist_free(concatlist);
1353
1354         for (cur = convlist; cur != NULL; cur = g_slist_next(cur)) {
1355                 gchar *attribute, *key, *value;
1356                 gchar *charset, *lang, *oldvalue, *newvalue;
1357
1358                 attribute = (gchar *) cur->data;
1359                 if (!g_hash_table_lookup_extended(table, attribute, (gpointer *) &key, (gpointer *) &value))
1360                         continue;
1361
1362                 charset = value;
1363                 lang = strchr(charset, '\'');
1364                 if (lang == NULL)
1365                         continue;
1366                 lang[0] = '\0';
1367                 lang++;
1368                 oldvalue = strchr(lang, '\'');
1369                 if (oldvalue == NULL)
1370                         continue;
1371                 oldvalue[0] = '\0';
1372                 oldvalue++;
1373
1374                 newvalue = conv_codeset_strdup(oldvalue, charset, conv_get_current_charset_str());
1375
1376                 g_hash_table_remove(table, attribute);
1377                 g_free(key);
1378                 g_free(value);
1379
1380                 g_hash_table_insert(table, g_strdup(attribute), newvalue);
1381         }
1382         slist_free_strings(convlist);
1383         g_slist_free(convlist);
1384
1385         g_free(params);
1386 }       
1387
1388 static void procmime_parse_content_type(const gchar *content_type, MimeInfo *mimeinfo)
1389 {
1390         g_return_if_fail(content_type != NULL);
1391         g_return_if_fail(mimeinfo != NULL);
1392
1393         /* RFC 2045, page 13 says that the mime subtype is MANDATORY;
1394          * if it's not available we use the default Content-Type */
1395         if ((content_type[0] == '\0') || (strchr(content_type, '/') == NULL)) {
1396                 mimeinfo->type = MIMETYPE_TEXT;
1397                 mimeinfo->subtype = g_strdup("plain");
1398                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1399                                        "charset") == NULL)
1400                         g_hash_table_insert(mimeinfo->typeparameters,
1401                                             g_strdup("charset"),
1402                                             g_strdup("us-ascii"));
1403         } else {
1404                 gchar *type, *subtype, *params;
1405
1406                 type = g_strdup(content_type);
1407                 subtype = strchr(type, '/') + 1;
1408                 *(subtype - 1) = '\0';
1409                 if ((params = strchr(subtype, ';')) != NULL) {
1410                         params[0] = '\0';
1411                         params++;
1412                 }
1413
1414                 mimeinfo->type = procmime_get_media_type(type);
1415                 mimeinfo->subtype = g_strdup(subtype);
1416
1417                 /* Get mimeinfo->typeparameters */
1418                 if (params != NULL)
1419                         parse_parameters(params, mimeinfo->typeparameters);
1420
1421                 g_free(type);
1422         }
1423 }
1424
1425 static void procmime_parse_content_disposition(const gchar *content_disposition, MimeInfo *mimeinfo)
1426 {
1427         gchar *tmp, *params;
1428
1429         g_return_if_fail(content_disposition != NULL);
1430         g_return_if_fail(mimeinfo != NULL);
1431
1432         tmp = g_strdup(content_disposition);
1433         if ((params = strchr(tmp, ';')) != NULL) {
1434                 params[0] = '\0';
1435                 params++;
1436         }       
1437         g_strstrip(tmp);
1438
1439         if (!g_ascii_strcasecmp(tmp, "inline")) 
1440                 mimeinfo->disposition = DISPOSITIONTYPE_INLINE;
1441         else if (!g_ascii_strcasecmp(tmp, "attachment"))
1442                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1443         else
1444                 mimeinfo->disposition = DISPOSITIONTYPE_ATTACHMENT;
1445         
1446         if (params != NULL)
1447                 parse_parameters(params, mimeinfo->dispositionparameters);
1448
1449         g_free(tmp);
1450 }
1451
1452
1453 static void procmime_parse_content_encoding(const gchar *content_encoding, MimeInfo *mimeinfo)
1454 {
1455         struct EncodingTable *enc_table;
1456         
1457         for (enc_table = encoding_table; enc_table->str != NULL; enc_table++) {
1458                 if (g_ascii_strcasecmp(enc_table->str, content_encoding) == 0) {
1459                         mimeinfo->encoding_type = enc_table->enc_type;
1460                         return;
1461                 }
1462         }
1463         mimeinfo->encoding_type = ENC_UNKNOWN;
1464         return;
1465 }
1466
1467 void procmime_parse_mimepart(MimeInfo *parent,
1468                              gchar *content_type,
1469                              gchar *content_encoding,
1470                              gchar *content_description,
1471                              gchar *content_id,
1472                              gchar *content_disposition,
1473                              const gchar *filename,
1474                              guint offset,
1475                              guint length)
1476 {
1477         MimeInfo *mimeinfo;
1478
1479         /* Create MimeInfo */
1480         mimeinfo = procmime_mimeinfo_new();
1481         mimeinfo->content = MIMECONTENT_FILE;
1482         if (parent != NULL)
1483                 g_node_append(parent->node, mimeinfo->node);
1484         mimeinfo->data.filename = g_strdup(filename);
1485         mimeinfo->offset = offset;
1486         mimeinfo->length = length;
1487
1488         if (content_type != NULL) {
1489                 procmime_parse_content_type(content_type, mimeinfo);
1490         } else {
1491                 mimeinfo->type = MIMETYPE_TEXT;
1492                 mimeinfo->subtype = g_strdup("plain");
1493                 if (g_hash_table_lookup(mimeinfo->typeparameters,
1494                                        "charset") == NULL)
1495                         g_hash_table_insert(mimeinfo->typeparameters, g_strdup("charset"), g_strdup("us-ascii"));
1496         }
1497
1498         if (content_encoding != NULL) {
1499                 procmime_parse_content_encoding(content_encoding, mimeinfo);
1500         } else {
1501                 mimeinfo->encoding_type = ENC_UNKNOWN;
1502         }
1503
1504         if (content_description != NULL)
1505                 mimeinfo->description = g_strdup(content_description);
1506         else
1507                 mimeinfo->description = NULL;
1508
1509         if (content_id != NULL)
1510                 mimeinfo->id = g_strdup(content_id);
1511         else
1512                 mimeinfo->id = NULL;
1513
1514         if (content_disposition != NULL) 
1515                 procmime_parse_content_disposition(content_disposition, mimeinfo);
1516         else
1517                 mimeinfo->disposition = DISPOSITIONTYPE_UNKNOWN;
1518
1519         /* Call parser for mime type */
1520         switch (mimeinfo->type) {
1521                 case MIMETYPE_MESSAGE:
1522                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0) {
1523                                 procmime_parse_message_rfc822(mimeinfo);
1524                         }
1525                         break;
1526                         
1527                 case MIMETYPE_MULTIPART:
1528                         procmime_parse_multipart(mimeinfo);
1529                         break;
1530                         
1531                 default:
1532                         break;
1533         }
1534 }
1535
1536 static gchar *typenames[] = {
1537     "text",
1538     "image",
1539     "audio",
1540     "video",
1541     "application",
1542     "message",
1543     "multipart",
1544     "unknown",
1545 };
1546
1547 static gboolean output_func(GNode *node, gpointer data)
1548 {
1549         guint i, depth;
1550         MimeInfo *mimeinfo = (MimeInfo *) node->data;
1551
1552         depth = g_node_depth(node);
1553         for (i = 0; i < depth; i++)
1554                 printf("    ");
1555         printf("%s/%s (offset:%d length:%d encoding: %d)\n", typenames[mimeinfo->type], mimeinfo->subtype, mimeinfo->offset, mimeinfo->length, mimeinfo->encoding_type);
1556
1557         return FALSE;
1558 }
1559
1560 static void output_mime_structure(MimeInfo *mimeinfo, int indent)
1561 {
1562         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, output_func, NULL);
1563 }
1564
1565 MimeInfo *procmime_scan_file_with_offset(const gchar *filename, int offset)
1566 {
1567         MimeInfo *mimeinfo;
1568         struct stat buf;
1569
1570         stat(filename, &buf);
1571
1572         mimeinfo = procmime_mimeinfo_new();
1573         mimeinfo->content = MIMECONTENT_FILE;
1574         mimeinfo->encoding_type = ENC_UNKNOWN;
1575         mimeinfo->type = MIMETYPE_MESSAGE;
1576         mimeinfo->subtype = g_strdup("rfc822");
1577         mimeinfo->data.filename = g_strdup(filename);
1578         mimeinfo->offset = offset;
1579         mimeinfo->length = buf.st_size - offset;
1580
1581         procmime_parse_message_rfc822(mimeinfo);
1582         if (debug_get_mode())
1583                 output_mime_structure(mimeinfo, 0);
1584
1585         return mimeinfo;
1586 }
1587
1588 MimeInfo *procmime_scan_file(const gchar *filename)
1589 {
1590         MimeInfo *mimeinfo;
1591
1592         g_return_val_if_fail(filename != NULL, NULL);
1593
1594         mimeinfo = procmime_scan_file_with_offset(filename, 0);
1595
1596         return mimeinfo;
1597 }
1598
1599 MimeInfo *procmime_scan_queue_file(const gchar *filename)
1600 {
1601         FILE *fp;
1602         MimeInfo *mimeinfo;
1603         gchar buf[BUFFSIZE];
1604         gint offset = 0;
1605
1606         g_return_val_if_fail(filename != NULL, NULL);
1607
1608         /* Open file */
1609         if ((fp = fopen(filename, "rb")) == NULL)
1610                 return NULL;
1611         /* Skip queue header */
1612         while (fgets(buf, sizeof(buf), fp) != NULL)
1613                 if (buf[0] == '\r' || buf[0] == '\n') break;
1614         offset = ftell(fp);
1615         fclose(fp);
1616
1617         mimeinfo = procmime_scan_file_with_offset(filename, offset);
1618
1619         return mimeinfo;
1620 }
1621
1622 typedef enum {
1623     ENC_AS_TOKEN,
1624     ENC_AS_QUOTED_STRING,
1625     ENC_AS_EXTENDED,
1626 } EncodeAs;
1627
1628 static void write_parameters(gpointer key, gpointer value, gpointer user_data)
1629 {
1630         gchar *param = key;
1631         gchar *val = value, *valpos;
1632         FILE *fp = user_data;
1633         EncodeAs encas = ENC_AS_TOKEN;
1634
1635         fprintf(fp, "; %s=", param);
1636
1637         for (valpos = val; *valpos != 0; valpos++) {
1638                 if (!IS_ASCII(*valpos)) {
1639                         encas = ENC_AS_EXTENDED;
1640                         break;
1641                 }
1642             
1643                 /* CTLs */
1644                 if (((*valpos >= 0) && (*valpos < 037)) || (*valpos == 0177)) {
1645                         encas = ENC_AS_QUOTED_STRING;
1646                         continue;
1647                 }
1648
1649                 /* tspecials + SPACE */
1650                 switch (*valpos) {
1651                 case ' ':
1652                 case '(': 
1653                 case ')':
1654                 case '<':
1655                 case '>':
1656                 case '@':
1657                 case ',':
1658                 case ';':
1659                 case ':':
1660                 case '\\':
1661                 case '"':
1662                 case '/':
1663                 case '[':
1664                 case ']':
1665                 case '?':
1666                 case '=':
1667                         encas = ENC_AS_QUOTED_STRING;
1668                         continue;
1669                 }
1670         }
1671
1672         switch (encas) {
1673         case ENC_AS_TOKEN:
1674                 fprintf(fp, "%s", val);
1675                 break;
1676
1677         case ENC_AS_QUOTED_STRING:
1678                 fprintf(fp, "\"%s\"", val);
1679                 break;
1680
1681         case ENC_AS_EXTENDED:
1682                 /* FIXME: not yet handled */
1683                 fprintf(fp, "%s", val);
1684                 break;
1685         }
1686 }
1687
1688 void procmime_write_mime_header(MimeInfo *mimeinfo, FILE *fp)
1689 {
1690         struct TypeTable *type_table;
1691
1692         debug_print("procmime_write_mime_header\n");
1693
1694         for (type_table = mime_type_table; type_table->str != NULL; type_table++)
1695                 if (mimeinfo->type == type_table->type) {
1696                         fprintf(fp, "Content-Type: %s/%s", type_table->str, mimeinfo->subtype);
1697                         break;
1698                 }
1699         g_hash_table_foreach(mimeinfo->typeparameters, write_parameters, fp);
1700         fprintf(fp, "\n");
1701
1702         if (mimeinfo->encoding_type != ENC_UNKNOWN)
1703                 fprintf(fp, "Content-Transfer-Encoding: %s\n", procmime_get_encoding_str(mimeinfo->encoding_type));
1704
1705         if (mimeinfo->description != NULL)
1706                 fprintf(fp, "Content-Description: %s\n", mimeinfo->description);
1707
1708         if (mimeinfo->id != NULL)
1709                 fprintf(fp, "Content-ID: %s\n", mimeinfo->id);
1710
1711         if (mimeinfo->disposition != DISPOSITIONTYPE_UNKNOWN) {
1712                 fprintf(fp, "Content-Disposition: ");
1713                 if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE)
1714                         fprintf(fp, "inline");
1715                 else if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT)
1716                         fprintf(fp, "attachment");
1717                 else
1718                         fprintf(fp, "unknown");
1719
1720                 /* FIXME: linebreaks after too many parameters */
1721                 g_hash_table_foreach(mimeinfo->dispositionparameters, write_parameters, fp);
1722                 fprintf(fp, "\n");
1723         }
1724
1725         fprintf(fp, "\n");
1726 }
1727
1728 gint procmime_write_message_rfc822(MimeInfo *mimeinfo, FILE *fp)
1729 {
1730         FILE *infp;
1731         GNode *childnode;
1732         MimeInfo *child;
1733         gchar buf[BUFFSIZE];
1734         gboolean skip = FALSE;;
1735
1736         debug_print("procmime_write_message_rfc822\n");
1737
1738         /* write header */
1739         switch (mimeinfo->content) {
1740         case MIMECONTENT_FILE:
1741                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1742                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1743                         return -1;
1744                 }
1745                 fseek(infp, mimeinfo->offset, SEEK_SET);
1746                 while (fgets(buf, sizeof(buf), infp) == buf) {
1747                         if (buf[0] == '\n' && buf[1] == '\0')
1748                                 break;
1749                         if (skip && (buf[0] == ' ' || buf[0] == '\t'))
1750                                 continue;
1751                         if (g_ascii_strncasecmp(buf, "Mime-Version:", 13) == 0 ||
1752                             g_ascii_strncasecmp(buf, "Content-Type:", 13) == 0 ||
1753                             g_ascii_strncasecmp(buf, "Content-Transfer-Encoding:", 26) == 0 ||
1754                             g_ascii_strncasecmp(buf, "Content-Description:", 20) == 0 ||
1755                             g_ascii_strncasecmp(buf, "Content-ID:", 11) == 0 ||
1756                             g_ascii_strncasecmp(buf, "Content-Disposition:", 20) == 0) {
1757                                 skip = TRUE;
1758                                 continue;
1759                         }
1760                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
1761                         skip = FALSE;
1762                 }
1763                 fclose(infp);
1764                 break;
1765
1766         case MIMECONTENT_MEM:
1767                 fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1768                 break;
1769
1770         default:
1771                 break;
1772         }
1773
1774         childnode = mimeinfo->node->children;
1775         if (childnode == NULL)
1776                 return -1;
1777
1778         child = (MimeInfo *) childnode->data;
1779         fprintf(fp, "Mime-Version: 1.0\n");
1780         procmime_write_mime_header(child, fp);
1781         return procmime_write_mimeinfo(child, fp);
1782 }
1783
1784 gint procmime_write_multipart(MimeInfo *mimeinfo, FILE *fp)
1785 {
1786         FILE *infp;
1787         GNode *childnode;
1788         gchar *boundary, *str, *str2;
1789         gchar buf[BUFFSIZE];
1790         gboolean firstboundary;
1791
1792         debug_print("procmime_write_multipart\n");
1793
1794         boundary = g_hash_table_lookup(mimeinfo->typeparameters, "boundary");
1795
1796         switch (mimeinfo->content) {
1797         case MIMECONTENT_FILE:
1798                 if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1799                         FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1800                         return -1;
1801                 }
1802                 fseek(infp, mimeinfo->offset, SEEK_SET);
1803                 while (fgets(buf, sizeof(buf), infp) == buf) {
1804                         if (IS_BOUNDARY(buf, boundary, strlen(boundary)))
1805                                 break;
1806                         fwrite(buf, sizeof(gchar), strlen(buf), fp);
1807                 }
1808                 fclose(infp);
1809                 break;
1810
1811         case MIMECONTENT_MEM:
1812                 str = g_strdup(mimeinfo->data.mem);
1813                 if (((str2 = strstr(str, boundary)) != NULL) && ((str2 - str) >= 2) &&
1814                     (*(str2 - 1) == '-') && (*(str2 - 2) == '-'))
1815                         *(str2 - 2) = '\0';
1816                 fwrite(str, strlen(str), sizeof(gchar), fp);
1817                 g_free(str);
1818                 break;
1819
1820         default:
1821                 break;
1822         }
1823
1824         childnode = mimeinfo->node->children;
1825         firstboundary = TRUE;
1826         while (childnode != NULL) {
1827                 MimeInfo *child = childnode->data;
1828
1829                 if (firstboundary)
1830                         firstboundary = FALSE;
1831                 else
1832                         fprintf(fp, "\n");
1833                 fprintf(fp, "--%s\n", boundary);
1834
1835                 procmime_write_mime_header(child, fp);
1836                 if (procmime_write_mimeinfo(child, fp) < 0)
1837                         return -1;
1838
1839                 childnode = g_node_next_sibling(childnode);
1840         }       
1841         fprintf(fp, "\n--%s--\n", boundary);
1842
1843         return 0;
1844 }
1845
1846 gint procmime_write_mimeinfo(MimeInfo *mimeinfo, FILE *fp)
1847 {
1848         FILE *infp;
1849
1850         debug_print("procmime_write_mimeinfo\n");
1851
1852         if (G_NODE_IS_LEAF(mimeinfo->node)) {
1853                 switch (mimeinfo->content) {
1854                 case MIMECONTENT_FILE:
1855                         if ((infp = fopen(mimeinfo->data.filename, "rb")) == NULL) {
1856                                 FILE_OP_ERROR(mimeinfo->data.filename, "fopen");
1857                                 return -1;
1858                         }
1859                         copy_file_part_to_fp(infp, mimeinfo->offset, mimeinfo->length, fp);
1860                         fclose(infp);
1861                         return 0;
1862
1863                 case MIMECONTENT_MEM:
1864                         fwrite(mimeinfo->data.mem, strlen(mimeinfo->data.mem), sizeof(gchar), fp);
1865                         return 0;
1866
1867                 default:
1868                         return 0;
1869                 }
1870         } else {
1871                 /* Call writer for mime type */
1872                 switch (mimeinfo->type) {
1873                 case MIMETYPE_MESSAGE:
1874                         if (g_ascii_strcasecmp(mimeinfo->subtype, "rfc822") == 0)
1875                                 return procmime_write_message_rfc822(mimeinfo, fp);
1876                         break;
1877                         
1878                 case MIMETYPE_MULTIPART:
1879                         return procmime_write_multipart(mimeinfo, fp);
1880                         
1881                 default:
1882                         break;
1883                 }
1884
1885                 return -1;
1886         }
1887
1888         return 0;
1889 }