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