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