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")) {
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 silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
233 mime->boundary = strdup(line + 1);
235 silc_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 */
275 p = silc_mime_decode(NULL, line, k - i);
279 silc_dlist_add(mime->multiparts, p);
285 /* Get data area. If we are at the end and we have fields present
286 there is no data area present, but, if fields are not present we
287 only have data area. */
288 if (i >= data_len && !silc_hash_table_count(mime->fields))
290 SILC_LOG_DEBUG(("Data len %d", data_len - i));
292 silc_mime_add_data(mime, tmp + i, data_len - i);
303 /* Encode MIME message */
305 unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
308 SilcHashTableList htl;
309 SilcBufferStruct buf;
311 char *field, *value, tmp[1024], tmp2[4];
315 SILC_LOG_DEBUG(("Encoding MIME message"));
320 memset(&buf, 0, sizeof(buf));
322 /* Encode the headers. Order doesn't matter */
324 silc_hash_table_list(mime->fields, &htl);
325 while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
326 memset(tmp, 0, sizeof(tmp));
327 SILC_LOG_DEBUG(("Header %s: %s", field, value));
328 silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
329 silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
332 silc_hash_table_list_reset(&htl);
334 silc_buffer_strformat(&buf, "\r\n", SILC_STRFMT_END);
336 /* Assemble the whole buffer */
337 buffer = silc_buffer_alloc_size(mime->data_len + silc_buffer_len(&buf));
342 if (silc_buffer_len(&buf)) {
343 silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
344 silc_buffer_pull(buffer, silc_buffer_len(&buf));
345 silc_buffer_purge(&buf);
350 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
351 silc_buffer_put(buffer, mime->data, mime->data_len);
355 if (mime->multiparts) {
356 SILC_LOG_DEBUG(("Encoding multiparts"));
358 silc_dlist_start(mime->multiparts);
360 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
364 /* Recursive encoding */
365 pd = silc_mime_encode(part, &pd_len);
369 memset(tmp, 0, sizeof(tmp));
370 memset(tmp2, 0, sizeof(tmp2));
372 /* If fields are not present, add extra CRLF */
373 if (!silc_hash_table_count(part->fields))
374 silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
375 silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
376 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
379 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
380 pd_len + strlen(tmp));
383 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
384 silc_buffer_pull_tail(buffer, strlen(tmp));
385 silc_buffer_put_tail(buffer, pd, pd_len);
386 silc_buffer_pull_tail(buffer, pd_len);
390 memset(tmp, 0, sizeof(tmp));
391 silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
392 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
396 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
397 silc_buffer_pull_tail(buffer, strlen(tmp));
400 ret = silc_buffer_steal(buffer, encoded_len);
401 silc_buffer_free(buffer);
406 /* Assembles MIME message from partial MIME messages */
408 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
410 char *type, *id = NULL, *tmp;
412 SilcMime p, complete;
413 int i, number, total = -1;
414 const unsigned char *data;
416 SilcBuffer compbuf = NULL;
418 SILC_LOG_DEBUG(("Assembling MIME fragments"));
420 if (!assembler || !partial)
423 type = (char *)silc_mime_get_field(partial, "Content-Type");
428 tmp = strstr(type, "id=");
431 if (strlen(tmp) <= 4)
438 *strchr(id, ';') = '\0';
439 if (strrchr(id, '"'))
440 *strrchr(id, '"') = '\0';
442 SILC_LOG_DEBUG(("Fragment ID %s", id));
444 /* Get fragment number */
445 tmp = strstr(type, "number=");
448 tmp = strchr(tmp, '=');
452 if (strchr(tmp, ';')) {
454 *strchr(tmp, ';') = '\0';
461 SILC_LOG_DEBUG(("Fragment number %d", number));
463 /* Find fragments with this ID. */
464 if (!silc_hash_table_find(assembler->fragments, (void *)id,
466 /* This is new fragment to new message. Add to hash table and return. */
467 f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
468 silc_mime_assemble_dest, NULL, TRUE);
471 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
472 silc_hash_table_add(assembler->fragments, id, f);
476 /* Try to get total number */
477 tmp = strstr(type, "total=");
479 tmp = strchr(tmp, '=');
483 if (strchr(tmp, ';')) {
485 *strchr(tmp, ';') = '\0';
492 SILC_LOG_DEBUG(("Fragment total %d", total));
495 /* If more fragments to come, add to hash table */
496 if (number != total) {
497 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
501 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
503 /* Verify that we really have all the fragments */
504 if (silc_hash_table_count(f) < total)
507 /* Assemble the complete MIME message now. We get them in order from
509 for (i = 1; i <= total; i++) {
510 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
513 /* The fragment is in the data portion of the partial message */
514 data = silc_mime_get_data(p, &data_len);
520 compbuf = silc_buffer_alloc_size(data_len);
523 silc_buffer_put(compbuf, data, data_len);
525 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
529 silc_buffer_put_tail(compbuf, data, data_len);
530 silc_buffer_pull_tail(compbuf, data_len);
534 /* Now parse the complete MIME message and deliver it */
535 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
536 silc_buffer_truelen(compbuf));
540 /* Delete the hash table entry. Destructors will free memory */
541 silc_hash_table_del(assembler->fragments, (void *)id);
543 silc_buffer_free(compbuf);
550 silc_buffer_free(compbuf);
551 silc_mime_free(partial);
555 /* Encodes partial MIME messages */
557 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
559 unsigned char *buf, *tmp;
560 SilcUInt32 buf_len, len, tmp_len, off;
564 char type[128], id[64];
567 SILC_LOG_DEBUG(("Fragmenting MIME message"));
569 /* Encode as normal */
570 buf = silc_mime_encode(mime, &buf_len);
574 list = silc_dlist_init();
576 /* Fragment if it is too large */
577 if (buf_len > max_size) {
578 memset(id, 0, sizeof(id));
579 memset(type, 0, sizeof(type));
580 gethostname(type, sizeof(type) - 1);
581 srand((time(NULL) + buf_len) ^ rand());
582 silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
583 (unsigned int)rand(), (unsigned int)time(NULL),
584 (unsigned int)buf_len, type);
586 SILC_LOG_DEBUG(("Fragment ID %s", id));
588 partial = silc_mime_alloc();
592 silc_mime_add_field(partial, "MIME-Version", "1.0");
593 memset(type, 0, sizeof(type));
594 silc_snprintf(type, sizeof(type) - 1,
595 "message/partial; id=\"%s\"; number=1", id);
596 silc_mime_add_field(partial, "Content-Type", type);
597 silc_mime_add_data(partial, buf, max_size);
599 tmp = silc_mime_encode(partial, &tmp_len);
602 silc_mime_free(partial);
605 buffer = silc_buffer_alloc_size(tmp_len);
608 silc_buffer_put(buffer, tmp, tmp_len);
609 silc_dlist_add(list, buffer);
612 len = buf_len - max_size;
616 partial = silc_mime_alloc();
620 memset(type, 0, sizeof(type));
621 silc_mime_add_field(partial, "MIME-Version", "1.0");
623 if (len > max_size) {
624 silc_snprintf(type, sizeof(type) - 1,
625 "message/partial; id=\"%s\"; number=%d",
627 silc_mime_add_data(partial, buf + off, max_size);
631 silc_snprintf(type, sizeof(type) - 1,
632 "message/partial; id=\"%s\"; number=%d; total=%d",
634 silc_mime_add_data(partial, buf + off, len);
638 silc_mime_add_field(partial, "Content-Type", type);
640 tmp = silc_mime_encode(partial, &tmp_len);
643 silc_mime_free(partial);
646 buffer = silc_buffer_alloc_size(tmp_len);
649 silc_buffer_put(buffer, tmp, tmp_len);
650 silc_dlist_add(list, buffer);
654 /* No need to fragment */
655 buffer = silc_buffer_alloc_size(buf_len);
658 silc_buffer_put(buffer, buf, buf_len);
659 silc_dlist_add(list, buffer);
667 /* Free partial MIME list */
669 void silc_mime_partial_free(SilcDList partials)
676 silc_dlist_start(partials);
677 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
678 silc_buffer_free(buf);
679 silc_dlist_uninit(partials);
684 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
686 if (!mime || !field || !value)
689 silc_hash_table_add(mime->fields, strdup(field), strdup(value));
694 const char *silc_mime_get_field(SilcMime mime, const char *field)
701 if (!silc_hash_table_find(mime->fields, (void *)field,
702 NULL, (void *)&value))
705 return (const char *)value;
710 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
717 silc_free(mime->data);
719 mime->data = silc_memdup(data, data_len);
720 mime->data_len = data_len;
725 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
731 *data_len = mime->data_len;
738 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
746 *data_len = mime->data_len;
756 /* Returns TRUE if partial message */
758 SilcBool silc_mime_is_partial(SilcMime mime)
760 const char *type = silc_mime_get_field(mime, "Content-Type");
764 if (!strstr(type, "message/partial"))
770 /* Set as multipart message */
772 void silc_mime_set_multipart(SilcMime mime, const char *type,
773 const char *boundary)
777 if (!mime || !type || !boundary)
780 memset(tmp, 0, sizeof(tmp));
781 silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
782 silc_mime_add_field(mime, "Content-Type", tmp);
783 silc_free(mime->boundary);
784 mime->boundary = strdup(boundary);
786 if (mime->multiparts)
788 mime->multiparts = silc_dlist_init();
793 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
795 if (!mime || !mime->multiparts || !part)
798 silc_dlist_add(mime->multiparts, part);
802 /* Return TRUE if has multiparts */
804 SilcBool silc_mime_is_multipart(SilcMime mime)
809 return mime->multiparts != NULL;
812 /* Returns multiparts */
814 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
820 *type = (const char *)mime->multitype;
822 return mime->multiparts;