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