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