Static analyzer fixes
[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 < 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   if (!compbuf)
539     goto err;
540
541   /* Now parse the complete MIME message and deliver it */
542   complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
543                               silc_buffer_truelen(compbuf));
544   if (!complete)
545     goto err;
546
547   /* Delete the hash table entry. Destructors will free memory */
548   silc_hash_table_del(assembler->fragments, (void *)id);
549   silc_free(id);
550   silc_buffer_free(compbuf);
551
552   return complete;
553
554  err:
555   silc_free(id);
556   if (compbuf)
557     silc_buffer_free(compbuf);
558   silc_mime_free(partial);
559   return NULL;
560 }
561
562 /* Encodes partial MIME messages */
563
564 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
565 {
566   unsigned char *buf, *tmp;
567   SilcUInt32 buf_len, len, tmp_len, off;
568   SilcDList list;
569   SilcBuffer buffer;
570   SilcMime partial;
571   char type[128], id[64];
572   int num;
573
574   SILC_LOG_DEBUG(("Fragmenting MIME message"));
575
576   /* Encode as normal */
577   buf = silc_mime_encode(mime, &buf_len);
578   if (!buf)
579     return NULL;
580
581   list = silc_dlist_init();
582
583   /* Fragment if it is too large */
584   if (buf_len > max_size) {
585     memset(id, 0, sizeof(id));
586     memset(type, 0, sizeof(type));
587     gethostname(type, sizeof(type) - 1);
588     srand((time(NULL) + buf_len) ^ rand());
589     silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
590              (unsigned int)rand(), (unsigned int)time(NULL),
591              (unsigned int)buf_len, type);
592
593     SILC_LOG_DEBUG(("Fragment ID %s", id));
594
595     partial = silc_mime_alloc();
596     if (!partial)
597       return NULL;
598
599     silc_mime_add_field(partial, "MIME-Version", "1.0");
600     memset(type, 0, sizeof(type));
601     silc_snprintf(type, sizeof(type) - 1,
602              "message/partial; id=\"%s\"; number=1", id);
603     silc_mime_add_field(partial, "Content-Type", type);
604     silc_mime_add_data(partial, buf, max_size);
605
606     tmp = silc_mime_encode(partial, &tmp_len);
607     if (!tmp)
608       return NULL;
609     silc_mime_free(partial);
610
611     /* Add to list */
612     buffer = silc_buffer_alloc_size(tmp_len);
613     if (!buffer)
614       return NULL;
615     silc_buffer_put(buffer, tmp, tmp_len);
616     silc_dlist_add(list, buffer);
617     silc_free(tmp);
618
619     len = buf_len - max_size;
620     off = max_size;
621     num = 2;
622     while (len > 0) {
623       partial = silc_mime_alloc();
624       if (!partial)
625         return NULL;
626
627       memset(type, 0, sizeof(type));
628       silc_mime_add_field(partial, "MIME-Version", "1.0");
629
630       if (len > max_size) {
631         silc_snprintf(type, sizeof(type) - 1,
632                  "message/partial; id=\"%s\"; number=%d",
633                  id, num++);
634         silc_mime_add_data(partial, buf + off, max_size);
635         off += max_size;
636         len -= max_size;
637       } else {
638         silc_snprintf(type, sizeof(type) - 1,
639                  "message/partial; id=\"%s\"; number=%d; total=%d",
640                  id, num, num);
641         silc_mime_add_data(partial, buf + off, len);
642         len = 0;
643       }
644
645       silc_mime_add_field(partial, "Content-Type", type);
646
647       tmp = silc_mime_encode(partial, &tmp_len);
648       if (!tmp)
649         return NULL;
650       silc_mime_free(partial);
651
652       /* Add to list */
653       buffer = silc_buffer_alloc_size(tmp_len);
654       if (!buffer)
655         return NULL;
656       silc_buffer_put(buffer, tmp, tmp_len);
657       silc_dlist_add(list, buffer);
658       silc_free(tmp);
659     }
660   } else {
661     /* No need to fragment */
662     buffer = silc_buffer_alloc_size(buf_len);
663     if (!buffer)
664       return NULL;
665     silc_buffer_put(buffer, buf, buf_len);
666     silc_dlist_add(list, buffer);
667   }
668
669   silc_free(buf);
670
671   return list;
672 }
673
674 /* Free partial MIME list */
675
676 void silc_mime_partial_free(SilcDList partials)
677 {
678   SilcBuffer buf;
679
680   if (!partials)
681     return;
682
683   silc_dlist_start(partials);
684   while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
685     silc_buffer_free(buf);
686   silc_dlist_uninit(partials);
687 }
688
689 /* Add field */
690
691 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
692 {
693   if (!mime || !field || !value)
694     return;
695
696   silc_hash_table_add(mime->fields, strdup(field), strdup(value));
697 }
698
699 /* Get field */
700
701 const char *silc_mime_get_field(SilcMime mime, const char *field)
702 {
703   char *value;
704
705   if (!mime || !field)
706     return NULL;
707
708   if (!silc_hash_table_find(mime->fields, (void *)field,
709                             NULL, (void *)&value))
710     return NULL;
711
712   return (const char *)value;
713 }
714
715 /* Add data */
716
717 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
718                         SilcUInt32 data_len)
719 {
720   if (!mime || !data)
721     return;
722
723   if (mime->data)
724     silc_free(mime->data);
725
726   mime->data = silc_memdup(data, data_len);
727   mime->data_len = data_len;
728 }
729
730 /* Get data */
731
732 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
733 {
734   if (!mime)
735     return NULL;
736
737   if (data_len)
738     *data_len = mime->data_len;
739
740   return mime->data;
741 }
742
743 /* Steal data */
744
745 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
746 {
747   unsigned char *data;
748
749   if (!mime)
750     return NULL;
751
752   if (data_len)
753     *data_len = mime->data_len;
754
755   data = mime->data;
756
757   mime->data = NULL;
758   mime->data_len = 0;
759
760   return data;
761 }
762
763 /* Returns TRUE if partial message */
764
765 SilcBool silc_mime_is_partial(SilcMime mime)
766 {
767   const char *type = silc_mime_get_field(mime, "Content-Type");
768   if (!type)
769     return FALSE;
770
771   if (!strstr(type, "message/partial"))
772     return FALSE;
773
774   return TRUE;
775 }
776
777 /* Set as multipart message */
778
779 void silc_mime_set_multipart(SilcMime mime, const char *type,
780                              const char *boundary)
781 {
782   char tmp[1024];
783
784   if (!mime || !type || !boundary)
785     return;
786
787   memset(tmp, 0, sizeof(tmp));
788   silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
789   silc_mime_add_field(mime, "Content-Type", tmp);
790   silc_free(mime->boundary);
791   mime->boundary = strdup(boundary);
792
793   if (mime->multiparts)
794     return;
795   mime->multiparts = silc_dlist_init();
796 }
797
798 /* Add multipart */
799
800 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
801 {
802   if (!mime || !mime->multiparts || !part)
803     return FALSE;
804
805   silc_dlist_add(mime->multiparts, part);
806   return TRUE;
807 }
808
809 /* Return TRUE if has multiparts */
810
811 SilcBool silc_mime_is_multipart(SilcMime mime)
812 {
813   if (!mime)
814     return FALSE;
815
816   return mime->multiparts != NULL;
817 }
818
819 /* Returns multiparts */
820
821 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
822 {
823   if (!mime)
824     return NULL;
825
826   if (type)
827     *type = (const char *)mime->multitype;
828
829   return mime->multiparts;
830 }