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