c3851ea26f7ec99b3d9de714a3779a3a33975a0d
[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 /************************** Types and definitions ***************************/
23
24 /* MIME fragment ID context */
25 typedef struct {
26   char *id;
27   SilcInt64 starttime;
28 } SilcMimeFragmentIdStruct, *SilcMimeFragmentId;
29
30 /************************ Static utility functions **************************/
31
32 /* MIME fields destructor */
33
34 static void silc_mime_field_dest(void *key, void *context, void *user_context)
35 {
36   silc_free(key);
37   silc_free(context);
38 }
39
40 /* Assembler fragment destructor */
41
42 static void silc_mime_assembler_dest(void *key, void *context,
43                                      void *user_context)
44 {
45   SilcMimeFragmentId id = key;
46
47   silc_free(id->id);
48   silc_free(id);
49
50   /* Free all fragments */
51   silc_hash_table_free(context);
52 }
53
54 /* Assembler partial MIME fragmentn destructor */
55
56 static void silc_mime_assemble_dest(void *key, void *context,
57                                     void *user_context)
58 {
59   silc_mime_free(context);
60 }
61
62 /* MIME fragment ID hashing */
63
64 static SilcUInt32 silc_mime_hash_id(void *key, void *user_context)
65 {
66   SilcMimeFragmentId id = key;
67   return silc_hash_string(id->id, user_context);
68 }
69
70 /* MIME fragment ID comparing */
71
72 static SilcBool silc_mime_id_compare(void *key1, void *key2,
73                                      void *user_context)
74 {
75   SilcMimeFragmentId id1 = key1, id2 = key2;
76   return silc_hash_string_compare(id1->id, id2->id, user_context);
77 }
78
79
80 /******************************* Public API *********************************/
81
82 /* Allocate MIME context */
83
84 SilcMime silc_mime_alloc(void)
85 {
86   SilcMime mime;
87
88   mime = silc_calloc(1, sizeof(*mime));
89   if (!mime)
90     return NULL;
91
92   mime->fields = silc_hash_table_alloc(NULL, 0, silc_hash_string, mime,
93                                        silc_hash_string_compare, mime,
94                                        silc_mime_field_dest, mime, TRUE);
95   if (!mime->fields) {
96     silc_mime_free(mime);
97     return NULL;
98   }
99
100   return mime;
101 }
102
103 /* Free MIME context */
104
105 void silc_mime_free(SilcMime mime)
106 {
107   SilcMime m;
108
109   if (mime->fields)
110     silc_hash_table_free(mime->fields);
111
112   if (mime->multiparts) {
113     silc_dlist_start(mime->multiparts);
114     while ((m = silc_dlist_get(mime->multiparts)) != SILC_LIST_END)
115       silc_mime_free(m);
116     silc_dlist_uninit(mime->multiparts);
117   }
118   silc_free(mime->boundary);
119   silc_free(mime->multitype);
120   silc_free(mime->data);
121   silc_free(mime);
122 }
123
124 /* Allocate MIME assembler */
125
126 SilcMimeAssembler silc_mime_assembler_alloc(void)
127 {
128   SilcMimeAssembler assembler;
129
130   assembler = silc_calloc(1, sizeof(*assembler));
131   if (!assembler)
132     return NULL;
133
134   assembler->fragments =
135     silc_hash_table_alloc(NULL, 0, silc_mime_hash_id, NULL,
136                           silc_mime_id_compare, NULL,
137                           silc_mime_assembler_dest, assembler, TRUE);
138   if (!assembler->fragments) {
139     silc_mime_assembler_free(assembler);
140     return NULL;
141   }
142
143   return assembler;
144 }
145
146 /* Free MIME assembler */
147
148 void silc_mime_assembler_free(SilcMimeAssembler assembler)
149 {
150   silc_hash_table_free(assembler->fragments);
151   silc_free(assembler);
152 }
153
154 /* Purge assembler from old unfinished fragments */
155
156 void silc_mime_assembler_purge(SilcMimeAssembler assembler,
157                                SilcUInt32 purge_minutes)
158 {
159   SilcMimeFragmentId id;
160   SilcHashTableList htl;
161   SilcInt64 curtime = silc_time();
162   SilcUInt32 timeout = purge_minutes ? purge_minutes * 60 : 5 * 60;
163
164   SILC_LOG_DEBUG(("Purge MIME assembler"));
165
166   silc_hash_table_list(assembler->fragments, &htl);
167   while (silc_hash_table_get(&htl, (void *)&id, NULL)) {
168     if (curtime - id->starttime <= timeout)
169       continue;
170
171     SILC_LOG_DEBUG(("Purge partial MIME id %s", id->id));
172
173     /* Purge */
174     silc_hash_table_del(assembler->fragments, id);
175   }
176   silc_hash_table_list_reset(&htl);
177 }
178
179 /* Decode MIME message */
180
181 SilcMime silc_mime_decode(SilcMime mime, const unsigned char *data,
182                           SilcUInt32 data_len)
183 {
184   SilcMime m = NULL;
185   int i, k;
186   char *tmp, *field, *value, *line;
187
188   SILC_LOG_DEBUG(("Parsing MIME message"));
189
190   if (!data)
191     return NULL;
192
193   if (!mime) {
194     mime = silc_mime_alloc();
195     if (!mime)
196       return NULL;
197     m = mime;
198   }
199
200   /* Parse the fields */
201   line = tmp = (char *)data;
202   for (i = 0; i < data_len; i++) {
203     /* Get field line */
204     if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
205       /* Get field */
206       field = strchr(line, ':');
207       if (!field)
208         goto err;
209       field = silc_memdup(line, field - line);
210       if (!field)
211         goto err;
212
213       /* Get value. Remove whitespaces too. */
214       value = strchr(line, ':');
215       if ((tmp + i) - value < 2)
216         goto err;
217       value++;
218       for (k = 0; k < (tmp + i) - value; k++) {
219         if (value[k] == '\r')
220           goto err;
221         if (value[k] != ' ' && value[k] != '\t')
222           break;
223       }
224       value += k;
225       if ((tmp + i) - value < 1)
226         goto err;
227       value = silc_memdup(value, (tmp + i) - value);
228       if (!value)
229         goto err;
230
231       SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
232
233       /* Add field and value */
234       silc_mime_add_field(mime, field, value);
235       silc_free(field);
236       silc_free(value);
237
238       /* Mark start of next line */
239       line = (tmp + i) + 2;
240       i += 2;
241
242       /* Break if this is last header */
243       if (data_len - i >= 2 &&
244           tmp[i] == '\r' && tmp[i + 1] == '\n') {
245         i += 2;
246         break;
247       }
248     }
249   }
250
251   /* Parse multiparts if present */
252   field = (char *)silc_mime_get_field(mime, "Content-Type");
253   if (field && strstr(field, "multipart")) {
254     char b[1024];
255     SilcMime p;
256     unsigned int len;
257
258     mime->multiparts = silc_dlist_init();
259     if (!mime->multiparts)
260       goto err;
261
262     /* Get multipart type */
263     value = strchr(field, '/');
264     if (!value)
265       goto err;
266     value++;
267     if (strchr(field, '"'))
268       value++;
269     if (!strchr(field, ';'))
270       goto err;
271     memset(b, 0, sizeof(b));
272     len = (unsigned int)(strchr(field, ';') - value);
273     if (len > sizeof(b) - 1)
274       goto err;
275     strncpy(b, value, len);
276     if (strchr(b, '"'))
277       *strchr(b, '"') = '\0';
278     mime->multitype = silc_memdup(b, strlen(b));
279
280     /* Get boundary */
281     value = strrchr(field, '=');
282     if (value && strlen(value) > 1) {
283       value++;
284
285       SILC_LOG_DEBUG(("Boundary '%s'", value));
286
287       memset(b, 0, sizeof(b));
288       line = strdup(value);
289       if (strrchr(line, '"')) {
290         *strrchr(line, '"') = '\0';
291         silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
292         mime->boundary = strdup(line + 1);
293       } else {
294         silc_snprintf(b, sizeof(b) - 1, "--%s", line);
295         mime->boundary = strdup(line);
296       }
297       silc_free(line);
298
299       for (i = i; i < data_len; i++) {
300         /* Get boundary data */
301         if (data_len - i >= strlen(b) &&
302             tmp[i] == '-' && tmp[i + 1] == '-') {
303           if (memcmp(tmp + i, b, strlen(b)))
304             continue;
305
306           i += strlen(b);
307
308           if (data_len - i >= 4 &&
309               tmp[i    ] == '\r' && tmp[i + 1] == '\n' &&
310               tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
311             i += 4;
312           else if (data_len - i >= 2 &&
313                    tmp[i] == '\r' && tmp[i + 1] == '\n')
314             i += 2;
315           else if (data_len - i >= 2 &&
316                    tmp[i] == '-' && tmp[i + 1] == '-')
317             break;
318
319           line = tmp + i;
320
321           /* Find end of boundary */
322           for (k = i; k < data_len; k++)
323             if (data_len - k >= strlen(b) &&
324                 tmp[k] == '-' && tmp[k + 1] == '-')
325               if (!memcmp(tmp + k, b, strlen(b)))
326                 break;
327           if (k >= data_len)
328             goto err;
329
330           /* Remove preceding CRLF */
331           k -= 2;
332
333           /* Parse the part */
334           p = silc_mime_decode(NULL, line, k - i);
335           if (!p)
336             goto err;
337
338           silc_dlist_add(mime->multiparts, p);
339           i += (k - i);
340         }
341       }
342     }
343   } else {
344     /* Get data area.  If we are at the end and we have fields present
345        there is no data area present, but, if fields are not present we
346        only have data area. */
347     if (i >= data_len && !silc_hash_table_count(mime->fields))
348       i = 0;
349     SILC_LOG_DEBUG(("Data len %d", data_len - i));
350     if (data_len - i)
351       silc_mime_add_data(mime, tmp + i, data_len - i);
352   }
353
354   return mime;
355
356  err:
357   if (m)
358     silc_mime_free(m);
359   return NULL;
360 }
361
362 /* Encode MIME message */
363
364 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
365 {
366   SilcMime part;
367   SilcHashTableList htl;
368   SilcBufferStruct buf;
369   SilcBuffer buffer;
370   char *field, *value, tmp[1024], tmp2[4];
371   unsigned char *ret;
372   int i;
373
374   SILC_LOG_DEBUG(("Encoding MIME message"));
375
376   if (!mime)
377     return NULL;
378
379   memset(&buf, 0, sizeof(buf));
380
381   /* Encode the headers. Order doesn't matter */
382   i = 0;
383   silc_hash_table_list(mime->fields, &htl);
384   while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
385     memset(tmp, 0, sizeof(tmp));
386     SILC_LOG_DEBUG(("Header %s: %s", field, value));
387     silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
388     silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
389     i++;
390   }
391   silc_hash_table_list_reset(&htl);
392   if (i)
393     silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
394
395   /* Assemble the whole buffer */
396   buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
397   if (!buffer)
398     return NULL;
399
400   /* Add headers */
401   if (silc_buffer_len(&buf)) {
402     silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
403     silc_buffer_pull(buffer, silc_buffer_len(&buf));
404     silc_buffer_purge(&buf);
405   }
406
407   /* Add data */
408   if (mime->data) {
409     SILC_LOG_DEBUG(("Data len %d", mime->data_len));
410     silc_buffer_put(buffer, mime->data, mime->data_len);
411   }
412
413   /* Add multiparts */
414   if (mime->multiparts) {
415     SILC_LOG_DEBUG(("Encoding multiparts"));
416
417     silc_dlist_start(mime->multiparts);
418     i = 0;
419     while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
420       unsigned char *pd;
421       SilcUInt32 pd_len;
422
423       /* Recursive encoding */
424       pd = silc_mime_encode(part, &pd_len);
425       if (!pd)
426         return NULL;
427
428       memset(tmp, 0, sizeof(tmp));
429       memset(tmp2, 0, sizeof(tmp2));
430
431       /* If fields are not present, add extra CRLF */
432       if (!silc_hash_table_count(part->fields))
433         silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
434       silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
435                i != 0 ? "\r\n" : "", mime->boundary, tmp2);
436       i = 1;
437
438       buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
439                                    pd_len + strlen(tmp));
440       if (!buffer)
441         return NULL;
442       silc_buffer_put_tail(buffer, tmp, strlen(tmp));
443       silc_buffer_pull_tail(buffer, strlen(tmp));
444       silc_buffer_put_tail(buffer, pd, pd_len);
445       silc_buffer_pull_tail(buffer, pd_len);
446       silc_free(pd);
447     }
448
449     memset(tmp, 0, sizeof(tmp));
450     silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
451     buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
452                                  strlen(tmp));
453     if (!buffer)
454       return NULL;
455     silc_buffer_put_tail(buffer, tmp, strlen(tmp));
456     silc_buffer_pull_tail(buffer, strlen(tmp));
457   }
458
459   ret = silc_buffer_steal(buffer, encoded_len);
460   silc_buffer_free(buffer);
461
462   return ret;
463 }
464
465 /* Assembles MIME message from partial MIME messages */
466
467 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
468 {
469   char *type, *id = NULL, *tmp;
470   SilcMimeFragmentIdStruct *fragid, query;
471   SilcHashTable f;
472   SilcMime p, complete;
473   int i, number, total = -1;
474   const unsigned char *data;
475   SilcUInt32 data_len;
476   SilcBuffer compbuf = NULL;
477
478   SILC_LOG_DEBUG(("Assembling MIME fragments"));
479
480   if (!assembler || !partial)
481     goto err;
482
483   type = (char *)silc_mime_get_field(partial, "Content-Type");
484   if (!type)
485     goto err;
486
487   /* Get ID */
488   tmp = strstr(type, "id=");
489   if (!tmp)
490     goto err;
491   if (strlen(tmp) <= 4)
492     goto err;
493   tmp += 3;
494   if (*tmp == '"')
495     tmp++;
496   id = strdup(tmp);
497   if (strchr(id, ';'))
498     *strchr(id, ';') = '\0';
499   if (strrchr(id, '"'))
500     *strrchr(id, '"') = '\0';
501
502   SILC_LOG_DEBUG(("Fragment ID %s", id));
503
504   /* Get fragment number */
505   tmp = strstr(type, "number=");
506   if (!tmp)
507     goto err;
508   tmp = strchr(tmp, '=');
509   if (strlen(tmp) < 2)
510     goto err;
511   tmp++;
512   if (strchr(tmp, ';')) {
513     tmp = strdup(tmp);
514     *strchr(tmp, ';') = '\0';
515     number = atoi(tmp);
516     silc_free(tmp);
517   } else {
518     number = atoi(tmp);
519   }
520
521   SILC_LOG_DEBUG(("Fragment number %d", number));
522
523   /* Find fragments with this ID. */
524   query.id = id;
525   if (!silc_hash_table_find(assembler->fragments, (void *)&query,
526                             NULL, (void *)&f)) {
527     /* This is new fragment to new message.  Add to hash table and return. */
528     f = silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL,
529                               silc_mime_assemble_dest, NULL, TRUE);
530     if (!f)
531       goto err;
532
533     fragid = silc_calloc(1, sizeof(*fragid));
534     if (!fragid)
535       goto err;
536     fragid->id = id;
537     fragid->starttime = silc_time();
538
539     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
540     silc_hash_table_add(assembler->fragments, fragid, f);
541     return NULL;
542   }
543
544   /* Try to get total number */
545   tmp = strstr(type, "total=");
546   if (tmp) {
547     tmp = strchr(tmp, '=');
548     if (strlen(tmp) < 2)
549       goto err;
550     tmp++;
551     if (strchr(tmp, ';')) {
552       tmp = strdup(tmp);
553       *strchr(tmp, ';') = '\0';
554       total = atoi(tmp);
555       silc_free(tmp);
556     } else {
557       total = atoi(tmp);
558     }
559
560     SILC_LOG_DEBUG(("Fragment total %d", total));
561   }
562
563   /* If more fragments to come, add to hash table */
564   if (number != total) {
565     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
566     silc_free(id);
567     return NULL;
568   }
569
570   silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
571
572   /* Verify that we really have all the fragments */
573   if (silc_hash_table_count(f) < total) {
574     silc_free(id);
575     return NULL;
576   }
577
578   /* Assemble the complete MIME message now. We get them in order from
579      the hash table. */
580   for (i = 1; i <= total; i++) {
581     if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
582       goto err;
583
584     /* The fragment is in the data portion of the partial message */
585     data = silc_mime_get_data(p, &data_len);
586     if (!data)
587       goto err;
588
589     /* Assemble */
590     if (!compbuf) {
591       compbuf = silc_buffer_alloc_size(data_len);
592       if (!compbuf)
593         goto err;
594       silc_buffer_put(compbuf, data, data_len);
595     } else {
596       compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
597                                     data_len);
598       if (!compbuf)
599         goto err;
600       silc_buffer_put_tail(compbuf, data, data_len);
601       silc_buffer_pull_tail(compbuf, data_len);
602     }
603   }
604
605   /* Now parse the complete MIME message and deliver it */
606   complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
607                               silc_buffer_truelen(compbuf));
608   if (!complete)
609     goto err;
610
611   /* Delete the hash table entry. Destructors will free memory */
612   silc_hash_table_del(assembler->fragments, (void *)&query);
613   silc_free(id);
614   silc_buffer_free(compbuf);
615
616   return complete;
617
618  err:
619   silc_free(id);
620   if (compbuf)
621     silc_buffer_free(compbuf);
622   silc_mime_free(partial);
623   return NULL;
624 }
625
626 /* Encodes partial MIME messages */
627
628 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
629 {
630   unsigned char *buf, *tmp;
631   SilcUInt32 buf_len, len, tmp_len, off;
632   SilcDList list;
633   SilcBuffer buffer;
634   SilcMime partial;
635   char type[128], id[64];
636   int num;
637
638   SILC_LOG_DEBUG(("Fragmenting MIME message"));
639
640   /* Encode as normal */
641   buf = silc_mime_encode(mime, &buf_len);
642   if (!buf)
643     return NULL;
644
645   list = silc_dlist_init();
646
647   /* Fragment if it is too large */
648   if (buf_len > max_size) {
649     memset(id, 0, sizeof(id));
650     memset(type, 0, sizeof(type));
651     gethostname(type, sizeof(type) - 1);
652     srand((time(NULL) + buf_len) ^ rand());
653     silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
654              (unsigned int)rand(), (unsigned int)time(NULL),
655              (unsigned int)buf_len, type);
656
657     SILC_LOG_DEBUG(("Fragment ID %s", id));
658
659     partial = silc_mime_alloc();
660     if (!partial)
661       return NULL;
662
663     silc_mime_add_field(partial, "MIME-Version", "1.0");
664     memset(type, 0, sizeof(type));
665     silc_snprintf(type, sizeof(type) - 1,
666              "message/partial; id=\"%s\"; number=1", id);
667     silc_mime_add_field(partial, "Content-Type", type);
668     silc_mime_add_data(partial, buf, max_size);
669
670     tmp = silc_mime_encode(partial, &tmp_len);
671     if (!tmp)
672       return NULL;
673     silc_mime_free(partial);
674
675     /* Add to list */
676     buffer = silc_buffer_alloc_size(tmp_len);
677     if (!buffer)
678       return NULL;
679     silc_buffer_put(buffer, tmp, tmp_len);
680     silc_dlist_add(list, buffer);
681     silc_free(tmp);
682
683     len = buf_len - max_size;
684     off = max_size;
685     num = 2;
686     while (len > 0) {
687       partial = silc_mime_alloc();
688       if (!partial)
689         return NULL;
690
691       memset(type, 0, sizeof(type));
692       silc_mime_add_field(partial, "MIME-Version", "1.0");
693
694       if (len > max_size) {
695         silc_snprintf(type, sizeof(type) - 1,
696                  "message/partial; id=\"%s\"; number=%d",
697                  id, num++);
698         silc_mime_add_data(partial, buf + off, max_size);
699         off += max_size;
700         len -= max_size;
701       } else {
702         silc_snprintf(type, sizeof(type) - 1,
703                  "message/partial; id=\"%s\"; number=%d; total=%d",
704                  id, num, num);
705         silc_mime_add_data(partial, buf + off, len);
706         len = 0;
707       }
708
709       silc_mime_add_field(partial, "Content-Type", type);
710
711       tmp = silc_mime_encode(partial, &tmp_len);
712       if (!tmp)
713         return NULL;
714       silc_mime_free(partial);
715
716       /* Add to list */
717       buffer = silc_buffer_alloc_size(tmp_len);
718       if (!buffer)
719         return NULL;
720       silc_buffer_put(buffer, tmp, tmp_len);
721       silc_dlist_add(list, buffer);
722       silc_free(tmp);
723     }
724   } else {
725     /* No need to fragment */
726     buffer = silc_buffer_alloc_size(buf_len);
727     if (!buffer)
728       return NULL;
729     silc_buffer_put(buffer, buf, buf_len);
730     silc_dlist_add(list, buffer);
731   }
732
733   silc_free(buf);
734
735   return list;
736 }
737
738 /* Free partial MIME list */
739
740 void silc_mime_partial_free(SilcDList partials)
741 {
742   SilcBuffer buf;
743
744   if (!partials)
745     return;
746
747   silc_dlist_start(partials);
748   while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
749     silc_buffer_free(buf);
750   silc_dlist_uninit(partials);
751 }
752
753 /* Add field */
754
755 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
756 {
757   if (!mime || !field || !value)
758     return;
759
760   silc_hash_table_add(mime->fields, strdup(field), strdup(value));
761 }
762
763 /* Get field */
764
765 const char *silc_mime_get_field(SilcMime mime, const char *field)
766 {
767   char *value;
768
769   if (!mime || !field)
770     return NULL;
771
772   if (!silc_hash_table_find(mime->fields, (void *)field,
773                             NULL, (void *)&value))
774     return NULL;
775
776   return (const char *)value;
777 }
778
779 /* Add data */
780
781 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
782                         SilcUInt32 data_len)
783 {
784   if (!mime || !data)
785     return;
786
787   if (mime->data)
788     silc_free(mime->data);
789
790   mime->data = silc_memdup(data, data_len);
791   mime->data_len = data_len;
792 }
793
794 /* Get data */
795
796 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
797 {
798   if (!mime)
799     return NULL;
800
801   if (data_len)
802     *data_len = mime->data_len;
803
804   return mime->data;
805 }
806
807 /* Steal data */
808
809 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
810 {
811   unsigned char *data;
812
813   if (!mime)
814     return NULL;
815
816   if (data_len)
817     *data_len = mime->data_len;
818
819   data = mime->data;
820
821   mime->data = NULL;
822   mime->data_len = 0;
823
824   return data;
825 }
826
827 /* Returns TRUE if partial message */
828
829 SilcBool silc_mime_is_partial(SilcMime mime)
830 {
831   const char *type = silc_mime_get_field(mime, "Content-Type");
832   if (!type)
833     return FALSE;
834
835   if (!strstr(type, "message/partial"))
836     return FALSE;
837
838   return TRUE;
839 }
840
841 /* Set as multipart message */
842
843 void silc_mime_set_multipart(SilcMime mime, const char *type,
844                              const char *boundary)
845 {
846   char tmp[1024];
847
848   if (!mime || !type || !boundary)
849     return;
850
851   memset(tmp, 0, sizeof(tmp));
852   silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
853   silc_mime_add_field(mime, "Content-Type", tmp);
854   silc_free(mime->boundary);
855   mime->boundary = strdup(boundary);
856
857   if (mime->multiparts)
858     return;
859   mime->multiparts = silc_dlist_init();
860 }
861
862 /* Add multipart */
863
864 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
865 {
866   if (!mime || !mime->multiparts || !part)
867     return FALSE;
868
869   silc_dlist_add(mime->multiparts, part);
870   return TRUE;
871 }
872
873 /* Return TRUE if has multiparts */
874
875 SilcBool silc_mime_is_multipart(SilcMime mime)
876 {
877   if (!mime)
878     return FALSE;
879
880   return mime->multiparts != NULL;
881 }
882
883 /* Returns multiparts */
884
885 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
886 {
887   if (!mime)
888     return NULL;
889
890   if (type)
891     *type = (const char *)mime->multitype;
892
893   return mime->multiparts;
894 }