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