5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2005 - 2008 Pekka Riikonen
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.
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.
20 #include "silcruntime.h"
22 /************************** Types and definitions ***************************/
24 /* MIME fragment ID context */
28 } SilcMimeFragmentIdStruct, *SilcMimeFragmentId;
30 /************************ Static utility functions **************************/
32 /* Assembler fragment destructor */
34 static void silc_mime_assembler_dest(void *key, void *context,
37 SilcMimeFragmentId id = key;
42 /* Free all fragments */
43 silc_hash_table_free(context);
46 /* Assembler partial MIME fragmentn destructor */
48 static void silc_mime_assemble_dest(void *key, void *context,
51 silc_mime_free(context);
54 /* MIME fragment ID hashing */
56 static SilcUInt32 silc_mime_hash_id(void *key, void *user_context)
58 SilcMimeFragmentId id = key;
59 return silc_hash_string_case(id->id, user_context);
62 /* MIME fragment ID comparing */
64 static SilcBool silc_mime_id_compare(void *key1, void *key2,
67 SilcMimeFragmentId id1 = key1, id2 = key2;
68 return silc_hash_string_case_compare(id1->id, id2->id, user_context);
72 /******************************* Public API *********************************/
74 /* Allocate MIME context */
76 SilcMime silc_mime_alloc(void)
80 mime = silc_calloc(1, sizeof(*mime));
84 mime->fields = silc_hash_table_alloc(NULL, 0, silc_hash_string_case, mime,
85 silc_hash_string_case_compare, mime,
86 silc_hash_destructor, mime, TRUE);
95 /* Free MIME context */
97 void silc_mime_free(SilcMime mime)
102 silc_hash_table_free(mime->fields);
104 if (mime->multiparts) {
105 silc_dlist_start(mime->multiparts);
106 while ((m = silc_dlist_get(mime->multiparts)) != SILC_LIST_END)
108 silc_dlist_uninit(mime->multiparts);
110 silc_free(mime->boundary);
111 silc_free(mime->multitype);
112 silc_free(mime->data);
116 /* Allocate MIME assembler */
118 SilcMimeAssembler silc_mime_assembler_alloc(void)
120 SilcMimeAssembler assembler;
122 assembler = silc_calloc(1, sizeof(*assembler));
126 assembler->fragments =
127 silc_hash_table_alloc(NULL, 0, silc_mime_hash_id, NULL,
128 silc_mime_id_compare, NULL,
129 silc_mime_assembler_dest, assembler, TRUE);
130 if (!assembler->fragments) {
131 silc_mime_assembler_free(assembler);
138 /* Free MIME assembler */
140 void silc_mime_assembler_free(SilcMimeAssembler assembler)
142 silc_hash_table_free(assembler->fragments);
143 silc_free(assembler);
146 /* Purge assembler from old unfinished fragments */
148 void silc_mime_assembler_purge(SilcMimeAssembler assembler,
149 SilcUInt32 purge_minutes)
151 SilcMimeFragmentId id;
152 SilcHashTableList htl;
153 SilcInt64 curtime = silc_time();
154 SilcUInt32 timeout = purge_minutes ? purge_minutes * 60 : 5 * 60;
156 SILC_LOG_DEBUG(("Purge MIME assembler"));
158 silc_hash_table_list(assembler->fragments, &htl);
159 while (silc_hash_table_get(&htl, (void *)&id, NULL)) {
160 if (curtime - id->starttime <= timeout)
163 SILC_LOG_DEBUG(("Purge partial MIME id %s", id->id));
166 silc_hash_table_del(assembler->fragments, id);
168 silc_hash_table_list_reset(&htl);
171 /* Decode MIME message */
173 SilcMime silc_mime_decode(SilcMime mime, const unsigned char *data,
178 char *tmp, *field, *value, *line;
180 SILC_LOG_DEBUG(("Parsing MIME message"));
183 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
188 mime = silc_mime_alloc();
194 /* Parse the fields */
195 line = tmp = (char *)data;
196 for (i = 0; i < data_len; i++) {
198 if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
200 field = strchr(line, ':');
202 silc_set_errno(SILC_ERR_BAD_ENCODING);
205 field = silc_memdup(line, field - line);
209 /* Get value. Remove whitespaces too. */
210 value = strchr(line, ':');
211 if ((tmp + i) - value < 2) {
212 silc_set_errno(SILC_ERR_OVERFLOW);
216 for (k = 0; k < (tmp + i) - value; k++) {
217 if (value[k] == '\r') {
218 silc_set_errno(SILC_ERR_BAD_ENCODING);
221 if (value[k] != ' ' && value[k] != '\t')
225 if ((tmp + i) - value < 1) {
226 silc_set_errno(SILC_ERR_OVERFLOW);
229 value = silc_memdup(value, (tmp + i) - value);
233 SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
235 /* Add field and value */
236 silc_mime_add_field(mime, field, value);
240 /* Mark start of next line */
241 line = (tmp + i) + 2;
244 /* Break if this is last header */
245 if (data_len - i >= 2 &&
246 tmp[i] == '\r' && tmp[i + 1] == '\n') {
253 /* Parse multiparts if present */
254 field = (char *)silc_mime_get_field(mime, "Content-Type");
255 if (field && strstr(field, "multipart")) {
260 mime->multiparts = silc_dlist_init();
261 if (!mime->multiparts)
264 /* Get multipart type */
265 value = strchr(field, '/');
267 silc_set_errno(SILC_ERR_BAD_ENCODING);
271 if (strchr(field, '"'))
273 if (!strchr(field, ';')) {
274 silc_set_errno(SILC_ERR_BAD_ENCODING);
277 memset(b, 0, sizeof(b));
278 len = (unsigned int)(strchr(field, ';') - value);
279 if (len > sizeof(b) - 1) {
280 silc_set_errno(SILC_ERR_OVERFLOW);
283 strncpy(b, value, len);
285 *strchr(b, '"') = '\0';
286 mime->multitype = silc_memdup(b, strlen(b));
289 value = strrchr(field, '=');
290 if (value && strlen(value) > 1) {
293 SILC_LOG_DEBUG(("Boundary '%s'", value));
295 memset(b, 0, sizeof(b));
296 line = silc_strdup(value);
297 if (strrchr(line, '"')) {
298 *strrchr(line, '"') = '\0';
299 silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
300 mime->boundary = silc_strdup(line + 1);
302 silc_snprintf(b, sizeof(b) - 1, "--%s", line);
303 mime->boundary = silc_strdup(line);
307 for (i = i; i < data_len; i++) {
308 /* Get boundary data */
309 if (data_len - i >= strlen(b) &&
310 tmp[i] == '-' && tmp[i + 1] == '-') {
311 if (memcmp(tmp + i, b, strlen(b)))
316 if (data_len - i >= 4 &&
317 tmp[i ] == '\r' && tmp[i + 1] == '\n' &&
318 tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
320 else if (data_len - i >= 2 &&
321 tmp[i] == '\r' && tmp[i + 1] == '\n')
323 else if (data_len - i >= 2 &&
324 tmp[i] == '-' && tmp[i + 1] == '-')
329 /* Find end of boundary */
330 for (k = i; k < data_len; k++)
331 if (data_len - k >= strlen(b) &&
332 tmp[k] == '-' && tmp[k + 1] == '-')
333 if (!memcmp(tmp + k, b, strlen(b)))
336 silc_set_errno(SILC_ERR_OVERFLOW);
340 /* Remove preceding CRLF */
344 p = silc_mime_decode(NULL, line, k - i);
348 silc_dlist_add(mime->multiparts, p);
354 /* Get data area. If we are at the end and we have fields present
355 there is no data area present, but, if fields are not present we
356 only have data area. */
357 if (i >= data_len && !silc_hash_table_count(mime->fields))
359 SILC_LOG_DEBUG(("Data len %d", data_len - i));
361 silc_mime_add_data(mime, tmp + i, data_len - i);
372 /* Encode MIME message */
374 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
377 SilcHashTableList htl;
378 SilcBufferStruct buf;
380 char *field, *value, tmp[1024], tmp2[4];
384 SILC_LOG_DEBUG(("Encoding MIME message"));
389 memset(&buf, 0, sizeof(buf));
391 /* Encode the headers. Order doesn't matter */
393 silc_hash_table_list(mime->fields, &htl);
394 while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
395 memset(tmp, 0, sizeof(tmp));
396 SILC_LOG_DEBUG(("Header %s: %s", field, value));
397 silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
398 silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
401 silc_hash_table_list_reset(&htl);
403 silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
405 /* Assemble the whole buffer */
406 buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
411 if (silc_buffer_len(&buf)) {
412 silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
413 silc_buffer_pull(buffer, silc_buffer_len(&buf));
414 silc_buffer_purge(&buf);
419 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
420 silc_buffer_put(buffer, mime->data, mime->data_len);
424 if (mime->multiparts) {
425 SILC_LOG_DEBUG(("Encoding multiparts"));
427 silc_dlist_start(mime->multiparts);
429 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
433 /* Recursive encoding */
434 pd = silc_mime_encode(part, &pd_len);
438 memset(tmp, 0, sizeof(tmp));
439 memset(tmp2, 0, sizeof(tmp2));
441 /* If fields are not present, add extra CRLF */
442 if (!silc_hash_table_count(part->fields))
443 silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
444 silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
445 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
448 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
449 pd_len + strlen(tmp));
452 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
453 silc_buffer_pull_tail(buffer, strlen(tmp));
454 silc_buffer_put_tail(buffer, pd, pd_len);
455 silc_buffer_pull_tail(buffer, pd_len);
459 memset(tmp, 0, sizeof(tmp));
460 silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
461 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
465 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
466 silc_buffer_pull_tail(buffer, strlen(tmp));
469 ret = silc_buffer_steal(buffer, encoded_len);
470 silc_buffer_free(buffer);
475 /* Assembles MIME message from partial MIME messages */
477 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
479 char *type, *id = NULL, *tmp;
480 SilcMimeFragmentIdStruct *fragid, query;
482 SilcMime p, complete;
483 int i, number, total = -1;
484 const unsigned char *data;
486 SilcBuffer compbuf = NULL;
488 SILC_LOG_DEBUG(("Assembling MIME fragments"));
490 if (!assembler || !partial) {
491 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
495 type = (char *)silc_mime_get_field(partial, "Content-Type");
497 silc_set_errno(SILC_ERR_BAD_ENCODING);
502 tmp = strstr(type, "id=");
504 silc_set_errno(SILC_ERR_BAD_ENCODING);
507 if (strlen(tmp) <= 4) {
508 silc_set_errno(SILC_ERR_OVERFLOW);
514 id = silc_strdup(tmp);
516 *strchr(id, ';') = '\0';
517 if (strrchr(id, '"'))
518 *strrchr(id, '"') = '\0';
520 SILC_LOG_DEBUG(("Fragment ID %s", id));
522 /* Get fragment number */
523 tmp = strstr(type, "number=");
525 silc_set_errno(SILC_ERR_BAD_ENCODING);
528 tmp = strchr(tmp, '=');
529 if (strlen(tmp) < 2) {
530 silc_set_errno(SILC_ERR_OVERFLOW);
534 if (strchr(tmp, ';')) {
535 tmp = silc_strdup(tmp);
536 *strchr(tmp, ';') = '\0';
543 SILC_LOG_DEBUG(("Fragment number %d", number));
545 /* Find fragments with this ID. */
547 if (!silc_hash_table_find(assembler->fragments, (void *)&query,
549 /* This is new fragment to new message. Add to hash table and return. */
550 f = silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL,
551 silc_mime_assemble_dest, NULL, TRUE);
555 fragid = silc_calloc(1, sizeof(*fragid));
559 fragid->starttime = silc_time();
561 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
562 silc_hash_table_add(assembler->fragments, fragid, f);
566 /* Try to get total number */
567 tmp = strstr(type, "total=");
569 tmp = strchr(tmp, '=');
570 if (strlen(tmp) < 2) {
571 silc_set_errno(SILC_ERR_OVERFLOW);
575 if (strchr(tmp, ';')) {
576 tmp = silc_strdup(tmp);
577 *strchr(tmp, ';') = '\0';
584 SILC_LOG_DEBUG(("Fragment total %d", total));
587 /* If more fragments to come, add to hash table */
588 if (number != total) {
589 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
594 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
596 /* Verify that we really have all the fragments */
597 if (silc_hash_table_count(f) < total) {
602 /* Assemble the complete MIME message now. We get them in order from
604 for (i = 1; i <= total; i++) {
605 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
608 /* The fragment is in the data portion of the partial message */
609 data = silc_mime_get_data(p, &data_len);
611 silc_set_errno(SILC_ERR_BAD_ENCODING);
617 compbuf = silc_buffer_alloc_size(data_len);
620 silc_buffer_put(compbuf, data, data_len);
622 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
626 silc_buffer_put_tail(compbuf, data, data_len);
627 silc_buffer_pull_tail(compbuf, data_len);
631 /* Now parse the complete MIME message and deliver it */
632 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
633 silc_buffer_truelen(compbuf));
637 /* Delete the hash table entry. Destructors will free memory */
638 silc_hash_table_del(assembler->fragments, (void *)&query);
640 silc_buffer_free(compbuf);
647 silc_buffer_free(compbuf);
648 silc_mime_free(partial);
652 /* Encodes partial MIME messages */
654 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
656 unsigned char *buf, *tmp;
657 SilcUInt32 buf_len, len, tmp_len, off;
661 char type[128], id[64];
664 SILC_LOG_DEBUG(("Fragmenting MIME message"));
666 /* Encode as normal */
667 buf = silc_mime_encode(mime, &buf_len);
671 list = silc_dlist_init();
673 /* Fragment if it is too large */
674 if (buf_len > max_size) {
675 memset(id, 0, sizeof(id));
676 memset(type, 0, sizeof(type));
677 gethostname(type, sizeof(type) - 1);
678 srand((time(NULL) + buf_len) ^ rand());
679 silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
680 (unsigned int)rand(), (unsigned int)time(NULL),
681 (unsigned int)buf_len, type);
683 SILC_LOG_DEBUG(("Fragment ID %s", id));
685 partial = silc_mime_alloc();
689 silc_mime_add_field(partial, "MIME-Version", "1.0");
690 memset(type, 0, sizeof(type));
691 silc_snprintf(type, sizeof(type) - 1,
692 "message/partial; id=\"%s\"; number=1", id);
693 silc_mime_add_field(partial, "Content-Type", type);
694 silc_mime_add_data(partial, buf, max_size);
696 tmp = silc_mime_encode(partial, &tmp_len);
699 silc_mime_free(partial);
702 buffer = silc_buffer_alloc_size(tmp_len);
705 silc_buffer_put(buffer, tmp, tmp_len);
706 silc_dlist_add(list, buffer);
709 len = buf_len - max_size;
713 partial = silc_mime_alloc();
717 memset(type, 0, sizeof(type));
718 silc_mime_add_field(partial, "MIME-Version", "1.0");
720 if (len > max_size) {
721 silc_snprintf(type, sizeof(type) - 1,
722 "message/partial; id=\"%s\"; number=%d",
724 silc_mime_add_data(partial, buf + off, max_size);
728 silc_snprintf(type, sizeof(type) - 1,
729 "message/partial; id=\"%s\"; number=%d; total=%d",
731 silc_mime_add_data(partial, buf + off, len);
735 silc_mime_add_field(partial, "Content-Type", type);
737 tmp = silc_mime_encode(partial, &tmp_len);
740 silc_mime_free(partial);
743 buffer = silc_buffer_alloc_size(tmp_len);
746 silc_buffer_put(buffer, tmp, tmp_len);
747 silc_dlist_add(list, buffer);
751 /* No need to fragment */
752 buffer = silc_buffer_alloc_size(buf_len);
755 silc_buffer_put(buffer, buf, buf_len);
756 silc_dlist_add(list, buffer);
764 /* Free partial MIME list */
766 void silc_mime_partial_free(SilcDList partials)
773 silc_dlist_start(partials);
774 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
775 silc_buffer_free(buf);
776 silc_dlist_uninit(partials);
781 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
783 if (!mime || !field || !value)
786 silc_hash_table_add(mime->fields, silc_strdup(field), silc_strdup(value));
791 const char *silc_mime_get_field(SilcMime mime, const char *field)
798 if (!silc_hash_table_find(mime->fields, (void *)field,
799 NULL, (void *)&value))
802 return (const char *)value;
807 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
814 silc_free(mime->data);
816 mime->data = silc_memdup(data, data_len);
817 mime->data_len = data_len;
822 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
828 *data_len = mime->data_len;
835 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
843 *data_len = mime->data_len;
853 /* Returns TRUE if partial message */
855 SilcBool silc_mime_is_partial(SilcMime mime)
857 const char *type = silc_mime_get_field(mime, "Content-Type");
861 if (!strstr(type, "message/partial"))
867 /* Set as multipart message */
869 void silc_mime_set_multipart(SilcMime mime, const char *type,
870 const char *boundary)
874 if (!mime || !type || !boundary)
877 memset(tmp, 0, sizeof(tmp));
878 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
879 silc_mime_add_field(mime, "Content-Type", tmp);
880 silc_free(mime->boundary);
881 mime->boundary = silc_strdup(boundary);
883 if (mime->multiparts)
885 mime->multiparts = silc_dlist_init();
890 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
892 if (!mime || !mime->multiparts || !part) {
893 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
897 silc_dlist_add(mime->multiparts, part);
901 /* Return TRUE if has multiparts */
903 SilcBool silc_mime_is_multipart(SilcMime mime)
906 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
910 return mime->multiparts != NULL;
913 /* Returns multiparts */
915 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
918 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
923 *type = (const char *)mime->multitype;
925 return mime->multiparts;