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);
541 /* Now parse the complete MIME message and deliver it */
542 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
543 silc_buffer_truelen(compbuf));
547 /* Delete the hash table entry. Destructors will free memory */
548 silc_hash_table_del(assembler->fragments, (void *)id);
550 silc_buffer_free(compbuf);
557 silc_buffer_free(compbuf);
558 silc_mime_free(partial);
562 /* Encodes partial MIME messages */
564 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
566 unsigned char *buf, *tmp;
567 SilcUInt32 buf_len, len, tmp_len, off;
571 char type[128], id[64];
574 SILC_LOG_DEBUG(("Fragmenting MIME message"));
576 /* Encode as normal */
577 buf = silc_mime_encode(mime, &buf_len);
581 list = silc_dlist_init();
583 /* Fragment if it is too large */
584 if (buf_len > max_size) {
585 memset(id, 0, sizeof(id));
586 memset(type, 0, sizeof(type));
587 gethostname(type, sizeof(type) - 1);
588 srand((time(NULL) + buf_len) ^ rand());
589 silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
590 (unsigned int)rand(), (unsigned int)time(NULL),
591 (unsigned int)buf_len, type);
593 SILC_LOG_DEBUG(("Fragment ID %s", id));
595 partial = silc_mime_alloc();
599 silc_mime_add_field(partial, "MIME-Version", "1.0");
600 memset(type, 0, sizeof(type));
601 silc_snprintf(type, sizeof(type) - 1,
602 "message/partial; id=\"%s\"; number=1", id);
603 silc_mime_add_field(partial, "Content-Type", type);
604 silc_mime_add_data(partial, buf, max_size);
606 tmp = silc_mime_encode(partial, &tmp_len);
609 silc_mime_free(partial);
612 buffer = silc_buffer_alloc_size(tmp_len);
615 silc_buffer_put(buffer, tmp, tmp_len);
616 silc_dlist_add(list, buffer);
619 len = buf_len - max_size;
623 partial = silc_mime_alloc();
627 memset(type, 0, sizeof(type));
628 silc_mime_add_field(partial, "MIME-Version", "1.0");
630 if (len > max_size) {
631 silc_snprintf(type, sizeof(type) - 1,
632 "message/partial; id=\"%s\"; number=%d",
634 silc_mime_add_data(partial, buf + off, max_size);
638 silc_snprintf(type, sizeof(type) - 1,
639 "message/partial; id=\"%s\"; number=%d; total=%d",
641 silc_mime_add_data(partial, buf + off, len);
645 silc_mime_add_field(partial, "Content-Type", type);
647 tmp = silc_mime_encode(partial, &tmp_len);
650 silc_mime_free(partial);
653 buffer = silc_buffer_alloc_size(tmp_len);
656 silc_buffer_put(buffer, tmp, tmp_len);
657 silc_dlist_add(list, buffer);
661 /* No need to fragment */
662 buffer = silc_buffer_alloc_size(buf_len);
665 silc_buffer_put(buffer, buf, buf_len);
666 silc_dlist_add(list, buffer);
674 /* Free partial MIME list */
676 void silc_mime_partial_free(SilcDList partials)
683 silc_dlist_start(partials);
684 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
685 silc_buffer_free(buf);
686 silc_dlist_uninit(partials);
691 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
693 if (!mime || !field || !value)
696 silc_hash_table_add(mime->fields, strdup(field), strdup(value));
701 const char *silc_mime_get_field(SilcMime mime, const char *field)
708 if (!silc_hash_table_find(mime->fields, (void *)field,
709 NULL, (void *)&value))
712 return (const char *)value;
717 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
724 silc_free(mime->data);
726 mime->data = silc_memdup(data, data_len);
727 mime->data_len = data_len;
732 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
738 *data_len = mime->data_len;
745 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
753 *data_len = mime->data_len;
763 /* Returns TRUE if partial message */
765 SilcBool silc_mime_is_partial(SilcMime mime)
767 const char *type = silc_mime_get_field(mime, "Content-Type");
771 if (!strstr(type, "message/partial"))
777 /* Set as multipart message */
779 void silc_mime_set_multipart(SilcMime mime, const char *type,
780 const char *boundary)
784 if (!mime || !type || !boundary)
787 memset(tmp, 0, sizeof(tmp));
788 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
789 silc_mime_add_field(mime, "Content-Type", tmp);
790 silc_free(mime->boundary);
791 mime->boundary = strdup(boundary);
793 if (mime->multiparts)
795 mime->multiparts = silc_dlist_init();
800 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
802 if (!mime || !mime->multiparts || !part)
805 silc_dlist_add(mime->multiparts, part);
809 /* Return TRUE if has multiparts */
811 SilcBool silc_mime_is_multipart(SilcMime mime)
816 return mime->multiparts != NULL;
819 /* Returns multiparts */
821 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
827 *type = (const char *)mime->multitype;
829 return mime->multiparts;