d7d2ddc24b727d632abea52f0d150e665446f4b0
[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 + silc_buffer_len(&buf));
315   if (!buffer)
316     return NULL;
317
318   /* Add headers */
319   if (silc_buffer_len(&buf)) {
320     silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
321     silc_buffer_pull(buffer, silc_buffer_len(&buf));
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, silc_buffer_truelen(buffer) +
356                                    pd_len + 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, silc_buffer_truelen(buffer) +
369                                  strlen(tmp));
370     if (!buffer)
371       return NULL;
372     silc_buffer_put_tail(buffer, tmp, strlen(tmp));
373     silc_buffer_pull_tail(buffer, strlen(tmp));
374   }
375
376   ret = silc_buffer_steal(buffer, encoded_len);
377   silc_buffer_free(buffer);
378
379   return ret;
380 }
381
382 static void silc_mime_assemble_dest(void *key, void *context,
383                                                          void *user_context)
384 {
385   silc_mime_free(context);
386 }
387
388 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
389 {
390   char *type, *id = NULL, *tmp;
391   SilcHashTable f;
392   SilcMime p, complete;
393   int i, number, total = -1;
394   const unsigned char *data;
395   SilcUInt32 data_len;
396   SilcBuffer compbuf = NULL;
397
398   SILC_LOG_DEBUG(("Assembling MIME fragments"));
399
400   if (!assembler || !partial)
401     goto err;
402
403   type = (char *)silc_mime_get_field(partial, "Content-Type");
404   if (!type)
405     goto err;
406
407   /* Get ID */
408   tmp = strstr(type, "id=");
409   if (!tmp)
410     goto err;
411   if (strlen(tmp) <= 4)
412     goto err;
413   tmp += 3;
414   if (*tmp == '"')
415     tmp++;
416   id = strdup(tmp);
417   if (strchr(id, ';'))
418     *strchr(id, ';') = '\0';
419   if (strrchr(id, '"'))
420     *strrchr(id, '"') = '\0';
421
422   SILC_LOG_DEBUG(("Fragment ID %s", id));
423
424   /* Get fragment number */
425   tmp = strstr(type, "number=");
426   if (!tmp)
427     goto err;
428   tmp = strchr(tmp, '=');
429   if (strlen(tmp) < 2)
430     goto err;
431   tmp++;
432   if (strchr(tmp, ';')) {
433     tmp = strdup(tmp);
434     *strchr(tmp, ';') = '\0';
435     number = atoi(tmp);
436     silc_free(tmp);
437   } else {
438     number = atoi(tmp);
439   }
440
441   SILC_LOG_DEBUG(("Fragment number %d", number));
442
443   /* Find fragments with this ID. */
444   if (!silc_hash_table_find(assembler->fragments, (void *)id,
445                             NULL, (void **)&f)) {
446     /* This is new fragment to new message.  Add to hash table and return. */
447     f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
448                               silc_mime_assemble_dest, NULL, TRUE);
449     if (!f)
450          goto err;
451     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
452     silc_hash_table_add(assembler->fragments, id, f);
453     return NULL;
454   }
455
456   /* Try to get total number */
457   tmp = strstr(type, "total=");
458   if (tmp) {
459     tmp = strchr(tmp, '=');
460     if (strlen(tmp) < 2)
461       goto err;
462     tmp++;
463     if (strchr(tmp, ';')) {
464       tmp = strdup(tmp);
465       *strchr(tmp, ';') = '\0';
466       total = atoi(tmp);
467       silc_free(tmp);
468     } else {
469       total = atoi(tmp);
470     }
471
472     SILC_LOG_DEBUG(("Fragment total %d", total));
473   }
474
475   /* If more fragments to come, add to hash table */
476   if (number != total) {
477     silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
478     return NULL;
479   }
480
481   silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
482
483   /* Verify that we really have all the fragments */
484   if (silc_hash_table_count(f) < total)
485     return NULL;
486
487   /* Assemble the complete MIME message now. We get them in order from
488      the hash table. */
489   for (i = 1; i <= total; i++) {
490     if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void **)&p))
491       goto err;
492
493     /* The fragment is in the data portion of the partial message */
494     data = silc_mime_get_data(p, &data_len);
495     if (!data)
496       goto err;
497
498     /* Assemble */
499     if (!compbuf) {
500       compbuf = silc_buffer_alloc_size(data_len);
501       if (!compbuf)
502         goto err;
503       silc_buffer_put(compbuf, data, data_len);
504     } else {
505       compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
506                                     data_len);
507       if (!compbuf)
508         goto err;
509       silc_buffer_put_tail(compbuf, data, data_len);
510       silc_buffer_pull_tail(compbuf, data_len);
511     }
512   }
513
514   /* Now parse the complete MIME message and deliver it */
515   complete = silc_mime_decode((const unsigned char *)compbuf->head,
516                               silc_buffer_truelen(compbuf));
517   if (!complete)
518     goto err;
519
520   /* Delete the hash table entry. Destructors will free memory */
521   silc_hash_table_del(assembler->fragments, (void *)id);
522   silc_free(id);
523   silc_buffer_free(compbuf);
524
525   return complete;
526
527  err:
528   silc_free(id);
529   if (compbuf)
530     silc_buffer_free(compbuf);
531   silc_mime_free(partial);
532   return NULL;
533 }
534
535 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
536 {
537   unsigned char *buf, *tmp;
538   SilcUInt32 buf_len, len, tmp_len, off;
539   SilcDList list;
540   SilcBuffer buffer;
541   SilcMime partial;
542   char type[128], id[64];
543   int num;
544
545   SILC_LOG_DEBUG(("Fragmenting MIME message"));
546
547   /* Encode as normal */
548   buf = silc_mime_encode(mime, &buf_len);
549   if (!buf)
550     return NULL;
551
552   list = silc_dlist_init();
553
554   /* Fragment if it is too large */
555   if (buf_len > max_size) {
556     memset(id, 0, sizeof(id));
557     memset(type, 0, sizeof(type));
558     gethostname(type, sizeof(type) - 1);
559     srand((time(NULL) + buf_len) ^ rand());
560     snprintf(id, sizeof(id) - 1, "%X%X%X%s",
561              (unsigned int)rand(), (unsigned int)time(NULL),
562              (unsigned int)buf_len, type);
563
564     SILC_LOG_DEBUG(("Fragment ID %s", id));
565
566     partial = silc_mime_alloc();
567     if (!partial)
568       return NULL;
569
570     silc_mime_add_field(partial, "MIME-Version", "1.0");
571     memset(type, 0, sizeof(type));
572     snprintf(type, sizeof(type) - 1,
573              "message/partial; id=\"%s\"; number=1", id);
574     silc_mime_add_field(partial, "Content-Type", type);
575     silc_mime_add_data(partial, buf, max_size);
576
577     tmp = silc_mime_encode(partial, &tmp_len);
578     if (!tmp)
579       return NULL;
580     silc_mime_free(partial);
581
582     /* Add to list */
583     buffer = silc_buffer_alloc_size(tmp_len);
584     if (!buffer)
585       return NULL;
586     silc_buffer_put(buffer, tmp, tmp_len);
587     silc_dlist_add(list, buffer);
588     silc_free(tmp);
589
590     len = buf_len - max_size;
591     off = max_size;
592     num = 2;
593     while (len > 0) {
594       partial = silc_mime_alloc();
595       if (!partial)
596         return NULL;
597
598       memset(type, 0, sizeof(type));
599       silc_mime_add_field(partial, "MIME-Version", "1.0");
600
601       if (len > max_size) {
602         snprintf(type, sizeof(type) - 1,
603                  "message/partial; id=\"%s\"; number=%d",
604                  id, num++);
605         silc_mime_add_data(partial, buf + off, max_size);
606         off += max_size;
607         len -= max_size;
608       } else {
609         snprintf(type, sizeof(type) - 1,
610                  "message/partial; id=\"%s\"; number=%d; total=%d",
611                  id, num, num);
612         silc_mime_add_data(partial, buf + off, len);
613         len = 0;
614       }
615
616       silc_mime_add_field(partial, "Content-Type", type);
617
618       tmp = silc_mime_encode(partial, &tmp_len);
619       if (!tmp)
620         return NULL;
621       silc_mime_free(partial);
622
623       /* Add to list */
624       buffer = silc_buffer_alloc_size(tmp_len);
625       if (!buffer)
626         return NULL;
627       silc_buffer_put(buffer, tmp, tmp_len);
628       silc_dlist_add(list, buffer);
629       silc_free(tmp);
630     }
631   } else {
632     /* No need to fragment */
633     buffer = silc_buffer_alloc_size(buf_len);
634     if (!buffer)
635       return NULL;
636     silc_buffer_put(buffer, buf, buf_len);
637     silc_dlist_add(list, buffer);
638   }
639
640   silc_free(buf);
641
642   return list;
643 }
644
645 void silc_mime_partial_free(SilcDList partials)
646 {
647   SilcBuffer buf;
648
649   if (!partials)
650     return;
651
652   silc_dlist_start(partials);
653   while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
654     silc_buffer_free(buf);
655   silc_dlist_uninit(partials);
656 }
657
658 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
659 {
660   if (!mime || !field || !value)
661     return;
662
663   silc_hash_table_add(mime->fields, strdup(field), strdup(value));
664 }
665
666 const char *silc_mime_get_field(SilcMime mime, const char *field)
667 {
668   char *value;
669
670   if (!mime || !field)
671     return NULL;
672
673   if (!silc_hash_table_find(mime->fields, (void *)field,
674                             NULL, (void **)&value))
675     return NULL;
676
677   return (const char *)value;
678 }
679
680 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
681                         SilcUInt32 data_len)
682 {
683   if (!mime || !data)
684     return;
685
686   if (mime->data)
687     silc_free(mime->data);
688
689   mime->data = silc_memdup(data, data_len);
690   mime->data_len = data_len;
691 }
692
693 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
694 {
695   if (!mime)
696     return NULL;
697
698   if (data_len)
699     *data_len = mime->data_len;
700
701   return mime->data;
702 }
703
704 SilcBool silc_mime_is_partial(SilcMime mime)
705 {
706   const char *type = silc_mime_get_field(mime, "Content-Type");
707   if (!type)
708     return FALSE;
709
710   if (!strstr(type, "message/partial"))
711     return FALSE;
712
713   return TRUE;
714 }
715
716 void silc_mime_set_multipart(SilcMime mime, const char *type,
717                              const char *boundary)
718 {
719   char tmp[1024];
720
721   if (!mime || !type || !boundary)
722     return;
723
724   memset(tmp, 0, sizeof(tmp));
725   snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
726   silc_mime_add_field(mime, "Content-Type", tmp);
727   silc_free(mime->boundary);
728   mime->boundary = strdup(boundary);
729
730   if (mime->multiparts)
731     return;
732   mime->multiparts = silc_dlist_init();
733 }
734
735 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
736 {
737   if (!mime || !mime->multiparts || !part)
738     return FALSE;
739
740   silc_dlist_add(mime->multiparts, part);
741   return TRUE;
742 }
743
744 SilcBool silc_mime_is_multipart(SilcMime mime)
745 {
746   if (!mime)
747     return FALSE;
748
749   return mime->multiparts != NULL;
750 }
751
752 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
753 {
754   if (!mime)
755     return NULL;
756
757   if (type)
758     *type = (const char *)mime->multitype;
759
760   return mime->multiparts;
761 }