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 silc_silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
233 mime->boundary = strdup(line + 1);
235 silc_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_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));
349 SILC_LOG_DEBUG(("Data len %d", mime->data_len));
350 silc_buffer_put(buffer, mime->data, mime->data_len);
354 if (mime->multiparts) {
355 SILC_LOG_DEBUG(("Encoding multiparts"));
357 silc_dlist_start(mime->multiparts);
359 while ((part = silc_dlist_get(mime->multiparts)) != SILC_LIST_END) {
363 /* Recursive encoding */
364 pd = silc_mime_encode(part, &pd_len);
368 memset(tmp, 0, sizeof(tmp));
369 memset(tmp2, 0, sizeof(tmp2));
371 /* If fields are not present, add extra CRLF */
372 if (!silc_hash_table_count(part->fields))
373 silc_silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
374 silc_silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
375 i != 0 ? "\r\n" : "", mime->boundary, tmp2);
378 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
379 pd_len + strlen(tmp));
382 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
383 silc_buffer_pull_tail(buffer, strlen(tmp));
384 silc_buffer_put_tail(buffer, pd, pd_len);
385 silc_buffer_pull_tail(buffer, pd_len);
389 memset(tmp, 0, sizeof(tmp));
390 silc_silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
391 buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
395 silc_buffer_put_tail(buffer, tmp, strlen(tmp));
396 silc_buffer_pull_tail(buffer, strlen(tmp));
399 ret = silc_buffer_steal(buffer, encoded_len);
400 silc_buffer_free(buffer);
405 /* Assembles MIME message from partial MIME messages */
407 SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
409 char *type, *id = NULL, *tmp;
411 SilcMime p, complete;
412 int i, number, total = -1;
413 const unsigned char *data;
415 SilcBuffer compbuf = NULL;
417 SILC_LOG_DEBUG(("Assembling MIME fragments"));
419 if (!assembler || !partial)
422 type = (char *)silc_mime_get_field(partial, "Content-Type");
427 tmp = strstr(type, "id=");
430 if (strlen(tmp) <= 4)
437 *strchr(id, ';') = '\0';
438 if (strrchr(id, '"'))
439 *strrchr(id, '"') = '\0';
441 SILC_LOG_DEBUG(("Fragment ID %s", id));
443 /* Get fragment number */
444 tmp = strstr(type, "number=");
447 tmp = strchr(tmp, '=');
451 if (strchr(tmp, ';')) {
453 *strchr(tmp, ';') = '\0';
460 SILC_LOG_DEBUG(("Fragment number %d", number));
462 /* Find fragments with this ID. */
463 if (!silc_hash_table_find(assembler->fragments, (void *)id,
464 NULL, (void **)&f)) {
465 /* This is new fragment to new message. Add to hash table and return. */
466 f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
467 silc_mime_assemble_dest, NULL, TRUE);
470 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
471 silc_hash_table_add(assembler->fragments, id, f);
475 /* Try to get total number */
476 tmp = strstr(type, "total=");
478 tmp = strchr(tmp, '=');
482 if (strchr(tmp, ';')) {
484 *strchr(tmp, ';') = '\0';
491 SILC_LOG_DEBUG(("Fragment total %d", total));
494 /* If more fragments to come, add to hash table */
495 if (number != total) {
496 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
500 silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
502 /* Verify that we really have all the fragments */
503 if (silc_hash_table_count(f) < total)
506 /* Assemble the complete MIME message now. We get them in order from
508 for (i = 1; i <= total; i++) {
509 if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void **)&p))
512 /* The fragment is in the data portion of the partial message */
513 data = silc_mime_get_data(p, &data_len);
519 compbuf = silc_buffer_alloc_size(data_len);
522 silc_buffer_put(compbuf, data, data_len);
524 compbuf = silc_buffer_realloc(compbuf, silc_buffer_truelen(compbuf) +
528 silc_buffer_put_tail(compbuf, data, data_len);
529 silc_buffer_pull_tail(compbuf, data_len);
533 /* Now parse the complete MIME message and deliver it */
534 complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
535 silc_buffer_truelen(compbuf));
539 /* Delete the hash table entry. Destructors will free memory */
540 silc_hash_table_del(assembler->fragments, (void *)id);
542 silc_buffer_free(compbuf);
549 silc_buffer_free(compbuf);
550 silc_mime_free(partial);
554 /* Encodes partial MIME messages */
556 SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
558 unsigned char *buf, *tmp;
559 SilcUInt32 buf_len, len, tmp_len, off;
563 char type[128], id[64];
566 SILC_LOG_DEBUG(("Fragmenting MIME message"));
568 /* Encode as normal */
569 buf = silc_mime_encode(mime, &buf_len);
573 list = silc_dlist_init();
575 /* Fragment if it is too large */
576 if (buf_len > max_size) {
577 memset(id, 0, sizeof(id));
578 memset(type, 0, sizeof(type));
579 gethostname(type, sizeof(type) - 1);
580 srand((time(NULL) + buf_len) ^ rand());
581 silc_silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
582 (unsigned int)rand(), (unsigned int)time(NULL),
583 (unsigned int)buf_len, type);
585 SILC_LOG_DEBUG(("Fragment ID %s", id));
587 partial = silc_mime_alloc();
591 silc_mime_add_field(partial, "MIME-Version", "1.0");
592 memset(type, 0, sizeof(type));
593 silc_silc_snprintf(type, sizeof(type) - 1,
594 "message/partial; id=\"%s\"; number=1", id);
595 silc_mime_add_field(partial, "Content-Type", type);
596 silc_mime_add_data(partial, buf, max_size);
598 tmp = silc_mime_encode(partial, &tmp_len);
601 silc_mime_free(partial);
604 buffer = silc_buffer_alloc_size(tmp_len);
607 silc_buffer_put(buffer, tmp, tmp_len);
608 silc_dlist_add(list, buffer);
611 len = buf_len - max_size;
615 partial = silc_mime_alloc();
619 memset(type, 0, sizeof(type));
620 silc_mime_add_field(partial, "MIME-Version", "1.0");
622 if (len > max_size) {
623 silc_silc_snprintf(type, sizeof(type) - 1,
624 "message/partial; id=\"%s\"; number=%d",
626 silc_mime_add_data(partial, buf + off, max_size);
630 silc_silc_snprintf(type, sizeof(type) - 1,
631 "message/partial; id=\"%s\"; number=%d; total=%d",
633 silc_mime_add_data(partial, buf + off, len);
637 silc_mime_add_field(partial, "Content-Type", type);
639 tmp = silc_mime_encode(partial, &tmp_len);
642 silc_mime_free(partial);
645 buffer = silc_buffer_alloc_size(tmp_len);
648 silc_buffer_put(buffer, tmp, tmp_len);
649 silc_dlist_add(list, buffer);
653 /* No need to fragment */
654 buffer = silc_buffer_alloc_size(buf_len);
657 silc_buffer_put(buffer, buf, buf_len);
658 silc_dlist_add(list, buffer);
666 /* Free partial MIME list */
668 void silc_mime_partial_free(SilcDList partials)
675 silc_dlist_start(partials);
676 while ((buf = silc_dlist_get(partials)) != SILC_LIST_END)
677 silc_buffer_free(buf);
678 silc_dlist_uninit(partials);
683 void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
685 if (!mime || !field || !value)
688 silc_hash_table_add(mime->fields, strdup(field), strdup(value));
693 const char *silc_mime_get_field(SilcMime mime, const char *field)
700 if (!silc_hash_table_find(mime->fields, (void *)field,
701 NULL, (void **)&value))
704 return (const char *)value;
709 void silc_mime_add_data(SilcMime mime, const unsigned char *data,
716 silc_free(mime->data);
718 mime->data = silc_memdup(data, data_len);
719 mime->data_len = data_len;
724 const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
730 *data_len = mime->data_len;
737 unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
745 *data_len = mime->data_len;
755 /* Returns TRUE if partial message */
757 SilcBool silc_mime_is_partial(SilcMime mime)
759 const char *type = silc_mime_get_field(mime, "Content-Type");
763 if (!strstr(type, "message/partial"))
769 /* Set as multipart message */
771 void silc_mime_set_multipart(SilcMime mime, const char *type,
772 const char *boundary)
776 if (!mime || !type || !boundary)
779 memset(tmp, 0, sizeof(tmp));
780 silc_silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
781 silc_mime_add_field(mime, "Content-Type", tmp);
782 silc_free(mime->boundary);
783 mime->boundary = strdup(boundary);
785 if (mime->multiparts)
787 mime->multiparts = silc_dlist_init();
792 SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
794 if (!mime || !mime->multiparts || !part)
797 silc_dlist_add(mime->multiparts, part);
801 /* Return TRUE if has multiparts */
803 SilcBool silc_mime_is_multipart(SilcMime mime)
808 return mime->multiparts != NULL;
811 /* Returns multiparts */
813 SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
819 *type = (const char *)mime->multitype;
821 return mime->multiparts;