Added preliminary Symbian support.
[silc.git] / lib / silcutil / silcmime.c
1 /*
2
3   silcmime.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2006 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silc.h"
21
22 /************************ Static utility functions **************************/
23
24 /* MIME fields destructor */
25
26 static void silc_mime_field_dest(void *key, void *context, void *user_context)
27 {
28   silc_free(key);
29   silc_free(context);
30 }
31
32 /* Assembler fragment destructor */
33
34 static void silc_mime_assembler_dest(void *key, void *context,
35                                      void *user_context)
36 {
37   silc_free(key);
38   silc_hash_table_free(context);
39 }
40
41 /* Assembler partial MIME destructor */
42
43 static void silc_mime_assemble_dest(void *key, void *context,
44                                     void *user_context)
45 {
46   silc_mime_free(context);
47 }
48
49
50 /******************************* Public API *********************************/
51
52 /* Allocate MIME context */
53
54 SilcMime silc_mime_alloc(void)
55 {
56   SilcMime mime;
57
58   mime = silc_calloc(1, sizeof(*mime));
59   if (!mime)
60     return NULL;
61
62   mime->fields = silc_hash_table_alloc(0, silc_hash_string, mime,
63                                        silc_hash_string_compare, mime,
64                                        silc_mime_field_dest, mime, TRUE);
65   if (!mime->fields) {
66     silc_mime_free(mime);
67     return NULL;
68   }
69
70   return mime;
71 }
72
73 /* Free MIME context */
74
75 void silc_mime_free(SilcMime mime)
76 {
77   SilcMime m;
78
79   if (mime->fields)
80     silc_hash_table_free(mime->fields);
81
82   if (mime->multiparts) {
83     silc_dlist_start(mime->multiparts);
84     while ((m = silc_dlist_get(mime->multiparts)) != SILC_LIST_END)
85       silc_mime_free(m);
86     silc_dlist_uninit(mime->multiparts);
87   }
88   silc_free(mime->boundary);
89   silc_free(mime->multitype);
90   silc_free(mime->data);
91   silc_free(mime);
92 }
93
94 /* Allocate MIME assembler */
95
96 SilcMimeAssembler silc_mime_assembler_alloc(void)
97 {
98   SilcMimeAssembler assembler;
99
100   assembler = silc_calloc(1, sizeof(*assembler));
101   if (!assembler)
102     return NULL;
103
104   assembler->fragments =
105     silc_hash_table_alloc(0, silc_hash_string, NULL,
106                           silc_hash_string_compare, NULL,
107                           silc_mime_assembler_dest, assembler, TRUE);
108   if (!assembler->fragments) {
109     silc_mime_assembler_free(assembler);
110     return NULL;
111   }
112
113   return assembler;
114 }
115
116 /* Free MIME assembler */
117
118 void silc_mime_assembler_free(SilcMimeAssembler assembler)
119 {
120   silc_hash_table_free(assembler->fragments);
121   silc_free(assembler);
122 }
123
124 /* Decode MIME message */
125
126 SilcMime silc_mime_decode(SilcMime mime, const unsigned char *data,
127                           SilcUInt32 data_len)
128 {
129   SilcMime m = NULL;
130   int i, k;
131   char *tmp, *field, *value, *line;
132
133   SILC_LOG_DEBUG(("Parsing MIME message"));
134
135   if (!data)
136     return NULL;
137
138   if (!mime) {
139     mime = silc_mime_alloc();
140     if (!mime)
141       return NULL;
142     m = mime;
143   }
144
145   /* Parse the fields */
146   line = tmp = (char *)data;
147   for (i = 0; i < data_len; i++) {
148     /* Get field line */
149     if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
150       /* Get field */
151       field = strchr(line, ':');
152       if (!field)
153         goto err;
154       field = silc_memdup(line, field - line);
155       if (!field)
156         goto err;
157
158       /* Get value. Remove whitespaces too. */
159       value = strchr(line, ':');
160       if ((tmp + i) - value < 2)
161         goto err;
162       value++;
163       for (k = 0; k < (tmp + i) - value; k++) {
164         if (value[k] == '\r')
165           goto err;
166         if (value[k] != ' ' && value[k] != '\t')
167           break;
168       }
169       value += k;
170       if ((tmp + i) - value < 1)
171         goto err;
172       value = silc_memdup(value, (tmp + i) - value);
173       if (!value)
174         goto err;
175
176       SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
177
178       /* Add field and value */
179       silc_mime_add_field(mime, field, value);
180       silc_free(field);
181       silc_free(value);
182
183       /* Mark start of next line */
184       line = (tmp + i) + 2;
185       i += 2;
186
187       /* Break if this is last header */
188       if (data_len - i >= 2 &&
189           tmp[i] == '\r' && tmp[i + 1] == '\n') {
190         i += 2;
191         break;
192       }
193     }
194   }
195
196   /* Parse multiparts if present */
197   field = (char *)silc_mime_get_field(mime, "Content-Type");
198   if (field && strstr(field, "multipart")) {
199     char b[1024];
200     SilcMime p;
201
202     mime->multiparts = silc_dlist_init();
203     if (!mime->multiparts)
204       goto err;
205
206     /* Get multipart type */
207     value = strchr(field, '/');
208     if (!value)
209       goto err;
210     value++;
211     if (strchr(field, '"'))
212       value++;
213     if (!strchr(field, ';'))
214       goto err;
215     memset(b, 0, sizeof(b));
216     strncat(b, value, strchr(field, ';') - value);
217     if (strchr(b, '"'))
218       *strchr(b, '"') = '\0';
219     mime->multitype = silc_memdup(b, strlen(b));
220
221     /* Get boundary */
222     value = strrchr(field, '=');
223     if (value && strlen(value) > 1) {
224       value++;
225
226       SILC_LOG_DEBUG(("Boundary '%s'", value));
227
228       memset(b, 0, sizeof(b));
229       line = strdup(value);
230       if (strrchr(line, '"')) {
231         *strrchr(line, '"') = '\0';
232         silc_silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
233         mime->boundary = strdup(line + 1);
234       } else {
235         silc_silc_snprintf(b, sizeof(b) - 1, "--%s", line);
236         mime->boundary = strdup(line);
237       }
238       silc_free(line);
239
240       for (i = i; i < data_len; i++) {
241         /* Get boundary data */
242         if (data_len - i >= strlen(b) &&
243             tmp[i] == '-' && tmp[i + 1] == '-') {
244           if (memcmp(tmp + i, b, strlen(b)))
245             continue;
246
247           i += strlen(b);
248
249           if (data_len - i >= 4 &&
250               tmp[i    ] == '\r' && tmp[i + 1] == '\n' &&
251               tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
252             i += 4;
253           else if (data_len - i >= 2 &&
254                    tmp[i] == '\r' && tmp[i + 1] == '\n')
255             i += 2;
256           else if (data_len - i >= 2 &&
257                    tmp[i] == '-' && tmp[i + 1] == '-')
258             break;
259
260           line = tmp + i;
261
262           /* Find end of boundary */
263           for (k = i; k < data_len; k++)
264             if (data_len - k >= strlen(b) &&
265                 tmp[k] == '-' && tmp[k + 1] == '-')
266               if (!memcmp(tmp + k, b, strlen(b)))
267                 break;
268           if (k >= data_len)
269             goto err;
270
271           /* Remove preceding CRLF */
272           k -= 2;
273
274           /* Parse the part */
275           p = silc_mime_decode(NULL, line, k - i);
276           if (!p)
277             goto err;
278
279           silc_dlist_add(mime->multiparts, p);
280           i += (k - i);
281         }
282       }
283     }
284   } else {
285     /* Get data area.  If we are at the end and we have fields present
286        there is no data area present, but, if fields are not present we
287        only have data area. */
288     if (i >= data_len && !silc_hash_table_count(mime->fields))
289       i = 0;
290     SILC_LOG_DEBUG(("Data len %d", data_len - i));
291     if (data_len - i)
292       silc_mime_add_data(mime, tmp + i, data_len - i);
293   }
294
295   return mime;
296
297  err:
298   if (m)
299     silc_mime_free(m);
300   return NULL;
301 }
302
303 /* Encode MIME message */
304
305 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
306 {
307   SilcMime part;
308   SilcHashTableList htl;
309   SilcBufferStruct buf;
310   SilcBuffer buffer;
311   char *field, *value, tmp[1024], tmp2[4];
312   unsigned char *ret;
313   int i;
314
315   SILC_LOG_DEBUG(("Encoding MIME message"));
316
317   if (!mime)
318     return NULL;
319
320   memset(&buf, 0, sizeof(buf));
321
322   /* Encode the headers. Order doesn't matter */
323   i = 0;
324   silc_hash_table_list(mime->fields, &htl);
325   while (silc_hash_table_get(&htl, (void **)&field, (void **)&value)) {
326     memset(tmp, 0, sizeof(tmp));
327     SILC_LOG_DEBUG(("Header %s: %s", field, value));
328     silc_silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
329     silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
330     i++;
331   }
332   silc_hash_table_list_reset(&htl);
333   if (i)
334     silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
335
336   /* Assemble the whole buffer */
337   buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
338   if (!buffer)
339     return NULL;
340
341   /* Add headers */
342   if (silc_buffer_len(&buf)) {
343     silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
344     silc_buffer_pull(buffer, silc_buffer_len(&buf));
345   }
346
347   /* Add data */
348   if (mime->data) {
349     SILC_LOG_DEBUG(("Data len %d", mime->data_len));
350     silc_buffer_put(buffer, mime->data, mime->data_len);
351   }
352
353   /* Add multiparts */
354   if (mime->multiparts) {
355     SILC_LOG_DEBUG(("Encoding multiparts"));
356
357     silc_dlist_start(mime->multiparts);
358     i = 0;
359     while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
360       unsigned char *pd;
361       SilcUInt32 pd_len;
362
363       /* Recursive encoding */
364       pd = silc_mime_encode(part, &pd_len);
365       if (!pd)
366         return NULL;
367
368       memset(tmp, 0, sizeof(tmp));
369       memset(tmp2, 0, sizeof(tmp2));
370
371       /* If fields are not present, add extra CRLF */
372       if (!silc_hash_table_count(part->fields))
373         silc_silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
374       silc_silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
375                i != 0 ? "\r\n" : "", mime->boundary, tmp2);
376       i = 1;
377
378       buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
379                                    pd_len + strlen(tmp));
380       if (!buffer)
381         return NULL;
382       silc_buffer_put_tail(buffer, tmp, strlen(tmp));
383       silc_buffer_pull_tail(buffer, strlen(tmp));
384       silc_buffer_put_tail(buffer, pd, pd_len);
385       silc_buffer_pull_tail(buffer, pd_len);
386       silc_free(pd);
387     }
388
389     memset(tmp, 0, sizeof(tmp));
390     silc_silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
391     buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
392                                  strlen(tmp));
393     if (!buffer)
394       return NULL;
395     silc_buffer_put_tail(buffer, tmp, strlen(tmp));
396     silc_buffer_pull_tail(buffer, strlen(tmp));
397   }
398
399   ret = silc_buffer_steal(buffer, encoded_len);
400   silc_buffer_free(buffer);
401
402   return ret;
403 }
404
405 /* Assembles MIME message from partial MIME messages */
406
407 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
408 {
409   char *type, *id = NULL, *tmp;
410   SilcHashTable f;
411   SilcMime p, complete;
412   int i, number, total = -1;
413   const unsigned char *data;
414   SilcUInt32 data_len;
415   SilcBuffer compbuf = NULL;
416
417   SILC_LOG_DEBUG(("Assembling MIME fragments"));
418
419   if (!assembler || !partial)
420     goto err;
421
422   type = (char *)silc_mime_get_field(partial, "Content-Type");
423   if (!type)
424     goto err;
425
426   /* Get ID */
427   tmp = strstr(type, "id=");
428   if (!tmp)
429     goto err;
430   if (strlen(tmp) <= 4)
431     goto err;
432   tmp += 3;
433   if (*tmp == '"')
434     tmp++;
435   id = strdup(tmp);
436   if (strchr(id, ';'))
437     *strchr(id, ';') = '\0';
438   if (strrchr(id, '"'))
439     *strrchr(id, '"') = '\0';
440
441   SILC_LOG_DEBUG(("Fragment ID %s", id));
442
443   /* Get fragment number */
444   tmp = strstr(type, "number=");
445   if (!tmp)
446     goto err;
447   tmp = strchr(tmp, '=');
448   if (strlen(tmp) < 2)
449     goto err;
450   tmp++;
451   if (strchr(tmp, ';')) {
452     tmp = strdup(tmp);
453     *strchr(tmp, ';') = '\0';
454     number = atoi(tmp);
455     silc_free(tmp);
456   } else {
457     number = atoi(tmp);
458   }
459
460   SILC_LOG_DEBUG(("Fragment number %d", number));
461
462   /* Find fragments with this ID. */
463   if (!silc_hash_table_find(assembler->fragments, (void *)id,
464                             NULL, (void **)&f)) {
465     /* This is new fragment to new message.  Add to hash table and return. */
466     f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
467                               silc_mime_assemble_dest, NULL, TRUE);
468     if (!f)
469          goto err;
470     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
471     silc_hash_table_add(assembler->fragments, id, f);
472     return NULL;
473   }
474
475   /* Try to get total number */
476   tmp = strstr(type, "total=");
477   if (tmp) {
478     tmp = strchr(tmp, '=');
479     if (strlen(tmp) < 2)
480       goto err;
481     tmp++;
482     if (strchr(tmp, ';')) {
483       tmp = strdup(tmp);
484       *strchr(tmp, ';') = '\0';
485       total = atoi(tmp);
486       silc_free(tmp);
487     } else {
488       total = atoi(tmp);
489     }
490
491     SILC_LOG_DEBUG(("Fragment total %d", total));
492   }
493
494   /* If more fragments to come, add to hash table */
495   if (number != total) {
496     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
497     return NULL;
498   }
499
500   silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
501
502   /* Verify that we really have all the fragments */
503   if (silc_hash_table_count(f) < total)
504     return NULL;
505
506   /* Assemble the complete MIME message now. We get them in order from
507      the hash table. */
508   for (i = 1; i <= total; i++) {
509     if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void **)&p))
510       goto err;
511
512     /* The fragment is in the data portion of the partial message */
513     data = silc_mime_get_data(p, &data_len);
514     if (!data)
515       goto err;
516
517     /* Assemble */
518     if (!compbuf) {
519       compbuf = silc_buffer_alloc_size(data_len);
520       if (!compbuf)
521         goto err;
522       silc_buffer_put(compbuf, data, data_len);
523     } else {
524       compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
525                                     data_len);
526       if (!compbuf)
527         goto err;
528       silc_buffer_put_tail(compbuf, data, data_len);
529       silc_buffer_pull_tail(compbuf, data_len);
530     }
531   }
532
533   /* Now parse the complete MIME message and deliver it */
534   complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
535                               silc_buffer_truelen(compbuf));
536   if (!complete)
537     goto err;
538
539   /* Delete the hash table entry. Destructors will free memory */
540   silc_hash_table_del(assembler->fragments, (void *)id);
541   silc_free(id);
542   silc_buffer_free(compbuf);
543
544   return complete;
545
546  err:
547   silc_free(id);
548   if (compbuf)
549     silc_buffer_free(compbuf);
550   silc_mime_free(partial);
551   return NULL;
552 }
553
554 /* Encodes partial MIME messages */
555
556 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
557 {
558   unsigned char *buf, *tmp;
559   SilcUInt32 buf_len, len, tmp_len, off;
560   SilcDList list;
561   SilcBuffer buffer;
562   SilcMime partial;
563   char type[128], id[64];
564   int num;
565
566   SILC_LOG_DEBUG(("Fragmenting MIME message"));
567
568   /* Encode as normal */
569   buf = silc_mime_encode(mime, &buf_len);
570   if (!buf)
571     return NULL;
572
573   list = silc_dlist_init();
574
575   /* Fragment if it is too large */
576   if (buf_len > max_size) {
577     memset(id, 0, sizeof(id));
578     memset(type, 0, sizeof(type));
579     gethostname(type, sizeof(type) - 1);
580     srand((time(NULL) + buf_len) ^ rand());
581     silc_silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
582              (unsigned int)rand(), (unsigned int)time(NULL),
583              (unsigned int)buf_len, type);
584
585     SILC_LOG_DEBUG(("Fragment ID %s", id));
586
587     partial = silc_mime_alloc();
588     if (!partial)
589       return NULL;
590
591     silc_mime_add_field(partial, "MIME-Version", "1.0");
592     memset(type, 0, sizeof(type));
593     silc_silc_snprintf(type, sizeof(type) - 1,
594              "message/partial; id=\"%s\"; number=1", id);
595     silc_mime_add_field(partial, "Content-Type", type);
596     silc_mime_add_data(partial, buf, max_size);
597
598     tmp = silc_mime_encode(partial, &tmp_len);
599     if (!tmp)
600       return NULL;
601     silc_mime_free(partial);
602
603     /* Add to list */
604     buffer = silc_buffer_alloc_size(tmp_len);
605     if (!buffer)
606       return NULL;
607     silc_buffer_put(buffer, tmp, tmp_len);
608     silc_dlist_add(list, buffer);
609     silc_free(tmp);
610
611     len = buf_len - max_size;
612     off = max_size;
613     num = 2;
614     while (len > 0) {
615       partial = silc_mime_alloc();
616       if (!partial)
617         return NULL;
618
619       memset(type, 0, sizeof(type));
620       silc_mime_add_field(partial, "MIME-Version", "1.0");
621
622       if (len > max_size) {
623         silc_silc_snprintf(type, sizeof(type) - 1,
624                  "message/partial; id=\"%s\"; number=%d",
625                  id, num++);
626         silc_mime_add_data(partial, buf + off, max_size);
627         off += max_size;
628         len -= max_size;
629       } else {
630         silc_silc_snprintf(type, sizeof(type) - 1,
631                  "message/partial; id=\"%s\"; number=%d; total=%d",
632                  id, num, num);
633         silc_mime_add_data(partial, buf + off, len);
634         len = 0;
635       }
636
637       silc_mime_add_field(partial, "Content-Type", type);
638
639       tmp = silc_mime_encode(partial, &tmp_len);
640       if (!tmp)
641         return NULL;
642       silc_mime_free(partial);
643
644       /* Add to list */
645       buffer = silc_buffer_alloc_size(tmp_len);
646       if (!buffer)
647         return NULL;
648       silc_buffer_put(buffer, tmp, tmp_len);
649       silc_dlist_add(list, buffer);
650       silc_free(tmp);
651     }
652   } else {
653     /* No need to fragment */
654     buffer = silc_buffer_alloc_size(buf_len);
655     if (!buffer)
656       return NULL;
657     silc_buffer_put(buffer, buf, buf_len);
658     silc_dlist_add(list, buffer);
659   }
660
661   silc_free(buf);
662
663   return list;
664 }
665
666 /* Free partial MIME list */
667
668 void silc_mime_partial_free(SilcDList partials)
669 {
670   SilcBuffer buf;
671
672   if (!partials)
673     return;
674
675   silc_dlist_start(partials);
676   while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
677     silc_buffer_free(buf);
678   silc_dlist_uninit(partials);
679 }
680
681 /* Add field */
682
683 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
684 {
685   if (!mime || !field || !value)
686     return;
687
688   silc_hash_table_add(mime->fields, strdup(field), strdup(value));
689 }
690
691 /* Get field */
692
693 const char *silc_mime_get_field(SilcMime mime, const char *field)
694 {
695   char *value;
696
697   if (!mime || !field)
698     return NULL;
699
700   if (!silc_hash_table_find(mime->fields, (void *)field,
701                             NULL, (void **)&value))
702     return NULL;
703
704   return (const char *)value;
705 }
706
707 /* Add data */
708
709 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
710                         SilcUInt32 data_len)
711 {
712   if (!mime || !data)
713     return;
714
715   if (mime->data)
716     silc_free(mime->data);
717
718   mime->data = silc_memdup(data, data_len);
719   mime->data_len = data_len;
720 }
721
722 /* Get data */
723
724 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
725 {
726   if (!mime)
727     return NULL;
728
729   if (data_len)
730     *data_len = mime->data_len;
731
732   return mime->data;
733 }
734
735 /* Steal data */
736
737 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
738 {
739   unsigned char *data;
740
741   if (!mime)
742     return NULL;
743
744   if (data_len)
745     *data_len = mime->data_len;
746
747   data = mime->data;
748
749   mime->data = NULL;
750   mime->data_len = 0;
751
752   return data;
753 }
754
755 /* Returns TRUE if partial message */
756
757 SilcBool silc_mime_is_partial(SilcMime mime)
758 {
759   const char *type = silc_mime_get_field(mime, "Content-Type");
760   if (!type)
761     return FALSE;
762
763   if (!strstr(type, "message/partial"))
764     return FALSE;
765
766   return TRUE;
767 }
768
769 /* Set as multipart message */
770
771 void silc_mime_set_multipart(SilcMime mime, const char *type,
772                              const char *boundary)
773 {
774   char tmp[1024];
775
776   if (!mime || !type || !boundary)
777     return;
778
779   memset(tmp, 0, sizeof(tmp));
780   silc_silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
781   silc_mime_add_field(mime, "Content-Type", tmp);
782   silc_free(mime->boundary);
783   mime->boundary = strdup(boundary);
784
785   if (mime->multiparts)
786     return;
787   mime->multiparts = silc_dlist_init();
788 }
789
790 /* Add multipart */
791
792 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
793 {
794   if (!mime || !mime->multiparts || !part)
795     return FALSE;
796
797   silc_dlist_add(mime->multiparts, part);
798   return TRUE;
799 }
800
801 /* Return TRUE if has multiparts */
802
803 SilcBool silc_mime_is_multipart(SilcMime mime)
804 {
805   if (!mime)
806     return FALSE;
807
808   return mime->multiparts != NULL;
809 }
810
811 /* Returns multiparts */
812
813 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
814 {
815   if (!mime)
816     return NULL;
817
818   if (type)
819     *type = (const char *)mime->multitype;
820
821   return mime->multiparts;
822 }