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