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