5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2005 - 2006 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 /************************ Static utility functions **************************/
24 /* MIME fields destructor */
26 static void silc_mime_field_dest(void *key, void *context, void *user_context)
32 /* Assembler fragment destructor */
34 static void silc_mime_assembler_dest(void *key, void *context,
38 silc_hash_table_free(context);
41 /* Assembler partial MIME destructor */
43 static void silc_mime_assemble_dest(void *key, void *context,
46 silc_mime_free(context);
50 /******************************* Public API *********************************/
52 /* Allocate MIME context */
54 SilcMime silc_mime_alloc(void)
58 mime = silc_calloc(1, sizeof(*mime));
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);
73 /* Free MIME context */
75 void silc_mime_free(SilcMime mime)
80 silc_hash_table_free(mime->fields);
82 if (mime->multiparts) {
83 silc_dlist_start(mime->multiparts);
84 while ((m = silc_dlist_get(mime->multiparts)) != SILC_LIST_END)
86 silc_dlist_uninit(mime->multiparts);
88 silc_free(mime->boundary);
89 silc_free(mime->multitype);
90 silc_free(mime->data);
94 /* Allocate MIME assembler */
96 SilcMimeAssembler silc_mime_assembler_alloc(void)
98 SilcMimeAssembler assembler;
100 assembler = silc_calloc(1, sizeof(*assembler));
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);
116 /* Free MIME assembler */
118 void silc_mime_assembler_free(SilcMimeAssembler assembler)
120 silc_hash_table_free(assembler->fragments);
121 silc_free(assembler);
124 /* Decode MIME message */
126 SilcMime silc_mime_decode(SilcMime mime, const unsigned char *data,
131 char *tmp, *field, *value, *line;
133 SILC_LOG_DEBUG(("Parsing MIME message"));
139 mime = silc_mime_alloc();
145 /* Parse the fields */
146 line = tmp = (char *)data;
147 for (i = 0; i < data_len; i++) {
149 if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
151 field = strchr(line, ':');
154 field = silc_memdup(line, field - line);
158 /* Get value. Remove whitespaces too. */
159 value = strchr(line, ':');
160 if ((tmp + i) - value < 2)
163 for (k = 0; k < (tmp + i) - value; k++) {
164 if (value[k] == '\r')
166 if (value[k] != ' ' && value[k] != '\t')
170 if ((tmp + i) - value < 1)
172 value = silc_memdup(value, (tmp + i) - value);
176 SILC_LOG_DEBUG(("Header '%s' '%s'", field, value));
178 /* Add field and value */
179 silc_mime_add_field(mime, field, value);
183 /* Mark start of next line */
184 line = (tmp + i) + 2;
187 /* Break if this is last header */
188 if (data_len - i >= 2 &&
189 tmp[i] == '\r' && tmp[i + 1] == '\n') {
196 /* Parse multiparts if present */
197 field = (char *)silc_mime_get_field(mime, "Content-Type");
198 if (field && strstr(field, "multipart")) {
202 mime->multiparts = silc_dlist_init();
203 if (!mime->multiparts)
206 /* Get multipart type */
207 value = strchr(field, '/');
211 if (strchr(field, '"'))
213 if (!strchr(field, ';'))
215 memset(b, 0, sizeof(b));
216 strncat(b, value, strchr(field, ';') - value);
218 *strchr(b, '"') = '\0';
219 mime->multitype = silc_memdup(b, strlen(b));
222 value = strrchr(field, '=');
223 if (value && strlen(value) > 1) {
226 SILC_LOG_DEBUG(("Boundary '%s'", value));
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);
235 snprintf(b, sizeof(b) - 1, "--%s", line);
236 mime->boundary = strdup(line);
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)))
249 if (data_len - i >= 4 &&
250 tmp[i ] == '\r' && tmp[i + 1] == '\n' &&
251 tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
253 else if (data_len - i >= 2 &&
254 tmp[i] == '\r' && tmp[i + 1] == '\n')
256 else if (data_len - i >= 2 &&
257 tmp[i] == '-' && tmp[i + 1] == '-')
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)))
271 /* Remove preceding CRLF */
274 SILC_LOG_HEXDUMP(("line %d", k - i), line, k - i);
277 p = silc_mime_decode(NULL, line, k - i);
281 silc_dlist_add(mime->multiparts, p);
287 /* Get data area. If we are at the end and we have fields present
288 there is no data area present, but, if fields are not present we
289 only have data area. */
290 if (i >= data_len && !silc_hash_table_count(mime->fields))
292 SILC_LOG_DEBUG(("Data len %d", data_len - i));
294 silc_mime_add_data(mime, tmp + i, data_len - i);
305 /* Encode MIME message */
307 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
310 SilcHashTableList htl;
311 SilcBufferStruct buf;
313 char *field, *value, tmp[1024], tmp2[4];
317 SILC_LOG_DEBUG(("Encoding MIME message"));
322 memset(&buf, 0, sizeof(buf));
324 /* Encode the headers. Order doesn't matter */
326 silc_hash_table_list(mime->fields, &htl);
327 while (silc_hash_table_get(&htl, (void **)&field, (void **)&value)) {
328 memset(tmp, 0, sizeof(tmp));
329 SILC_LOG_DEBUG(("Header %s: %s", field, value));
330 snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
331 silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
334 silc_hash_table_list_reset(&htl);
336 silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
338 /* Assemble the whole buffer */
339 buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
344 if (silc_buffer_len(&buf)) {
345 silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
346 silc_buffer_pull(buffer, silc_buffer_len(&buf));
351 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
352 silc_buffer_put(buffer, mime->data, mime->data_len);
356 if (mime->multiparts) {
357 SILC_LOG_DEBUG(("Encoding multiparts"));
359 silc_dlist_start(mime->multiparts);
361 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
365 /* Recursive encoding */
366 pd = silc_mime_encode(part, &pd_len);
370 memset(tmp, 0, sizeof(tmp));
371 memset(tmp2, 0, sizeof(tmp2));
373 /* If fields are not present, add extra CRLF */
374 if (!silc_hash_table_count(part->fields))
375 snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
376 snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
377 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
380 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
381 pd_len + strlen(tmp));
384 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
385 silc_buffer_pull_tail(buffer, strlen(tmp));
386 silc_buffer_put_tail(buffer, pd, pd_len);
387 silc_buffer_pull_tail(buffer, pd_len);
391 memset(tmp, 0, sizeof(tmp));
392 snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
393 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
397 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
398 silc_buffer_pull_tail(buffer, strlen(tmp));
401 ret = silc_buffer_steal(buffer, encoded_len);
402 silc_buffer_free(buffer);
407 /* Assembles MIME message from partial MIME messages */
409 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
411 char *type, *id = NULL, *tmp;
413 SilcMime p, complete;
414 int i, number, total = -1;
415 const unsigned char *data;
417 SilcBuffer compbuf = NULL;
419 SILC_LOG_DEBUG(("Assembling MIME fragments"));
421 if (!assembler || !partial)
424 type = (char *)silc_mime_get_field(partial, "Content-Type");
429 tmp = strstr(type, "id=");
432 if (strlen(tmp) <= 4)
439 *strchr(id, ';') = '\0';
440 if (strrchr(id, '"'))
441 *strrchr(id, '"') = '\0';
443 SILC_LOG_DEBUG(("Fragment ID %s", id));
445 /* Get fragment number */
446 tmp = strstr(type, "number=");
449 tmp = strchr(tmp, '=');
453 if (strchr(tmp, ';')) {
455 *strchr(tmp, ';') = '\0';
462 SILC_LOG_DEBUG(("Fragment number %d", number));
464 /* Find fragments with this ID. */
465 if (!silc_hash_table_find(assembler->fragments, (void *)id,
466 NULL, (void **)&f)) {
467 /* This is new fragment to new message. Add to hash table and return. */
468 f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
469 silc_mime_assemble_dest, NULL, TRUE);
472 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
473 silc_hash_table_add(assembler->fragments, id, f);
477 /* Try to get total number */
478 tmp = strstr(type, "total=");
480 tmp = strchr(tmp, '=');
484 if (strchr(tmp, ';')) {
486 *strchr(tmp, ';') = '\0';
493 SILC_LOG_DEBUG(("Fragment total %d", total));
496 /* If more fragments to come, add to hash table */
497 if (number != total) {
498 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
502 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
504 /* Verify that we really have all the fragments */
505 if (silc_hash_table_count(f) < total)
508 /* Assemble the complete MIME message now. We get them in order from
510 for (i = 1; i <= total; i++) {
511 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void **)&p))
514 /* The fragment is in the data portion of the partial message */
515 data = silc_mime_get_data(p, &data_len);
521 compbuf = silc_buffer_alloc_size(data_len);
524 silc_buffer_put(compbuf, data, data_len);
526 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
530 silc_buffer_put_tail(compbuf, data, data_len);
531 silc_buffer_pull_tail(compbuf, data_len);
535 /* Now parse the complete MIME message and deliver it */
536 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
537 silc_buffer_truelen(compbuf));
541 /* Delete the hash table entry. Destructors will free memory */
542 silc_hash_table_del(assembler->fragments, (void *)id);
544 silc_buffer_free(compbuf);
551 silc_buffer_free(compbuf);
552 silc_mime_free(partial);
556 /* Encodes partial MIME messages */
558 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
560 unsigned char *buf, *tmp;
561 SilcUInt32 buf_len, len, tmp_len, off;
565 char type[128], id[64];
568 SILC_LOG_DEBUG(("Fragmenting MIME message"));
570 /* Encode as normal */
571 buf = silc_mime_encode(mime, &buf_len);
575 list = silc_dlist_init();
577 /* Fragment if it is too large */
578 if (buf_len > max_size) {
579 memset(id, 0, sizeof(id));
580 memset(type, 0, sizeof(type));
581 gethostname(type, sizeof(type) - 1);
582 srand((time(NULL) + buf_len) ^ rand());
583 snprintf(id, sizeof(id) - 1, "%X%X%X%s",
584 (unsigned int)rand(), (unsigned int)time(NULL),
585 (unsigned int)buf_len, type);
587 SILC_LOG_DEBUG(("Fragment ID %s", id));
589 partial = silc_mime_alloc();
593 silc_mime_add_field(partial, "MIME-Version", "1.0");
594 memset(type, 0, sizeof(type));
595 snprintf(type, sizeof(type) - 1,
596 "message/partial; id=\"%s\"; number=1", id);
597 silc_mime_add_field(partial, "Content-Type", type);
598 silc_mime_add_data(partial, buf, max_size);
600 tmp = silc_mime_encode(partial, &tmp_len);
603 silc_mime_free(partial);
606 buffer = silc_buffer_alloc_size(tmp_len);
609 silc_buffer_put(buffer, tmp, tmp_len);
610 silc_dlist_add(list, buffer);
613 len = buf_len - max_size;
617 partial = silc_mime_alloc();
621 memset(type, 0, sizeof(type));
622 silc_mime_add_field(partial, "MIME-Version", "1.0");
624 if (len > max_size) {
625 snprintf(type, sizeof(type) - 1,
626 "message/partial; id=\"%s\"; number=%d",
628 silc_mime_add_data(partial, buf + off, max_size);
632 snprintf(type, sizeof(type) - 1,
633 "message/partial; id=\"%s\"; number=%d; total=%d",
635 silc_mime_add_data(partial, buf + off, len);
639 silc_mime_add_field(partial, "Content-Type", type);
641 tmp = silc_mime_encode(partial, &tmp_len);
644 silc_mime_free(partial);
647 buffer = silc_buffer_alloc_size(tmp_len);
650 silc_buffer_put(buffer, tmp, tmp_len);
651 silc_dlist_add(list, buffer);
655 /* No need to fragment */
656 buffer = silc_buffer_alloc_size(buf_len);
659 silc_buffer_put(buffer, buf, buf_len);
660 silc_dlist_add(list, buffer);
668 /* Free partial MIME list */
670 void silc_mime_partial_free(SilcDList partials)
677 silc_dlist_start(partials);
678 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
679 silc_buffer_free(buf);
680 silc_dlist_uninit(partials);
685 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
687 if (!mime || !field || !value)
690 silc_hash_table_add(mime->fields, strdup(field), strdup(value));
695 const char *silc_mime_get_field(SilcMime mime, const char *field)
702 if (!silc_hash_table_find(mime->fields, (void *)field,
703 NULL, (void **)&value))
706 return (const char *)value;
711 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
718 silc_free(mime->data);
720 mime->data = silc_memdup(data, data_len);
721 mime->data_len = data_len;
726 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
732 *data_len = mime->data_len;
739 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
747 *data_len = mime->data_len;
757 /* Returns TRUE if partial message */
759 SilcBool silc_mime_is_partial(SilcMime mime)
761 const char *type = silc_mime_get_field(mime, "Content-Type");
765 if (!strstr(type, "message/partial"))
771 /* Set as multipart message */
773 void silc_mime_set_multipart(SilcMime mime, const char *type,
774 const char *boundary)
778 if (!mime || !type || !boundary)
781 memset(tmp, 0, sizeof(tmp));
782 snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
783 silc_mime_add_field(mime, "Content-Type", tmp);
784 silc_free(mime->boundary);
785 mime->boundary = strdup(boundary);
787 if (mime->multiparts)
789 mime->multiparts = silc_dlist_init();
794 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
796 if (!mime || !mime->multiparts || !part)
799 silc_dlist_add(mime->multiparts, part);
803 /* Return TRUE if has multiparts */
805 SilcBool silc_mime_is_multipart(SilcMime mime)
810 return mime->multiparts != NULL;
813 /* Returns multiparts */
815 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
821 *type = (const char *)mime->multitype;
823 return mime->multiparts;