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 /************************ 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")) {
203 mime->multiparts = silc_dlist_init();
204 if (!mime->multiparts)
207 /* Get multipart type */
208 value = strchr(field, '/');
212 if (strchr(field, '"'))
214 if (!strchr(field, ';'))
216 memset(b, 0, sizeof(b));
217 len = (unsigned int)(strchr(field, ';') - value);
218 if (len > sizeof(b) - 1)
220 strncpy(b, value, len);
222 *strchr(b, '"') = '\0';
223 mime->multitype = silc_memdup(b, strlen(b));
226 value = strrchr(field, '=');
227 if (value && strlen(value) > 1) {
230 SILC_LOG_DEBUG(("Boundary '%s'", value));
232 memset(b, 0, sizeof(b));
233 line = strdup(value);
234 if (strrchr(line, '"')) {
235 *strrchr(line, '"') = '\0';
236 silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
237 mime->boundary = strdup(line + 1);
239 silc_snprintf(b, sizeof(b) - 1, "--%s", line);
240 mime->boundary = strdup(line);
244 for (i = i; i < data_len; i++) {
245 /* Get boundary data */
246 if (data_len - i >= strlen(b) &&
247 tmp[i] == '-' && tmp[i + 1] == '-') {
248 if (memcmp(tmp + i, b, strlen(b)))
253 if (data_len - i >= 4 &&
254 tmp[i ] == '\r' && tmp[i + 1] == '\n' &&
255 tmp[i + 2] == '\r' && tmp[i + 3] == '\n')
257 else if (data_len - i >= 2 &&
258 tmp[i] == '\r' && tmp[i + 1] == '\n')
260 else if (data_len - i >= 2 &&
261 tmp[i] == '-' && tmp[i + 1] == '-')
266 /* Find end of boundary */
267 for (k = i; k < data_len; k++)
268 if (data_len - k >= strlen(b) &&
269 tmp[k] == '-' && tmp[k + 1] == '-')
270 if (!memcmp(tmp + k, b, strlen(b)))
275 /* Remove preceding CRLF */
279 p = silc_mime_decode(NULL, line, k - i);
283 silc_dlist_add(mime->multiparts, p);
289 /* Get data area. If we are at the end and we have fields present
290 there is no data area present, but, if fields are not present we
291 only have data area. */
292 if (i >= data_len && !silc_hash_table_count(mime->fields))
294 SILC_LOG_DEBUG(("Data len %d", data_len - i));
296 silc_mime_add_data(mime, tmp + i, data_len - i);
307 /* Encode MIME message */
309 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
312 SilcHashTableList htl;
313 SilcBufferStruct buf;
315 char *field, *value, tmp[1024], tmp2[4];
319 SILC_LOG_DEBUG(("Encoding MIME message"));
324 memset(&buf, 0, sizeof(buf));
326 /* Encode the headers. Order doesn't matter */
328 silc_hash_table_list(mime->fields, &htl);
329 while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
330 memset(tmp, 0, sizeof(tmp));
331 SILC_LOG_DEBUG(("Header %s: %s", field, value));
332 silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
333 silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
336 silc_hash_table_list_reset(&htl);
338 silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
340 /* Assemble the whole buffer */
341 buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
346 if (silc_buffer_len(&buf)) {
347 silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
348 silc_buffer_pull(buffer, silc_buffer_len(&buf));
349 silc_buffer_purge(&buf);
354 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
355 silc_buffer_put(buffer, mime->data, mime->data_len);
359 if (mime->multiparts) {
360 SILC_LOG_DEBUG(("Encoding multiparts"));
362 silc_dlist_start(mime->multiparts);
364 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
368 /* Recursive encoding */
369 pd = silc_mime_encode(part, &pd_len);
373 memset(tmp, 0, sizeof(tmp));
374 memset(tmp2, 0, sizeof(tmp2));
376 /* If fields are not present, add extra CRLF */
377 if (!silc_hash_table_count(part->fields))
378 silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
379 silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
380 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
383 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
384 pd_len + strlen(tmp));
387 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
388 silc_buffer_pull_tail(buffer, strlen(tmp));
389 silc_buffer_put_tail(buffer, pd, pd_len);
390 silc_buffer_pull_tail(buffer, pd_len);
394 memset(tmp, 0, sizeof(tmp));
395 silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
396 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
400 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
401 silc_buffer_pull_tail(buffer, strlen(tmp));
404 ret = silc_buffer_steal(buffer, encoded_len);
405 silc_buffer_free(buffer);
410 /* Assembles MIME message from partial MIME messages */
412 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
414 char *type, *id = NULL, *tmp;
416 SilcMime p, complete;
417 int i, number, total = -1;
418 const unsigned char *data;
420 SilcBuffer compbuf = NULL;
422 SILC_LOG_DEBUG(("Assembling MIME fragments"));
424 if (!assembler || !partial)
427 type = (char *)silc_mime_get_field(partial, "Content-Type");
432 tmp = strstr(type, "id=");
435 if (strlen(tmp) <= 4)
442 *strchr(id, ';') = '\0';
443 if (strrchr(id, '"'))
444 *strrchr(id, '"') = '\0';
446 SILC_LOG_DEBUG(("Fragment ID %s", id));
448 /* Get fragment number */
449 tmp = strstr(type, "number=");
452 tmp = strchr(tmp, '=');
456 if (strchr(tmp, ';')) {
458 *strchr(tmp, ';') = '\0';
465 SILC_LOG_DEBUG(("Fragment number %d", number));
467 /* Find fragments with this ID. */
468 if (!silc_hash_table_find(assembler->fragments, (void *)id,
470 /* This is new fragment to new message. Add to hash table and return. */
471 f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
472 silc_mime_assemble_dest, NULL, TRUE);
475 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
476 silc_hash_table_add(assembler->fragments, id, f);
480 /* Try to get total number */
481 tmp = strstr(type, "total=");
483 tmp = strchr(tmp, '=');
487 if (strchr(tmp, ';')) {
489 *strchr(tmp, ';') = '\0';
496 SILC_LOG_DEBUG(("Fragment total %d", total));
499 /* If more fragments to come, add to hash table */
500 if (number != total) {
501 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
505 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
507 /* Verify that we really have all the fragments */
508 if (silc_hash_table_count(f) < total)
511 /* Assemble the complete MIME message now. We get them in order from
513 for (i = 1; i <= total; i++) {
514 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
517 /* The fragment is in the data portion of the partial message */
518 data = silc_mime_get_data(p, &data_len);
524 compbuf = silc_buffer_alloc_size(data_len);
527 silc_buffer_put(compbuf, data, data_len);
529 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
533 silc_buffer_put_tail(compbuf, data, data_len);
534 silc_buffer_pull_tail(compbuf, data_len);
538 /* Now parse the complete MIME message and deliver it */
539 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
540 silc_buffer_truelen(compbuf));
544 /* Delete the hash table entry. Destructors will free memory */
545 silc_hash_table_del(assembler->fragments, (void *)id);
547 silc_buffer_free(compbuf);
554 silc_buffer_free(compbuf);
555 silc_mime_free(partial);
559 /* Encodes partial MIME messages */
561 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
563 unsigned char *buf, *tmp;
564 SilcUInt32 buf_len, len, tmp_len, off;
568 char type[128], id[64];
571 SILC_LOG_DEBUG(("Fragmenting MIME message"));
573 /* Encode as normal */
574 buf = silc_mime_encode(mime, &buf_len);
578 list = silc_dlist_init();
580 /* Fragment if it is too large */
581 if (buf_len > max_size) {
582 memset(id, 0, sizeof(id));
583 memset(type, 0, sizeof(type));
584 gethostname(type, sizeof(type) - 1);
585 srand((time(NULL) + buf_len) ^ rand());
586 silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
587 (unsigned int)rand(), (unsigned int)time(NULL),
588 (unsigned int)buf_len, type);
590 SILC_LOG_DEBUG(("Fragment ID %s", id));
592 partial = silc_mime_alloc();
596 silc_mime_add_field(partial, "MIME-Version", "1.0");
597 memset(type, 0, sizeof(type));
598 silc_snprintf(type, sizeof(type) - 1,
599 "message/partial; id=\"%s\"; number=1", id);
600 silc_mime_add_field(partial, "Content-Type", type);
601 silc_mime_add_data(partial, buf, max_size);
603 tmp = silc_mime_encode(partial, &tmp_len);
606 silc_mime_free(partial);
609 buffer = silc_buffer_alloc_size(tmp_len);
612 silc_buffer_put(buffer, tmp, tmp_len);
613 silc_dlist_add(list, buffer);
616 len = buf_len - max_size;
620 partial = silc_mime_alloc();
624 memset(type, 0, sizeof(type));
625 silc_mime_add_field(partial, "MIME-Version", "1.0");
627 if (len > max_size) {
628 silc_snprintf(type, sizeof(type) - 1,
629 "message/partial; id=\"%s\"; number=%d",
631 silc_mime_add_data(partial, buf + off, max_size);
635 silc_snprintf(type, sizeof(type) - 1,
636 "message/partial; id=\"%s\"; number=%d; total=%d",
638 silc_mime_add_data(partial, buf + off, len);
642 silc_mime_add_field(partial, "Content-Type", type);
644 tmp = silc_mime_encode(partial, &tmp_len);
647 silc_mime_free(partial);
650 buffer = silc_buffer_alloc_size(tmp_len);
653 silc_buffer_put(buffer, tmp, tmp_len);
654 silc_dlist_add(list, buffer);
658 /* No need to fragment */
659 buffer = silc_buffer_alloc_size(buf_len);
662 silc_buffer_put(buffer, buf, buf_len);
663 silc_dlist_add(list, buffer);
671 /* Free partial MIME list */
673 void silc_mime_partial_free(SilcDList partials)
680 silc_dlist_start(partials);
681 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
682 silc_buffer_free(buf);
683 silc_dlist_uninit(partials);
688 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
690 if (!mime || !field || !value)
693 silc_hash_table_add(mime->fields, strdup(field), strdup(value));
698 const char *silc_mime_get_field(SilcMime mime, const char *field)
705 if (!silc_hash_table_find(mime->fields, (void *)field,
706 NULL, (void *)&value))
709 return (const char *)value;
714 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
721 silc_free(mime->data);
723 mime->data = silc_memdup(data, data_len);
724 mime->data_len = data_len;
729 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
735 *data_len = mime->data_len;
742 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
750 *data_len = mime->data_len;
760 /* Returns TRUE if partial message */
762 SilcBool silc_mime_is_partial(SilcMime mime)
764 const char *type = silc_mime_get_field(mime, "Content-Type");
768 if (!strstr(type, "message/partial"))
774 /* Set as multipart message */
776 void silc_mime_set_multipart(SilcMime mime, const char *type,
777 const char *boundary)
781 if (!mime || !type || !boundary)
784 memset(tmp, 0, sizeof(tmp));
785 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
786 silc_mime_add_field(mime, "Content-Type", tmp);
787 silc_free(mime->boundary);
788 mime->boundary = strdup(boundary);
790 if (mime->multiparts)
792 mime->multiparts = silc_dlist_init();
797 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
799 if (!mime || !mime->multiparts || !part)
802 silc_dlist_add(mime->multiparts, part);
806 /* Return TRUE if has multiparts */
808 SilcBool silc_mime_is_multipart(SilcMime mime)
813 return mime->multiparts != NULL;
816 /* Returns multiparts */
818 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
824 *type = (const char *)mime->multitype;
826 return mime->multiparts;