Changed MIME code to use SILC Rand API to create fragment ID.
[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     silc_snprintf(id, sizeof(id) - 1, "%X%x%X%x",
678                   (unsigned int)silc_rand(), (unsigned int)silc_time_usec(),
679                   (unsigned int)buf_len, (unsigned int)silc_rand());
680
681     SILC_LOG_DEBUG(("Fragment ID %s", id));
682
683     partial = silc_mime_alloc();
684     if (!partial)
685       return NULL;
686
687     silc_mime_add_field(partial, "MIME-Version", "1.0");
688     memset(type, 0, sizeof(type));
689     silc_snprintf(type, sizeof(type) - 1,
690              "message/partial; id=\"%s\"; number=1", id);
691     silc_mime_add_field(partial, "Content-Type", type);
692     silc_mime_add_data(partial, buf, max_size);
693
694     tmp = silc_mime_encode(partial, &tmp_len);
695     if (!tmp)
696       return NULL;
697     silc_mime_free(partial);
698
699     /* Add to list */
700     buffer = silc_buffer_alloc_size(tmp_len);
701     if (!buffer)
702       return NULL;
703     silc_buffer_put(buffer, tmp, tmp_len);
704     silc_dlist_add(list, buffer);
705     silc_free(tmp);
706
707     len = buf_len - max_size;
708     off = max_size;
709     num = 2;
710     while (len > 0) {
711       partial = silc_mime_alloc();
712       if (!partial)
713         return NULL;
714
715       memset(type, 0, sizeof(type));
716       silc_mime_add_field(partial, "MIME-Version", "1.0");
717
718       if (len > max_size) {
719         silc_snprintf(type, sizeof(type) - 1,
720                  "message/partial; id=\"%s\"; number=%d",
721                  id, num++);
722         silc_mime_add_data(partial, buf + off, max_size);
723         off += max_size;
724         len -= max_size;
725       } else {
726         silc_snprintf(type, sizeof(type) - 1,
727                  "message/partial; id=\"%s\"; number=%d; total=%d",
728                  id, num, num);
729         silc_mime_add_data(partial, buf + off, len);
730         len = 0;
731       }
732
733       silc_mime_add_field(partial, "Content-Type", type);
734
735       tmp = silc_mime_encode(partial, &tmp_len);
736       if (!tmp)
737         return NULL;
738       silc_mime_free(partial);
739
740       /* Add to list */
741       buffer = silc_buffer_alloc_size(tmp_len);
742       if (!buffer)
743         return NULL;
744       silc_buffer_put(buffer, tmp, tmp_len);
745       silc_dlist_add(list, buffer);
746       silc_free(tmp);
747     }
748   } else {
749     /* No need to fragment */
750     buffer = silc_buffer_alloc_size(buf_len);
751     if (!buffer)
752       return NULL;
753     silc_buffer_put(buffer, buf, buf_len);
754     silc_dlist_add(list, buffer);
755   }
756
757   silc_free(buf);
758
759   return list;
760 }
761
762 /* Free partial MIME list */
763
764 void silc_mime_partial_free(SilcDList partials)
765 {
766   SilcBuffer buf;
767
768   if (!partials)
769     return;
770
771   silc_dlist_start(partials);
772   while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
773     silc_buffer_free(buf);
774   silc_dlist_uninit(partials);
775 }
776
777 /* Add field */
778
779 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
780 {
781   if (!mime || !field || !value)
782     return;
783
784   silc_hash_table_add(mime->fields, silc_strdup(field), silc_strdup(value));
785 }
786
787 /* Get field */
788
789 const char *silc_mime_get_field(SilcMime mime, const char *field)
790 {
791   char *value;
792
793   if (!mime || !field)
794     return NULL;
795
796   if (!silc_hash_table_find(mime->fields, (void *)field,
797                             NULL, (void *)&value))
798     return NULL;
799
800   return (const char *)value;
801 }
802
803 /* Add data */
804
805 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
806                         SilcUInt32 data_len)
807 {
808   if (!mime || !data)
809     return;
810
811   if (mime->data)
812     silc_free(mime->data);
813
814   mime->data = silc_memdup(data, data_len);
815   mime->data_len = data_len;
816 }
817
818 /* Get data */
819
820 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
821 {
822   if (!mime)
823     return NULL;
824
825   if (data_len)
826     *data_len = mime->data_len;
827
828   return mime->data;
829 }
830
831 /* Steal data */
832
833 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
834 {
835   unsigned char *data;
836
837   if (!mime)
838     return NULL;
839
840   if (data_len)
841     *data_len = mime->data_len;
842
843   data = mime->data;
844
845   mime->data = NULL;
846   mime->data_len = 0;
847
848   return data;
849 }
850
851 /* Returns TRUE if partial message */
852
853 SilcBool silc_mime_is_partial(SilcMime mime)
854 {
855   const char *type = silc_mime_get_field(mime, "Content-Type");
856   if (!type)
857     return FALSE;
858
859   if (!strstr(type, "message/partial"))
860     return FALSE;
861
862   return TRUE;
863 }
864
865 /* Set as multipart message */
866
867 void silc_mime_set_multipart(SilcMime mime, const char *type,
868                              const char *boundary)
869 {
870   char tmp[1024];
871
872   if (!mime || !type || !boundary)
873     return;
874
875   memset(tmp, 0, sizeof(tmp));
876   silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
877   silc_mime_add_field(mime, "Content-Type", tmp);
878   silc_free(mime->boundary);
879   mime->boundary = silc_strdup(boundary);
880
881   if (mime->multiparts)
882     return;
883   mime->multiparts = silc_dlist_init();
884 }
885
886 /* Add multipart */
887
888 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
889 {
890   if (!mime || !mime->multiparts || !part) {
891     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
892     return FALSE;
893   }
894
895   silc_dlist_add(mime->multiparts, part);
896   return TRUE;
897 }
898
899 /* Return TRUE if has multiparts */
900
901 SilcBool silc_mime_is_multipart(SilcMime mime)
902 {
903   if (!mime) {
904     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
905     return FALSE;
906   }
907
908   return mime->multiparts != NULL;
909 }
910
911 /* Returns multiparts */
912
913 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
914 {
915   if (!mime) {
916     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
917     return NULL;
918   }
919
920   if (type)
921     *type = (const char *)mime->multitype;
922
923   return mime->multiparts;
924 }