5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2005 - 2007 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.
22 /************************** Types and definitions ***************************/
24 /* MIME fragment ID context */
28 } SilcMimeFragmentIdStruct, *SilcMimeFragmentId;
30 /************************ Static utility functions **************************/
32 /* MIME fields destructor */
34 static void silc_mime_field_dest(void *key, void *context, void *user_context)
40 /* Assembler fragment destructor */
42 static void silc_mime_assembler_dest(void *key, void *context,
45 SilcMimeFragmentId id = key;
50 /* Free all fragments */
51 silc_hash_table_free(context);
54 /* Assembler partial MIME fragmentn destructor */
56 static void silc_mime_assemble_dest(void *key, void *context,
59 silc_mime_free(context);
62 /* MIME fragment ID hashing */
64 static SilcUInt32 silc_mime_hash_id(void *key, void *user_context)
66 SilcMimeFragmentId id = key;
67 return silc_hash_string(id->id, user_context);
70 /* MIME fragment ID comparing */
72 static SilcBool silc_mime_id_compare(void *key1, void *key2,
75 SilcMimeFragmentId id1 = key1, id2 = key2;
76 return silc_hash_string_compare(id1->id, id2->id, user_context);
80 /******************************* Public API *********************************/
82 /* Allocate MIME context */
84 SilcMime silc_mime_alloc(void)
88 mime = silc_calloc(1, sizeof(*mime));
92 mime->fields = silc_hash_table_alloc(NULL, 0, silc_hash_string, mime,
93 silc_hash_string_compare, mime,
94 silc_mime_field_dest, mime, TRUE);
103 /* Free MIME context */
105 void silc_mime_free(SilcMime mime)
110 silc_hash_table_free(mime->fields);
112 if (mime->multiparts) {
113 silc_dlist_start(mime->multiparts);
114 while ((m = silc_dlist_get(mime->multiparts)) != SILC_LIST_END)
116 silc_dlist_uninit(mime->multiparts);
118 silc_free(mime->boundary);
119 silc_free(mime->multitype);
120 silc_free(mime->data);
124 /* Allocate MIME assembler */
126 SilcMimeAssembler silc_mime_assembler_alloc(void)
128 SilcMimeAssembler assembler;
130 assembler = silc_calloc(1, sizeof(*assembler));
134 assembler->fragments =
135 silc_hash_table_alloc(NULL, 0, silc_mime_hash_id, NULL,
136 silc_mime_id_compare, NULL,
137 silc_mime_assembler_dest, assembler, TRUE);
138 if (!assembler->fragments) {
139 silc_mime_assembler_free(assembler);
146 /* Free MIME assembler */
148 void silc_mime_assembler_free(SilcMimeAssembler assembler)
150 silc_hash_table_free(assembler->fragments);
151 silc_free(assembler);
154 /* Purge assembler from old unfinished fragments */
156 void silc_mime_assembler_purge(SilcMimeAssembler assembler,
157 SilcUInt32 purge_minutes)
159 SilcMimeFragmentId id;
160 SilcHashTableList htl;
161 SilcInt64 curtime = silc_time();
162 SilcUInt32 timeout = purge_minutes ? purge_minutes * 60 : 5 * 60;
164 SILC_LOG_DEBUG(("Purge MIME assembler"));
166 silc_hash_table_list(assembler->fragments, &htl);
167 while (silc_hash_table_get(&htl, (void *)&id, NULL)) {
168 if (curtime - id->starttime <= timeout)
171 SILC_LOG_DEBUG(("Purge partial MIME id %s", id->id));
174 silc_hash_table_del(assembler->fragments, id);
176 silc_hash_table_list_reset(&htl);
179 /* Decode MIME message */
181 SilcMime silc_mime_decode(SilcMime mime, const unsigned char *data,
186 char *tmp, *field, *value, *line;
188 SILC_LOG_DEBUG(("Parsing MIME message"));
191 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
196 mime = silc_mime_alloc();
202 /* Parse the fields */
203 line = tmp = (char *)data;
204 for (i = 0; i < data_len; i++) {
206 if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
208 field = strchr(line, ':');
210 silc_set_errno(SILC_ERR_BAD_ENCODING);
213 field = silc_memdup(line, field - line);
217 /* Get value. Remove whitespaces too. */
218 value = strchr(line, ':');
219 if ((tmp + i) - value < 2) {
220 silc_set_errno(SILC_ERR_OVERFLOW);
224 for (k = 0; k < (tmp + i) - value; k++) {
225 if (value[k] == '\r') {
226 silc_set_errno(SILC_ERR_BAD_ENCODING);
229 if (value[k] != ' ' && value[k] != '\t')
233 if ((tmp + i) - value < 1) {
234 silc_set_errno(SILC_ERR_OVERFLOW);
237 value = silc_memdup(value, (tmp + i) - value);
241 SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
243 /* Add field and value */
244 silc_mime_add_field(mime, field, value);
248 /* Mark start of next line */
249 line = (tmp + i) + 2;
252 /* Break if this is last header */
253 if (data_len - i >= 2 &&
254 tmp[i] == '\r' && tmp[i + 1] == '\n') {
261 /* Parse multiparts if present */
262 field = (char *)silc_mime_get_field(mime, "Content-Type");
263 if (field && strstr(field, "multipart")) {
268 mime->multiparts = silc_dlist_init();
269 if (!mime->multiparts)
272 /* Get multipart type */
273 value = strchr(field, '/');
275 silc_set_errno(SILC_ERR_BAD_ENCODING);
279 if (strchr(field, '"'))
281 if (!strchr(field, ';')) {
282 silc_set_errno(SILC_ERR_BAD_ENCODING);
285 memset(b, 0, sizeof(b));
286 len = (unsigned int)(strchr(field, ';') - value);
287 if (len > sizeof(b) - 1) {
288 silc_set_errno(SILC_ERR_OVERFLOW);
291 strncpy(b, value, len);
293 *strchr(b, '"') = '\0';
294 mime->multitype = silc_memdup(b, strlen(b));
297 value = strrchr(field, '=');
298 if (value && strlen(value) > 1) {
301 SILC_LOG_DEBUG(("Boundary '%s'", value));
303 memset(b, 0, sizeof(b));
304 line = silc_strdup(value);
305 if (strrchr(line, '"')) {
306 *strrchr(line, '"') = '\0';
307 silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
308 mime->boundary = silc_strdup(line + 1);
310 silc_snprintf(b, sizeof(b) - 1, "--%s", line);
311 mime->boundary = silc_strdup(line);
315 for (i = i; i < data_len; i++) {
316 /* Get boundary data */
317 if (data_len - i >= strlen(b) &&
318 tmp[i] == '-' && tmp[i + 1] == '-') {
319 if (memcmp(tmp + i, b, strlen(b)))
324 if (data_len - i >= 4 &&
325 tmp[i ] == '\r' && tmp[i + 1] == '\n' &&
326 tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
328 else if (data_len - i >= 2 &&
329 tmp[i] == '\r' && tmp[i + 1] == '\n')
331 else if (data_len - i >= 2 &&
332 tmp[i] == '-' && tmp[i + 1] == '-')
337 /* Find end of boundary */
338 for (k = i; k < data_len; k++)
339 if (data_len - k >= strlen(b) &&
340 tmp[k] == '-' && tmp[k + 1] == '-')
341 if (!memcmp(tmp + k, b, strlen(b)))
344 silc_set_errno(SILC_ERR_OVERFLOW);
348 /* Remove preceding CRLF */
352 p = silc_mime_decode(NULL, line, k - i);
356 silc_dlist_add(mime->multiparts, p);
362 /* Get data area. If we are at the end and we have fields present
363 there is no data area present, but, if fields are not present we
364 only have data area. */
365 if (i >= data_len && !silc_hash_table_count(mime->fields))
367 SILC_LOG_DEBUG(("Data len %d", data_len - i));
369 silc_mime_add_data(mime, tmp + i, data_len - i);
380 /* Encode MIME message */
382 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
385 SilcHashTableList htl;
386 SilcBufferStruct buf;
388 char *field, *value, tmp[1024], tmp2[4];
392 SILC_LOG_DEBUG(("Encoding MIME message"));
397 memset(&buf, 0, sizeof(buf));
399 /* Encode the headers. Order doesn't matter */
401 silc_hash_table_list(mime->fields, &htl);
402 while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
403 memset(tmp, 0, sizeof(tmp));
404 SILC_LOG_DEBUG(("Header %s: %s", field, value));
405 silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
406 silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
409 silc_hash_table_list_reset(&htl);
411 silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
413 /* Assemble the whole buffer */
414 buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
419 if (silc_buffer_len(&buf)) {
420 silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
421 silc_buffer_pull(buffer, silc_buffer_len(&buf));
422 silc_buffer_purge(&buf);
427 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
428 silc_buffer_put(buffer, mime->data, mime->data_len);
432 if (mime->multiparts) {
433 SILC_LOG_DEBUG(("Encoding multiparts"));
435 silc_dlist_start(mime->multiparts);
437 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
441 /* Recursive encoding */
442 pd = silc_mime_encode(part, &pd_len);
446 memset(tmp, 0, sizeof(tmp));
447 memset(tmp2, 0, sizeof(tmp2));
449 /* If fields are not present, add extra CRLF */
450 if (!silc_hash_table_count(part->fields))
451 silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
452 silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
453 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
456 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
457 pd_len + strlen(tmp));
460 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
461 silc_buffer_pull_tail(buffer, strlen(tmp));
462 silc_buffer_put_tail(buffer, pd, pd_len);
463 silc_buffer_pull_tail(buffer, pd_len);
467 memset(tmp, 0, sizeof(tmp));
468 silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
469 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
473 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
474 silc_buffer_pull_tail(buffer, strlen(tmp));
477 ret = silc_buffer_steal(buffer, encoded_len);
478 silc_buffer_free(buffer);
483 /* Assembles MIME message from partial MIME messages */
485 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
487 char *type, *id = NULL, *tmp;
488 SilcMimeFragmentIdStruct *fragid, query;
490 SilcMime p, complete;
491 int i, number, total = -1;
492 const unsigned char *data;
494 SilcBuffer compbuf = NULL;
496 SILC_LOG_DEBUG(("Assembling MIME fragments"));
498 if (!assembler || !partial) {
499 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
503 type = (char *)silc_mime_get_field(partial, "Content-Type");
505 silc_set_errno(SILC_ERR_BAD_ENCODING);
510 tmp = strstr(type, "id=");
512 silc_set_errno(SILC_ERR_BAD_ENCODING);
515 if (strlen(tmp) <= 4) {
516 silc_set_errno(SILC_ERR_OVERFLOW);
522 id = silc_strdup(tmp);
524 *strchr(id, ';') = '\0';
525 if (strrchr(id, '"'))
526 *strrchr(id, '"') = '\0';
528 SILC_LOG_DEBUG(("Fragment ID %s", id));
530 /* Get fragment number */
531 tmp = strstr(type, "number=");
533 silc_set_errno(SILC_ERR_BAD_ENCODING);
536 tmp = strchr(tmp, '=');
537 if (strlen(tmp) < 2) {
538 silc_set_errno(SILC_ERR_OVERFLOW);
542 if (strchr(tmp, ';')) {
543 tmp = silc_strdup(tmp);
544 *strchr(tmp, ';') = '\0';
551 SILC_LOG_DEBUG(("Fragment number %d", number));
553 /* Find fragments with this ID. */
555 if (!silc_hash_table_find(assembler->fragments, (void *)&query,
557 /* This is new fragment to new message. Add to hash table and return. */
558 f = silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL,
559 silc_mime_assemble_dest, NULL, TRUE);
563 fragid = silc_calloc(1, sizeof(*fragid));
567 fragid->starttime = silc_time();
569 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
570 silc_hash_table_add(assembler->fragments, fragid, f);
574 /* Try to get total number */
575 tmp = strstr(type, "total=");
577 tmp = strchr(tmp, '=');
578 if (strlen(tmp) < 2) {
579 silc_set_errno(SILC_ERR_OVERFLOW);
583 if (strchr(tmp, ';')) {
584 tmp = silc_strdup(tmp);
585 *strchr(tmp, ';') = '\0';
592 SILC_LOG_DEBUG(("Fragment total %d", total));
595 /* If more fragments to come, add to hash table */
596 if (number != total) {
597 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
602 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
604 /* Verify that we really have all the fragments */
605 if (silc_hash_table_count(f) < total) {
610 /* Assemble the complete MIME message now. We get them in order from
612 for (i = 1; i <= total; i++) {
613 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
616 /* The fragment is in the data portion of the partial message */
617 data = silc_mime_get_data(p, &data_len);
619 silc_set_errno(SILC_ERR_BAD_ENCODING);
625 compbuf = silc_buffer_alloc_size(data_len);
628 silc_buffer_put(compbuf, data, data_len);
630 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
634 silc_buffer_put_tail(compbuf, data, data_len);
635 silc_buffer_pull_tail(compbuf, data_len);
639 /* Now parse the complete MIME message and deliver it */
640 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
641 silc_buffer_truelen(compbuf));
645 /* Delete the hash table entry. Destructors will free memory */
646 silc_hash_table_del(assembler->fragments, (void *)&query);
648 silc_buffer_free(compbuf);
655 silc_buffer_free(compbuf);
656 silc_mime_free(partial);
660 /* Encodes partial MIME messages */
662 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
664 unsigned char *buf, *tmp;
665 SilcUInt32 buf_len, len, tmp_len, off;
669 char type[128], id[64];
672 SILC_LOG_DEBUG(("Fragmenting MIME message"));
674 /* Encode as normal */
675 buf = silc_mime_encode(mime, &buf_len);
679 list = silc_dlist_init();
681 /* Fragment if it is too large */
682 if (buf_len > max_size) {
683 memset(id, 0, sizeof(id));
684 memset(type, 0, sizeof(type));
685 gethostname(type, sizeof(type) - 1);
686 srand((time(NULL) + buf_len) ^ rand());
687 silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
688 (unsigned int)rand(), (unsigned int)time(NULL),
689 (unsigned int)buf_len, type);
691 SILC_LOG_DEBUG(("Fragment ID %s", id));
693 partial = silc_mime_alloc();
697 silc_mime_add_field(partial, "MIME-Version", "1.0");
698 memset(type, 0, sizeof(type));
699 silc_snprintf(type, sizeof(type) - 1,
700 "message/partial; id=\"%s\"; number=1", id);
701 silc_mime_add_field(partial, "Content-Type", type);
702 silc_mime_add_data(partial, buf, max_size);
704 tmp = silc_mime_encode(partial, &tmp_len);
707 silc_mime_free(partial);
710 buffer = silc_buffer_alloc_size(tmp_len);
713 silc_buffer_put(buffer, tmp, tmp_len);
714 silc_dlist_add(list, buffer);
717 len = buf_len - max_size;
721 partial = silc_mime_alloc();
725 memset(type, 0, sizeof(type));
726 silc_mime_add_field(partial, "MIME-Version", "1.0");
728 if (len > max_size) {
729 silc_snprintf(type, sizeof(type) - 1,
730 "message/partial; id=\"%s\"; number=%d",
732 silc_mime_add_data(partial, buf + off, max_size);
736 silc_snprintf(type, sizeof(type) - 1,
737 "message/partial; id=\"%s\"; number=%d; total=%d",
739 silc_mime_add_data(partial, buf + off, len);
743 silc_mime_add_field(partial, "Content-Type", type);
745 tmp = silc_mime_encode(partial, &tmp_len);
748 silc_mime_free(partial);
751 buffer = silc_buffer_alloc_size(tmp_len);
754 silc_buffer_put(buffer, tmp, tmp_len);
755 silc_dlist_add(list, buffer);
759 /* No need to fragment */
760 buffer = silc_buffer_alloc_size(buf_len);
763 silc_buffer_put(buffer, buf, buf_len);
764 silc_dlist_add(list, buffer);
772 /* Free partial MIME list */
774 void silc_mime_partial_free(SilcDList partials)
781 silc_dlist_start(partials);
782 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
783 silc_buffer_free(buf);
784 silc_dlist_uninit(partials);
789 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
791 if (!mime || !field || !value)
794 silc_hash_table_add(mime->fields, silc_strdup(field), silc_strdup(value));
799 const char *silc_mime_get_field(SilcMime mime, const char *field)
806 if (!silc_hash_table_find(mime->fields, (void *)field,
807 NULL, (void *)&value))
810 return (const char *)value;
815 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
822 silc_free(mime->data);
824 mime->data = silc_memdup(data, data_len);
825 mime->data_len = data_len;
830 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
836 *data_len = mime->data_len;
843 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
851 *data_len = mime->data_len;
861 /* Returns TRUE if partial message */
863 SilcBool silc_mime_is_partial(SilcMime mime)
865 const char *type = silc_mime_get_field(mime, "Content-Type");
869 if (!strstr(type, "message/partial"))
875 /* Set as multipart message */
877 void silc_mime_set_multipart(SilcMime mime, const char *type,
878 const char *boundary)
882 if (!mime || !type || !boundary)
885 memset(tmp, 0, sizeof(tmp));
886 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
887 silc_mime_add_field(mime, "Content-Type", tmp);
888 silc_free(mime->boundary);
889 mime->boundary = silc_strdup(boundary);
891 if (mime->multiparts)
893 mime->multiparts = silc_dlist_init();
898 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
900 if (!mime || !mime->multiparts || !part) {
901 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
905 silc_dlist_add(mime->multiparts, part);
909 /* Return TRUE if has multiparts */
911 SilcBool silc_mime_is_multipart(SilcMime mime)
914 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
918 return mime->multiparts != NULL;
921 /* Returns multiparts */
923 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
926 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
931 *type = (const char *)mime->multitype;
933 return mime->multiparts;