f719e77961694509b9868e4dd8c4b966576315bd
[crypto.git] / lib / silcutil / silcmime.c
1 /*
2
3   silcmime.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2007 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_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
233         mime->boundary = strdup(line + 1);
234       } else {
235         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_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     silc_buffer_purge(&buf);
346   }
347
348   /* Add data */
349   if (mime->data) {
350     SILC_LOG_DEBUG(("Data len %d", mime->data_len));
351     silc_buffer_put(buffer, mime->data, mime->data_len);
352   }
353
354   /* Add multiparts */
355   if (mime->multiparts) {
356     SILC_LOG_DEBUG(("Encoding multiparts"));
357
358     silc_dlist_start(mime->multiparts);
359     i = 0;
360     while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
361       unsigned char *pd;
362       SilcUInt32 pd_len;
363
364       /* Recursive encoding */
365       pd = silc_mime_encode(part, &pd_len);
366       if (!pd)
367         return NULL;
368
369       memset(tmp, 0, sizeof(tmp));
370       memset(tmp2, 0, sizeof(tmp2));
371
372       /* If fields are not present, add extra CRLF */
373       if (!silc_hash_table_count(part->fields))
374         silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
375       silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
376                i != 0 ? "\r\n" : "", mime->boundary, tmp2);
377       i = 1;
378
379       buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
380                                    pd_len + strlen(tmp));
381       if (!buffer)
382         return NULL;
383       silc_buffer_put_tail(buffer, tmp, strlen(tmp));
384       silc_buffer_pull_tail(buffer, strlen(tmp));
385       silc_buffer_put_tail(buffer, pd, pd_len);
386       silc_buffer_pull_tail(buffer, pd_len);
387       silc_free(pd);
388     }
389
390     memset(tmp, 0, sizeof(tmp));
391     silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
392     buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
393                                  strlen(tmp));
394     if (!buffer)
395       return NULL;
396     silc_buffer_put_tail(buffer, tmp, strlen(tmp));
397     silc_buffer_pull_tail(buffer, strlen(tmp));
398   }
399
400   ret = silc_buffer_steal(buffer, encoded_len);
401   silc_buffer_free(buffer);
402
403   return ret;
404 }
405
406 /* Assembles MIME message from partial MIME messages */
407
408 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
409 {
410   char *type, *id = NULL, *tmp;
411   SilcHashTable f;
412   SilcMime p, complete;
413   int i, number, total = -1;
414   const unsigned char *data;
415   SilcUInt32 data_len;
416   SilcBuffer compbuf = NULL;
417
418   SILC_LOG_DEBUG(("Assembling MIME fragments"));
419
420   if (!assembler || !partial)
421     goto err;
422
423   type = (char *)silc_mime_get_field(partial, "Content-Type");
424   if (!type)
425     goto err;
426
427   /* Get ID */
428   tmp = strstr(type, "id=");
429   if (!tmp)
430     goto err;
431   if (strlen(tmp) <= 4)
432     goto err;
433   tmp += 3;
434   if (*tmp == '"')
435     tmp++;
436   id = strdup(tmp);
437   if (strchr(id, ';'))
438     *strchr(id, ';') = '\0';
439   if (strrchr(id, '"'))
440     *strrchr(id, '"') = '\0';
441
442   SILC_LOG_DEBUG(("Fragment ID %s", id));
443
444   /* Get fragment number */
445   tmp = strstr(type, "number=");
446   if (!tmp)
447     goto err;
448   tmp = strchr(tmp, '=');
449   if (strlen(tmp) < 2)
450     goto err;
451   tmp++;
452   if (strchr(tmp, ';')) {
453     tmp = strdup(tmp);
454     *strchr(tmp, ';') = '\0';
455     number = atoi(tmp);
456     silc_free(tmp);
457   } else {
458     number = atoi(tmp);
459   }
460
461   SILC_LOG_DEBUG(("Fragment number %d", number));
462
463   /* Find fragments with this ID. */
464   if (!silc_hash_table_find(assembler->fragments, (void *)id,
465                             NULL, (void *)&f)) {
466     /* This is new fragment to new message.  Add to hash table and return. */
467     f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
468                               silc_mime_assemble_dest, NULL, TRUE);
469     if (!f)
470          goto err;
471     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
472     silc_hash_table_add(assembler->fragments, id, f);
473     return NULL;
474   }
475
476   /* Try to get total number */
477   tmp = strstr(type, "total=");
478   if (tmp) {
479     tmp = strchr(tmp, '=');
480     if (strlen(tmp) < 2)
481       goto err;
482     tmp++;
483     if (strchr(tmp, ';')) {
484       tmp = strdup(tmp);
485       *strchr(tmp, ';') = '\0';
486       total = atoi(tmp);
487       silc_free(tmp);
488     } else {
489       total = atoi(tmp);
490     }
491
492     SILC_LOG_DEBUG(("Fragment total %d", total));
493   }
494
495   /* If more fragments to come, add to hash table */
496   if (number != total) {
497     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
498     return NULL;
499   }
500
501   silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
502
503   /* Verify that we really have all the fragments */
504   if (silc_hash_table_count(f) < total)
505     return NULL;
506
507   /* Assemble the complete MIME message now. We get them in order from
508      the hash table. */
509   for (i = 1; i <= total; i++) {
510     if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
511       goto err;
512
513     /* The fragment is in the data portion of the partial message */
514     data = silc_mime_get_data(p, &data_len);
515     if (!data)
516       goto err;
517
518     /* Assemble */
519     if (!compbuf) {
520       compbuf = silc_buffer_alloc_size(data_len);
521       if (!compbuf)
522         goto err;
523       silc_buffer_put(compbuf, data, data_len);
524     } else {
525       compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
526                                     data_len);
527       if (!compbuf)
528         goto err;
529       silc_buffer_put_tail(compbuf, data, data_len);
530       silc_buffer_pull_tail(compbuf, data_len);
531     }
532   }
533
534   /* Now parse the complete MIME message and deliver it */
535   complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
536                               silc_buffer_truelen(compbuf));
537   if (!complete)
538     goto err;
539
540   /* Delete the hash table entry. Destructors will free memory */
541   silc_hash_table_del(assembler->fragments, (void *)id);
542   silc_free(id);
543   silc_buffer_free(compbuf);
544
545   return complete;
546
547  err:
548   silc_free(id);
549   if (compbuf)
550     silc_buffer_free(compbuf);
551   silc_mime_free(partial);
552   return NULL;
553 }
554
555 /* Encodes partial MIME messages */
556
557 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
558 {
559   unsigned char *buf, *tmp;
560   SilcUInt32 buf_len, len, tmp_len, off;
561   SilcDList list;
562   SilcBuffer buffer;
563   SilcMime partial;
564   char type[128], id[64];
565   int num;
566
567   SILC_LOG_DEBUG(("Fragmenting MIME message"));
568
569   /* Encode as normal */
570   buf = silc_mime_encode(mime, &buf_len);
571   if (!buf)
572     return NULL;
573
574   list = silc_dlist_init();
575
576   /* Fragment if it is too large */
577   if (buf_len > max_size) {
578     memset(id, 0, sizeof(id));
579     memset(type, 0, sizeof(type));
580     gethostname(type, sizeof(type) - 1);
581     srand((time(NULL) + buf_len) ^ rand());
582     silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
583              (unsigned int)rand(), (unsigned int)time(NULL),
584              (unsigned int)buf_len, type);
585
586     SILC_LOG_DEBUG(("Fragment ID %s", id));
587
588     partial = silc_mime_alloc();
589     if (!partial)
590       return NULL;
591
592     silc_mime_add_field(partial, "MIME-Version", "1.0");
593     memset(type, 0, sizeof(type));
594     silc_snprintf(type, sizeof(type) - 1,
595              "message/partial; id=\"%s\"; number=1", id);
596     silc_mime_add_field(partial, "Content-Type", type);
597     silc_mime_add_data(partial, buf, max_size);
598
599     tmp = silc_mime_encode(partial, &tmp_len);
600     if (!tmp)
601       return NULL;
602     silc_mime_free(partial);
603
604     /* Add to list */
605     buffer = silc_buffer_alloc_size(tmp_len);
606     if (!buffer)
607       return NULL;
608     silc_buffer_put(buffer, tmp, tmp_len);
609     silc_dlist_add(list, buffer);
610     silc_free(tmp);
611
612     len = buf_len - max_size;
613     off = max_size;
614     num = 2;
615     while (len > 0) {
616       partial = silc_mime_alloc();
617       if (!partial)
618         return NULL;
619
620       memset(type, 0, sizeof(type));
621       silc_mime_add_field(partial, "MIME-Version", "1.0");
622
623       if (len > max_size) {
624         silc_snprintf(type, sizeof(type) - 1,
625                  "message/partial; id=\"%s\"; number=%d",
626                  id, num++);
627         silc_mime_add_data(partial, buf + off, max_size);
628         off += max_size;
629         len -= max_size;
630       } else {
631         silc_snprintf(type, sizeof(type) - 1,
632                  "message/partial; id=\"%s\"; number=%d; total=%d",
633                  id, num, num);
634         silc_mime_add_data(partial, buf + off, len);
635         len = 0;
636       }
637
638       silc_mime_add_field(partial, "Content-Type", type);
639
640       tmp = silc_mime_encode(partial, &tmp_len);
641       if (!tmp)
642         return NULL;
643       silc_mime_free(partial);
644
645       /* Add to list */
646       buffer = silc_buffer_alloc_size(tmp_len);
647       if (!buffer)
648         return NULL;
649       silc_buffer_put(buffer, tmp, tmp_len);
650       silc_dlist_add(list, buffer);
651       silc_free(tmp);
652     }
653   } else {
654     /* No need to fragment */
655     buffer = silc_buffer_alloc_size(buf_len);
656     if (!buffer)
657       return NULL;
658     silc_buffer_put(buffer, buf, buf_len);
659     silc_dlist_add(list, buffer);
660   }
661
662   silc_free(buf);
663
664   return list;
665 }
666
667 /* Free partial MIME list */
668
669 void silc_mime_partial_free(SilcDList partials)
670 {
671   SilcBuffer buf;
672
673   if (!partials)
674     return;
675
676   silc_dlist_start(partials);
677   while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
678     silc_buffer_free(buf);
679   silc_dlist_uninit(partials);
680 }
681
682 /* Add field */
683
684 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
685 {
686   if (!mime || !field || !value)
687     return;
688
689   silc_hash_table_add(mime->fields, strdup(field), strdup(value));
690 }
691
692 /* Get field */
693
694 const char *silc_mime_get_field(SilcMime mime, const char *field)
695 {
696   char *value;
697
698   if (!mime || !field)
699     return NULL;
700
701   if (!silc_hash_table_find(mime->fields, (void *)field,
702                             NULL, (void *)&value))
703     return NULL;
704
705   return (const char *)value;
706 }
707
708 /* Add data */
709
710 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
711                         SilcUInt32 data_len)
712 {
713   if (!mime || !data)
714     return;
715
716   if (mime->data)
717     silc_free(mime->data);
718
719   mime->data = silc_memdup(data, data_len);
720   mime->data_len = data_len;
721 }
722
723 /* Get data */
724
725 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
726 {
727   if (!mime)
728     return NULL;
729
730   if (data_len)
731     *data_len = mime->data_len;
732
733   return mime->data;
734 }
735
736 /* Steal data */
737
738 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
739 {
740   unsigned char *data;
741
742   if (!mime)
743     return NULL;
744
745   if (data_len)
746     *data_len = mime->data_len;
747
748   data = mime->data;
749
750   mime->data = NULL;
751   mime->data_len = 0;
752
753   return data;
754 }
755
756 /* Returns TRUE if partial message */
757
758 SilcBool silc_mime_is_partial(SilcMime mime)
759 {
760   const char *type = silc_mime_get_field(mime, "Content-Type");
761   if (!type)
762     return FALSE;
763
764   if (!strstr(type, "message/partial"))
765     return FALSE;
766
767   return TRUE;
768 }
769
770 /* Set as multipart message */
771
772 void silc_mime_set_multipart(SilcMime mime, const char *type,
773                              const char *boundary)
774 {
775   char tmp[1024];
776
777   if (!mime || !type || !boundary)
778     return;
779
780   memset(tmp, 0, sizeof(tmp));
781   silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
782   silc_mime_add_field(mime, "Content-Type", tmp);
783   silc_free(mime->boundary);
784   mime->boundary = strdup(boundary);
785
786   if (mime->multiparts)
787     return;
788   mime->multiparts = silc_dlist_init();
789 }
790
791 /* Add multipart */
792
793 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
794 {
795   if (!mime || !mime->multiparts || !part)
796     return FALSE;
797
798   silc_dlist_add(mime->multiparts, part);
799   return TRUE;
800 }
801
802 /* Return TRUE if has multiparts */
803
804 SilcBool silc_mime_is_multipart(SilcMime mime)
805 {
806   if (!mime)
807     return FALSE;
808
809   return mime->multiparts != NULL;
810 }
811
812 /* Returns multiparts */
813
814 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
815 {
816   if (!mime)
817     return NULL;
818
819   if (type)
820     *type = (const char *)mime->multitype;
821
822   return mime->multiparts;
823 }