Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-mime-part.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /* camelMimePart.c : Abstract class for a mime_part */
3
4 /* 
5  * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
6  *          Michael Zucchi <notzed@ximian.com>
7  *          Jeffrey Stedfast <fejj@ximian.com>
8  *
9  * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
10  *
11  * This program is free software; you can redistribute it and/or 
12  * modify it under the terms of version 2 of the GNU Lesser General Public 
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
23  * USA
24  */
25
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <ctype.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <string.h>
35
36 #include <libedataserver/e-iconv.h>
37
38 #include "camel-charset-map.h"
39 #include "camel-exception.h"
40 #include "camel-mime-filter-basic.h"
41 #include "camel-mime-filter-charset.h"
42 #include "camel-mime-filter-crlf.h"
43 #include "camel-mime-parser.h"
44 #include "camel-mime-part-utils.h"
45 #include "camel-mime-part.h"
46 #include "camel-mime-utils.h"
47 #include "camel-stream-filter.h"
48 #include "camel-stream-mem.h"
49 #include "camel-string-utils.h"
50
51 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
52
53 typedef enum {
54         HEADER_UNKNOWN,
55         HEADER_DESCRIPTION,
56         HEADER_DISPOSITION,
57         HEADER_CONTENT_ID,
58         HEADER_ENCODING,
59         HEADER_CONTENT_MD5,
60         HEADER_CONTENT_LOCATION,
61         HEADER_CONTENT_LANGUAGES,
62         HEADER_CONTENT_TYPE
63 } CamelHeaderType;
64
65
66 static GHashTable *header_name_table;
67 static GHashTable *header_formatted_table;
68
69 static CamelMediumClass *parent_class=NULL;
70
71 /* Returns the class for a CamelMimePart */
72 #define CMP_CLASS(so) CAMEL_MIME_PART_CLASS (CAMEL_OBJECT_GET_CLASS(so))
73 #define CDW_CLASS(so) CAMEL_DATA_WRAPPER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
74 #define CMD_CLASS(so) CAMEL_MEDIUM_CLASS (CAMEL_OBJECT_GET_CLASS(so))
75
76 /* from CamelDataWrapper */
77 static ssize_t         write_to_stream                 (CamelDataWrapper *dw, CamelStream *stream);
78 static int             construct_from_stream           (CamelDataWrapper *dw, CamelStream *stream);
79
80 /* from CamelMedium */ 
81 static void            add_header                      (CamelMedium *medium, const char *name, const void *value);
82 static void            set_header                      (CamelMedium *medium, const char *name, const void *value);
83 static void            remove_header                   (CamelMedium *medium, const char *name);
84 static const void     *get_header                      (CamelMedium *medium, const char *name);
85 static GArray         *get_headers                     (CamelMedium *medium);
86 static void            free_headers                    (CamelMedium *medium, GArray *headers);
87
88 static void            set_content_object              (CamelMedium *medium, CamelDataWrapper *content);
89
90 /* from camel mime parser */
91 static int             construct_from_parser           (CamelMimePart *mime_part, CamelMimeParser *mp);
92
93 /* forward references */
94 static void set_disposition (CamelMimePart *mime_part, const char *disposition);
95
96 /* format output of headers */
97 static ssize_t write_references(CamelStream *stream, struct _camel_header_raw *h);
98 /*static int write_fold(CamelStream *stream, struct _camel_header_raw *h);*/
99 static ssize_t write_raw(CamelStream *stream, struct _camel_header_raw *h);
100
101
102 /* loads in a hash table the set of header names we */
103 /* recognize and associate them with a unique enum  */
104 /* identifier (see CamelHeaderType above)           */
105 static void
106 init_header_name_table()
107 {
108         header_name_table = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
109         g_hash_table_insert (header_name_table, "Content-Description", (gpointer)HEADER_DESCRIPTION);
110         g_hash_table_insert (header_name_table, "Content-Disposition", (gpointer)HEADER_DISPOSITION);
111         g_hash_table_insert (header_name_table, "Content-id", (gpointer)HEADER_CONTENT_ID);
112         g_hash_table_insert (header_name_table, "Content-Transfer-Encoding", (gpointer)HEADER_ENCODING);
113         g_hash_table_insert (header_name_table, "Content-MD5", (gpointer)HEADER_CONTENT_MD5);
114         g_hash_table_insert (header_name_table, "Content-Location", (gpointer)HEADER_CONTENT_LOCATION);
115         g_hash_table_insert (header_name_table, "Content-Type", (gpointer)HEADER_CONTENT_TYPE);
116
117         header_formatted_table = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
118         g_hash_table_insert(header_formatted_table, "Content-Type", write_raw);
119         g_hash_table_insert(header_formatted_table, "Content-Disposition", write_raw);
120         g_hash_table_insert(header_formatted_table, "To", write_raw);
121         g_hash_table_insert(header_formatted_table, "From", write_raw);
122         g_hash_table_insert(header_formatted_table, "Reply-To", write_raw);
123         g_hash_table_insert(header_formatted_table, "Cc", write_raw);
124         g_hash_table_insert(header_formatted_table, "Bcc", write_raw);
125         g_hash_table_insert(header_formatted_table, "Message-ID", write_raw);
126         g_hash_table_insert(header_formatted_table, "In-Reply-To", write_raw);
127         g_hash_table_insert(header_formatted_table, "References", write_references);
128 }
129
130 static void
131 camel_mime_part_class_init (CamelMimePartClass *camel_mime_part_class)
132 {
133         CamelMediumClass *camel_medium_class = CAMEL_MEDIUM_CLASS (camel_mime_part_class);
134         CamelDataWrapperClass *camel_data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS (camel_mime_part_class);
135
136         parent_class = CAMEL_MEDIUM_CLASS (camel_type_get_global_classfuncs (camel_medium_get_type ()));
137         init_header_name_table();
138
139         camel_mime_part_class->construct_from_parser = construct_from_parser;
140         
141         /* virtual method overload */   
142         camel_medium_class->add_header                = add_header;
143         camel_medium_class->set_header                = set_header;
144         camel_medium_class->get_header                = get_header;
145         camel_medium_class->remove_header             = remove_header;
146         camel_medium_class->get_headers               = get_headers;
147         camel_medium_class->free_headers              = free_headers;
148         camel_medium_class->set_content_object        = set_content_object;
149         
150         camel_data_wrapper_class->write_to_stream     = write_to_stream;
151         camel_data_wrapper_class->construct_from_stream= construct_from_stream;
152 }
153
154 static void
155 camel_mime_part_init (gpointer object, gpointer klass)
156 {
157         CamelMimePart *mime_part = CAMEL_MIME_PART (object);
158         
159         if (((CamelDataWrapper *) mime_part)->mime_type)
160                 camel_content_type_unref (((CamelDataWrapper *) mime_part)->mime_type);
161         ((CamelDataWrapper *) mime_part)->mime_type = camel_content_type_new ("text", "plain");
162         
163         mime_part->description          = NULL;
164         mime_part->disposition          = NULL;
165         mime_part->content_id           = NULL;
166         mime_part->content_MD5          = NULL;
167         mime_part->content_location     = NULL;
168         mime_part->content_languages    = NULL;
169         mime_part->encoding = CAMEL_TRANSFER_ENCODING_DEFAULT;
170 }
171
172
173 static void           
174 camel_mime_part_finalize (CamelObject *object)
175 {
176         CamelMimePart *mime_part = CAMEL_MIME_PART (object);
177         
178         g_free (mime_part->description);
179         g_free (mime_part->content_id);
180         g_free (mime_part->content_MD5);
181         g_free (mime_part->content_location);
182         camel_string_list_free (mime_part->content_languages);
183         camel_content_disposition_unref(mime_part->disposition);
184         
185         camel_header_raw_clear(&mime_part->headers);
186 }
187
188
189
190 CamelType
191 camel_mime_part_get_type (void)
192 {
193         static CamelType type = CAMEL_INVALID_TYPE;
194         
195         if (type == CAMEL_INVALID_TYPE) {
196                 type = camel_type_register (CAMEL_MEDIUM_TYPE,
197                                             "CamelMimePart",
198                                             sizeof (CamelMimePart),
199                                             sizeof (CamelMimePartClass),
200                                             (CamelObjectClassInitFunc) camel_mime_part_class_init,
201                                             NULL,
202                                             (CamelObjectInitFunc) camel_mime_part_init,
203                                             (CamelObjectFinalizeFunc) camel_mime_part_finalize);
204         }
205         
206         return type;
207 }
208
209
210 /* **** */
211
212 static gboolean
213 process_header(CamelMedium *medium, const char *name, const char *value)
214 {
215         CamelMimePart *mime_part = CAMEL_MIME_PART (medium);
216         CamelHeaderType header_type;
217         const char *charset;
218         char *text;
219
220         /* Try to parse the header pair. If it corresponds to something   */
221         /* known, the job is done in the parsing routine. If not,         */
222         /* we simply add the header in a raw fashion                      */
223
224         header_type = (CamelHeaderType) g_hash_table_lookup (header_name_table, name);
225         switch (header_type) {
226         case HEADER_DESCRIPTION: /* raw header->utf8 conversion */
227                 g_free (mime_part->description);
228                 if (((CamelDataWrapper *) mime_part)->mime_type) {
229                         charset = camel_content_type_param (((CamelDataWrapper *) mime_part)->mime_type, "charset");
230                         charset = e_iconv_charset_name (charset);
231                 } else
232                         charset = NULL;
233                 mime_part->description = g_strstrip (camel_header_decode_string (value, charset));
234                 break;
235         case HEADER_DISPOSITION:
236                 set_disposition (mime_part, value);
237                 break;
238         case HEADER_CONTENT_ID:
239                 g_free (mime_part->content_id);
240                 mime_part->content_id = camel_header_contentid_decode (value);
241                 break;
242         case HEADER_ENCODING:
243                 text = camel_header_token_decode (value);
244                 mime_part->encoding = camel_transfer_encoding_from_string (text);
245                 g_free (text);
246                 break;
247         case HEADER_CONTENT_MD5:
248                 g_free (mime_part->content_MD5);
249                 mime_part->content_MD5 = g_strdup (value);
250                 break;
251         case HEADER_CONTENT_LOCATION:
252                 g_free (mime_part->content_location);
253                 mime_part->content_location = camel_header_location_decode (value);
254                 break;
255         case HEADER_CONTENT_TYPE:
256                 if (((CamelDataWrapper *) mime_part)->mime_type)
257                         camel_content_type_unref (((CamelDataWrapper *) mime_part)->mime_type);
258                 ((CamelDataWrapper *) mime_part)->mime_type = camel_content_type_decode (value);
259                 break;
260         default:
261                 return FALSE;
262         }
263         return TRUE;
264 }
265
266 static void
267 set_header (CamelMedium *medium, const char *name, const void *value)
268 {
269         CamelMimePart *part = CAMEL_MIME_PART (medium);
270         
271         process_header(medium, name, value);
272         camel_header_raw_replace(&part->headers, name, value, -1);
273 }
274
275 static void
276 add_header (CamelMedium *medium, const char *name, const void *value)
277 {
278         CamelMimePart *part = CAMEL_MIME_PART (medium);
279         
280         /* Try to parse the header pair. If it corresponds to something   */
281         /* known, the job is done in the parsing routine. If not,         */
282         /* we simply add the header in a raw fashion                      */
283
284         /* If it was one of the headers we handled, it must be unique, set it instead of add */
285         if (process_header(medium, name, value))
286                 camel_header_raw_replace(&part->headers, name, value, -1);
287         else
288                 camel_header_raw_append(&part->headers, name, value, -1);
289 }
290
291 static void
292 remove_header (CamelMedium *medium, const char *name)
293 {
294         CamelMimePart *part = (CamelMimePart *)medium;
295         
296         process_header(medium, name, NULL);
297         camel_header_raw_remove(&part->headers, name);
298 }
299
300 static const void *
301 get_header (CamelMedium *medium, const char *name)
302 {
303         CamelMimePart *part = (CamelMimePart *)medium;
304
305         return camel_header_raw_find(&part->headers, name, NULL);
306 }
307
308 static GArray *
309 get_headers (CamelMedium *medium)
310 {
311         CamelMimePart *part = (CamelMimePart *)medium;
312         GArray *headers;
313         CamelMediumHeader header;
314         struct _camel_header_raw *h;
315
316         headers = g_array_new (FALSE, FALSE, sizeof (CamelMediumHeader));
317         for (h = part->headers; h; h = h->next) {
318                 header.name = h->name;
319                 header.value = h->value;
320                 g_array_append_val (headers, header);
321         }
322
323         return headers;
324 }
325
326 static void
327 free_headers (CamelMedium *medium, GArray *gheaders)
328 {
329         g_array_free (gheaders, TRUE);
330 }
331
332 /* **** Content-Description */
333
334 /**
335  * camel_mime_part_set_description:
336  * @mime_part: a #CamelMimePart object
337  * @description: description of the MIME part
338  *
339  * Set a description on the MIME part.
340  **/
341 void
342 camel_mime_part_set_description (CamelMimePart *mime_part, const char *description)
343 {
344         char *text = camel_header_encode_string (description);
345         
346         camel_medium_set_header (CAMEL_MEDIUM (mime_part),
347                                  "Content-Description", text);
348         g_free (text);
349 }
350
351
352 /**
353  * camel_mime_part_get_description:
354  * @mime_part: a #CamelMimePart object
355  *
356  * Get the description of the MIME part.
357  *
358  * Returns the description
359  **/
360 const char *
361 camel_mime_part_get_description (CamelMimePart *mime_part)
362 {
363         return mime_part->description;
364 }
365
366 /* **** Content-Disposition */
367
368 static void
369 set_disposition (CamelMimePart *mime_part, const char *disposition)
370 {
371         camel_content_disposition_unref(mime_part->disposition);
372         if (disposition)
373                 mime_part->disposition = camel_content_disposition_decode(disposition);
374         else
375                 mime_part->disposition = NULL;
376 }
377
378
379 /**
380  * camel_mime_part_set_disposition:
381  * @mime_part: a #CamelMimePart object
382  * @disposition: disposition of the MIME part
383  *
384  * Set a disposition on the MIME part.
385  **/
386 void
387 camel_mime_part_set_disposition (CamelMimePart *mime_part, const char *disposition)
388 {
389         char *text;
390
391         /* we poke in a new disposition (so we dont lose 'filename', etc) */
392         if (mime_part->disposition == NULL) {
393                 set_disposition(mime_part, disposition);
394         }
395         if (mime_part->disposition != NULL) {
396                 g_free(mime_part->disposition->disposition);
397                 mime_part->disposition->disposition = g_strdup(disposition);
398         }
399         text = camel_content_disposition_format(mime_part->disposition);
400
401         camel_medium_set_header (CAMEL_MEDIUM (mime_part),
402                                  "Content-Disposition", text);
403
404         g_free(text);
405 }
406
407
408 /**
409  * camel_mime_part_get_disposition:
410  * @mime_part: a #CamelMimePart object
411  *
412  * Get the disposition of the MIME part.
413  *
414  * Returns the dispisition
415  **/
416 const char *
417 camel_mime_part_get_disposition (CamelMimePart *mime_part)
418 {
419         if (mime_part->disposition)
420                 return mime_part->disposition->disposition;
421         else
422                 return NULL;
423 }
424
425
426 /* **** Content-Disposition: filename="xxx" */
427
428 /**
429  * camel_mime_part_set_filename:
430  * @mime_part: a #CamelMimePart object
431  * @filename: filename given to the MIME part
432  *
433  * Set the filename on a MIME part.
434  **/
435 void
436 camel_mime_part_set_filename (CamelMimePart *mime_part, const char *filename)
437 {
438         char *str;
439         
440         if (mime_part->disposition == NULL)
441                 mime_part->disposition = camel_content_disposition_decode("attachment");
442
443         camel_header_set_param(&mime_part->disposition->params, "filename", filename);
444         str = camel_content_disposition_format(mime_part->disposition);
445
446         camel_medium_set_header (CAMEL_MEDIUM (mime_part),
447                                  "Content-Disposition", str);
448         g_free(str);
449         
450         camel_content_type_set_param (((CamelDataWrapper *) mime_part)->mime_type, "name", filename);
451         str = camel_content_type_format (((CamelDataWrapper *) mime_part)->mime_type);
452         camel_medium_set_header (CAMEL_MEDIUM (mime_part), "Content-Type", str);
453         g_free (str);
454 }
455
456
457 /**
458  * camel_mime_part_get_filename:
459  * @mime_part: a #CamelMimePart object
460  *
461  * Get the filename of a MIME part.
462  *
463  * Returns the filename of the MIME part
464  **/
465 const char *
466 camel_mime_part_get_filename (CamelMimePart *mime_part)
467 {
468         if (mime_part->disposition) {
469                 const char *name = camel_header_param (mime_part->disposition->params, "filename");
470                 if (name)
471                         return name;
472         }
473         
474         return camel_content_type_param (((CamelDataWrapper *) mime_part)->mime_type, "name");
475 }
476
477
478 /* **** Content-ID: */
479
480 /**
481  * camel_mime_part_set_content_id:
482  * @mime_part: a #CamelMimePart object
483  * @contentid: content id
484  *
485  * Set the content-id field on a MIME part.
486  **/
487 void
488 camel_mime_part_set_content_id (CamelMimePart *mime_part, const char *contentid)
489 {
490         char *cid, *id;
491         
492         if (contentid)
493                 id = g_strstrip (g_strdup (contentid));
494         else
495                 id = camel_header_msgid_generate ();
496         
497         cid = g_strdup_printf ("<%s>", id);
498         g_free (id);
499         camel_medium_set_header (CAMEL_MEDIUM (mime_part), "Content-ID", cid);
500         g_free (cid);
501 }
502
503
504 /**
505  * camel_mime_part_get_content_id:
506  * @mime_part: a #CamelMimePart object
507  *
508  * Get the content-id field of a MIME part.
509  *
510  * Returns the content-id field of the MIME part
511  **/
512 const char *
513 camel_mime_part_get_content_id (CamelMimePart *mime_part)
514 {
515         return mime_part->content_id;
516 }
517
518 /* **** Content-MD5: */
519
520 /**
521  * camel_mime_part_set_content_MD5:
522  * @mime_part: a #CamelMimePart object
523  * @md5sum: the md5sum of the MIME part
524  *
525  * Set the content-md5 field of the MIME part.
526  **/
527 void
528 camel_mime_part_set_content_MD5 (CamelMimePart *mime_part, const char *md5)
529 {
530         camel_medium_set_header (CAMEL_MEDIUM (mime_part), "Content-MD5", md5);
531 }
532
533
534 /**
535  * camel_mime_part_get_content_MD5:
536  * @mime_part: a #CamelMimePart object
537  *
538  * Get the content-md5 field of the MIME part.
539  *
540  * Returns the content-md5 field of the MIME part
541  **/
542 const char *
543 camel_mime_part_get_content_MD5 (CamelMimePart *mime_part)
544 {
545         return mime_part->content_MD5;
546 }
547
548 /* **** Content-Location: */
549
550
551 /**
552  * camel_mime_part_set_content_location:
553  * @mime_part: a #CamelMimePart object
554  * @location: the content-location value of the MIME part
555  *
556  * Set the content-location field of the MIME part.
557  **/
558 void
559 camel_mime_part_set_content_location (CamelMimePart *mime_part, const char *location)
560 {
561         /* FIXME: this should perform content-location folding */
562         camel_medium_set_header (CAMEL_MEDIUM (mime_part), "Content-Location", location);
563 }
564
565
566 /**
567  * camel_mime_part_get_content_location:
568  * @mime_part: a #CamelMimePart object
569  *
570  * Get the content-location field of a MIME part.
571  *
572  * Returns the content-location field of a MIME part
573  **/
574 const char *
575 camel_mime_part_get_content_location (CamelMimePart *mime_part)
576 {
577         return mime_part->content_location;
578 }
579
580 /* **** Content-Transfer-Encoding: */
581
582
583 /**
584  * camel_mime_part_set_encoding:
585  * @mime_part: a #CamelMimePart object
586  * @encoding: a #CamelTransferEncoding
587  *
588  * Set the Content-Transfer-Encoding to use on a MIME part.
589  **/
590 void
591 camel_mime_part_set_encoding (CamelMimePart *mime_part,
592                               CamelTransferEncoding encoding)
593 {
594         const char *text;
595
596         text = camel_transfer_encoding_to_string (encoding);
597         camel_medium_set_header (CAMEL_MEDIUM (mime_part),
598                                  "Content-Transfer-Encoding", text);
599 }
600
601
602 /**
603  * camel_mime_part_get_encoding:
604  * @mime_part: a #CamelMimePart object
605  *
606  * Get the Content-Transfer-Encoding of a MIME part.
607  *
608  * Returns a #CamelTransferEncoding
609  **/
610 CamelTransferEncoding
611 camel_mime_part_get_encoding (CamelMimePart *mime_part)
612 {
613         return mime_part->encoding;
614 }
615
616 /* FIXME: do something with this stuff ... */
617
618
619 /**
620  * camel_mime_part_set_content_languages:
621  * @mime_part: a #CamelMimePart object
622  * @content_languages: list of languages
623  *
624  * Set the Content-Languages field of a MIME part.
625  **/
626 void
627 camel_mime_part_set_content_languages (CamelMimePart *mime_part, GList *content_languages)
628 {
629         if (mime_part->content_languages)
630                 camel_string_list_free (mime_part->content_languages);
631         
632         mime_part->content_languages = content_languages;
633
634         /* FIXME: translate to a header and set it */
635 }
636
637
638 /**
639  * camel_mime_part_get_content_languages:
640  * @mime_part: a #CamelMimePart object
641  *
642  * Get the Content-Languages set on the MIME part.
643  *
644  * Returns a #GList of languages
645  **/
646 const GList *
647 camel_mime_part_get_content_languages (CamelMimePart *mime_part)
648 {
649         return mime_part->content_languages;
650 }
651
652
653 /* **** */
654
655 /* **** Content-Type: */
656
657 /**
658  * camel_mime_part_set_content_type:
659  * @mime_part: a #CamelMimePart object
660  * @content_type: content-type string
661  *
662  * Set the content-type on a MIME part.
663  **/
664 void
665 camel_mime_part_set_content_type (CamelMimePart *mime_part, const char *content_type)
666 {
667         camel_medium_set_header (CAMEL_MEDIUM (mime_part),
668                                  "Content-Type", content_type);
669 }
670
671
672 /**
673  * camel_mime_part_get_content_type:
674  * @mime_part: a #CamelMimePart object
675  *
676  * Get the Content-Type of a MIME part.
677  *
678  * Returns the parsed #CamelContentType of the MIME part
679  **/
680 CamelContentType *
681 camel_mime_part_get_content_type (CamelMimePart *mime_part)
682 {
683         return ((CamelDataWrapper *) mime_part)->mime_type;
684 }
685
686 static void
687 set_content_object (CamelMedium *medium, CamelDataWrapper *content)
688 {
689         CamelDataWrapper *mime_part = CAMEL_DATA_WRAPPER (medium);
690         CamelContentType *content_type;
691         
692         parent_class->set_content_object (medium, content);
693         
694         content_type = camel_data_wrapper_get_mime_type_field (content);
695         if (mime_part->mime_type != content_type) {
696                 char *txt;
697                 
698                 txt = camel_content_type_format (content_type);
699                 camel_medium_set_header (CAMEL_MEDIUM (mime_part), "Content-Type", txt);
700                 g_free (txt);
701         }
702 }
703
704 /**********************************************************************/
705
706 static ssize_t
707 write_references(CamelStream *stream, struct _camel_header_raw *h)
708 {
709         ssize_t len, out, total;
710         char *v, *ids, *ide;
711         
712         /* this is only approximate, based on the next >, this way it retains any content
713            from the original which may not be properly formatted, etc.  It also doesn't handle
714            the case where an individual messageid is too long, however thats a bad mail to
715            start with ... */
716
717         v = h->value;
718         len = strlen(h->name)+1;
719         total = camel_stream_printf(stream, "%s%s", h->name, isspace(v[0])?":":": ");
720         if (total == -1)
721                 return -1;
722         while (*v) {
723                 ids = v;
724                 ide = strchr(ids+1, '>');
725                 if (ide)
726                         v = ++ide;
727                 else
728                         ide = v = strlen(ids)+ids;
729
730                 if (len>0 && len + (ide - ids) >= CAMEL_FOLD_SIZE) {
731                         out = camel_stream_printf(stream, "\n\t");
732                         if (out == -1)
733                                 return -1;
734                         total += out;
735                         len = 0;
736                 }
737                 out = camel_stream_write(stream, ids, ide-ids);
738                 if (out == -1)
739                         return -1;
740                 len += out;
741                 total += out;
742         }
743         camel_stream_write(stream, "\n", 1);
744
745         return total;
746 }
747
748 #if 0
749 /* not needed - yet - handled by default case */
750 static ssize_t
751 write_fold(CamelStream *stream, struct _camel_header_raw *h)
752 {
753         char *val;
754         int count;
755
756         val = camel_header_fold(h->value, strlen(h->name));
757         count = camel_stream_printf(stream, "%s%s%s\n", h->name, isspace(val[0]) ? ":" : ": ", val);
758         g_free(val);
759
760         return count;
761 }
762 #endif
763
764 static ssize_t
765 write_raw(CamelStream *stream, struct _camel_header_raw *h)
766 {
767         char *val = h->value;
768
769         return camel_stream_printf(stream, "%s%s%s\n", h->name, isspace(val[0]) ? ":" : ": ", val);
770 }
771
772 static ssize_t
773 write_to_stream (CamelDataWrapper *dw, CamelStream *stream)
774 {
775         CamelMimePart *mp = CAMEL_MIME_PART (dw);
776         CamelMedium *medium = CAMEL_MEDIUM (dw);
777         CamelStream *ostream = stream;
778         CamelDataWrapper *content;
779         ssize_t total = 0;
780         ssize_t count;
781         int errnosav;
782         
783         d(printf("mime_part::write_to_stream\n"));
784         
785         /* FIXME: something needs to be done about this ... */
786         /* TODO: content-languages header? */
787         
788         if (mp->headers) {
789                 struct _camel_header_raw *h = mp->headers;
790                 char *val;
791                 ssize_t (*writefn)(CamelStream *stream, struct _camel_header_raw *);
792                 
793                 /* fold/write the headers.   But dont fold headers that are already formatted
794                    (e.g. ones with parameter-lists, that we know about, and have created) */
795                 while (h) {
796                         val = h->value;
797                         if (val == NULL) {
798                                 g_warning("h->value is NULL here for %s", h->name);
799                                 count = 0;
800                         } else if ((writefn = g_hash_table_lookup(header_formatted_table, h->name)) == NULL) {
801                                 val = camel_header_fold(val, strlen(h->name));
802                                 count = camel_stream_printf(stream, "%s%s%s\n", h->name, isspace(val[0]) ? ":" : ": ", val);
803                                 g_free(val);
804                         } else {
805                                 count = writefn(stream, h);
806                         }
807                         if (count == -1)
808                                 return -1;
809                         total += count;
810                         h = h->next;
811                 }
812         }
813         
814         count = camel_stream_write(stream, "\n", 1);
815         if (count == -1)
816                 return -1;
817         total += count;
818         
819         content = camel_medium_get_content_object(medium);
820         if (content) {
821                 CamelMimeFilter *filter = NULL;
822                 CamelStreamFilter *filter_stream = NULL;
823                 CamelMimeFilter *charenc = NULL;
824                 const char *content_charset = NULL;
825                 const char *part_charset = NULL;
826                 gboolean reencode = FALSE;
827                 const char *filename;
828                 
829                 if (camel_content_type_is (dw->mime_type, "text", "*")) {
830                         content_charset = camel_content_type_param (content->mime_type, "charset");
831                         part_charset = camel_content_type_param (dw->mime_type, "charset");
832                         
833                         if (content_charset && part_charset) {
834                                 content_charset = e_iconv_charset_name (content_charset);
835                                 part_charset = e_iconv_charset_name (part_charset);
836                         }
837                 }
838                 
839                 if (mp->encoding != content->encoding) {
840                         switch (mp->encoding) {
841                         case CAMEL_TRANSFER_ENCODING_BASE64:
842                                 filter = (CamelMimeFilter *) camel_mime_filter_basic_new_type (CAMEL_MIME_FILTER_BASIC_BASE64_ENC);
843                                 break;
844                         case CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE:
845                                 filter = (CamelMimeFilter *) camel_mime_filter_basic_new_type (CAMEL_MIME_FILTER_BASIC_QP_ENC);
846                                 break;
847                         case CAMEL_TRANSFER_ENCODING_UUENCODE:
848                                 filename = camel_mime_part_get_filename (mp);
849                                 count = camel_stream_printf (ostream, "begin 644 %s\n", filename ? filename : "untitled");
850                                 if (count == -1)
851                                         return -1;
852                                 total += count;
853                                 filter = (CamelMimeFilter *) camel_mime_filter_basic_new_type (CAMEL_MIME_FILTER_BASIC_UU_ENC);
854                                 break;
855                         default:
856                                 /* content is encoded but the part doesn't want to be... */
857                                 reencode = TRUE;
858                                 break;
859                         }
860                 }
861                 
862                 if (content_charset && part_charset && part_charset != content_charset)
863                         charenc = (CamelMimeFilter *) camel_mime_filter_charset_new_convert (content_charset, part_charset);
864                 
865                 if (filter || charenc) {
866                         filter_stream = camel_stream_filter_new_with_stream(stream);
867                         
868                         /* if we have a character encoder, add that always */
869                         if (charenc) {
870                                 camel_stream_filter_add(filter_stream, charenc);
871                                 camel_object_unref (charenc);
872                         }
873                         
874                         /* we only re-do crlf on encoded blocks */
875                         if (filter && camel_content_type_is (dw->mime_type, "text", "*")) {
876                                 CamelMimeFilter *crlf = camel_mime_filter_crlf_new(CAMEL_MIME_FILTER_CRLF_ENCODE,
877                                                                                    CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
878                                 
879                                 camel_stream_filter_add(filter_stream, crlf);
880                                 camel_object_unref (crlf);
881                         }
882                         
883                         if (filter) {
884                                 camel_stream_filter_add(filter_stream, filter);
885                                 camel_object_unref (filter);
886                         }
887                         
888                         stream = (CamelStream *)filter_stream;
889                         
890                         reencode = TRUE;
891                 }
892                 
893                 if (reencode)
894                         count = camel_data_wrapper_decode_to_stream (content, stream);
895                 else
896                         count = camel_data_wrapper_write_to_stream (content, stream);
897                 
898                 if (filter_stream) {
899                         errnosav = errno;
900                         camel_stream_flush (stream);
901                         camel_object_unref (filter_stream);
902                         errno = errnosav;
903                 }
904                 
905                 if (count == -1)
906                         return -1;
907                 
908                 total += count;
909                 
910                 if (reencode && mp->encoding == CAMEL_TRANSFER_ENCODING_UUENCODE) {
911                         count = camel_stream_write (ostream, "end\n", 4);
912                         if (count == -1)
913                                 return -1;
914                         total += count;
915                 }
916         } else {
917                 g_warning("No content for medium, nothing to write");
918         }
919         
920         return total;
921 }
922
923 /* mime_part */
924 static int
925 construct_from_parser (CamelMimePart *mime_part, CamelMimeParser *mp)
926 {
927         CamelDataWrapper *dw = (CamelDataWrapper *) mime_part;
928         struct _camel_header_raw *headers;
929         const char *content;
930         char *buf;
931         size_t len;
932         int err;
933         
934         d(printf("mime_part::construct_from_parser()\n"));
935         
936         switch (camel_mime_parser_step(mp, &buf, &len)) {
937         case CAMEL_MIME_PARSER_STATE_MESSAGE:
938                 /* set the default type of a message always */
939                 if (dw->mime_type)
940                         camel_content_type_unref (dw->mime_type);
941                 dw->mime_type = camel_content_type_decode ("message/rfc822");
942         case CAMEL_MIME_PARSER_STATE_HEADER:
943         case CAMEL_MIME_PARSER_STATE_MULTIPART:
944                 /* we have the headers, build them into 'us' */
945                 headers = camel_mime_parser_headers_raw(mp);
946
947                 /* if content-type exists, process it first, set for fallback charset in headers */
948                 content = camel_header_raw_find(&headers, "content-type", NULL);
949                 if (content)
950                         process_header((CamelMedium *)dw, "content-type", content);
951
952                 while (headers) {
953                         if (g_ascii_strcasecmp(headers->name, "content-type") == 0
954                             && headers->value != content)
955                                 camel_medium_add_header((CamelMedium *)dw, "X-Invalid-Content-Type", headers->value);
956                         else
957                                 camel_medium_add_header((CamelMedium *)dw, headers->name, headers->value);
958                         headers = headers->next;
959                 }
960
961                 camel_mime_part_construct_content_from_parser (mime_part, mp);
962                 break;
963         default:
964                 g_warning("Invalid state encountered???: %u", camel_mime_parser_state(mp));
965         }
966
967         d(printf("mime_part::construct_from_parser() leaving\n"));
968         err = camel_mime_parser_errno(mp);
969         if (err != 0) {
970                 errno = err;
971                 return -1;
972         }
973
974         return 0;
975 }
976
977 /**
978  * camel_mime_part_construct_from_parser:
979  * @mime_part: a #CamelMimePart object
980  * @parser: a #CamelMimeParser object
981  *
982  * Constructs a MIME part from a parser.
983  * 
984  * Returns %0 on success or %-1 on fail
985  **/
986 int
987 camel_mime_part_construct_from_parser(CamelMimePart *mime_part, CamelMimeParser *mp)
988 {
989         return CMP_CLASS (mime_part)->construct_from_parser (mime_part, mp);
990 }
991
992 static int
993 construct_from_stream(CamelDataWrapper *dw, CamelStream *s)
994 {
995         CamelMimeParser *mp;
996         int ret;
997
998         d(printf("mime_part::construct_from_stream()\n"));
999
1000         mp = camel_mime_parser_new();
1001         if (camel_mime_parser_init_with_stream(mp, s) == -1) {
1002                 g_warning("Cannot create parser for stream");
1003                 ret = -1;
1004         } else {
1005                 ret = camel_mime_part_construct_from_parser((CamelMimePart *)dw, mp);
1006         }
1007         camel_object_unref((CamelObject *)mp);
1008         return ret;
1009 }
1010
1011 /******************************/
1012 /**  Misc utility functions  **/
1013
1014 /**
1015  * camel_mime_part_new:
1016  *
1017  * Create a new MIME part.
1018  *
1019  * Returns a new #CamelMimePart object
1020  **/
1021 CamelMimePart *
1022 camel_mime_part_new (void)
1023 {
1024         return (CamelMimePart *)camel_object_new (CAMEL_MIME_PART_TYPE);
1025 }
1026
1027 /**
1028  * camel_mime_part_set_content:
1029  * @mime_part: a #CamelMimePart object
1030  * @data: data to put into the part
1031  * @length: length of @data
1032  * @type: Content-Type of the data
1033  * 
1034  * Utility function used to set the content of a mime part object to 
1035  * be the provided data. If @length is 0, this routine can be used as
1036  * a way to remove old content (in which case @data and @type are
1037  * ignored and may be %NULL).
1038  **/
1039 void 
1040 camel_mime_part_set_content (CamelMimePart *mime_part,
1041                              const char *data, int length,
1042                              const char *type) /* why on earth is the type last? */
1043 {
1044         CamelMedium *medium = CAMEL_MEDIUM (mime_part);
1045
1046         if (length) {
1047                 CamelDataWrapper *dw;
1048                 CamelStream *stream;
1049
1050                 dw = camel_data_wrapper_new ();
1051                 camel_data_wrapper_set_mime_type (dw, type);
1052                 stream = camel_stream_mem_new_with_buffer (data, length);
1053                 camel_data_wrapper_construct_from_stream (dw, stream);
1054                 camel_object_unref (stream);
1055                 camel_medium_set_content_object (medium, dw);
1056                 camel_object_unref (dw);
1057         } else {
1058                 if (medium->content)
1059                         camel_object_unref (medium->content);
1060                 medium->content = NULL;
1061         }
1062 }