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"));
194 mime = silc_mime_alloc();
200 /* Parse the fields */
201 line = tmp = (char *)data;
202 for (i = 0; i < data_len; i++) {
204 if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
206 field = strchr(line, ':');
209 field = silc_memdup(line, field - line);
213 /* Get value. Remove whitespaces too. */
214 value = strchr(line, ':');
215 if ((tmp + i) - value < 2)
218 for (k = 0; k < (tmp + i) - value; k++) {
219 if (value[k] == '\r')
221 if (value[k] != ' ' && value[k] != '\t')
225 if ((tmp + i) - value < 1)
227 value = silc_memdup(value, (tmp + i) - value);
231 SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
233 /* Add field and value */
234 silc_mime_add_field(mime, field, value);
238 /* Mark start of next line */
239 line = (tmp + i) + 2;
242 /* Break if this is last header */
243 if (data_len - i >= 2 &&
244 tmp[i] == '\r' && tmp[i + 1] == '\n') {
251 /* Parse multiparts if present */
252 field = (char *)silc_mime_get_field(mime, "Content-Type");
253 if (field && strstr(field, "multipart")) {
258 mime->multiparts = silc_dlist_init();
259 if (!mime->multiparts)
262 /* Get multipart type */
263 value = strchr(field, '/');
267 if (strchr(field, '"'))
269 if (!strchr(field, ';'))
271 memset(b, 0, sizeof(b));
272 len = (unsigned int)(strchr(field, ';') - value);
273 if (len > sizeof(b) - 1)
275 strncpy(b, value, len);
277 *strchr(b, '"') = '\0';
278 mime->multitype = silc_memdup(b, strlen(b));
281 value = strrchr(field, '=');
282 if (value && strlen(value) > 1) {
285 SILC_LOG_DEBUG(("Boundary '%s'", value));
287 memset(b, 0, sizeof(b));
288 line = strdup(value);
289 if (strrchr(line, '"')) {
290 *strrchr(line, '"') = '\0';
291 silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
292 mime->boundary = strdup(line + 1);
294 silc_snprintf(b, sizeof(b) - 1, "--%s", line);
295 mime->boundary = strdup(line);
299 for (i = i; i < data_len; i++) {
300 /* Get boundary data */
301 if (data_len - i >= strlen(b) &&
302 tmp[i] == '-' && tmp[i + 1] == '-') {
303 if (memcmp(tmp + i, b, strlen(b)))
308 if (data_len - i >= 4 &&
309 tmp[i ] == '\r' && tmp[i + 1] == '\n' &&
310 tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
312 else if (data_len - i >= 2 &&
313 tmp[i] == '\r' && tmp[i + 1] == '\n')
315 else if (data_len - i >= 2 &&
316 tmp[i] == '-' && tmp[i + 1] == '-')
321 /* Find end of boundary */
322 for (k = i; k < data_len; k++)
323 if (data_len - k >= strlen(b) &&
324 tmp[k] == '-' && tmp[k + 1] == '-')
325 if (!memcmp(tmp + k, b, strlen(b)))
330 /* Remove preceding CRLF */
334 p = silc_mime_decode(NULL, line, k - i);
338 silc_dlist_add(mime->multiparts, p);
344 /* Get data area. If we are at the end and we have fields present
345 there is no data area present, but, if fields are not present we
346 only have data area. */
347 if (i >= data_len && !silc_hash_table_count(mime->fields))
349 SILC_LOG_DEBUG(("Data len %d", data_len - i));
351 silc_mime_add_data(mime, tmp + i, data_len - i);
362 /* Encode MIME message */
364 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
367 SilcHashTableList htl;
368 SilcBufferStruct buf;
370 char *field, *value, tmp[1024], tmp2[4];
374 SILC_LOG_DEBUG(("Encoding MIME message"));
379 memset(&buf, 0, sizeof(buf));
381 /* Encode the headers. Order doesn't matter */
383 silc_hash_table_list(mime->fields, &htl);
384 while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
385 memset(tmp, 0, sizeof(tmp));
386 SILC_LOG_DEBUG(("Header %s: %s", field, value));
387 silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
388 silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
391 silc_hash_table_list_reset(&htl);
393 silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
395 /* Assemble the whole buffer */
396 buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
401 if (silc_buffer_len(&buf)) {
402 silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
403 silc_buffer_pull(buffer, silc_buffer_len(&buf));
404 silc_buffer_purge(&buf);
409 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
410 silc_buffer_put(buffer, mime->data, mime->data_len);
414 if (mime->multiparts) {
415 SILC_LOG_DEBUG(("Encoding multiparts"));
417 silc_dlist_start(mime->multiparts);
419 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
423 /* Recursive encoding */
424 pd = silc_mime_encode(part, &pd_len);
428 memset(tmp, 0, sizeof(tmp));
429 memset(tmp2, 0, sizeof(tmp2));
431 /* If fields are not present, add extra CRLF */
432 if (!silc_hash_table_count(part->fields))
433 silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
434 silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
435 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
438 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
439 pd_len + strlen(tmp));
442 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
443 silc_buffer_pull_tail(buffer, strlen(tmp));
444 silc_buffer_put_tail(buffer, pd, pd_len);
445 silc_buffer_pull_tail(buffer, pd_len);
449 memset(tmp, 0, sizeof(tmp));
450 silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
451 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
455 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
456 silc_buffer_pull_tail(buffer, strlen(tmp));
459 ret = silc_buffer_steal(buffer, encoded_len);
460 silc_buffer_free(buffer);
465 /* Assembles MIME message from partial MIME messages */
467 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
469 char *type, *id = NULL, *tmp;
470 SilcMimeFragmentIdStruct *fragid, query;
472 SilcMime p, complete;
473 int i, number, total = -1;
474 const unsigned char *data;
476 SilcBuffer compbuf = NULL;
478 SILC_LOG_DEBUG(("Assembling MIME fragments"));
480 if (!assembler || !partial)
483 type = (char *)silc_mime_get_field(partial, "Content-Type");
488 tmp = strstr(type, "id=");
491 if (strlen(tmp) <= 4)
498 *strchr(id, ';') = '\0';
499 if (strrchr(id, '"'))
500 *strrchr(id, '"') = '\0';
502 SILC_LOG_DEBUG(("Fragment ID %s", id));
504 /* Get fragment number */
505 tmp = strstr(type, "number=");
508 tmp = strchr(tmp, '=');
512 if (strchr(tmp, ';')) {
514 *strchr(tmp, ';') = '\0';
521 SILC_LOG_DEBUG(("Fragment number %d", number));
523 /* Find fragments with this ID. */
525 if (!silc_hash_table_find(assembler->fragments, (void *)&query,
527 /* This is new fragment to new message. Add to hash table and return. */
528 f = silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL,
529 silc_mime_assemble_dest, NULL, TRUE);
533 fragid = silc_calloc(1, sizeof(*fragid));
537 fragid->starttime = silc_time();
539 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
540 silc_hash_table_add(assembler->fragments, fragid, f);
544 /* Try to get total number */
545 tmp = strstr(type, "total=");
547 tmp = strchr(tmp, '=');
551 if (strchr(tmp, ';')) {
553 *strchr(tmp, ';') = '\0';
560 SILC_LOG_DEBUG(("Fragment total %d", total));
563 /* If more fragments to come, add to hash table */
564 if (number != total) {
565 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
570 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
572 /* Verify that we really have all the fragments */
573 if (silc_hash_table_count(f) < total) {
578 /* Assemble the complete MIME message now. We get them in order from
580 for (i = 1; i <= total; i++) {
581 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
584 /* The fragment is in the data portion of the partial message */
585 data = silc_mime_get_data(p, &data_len);
591 compbuf = silc_buffer_alloc_size(data_len);
594 silc_buffer_put(compbuf, data, data_len);
596 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
600 silc_buffer_put_tail(compbuf, data, data_len);
601 silc_buffer_pull_tail(compbuf, data_len);
605 /* Now parse the complete MIME message and deliver it */
606 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
607 silc_buffer_truelen(compbuf));
611 /* Delete the hash table entry. Destructors will free memory */
612 silc_hash_table_del(assembler->fragments, (void *)&query);
614 silc_buffer_free(compbuf);
621 silc_buffer_free(compbuf);
622 silc_mime_free(partial);
626 /* Encodes partial MIME messages */
628 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
630 unsigned char *buf, *tmp;
631 SilcUInt32 buf_len, len, tmp_len, off;
635 char type[128], id[64];
638 SILC_LOG_DEBUG(("Fragmenting MIME message"));
640 /* Encode as normal */
641 buf = silc_mime_encode(mime, &buf_len);
645 list = silc_dlist_init();
647 /* Fragment if it is too large */
648 if (buf_len > max_size) {
649 memset(id, 0, sizeof(id));
650 memset(type, 0, sizeof(type));
651 gethostname(type, sizeof(type) - 1);
652 srand((time(NULL) + buf_len) ^ rand());
653 silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
654 (unsigned int)rand(), (unsigned int)time(NULL),
655 (unsigned int)buf_len, type);
657 SILC_LOG_DEBUG(("Fragment ID %s", id));
659 partial = silc_mime_alloc();
663 silc_mime_add_field(partial, "MIME-Version", "1.0");
664 memset(type, 0, sizeof(type));
665 silc_snprintf(type, sizeof(type) - 1,
666 "message/partial; id=\"%s\"; number=1", id);
667 silc_mime_add_field(partial, "Content-Type", type);
668 silc_mime_add_data(partial, buf, max_size);
670 tmp = silc_mime_encode(partial, &tmp_len);
673 silc_mime_free(partial);
676 buffer = silc_buffer_alloc_size(tmp_len);
679 silc_buffer_put(buffer, tmp, tmp_len);
680 silc_dlist_add(list, buffer);
683 len = buf_len - max_size;
687 partial = silc_mime_alloc();
691 memset(type, 0, sizeof(type));
692 silc_mime_add_field(partial, "MIME-Version", "1.0");
694 if (len > max_size) {
695 silc_snprintf(type, sizeof(type) - 1,
696 "message/partial; id=\"%s\"; number=%d",
698 silc_mime_add_data(partial, buf + off, max_size);
702 silc_snprintf(type, sizeof(type) - 1,
703 "message/partial; id=\"%s\"; number=%d; total=%d",
705 silc_mime_add_data(partial, buf + off, len);
709 silc_mime_add_field(partial, "Content-Type", type);
711 tmp = silc_mime_encode(partial, &tmp_len);
714 silc_mime_free(partial);
717 buffer = silc_buffer_alloc_size(tmp_len);
720 silc_buffer_put(buffer, tmp, tmp_len);
721 silc_dlist_add(list, buffer);
725 /* No need to fragment */
726 buffer = silc_buffer_alloc_size(buf_len);
729 silc_buffer_put(buffer, buf, buf_len);
730 silc_dlist_add(list, buffer);
738 /* Free partial MIME list */
740 void silc_mime_partial_free(SilcDList partials)
747 silc_dlist_start(partials);
748 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
749 silc_buffer_free(buf);
750 silc_dlist_uninit(partials);
755 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
757 if (!mime || !field || !value)
760 silc_hash_table_add(mime->fields, strdup(field), strdup(value));
765 const char *silc_mime_get_field(SilcMime mime, const char *field)
772 if (!silc_hash_table_find(mime->fields, (void *)field,
773 NULL, (void *)&value))
776 return (const char *)value;
781 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
788 silc_free(mime->data);
790 mime->data = silc_memdup(data, data_len);
791 mime->data_len = data_len;
796 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
802 *data_len = mime->data_len;
809 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
817 *data_len = mime->data_len;
827 /* Returns TRUE if partial message */
829 SilcBool silc_mime_is_partial(SilcMime mime)
831 const char *type = silc_mime_get_field(mime, "Content-Type");
835 if (!strstr(type, "message/partial"))
841 /* Set as multipart message */
843 void silc_mime_set_multipart(SilcMime mime, const char *type,
844 const char *boundary)
848 if (!mime || !type || !boundary)
851 memset(tmp, 0, sizeof(tmp));
852 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
853 silc_mime_add_field(mime, "Content-Type", tmp);
854 silc_free(mime->boundary);
855 mime->boundary = strdup(boundary);
857 if (mime->multiparts)
859 mime->multiparts = silc_dlist_init();
864 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
866 if (!mime || !mime->multiparts || !part)
869 silc_dlist_add(mime->multiparts, part);
873 /* Return TRUE if has multiparts */
875 SilcBool silc_mime_is_multipart(SilcMime mime)
880 return mime->multiparts != NULL;
883 /* Returns multiparts */
885 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
891 *type = (const char *)mime->multitype;
893 return mime->multiparts;