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