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 silc_snprintf(id, sizeof(id) - 1, "%X%x%X%x",
678 (unsigned int)silc_rand(), (unsigned int)silc_time_usec(),
679 (unsigned int)buf_len, (unsigned int)silc_rand());
681 SILC_LOG_DEBUG(("Fragment ID %s", id));
683 partial = silc_mime_alloc();
687 silc_mime_add_field(partial, "MIME-Version", "1.0");
688 memset(type, 0, sizeof(type));
689 silc_snprintf(type, sizeof(type) - 1,
690 "message/partial; id=\"%s\"; number=1", id);
691 silc_mime_add_field(partial, "Content-Type", type);
692 silc_mime_add_data(partial, buf, max_size);
694 tmp = silc_mime_encode(partial, &tmp_len);
697 silc_mime_free(partial);
700 buffer = silc_buffer_alloc_size(tmp_len);
703 silc_buffer_put(buffer, tmp, tmp_len);
704 silc_dlist_add(list, buffer);
707 len = buf_len - max_size;
711 partial = silc_mime_alloc();
715 memset(type, 0, sizeof(type));
716 silc_mime_add_field(partial, "MIME-Version", "1.0");
718 if (len > max_size) {
719 silc_snprintf(type, sizeof(type) - 1,
720 "message/partial; id=\"%s\"; number=%d",
722 silc_mime_add_data(partial, buf + off, max_size);
726 silc_snprintf(type, sizeof(type) - 1,
727 "message/partial; id=\"%s\"; number=%d; total=%d",
729 silc_mime_add_data(partial, buf + off, len);
733 silc_mime_add_field(partial, "Content-Type", type);
735 tmp = silc_mime_encode(partial, &tmp_len);
738 silc_mime_free(partial);
741 buffer = silc_buffer_alloc_size(tmp_len);
744 silc_buffer_put(buffer, tmp, tmp_len);
745 silc_dlist_add(list, buffer);
749 /* No need to fragment */
750 buffer = silc_buffer_alloc_size(buf_len);
753 silc_buffer_put(buffer, buf, buf_len);
754 silc_dlist_add(list, buffer);
762 /* Free partial MIME list */
764 void silc_mime_partial_free(SilcDList partials)
771 silc_dlist_start(partials);
772 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
773 silc_buffer_free(buf);
774 silc_dlist_uninit(partials);
779 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
781 if (!mime || !field || !value)
784 silc_hash_table_add(mime->fields, silc_strdup(field), silc_strdup(value));
789 const char *silc_mime_get_field(SilcMime mime, const char *field)
796 if (!silc_hash_table_find(mime->fields, (void *)field,
797 NULL, (void *)&value))
800 return (const char *)value;
805 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
812 silc_free(mime->data);
814 mime->data = silc_memdup(data, data_len);
815 mime->data_len = data_len;
820 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
826 *data_len = mime->data_len;
833 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
841 *data_len = mime->data_len;
851 /* Returns TRUE if partial message */
853 SilcBool silc_mime_is_partial(SilcMime mime)
855 const char *type = silc_mime_get_field(mime, "Content-Type");
859 if (!strstr(type, "message/partial"))
865 /* Set as multipart message */
867 void silc_mime_set_multipart(SilcMime mime, const char *type,
868 const char *boundary)
872 if (!mime || !type || !boundary)
875 memset(tmp, 0, sizeof(tmp));
876 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
877 silc_mime_add_field(mime, "Content-Type", tmp);
878 silc_free(mime->boundary);
879 mime->boundary = silc_strdup(boundary);
881 if (mime->multiparts)
883 mime->multiparts = silc_dlist_init();
888 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
890 if (!mime || !mime->multiparts || !part) {
891 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
895 silc_dlist_add(mime->multiparts, part);
899 /* Return TRUE if has multiparts */
901 SilcBool silc_mime_is_multipart(SilcMime mime)
904 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
908 return mime->multiparts != NULL;
911 /* Returns multiparts */
913 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
916 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
921 *type = (const char *)mime->multitype;
923 return mime->multiparts;