Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 2005 Pekka Riikonen
+ Copyright (C) 2005 - 2007 Pekka Riikonen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
-#include "silcincludes.h"
-#include "silcmime.h"
+#include "silc.h"
-struct SilcMimeStruct {
- SilcHashTable fields;
- unsigned char *data;
- SilcUInt32 data_len;
- SilcDList multiparts;
- char *boundary;
- char *multitype;
-};
+/************************** Types and definitions ***************************/
+
+/* MIME fragment ID context */
+typedef struct {
+ char *id;
+ SilcInt64 starttime;
+} SilcMimeFragmentIdStruct, *SilcMimeFragmentId;
+
+/************************ Static utility functions **************************/
-struct SilcMimeAssemblerStruct {
- SilcHashTable fragments;
-};
+/* MIME fields destructor */
static void silc_mime_field_dest(void *key, void *context, void *user_context)
{
silc_free(context);
}
+/* Assembler fragment destructor */
+
+static void silc_mime_assembler_dest(void *key, void *context,
+ void *user_context)
+{
+ SilcMimeFragmentId id = key;
+
+ silc_free(id->id);
+ silc_free(id);
+
+ /* Free all fragments */
+ silc_hash_table_free(context);
+}
+
+/* Assembler partial MIME fragmentn destructor */
+
+static void silc_mime_assemble_dest(void *key, void *context,
+ void *user_context)
+{
+ silc_mime_free(context);
+}
+
+/* MIME fragment ID hashing */
+
+static SilcUInt32 silc_mime_hash_id(void *key, void *user_context)
+{
+ SilcMimeFragmentId id = key;
+ return silc_hash_string_case(id->id, user_context);
+}
+
+/* MIME fragment ID comparing */
+
+static SilcBool silc_mime_id_compare(void *key1, void *key2,
+ void *user_context)
+{
+ SilcMimeFragmentId id1 = key1, id2 = key2;
+ return silc_hash_string_case_compare(id1->id, id2->id, user_context);
+}
+
+
+/******************************* Public API *********************************/
+
+/* Allocate MIME context */
+
SilcMime silc_mime_alloc(void)
{
SilcMime mime;
if (!mime)
return NULL;
- mime->fields = silc_hash_table_alloc(0, silc_hash_string, mime,
- silc_hash_string_compare, mime,
+ mime->fields = silc_hash_table_alloc(NULL, 0, silc_hash_string_case, mime,
+ silc_hash_string_case_compare, mime,
silc_mime_field_dest, mime, TRUE);
if (!mime->fields) {
silc_mime_free(mime);
return mime;
}
+/* Free MIME context */
+
void silc_mime_free(SilcMime mime)
{
SilcMime m;
silc_free(mime);
}
-static void silc_mime_assembler_dest(void *key, void *context,
- void *user_context)
-{
- silc_free(key);
- silc_hash_table_free(context);
-}
+/* Allocate MIME assembler */
SilcMimeAssembler silc_mime_assembler_alloc(void)
{
return NULL;
assembler->fragments =
- silc_hash_table_alloc(0, silc_hash_string, NULL,
- silc_hash_string_compare, NULL,
+ silc_hash_table_alloc(NULL, 0, silc_mime_hash_id, NULL,
+ silc_mime_id_compare, NULL,
silc_mime_assembler_dest, assembler, TRUE);
if (!assembler->fragments) {
silc_mime_assembler_free(assembler);
return assembler;
}
+/* Free MIME assembler */
+
void silc_mime_assembler_free(SilcMimeAssembler assembler)
{
silc_hash_table_free(assembler->fragments);
silc_free(assembler);
}
-SilcMime silc_mime_decode(const unsigned char *data, SilcUInt32 data_len)
+/* Purge assembler from old unfinished fragments */
+
+void silc_mime_assembler_purge(SilcMimeAssembler assembler,
+ SilcUInt32 purge_minutes)
{
- SilcMime mime;
+ SilcMimeFragmentId id;
+ SilcHashTableList htl;
+ SilcInt64 curtime = silc_time();
+ SilcUInt32 timeout = purge_minutes ? purge_minutes * 60 : 5 * 60;
+
+ SILC_LOG_DEBUG(("Purge MIME assembler"));
+
+ silc_hash_table_list(assembler->fragments, &htl);
+ while (silc_hash_table_get(&htl, (void *)&id, NULL)) {
+ if (curtime - id->starttime <= timeout)
+ continue;
+
+ SILC_LOG_DEBUG(("Purge partial MIME id %s", id->id));
+
+ /* Purge */
+ silc_hash_table_del(assembler->fragments, id);
+ }
+ silc_hash_table_list_reset(&htl);
+}
+
+/* Decode MIME message */
+
+SilcMime silc_mime_decode(SilcMime mime, const unsigned char *data,
+ SilcUInt32 data_len)
+{
+ SilcMime m = NULL;
int i, k;
char *tmp, *field, *value, *line;
SILC_LOG_DEBUG(("Parsing MIME message"));
- if (!data)
+ if (!data) {
+ silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
return NULL;
+ }
- mime = silc_mime_alloc();
- if (!mime)
- return NULL;
+ if (!mime) {
+ mime = silc_mime_alloc();
+ if (!mime)
+ return NULL;
+ m = mime;
+ }
/* Parse the fields */
line = tmp = (char *)data;
if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') {
/* Get field */
field = strchr(line, ':');
- if (!field)
+ if (!field) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
field = silc_memdup(line, field - line);
if (!field)
goto err;
/* Get value. Remove whitespaces too. */
value = strchr(line, ':');
- if ((tmp + i) - value < 2)
+ if ((tmp + i) - value < 2) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
goto err;
+ }
value++;
for (k = 0; k < (tmp + i) - value; k++) {
- if (value[k] == '\r')
+ if (value[k] == '\r') {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
if (value[k] != ' ' && value[k] != '\t')
break;
}
value += k;
- if ((tmp + i) - value < 1)
+ if ((tmp + i) - value < 1) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
goto err;
+ }
value = silc_memdup(value, (tmp + i) - value);
if (!value)
goto err;
if (field && strstr(field, "multipart")) {
char b[1024];
SilcMime p;
+ unsigned int len;
mime->multiparts = silc_dlist_init();
if (!mime->multiparts)
/* Get multipart type */
value = strchr(field, '/');
- if (!value)
+ if (!value) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
value++;
if (strchr(field, '"'))
value++;
- if (!strchr(field, ';'))
+ if (!strchr(field, ';')) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
memset(b, 0, sizeof(b));
- strncat(b, value, strchr(field, ';') - value);
+ len = (unsigned int)(strchr(field, ';') - value);
+ if (len > sizeof(b) - 1) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
+ goto err;
+ }
+ strncpy(b, value, len);
if (strchr(b, '"'))
*strchr(b, '"') = '\0';
mime->multitype = silc_memdup(b, strlen(b));
SILC_LOG_DEBUG(("Boundary '%s'", value));
memset(b, 0, sizeof(b));
- line = strdup(value);
+ line = silc_strdup(value);
if (strrchr(line, '"')) {
*strrchr(line, '"') = '\0';
- snprintf(b, sizeof(b) - 1, "--%s", line + 1);
- mime->boundary = strdup(line + 1);
+ silc_snprintf(b, sizeof(b) - 1, "--%s", line + 1);
+ mime->boundary = silc_strdup(line + 1);
} else {
- snprintf(b, sizeof(b) - 1, "--%s", line);
- mime->boundary = strdup(line);
+ silc_snprintf(b, sizeof(b) - 1, "--%s", line);
+ mime->boundary = silc_strdup(line);
}
silc_free(line);
tmp[k] == '-' && tmp[k + 1] == '-')
if (!memcmp(tmp + k, b, strlen(b)))
break;
- if (k >= data_len)
+ if (k >= data_len) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
goto err;
+ }
/* Remove preceding CRLF */
k -= 2;
/* Parse the part */
- p = silc_mime_decode(line, k - i);
+ p = silc_mime_decode(NULL, line, k - i);
if (!p)
goto err;
}
}
} else {
- /* Get data area */
- if (i >= data_len)
+ /* Get data area. If we are at the end and we have fields present
+ there is no data area present, but, if fields are not present we
+ only have data area. */
+ if (i >= data_len && !silc_hash_table_count(mime->fields))
i = 0;
SILC_LOG_DEBUG(("Data len %d", data_len - i));
- silc_mime_add_data(mime, tmp + i, data_len - i);
+ if (data_len - i)
+ silc_mime_add_data(mime, tmp + i, data_len - i);
}
return mime;
err:
- silc_mime_free(mime);
+ if (m)
+ silc_mime_free(m);
return NULL;
}
+/* Encode MIME message */
+
unsigned char *silc_mime_encode(SilcMime mime, SilcUInt32 *encoded_len)
{
SilcMime part;
/* Encode the headers. Order doesn't matter */
i = 0;
silc_hash_table_list(mime->fields, &htl);
- while (silc_hash_table_get(&htl, (void **)&field, (void **)&value)) {
+ while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) {
memset(tmp, 0, sizeof(tmp));
SILC_LOG_DEBUG(("Header %s: %s", field, value));
- snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
+ silc_snprintf(tmp, sizeof(tmp) - 1, "%s: %s\r\n", field, value);
silc_buffer_strformat(&buf, tmp, SILC_STRFMT_END);
i++;
}
if (silc_buffer_len(&buf)) {
silc_buffer_put(buffer, buf.head, silc_buffer_len(&buf));
silc_buffer_pull(buffer, silc_buffer_len(&buf));
+ silc_buffer_purge(&buf);
}
/* Add data */
/* If fields are not present, add extra CRLF */
if (!silc_hash_table_count(part->fields))
- snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
- snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
+ silc_snprintf(tmp2, sizeof(tmp2) - 1, "\r\n");
+ silc_snprintf(tmp, sizeof(tmp) - 1, "%s--%s\r\n%s",
i != 0 ? "\r\n" : "", mime->boundary, tmp2);
i = 1;
}
memset(tmp, 0, sizeof(tmp));
- snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
+ silc_snprintf(tmp, sizeof(tmp) - 1, "\r\n--%s--\r\n", mime->boundary);
buffer = silc_buffer_realloc(buffer, silc_buffer_truelen(buffer) +
strlen(tmp));
if (!buffer)
return ret;
}
-static void silc_mime_assemble_dest(void *key, void *context,
- void *user_context)
-{
- silc_mime_free(context);
-}
+/* Assembles MIME message from partial MIME messages */
SilcMime silc_mime_assemble(SilcMimeAssembler assembler, SilcMime partial)
{
char *type, *id = NULL, *tmp;
+ SilcMimeFragmentIdStruct *fragid, query;
SilcHashTable f;
SilcMime p, complete;
int i, number, total = -1;
SILC_LOG_DEBUG(("Assembling MIME fragments"));
- if (!assembler || !partial)
+ if (!assembler || !partial) {
+ silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
goto err;
+ }
type = (char *)silc_mime_get_field(partial, "Content-Type");
- if (!type)
+ if (!type) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
/* Get ID */
tmp = strstr(type, "id=");
- if (!tmp)
+ if (!tmp) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
- if (strlen(tmp) <= 4)
+ }
+ if (strlen(tmp) <= 4) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
goto err;
+ }
tmp += 3;
if (*tmp == '"')
tmp++;
- id = strdup(tmp);
+ id = silc_strdup(tmp);
if (strchr(id, ';'))
*strchr(id, ';') = '\0';
if (strrchr(id, '"'))
/* Get fragment number */
tmp = strstr(type, "number=");
- if (!tmp)
+ if (!tmp) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
tmp = strchr(tmp, '=');
- if (strlen(tmp) < 2)
+ if (strlen(tmp) < 2) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
goto err;
+ }
tmp++;
if (strchr(tmp, ';')) {
- tmp = strdup(tmp);
+ tmp = silc_strdup(tmp);
*strchr(tmp, ';') = '\0';
number = atoi(tmp);
silc_free(tmp);
SILC_LOG_DEBUG(("Fragment number %d", number));
/* Find fragments with this ID. */
- if (!silc_hash_table_find(assembler->fragments, (void *)id,
- NULL, (void **)&f)) {
+ query.id = id;
+ if (!silc_hash_table_find(assembler->fragments, (void *)&query,
+ NULL, (void *)&f)) {
/* This is new fragment to new message. Add to hash table and return. */
- f = silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
+ f = silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL,
silc_mime_assemble_dest, NULL, TRUE);
if (!f)
- goto err;
+ goto err;
+
+ fragid = silc_calloc(1, sizeof(*fragid));
+ if (!fragid)
+ goto err;
+ fragid->id = id;
+ fragid->starttime = silc_time();
+
silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
- silc_hash_table_add(assembler->fragments, id, f);
+ silc_hash_table_add(assembler->fragments, fragid, f);
return NULL;
}
tmp = strstr(type, "total=");
if (tmp) {
tmp = strchr(tmp, '=');
- if (strlen(tmp) < 2)
+ if (strlen(tmp) < 2) {
+ silc_set_errno(SILC_ERR_OVERFLOW);
goto err;
+ }
tmp++;
if (strchr(tmp, ';')) {
- tmp = strdup(tmp);
+ tmp = silc_strdup(tmp);
*strchr(tmp, ';') = '\0';
total = atoi(tmp);
silc_free(tmp);
/* If more fragments to come, add to hash table */
if (number != total) {
silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
+ silc_free(id);
return NULL;
}
silc_hash_table_add(f, SILC_32_TO_PTR(number), partial);
/* Verify that we really have all the fragments */
- if (silc_hash_table_count(f) < total)
+ if (silc_hash_table_count(f) < total) {
+ silc_free(id);
return NULL;
+ }
/* Assemble the complete MIME message now. We get them in order from
the hash table. */
for (i = 1; i <= total; i++) {
- if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void **)&p))
+ if (!silc_hash_table_find(f, SILC_32_TO_PTR(i), NULL, (void *)&p))
goto err;
/* The fragment is in the data portion of the partial message */
data = silc_mime_get_data(p, &data_len);
- if (!data)
+ if (!data) {
+ silc_set_errno(SILC_ERR_BAD_ENCODING);
goto err;
+ }
/* Assemble */
if (!compbuf) {
}
/* Now parse the complete MIME message and deliver it */
- complete = silc_mime_decode((const unsigned char *)compbuf->head,
+ complete = silc_mime_decode(NULL, (const unsigned char *)compbuf->head,
silc_buffer_truelen(compbuf));
if (!complete)
goto err;
/* Delete the hash table entry. Destructors will free memory */
- silc_hash_table_del(assembler->fragments, (void *)id);
+ silc_hash_table_del(assembler->fragments, (void *)&query);
silc_free(id);
silc_buffer_free(compbuf);
return NULL;
}
+/* Encodes partial MIME messages */
+
SilcDList silc_mime_encode_partial(SilcMime mime, int max_size)
{
unsigned char *buf, *tmp;
memset(type, 0, sizeof(type));
gethostname(type, sizeof(type) - 1);
srand((time(NULL) + buf_len) ^ rand());
- snprintf(id, sizeof(id) - 1, "%X%X%X%s",
+ silc_snprintf(id, sizeof(id) - 1, "%X%X%X%s",
(unsigned int)rand(), (unsigned int)time(NULL),
(unsigned int)buf_len, type);
silc_mime_add_field(partial, "MIME-Version", "1.0");
memset(type, 0, sizeof(type));
- snprintf(type, sizeof(type) - 1,
+ silc_snprintf(type, sizeof(type) - 1,
"message/partial; id=\"%s\"; number=1", id);
silc_mime_add_field(partial, "Content-Type", type);
silc_mime_add_data(partial, buf, max_size);
silc_mime_add_field(partial, "MIME-Version", "1.0");
if (len > max_size) {
- snprintf(type, sizeof(type) - 1,
+ silc_snprintf(type, sizeof(type) - 1,
"message/partial; id=\"%s\"; number=%d",
id, num++);
silc_mime_add_data(partial, buf + off, max_size);
off += max_size;
len -= max_size;
} else {
- snprintf(type, sizeof(type) - 1,
+ silc_snprintf(type, sizeof(type) - 1,
"message/partial; id=\"%s\"; number=%d; total=%d",
id, num, num);
silc_mime_add_data(partial, buf + off, len);
return list;
}
+/* Free partial MIME list */
+
void silc_mime_partial_free(SilcDList partials)
{
SilcBuffer buf;
silc_dlist_uninit(partials);
}
+/* Add field */
+
void silc_mime_add_field(SilcMime mime, const char *field, const char *value)
{
if (!mime || !field || !value)
return;
- silc_hash_table_add(mime->fields, strdup(field), strdup(value));
+ silc_hash_table_add(mime->fields, silc_strdup(field), silc_strdup(value));
}
+/* Get field */
+
const char *silc_mime_get_field(SilcMime mime, const char *field)
{
char *value;
return NULL;
if (!silc_hash_table_find(mime->fields, (void *)field,
- NULL, (void **)&value))
+ NULL, (void *)&value))
return NULL;
return (const char *)value;
}
+/* Add data */
+
void silc_mime_add_data(SilcMime mime, const unsigned char *data,
SilcUInt32 data_len)
{
mime->data_len = data_len;
}
+/* Get data */
+
const unsigned char *silc_mime_get_data(SilcMime mime, SilcUInt32 *data_len)
{
if (!mime)
return mime->data;
}
+/* Steal data */
+
+unsigned char *silc_mime_steal_data(SilcMime mime, SilcUInt32 *data_len)
+{
+ unsigned char *data;
+
+ if (!mime)
+ return NULL;
+
+ if (data_len)
+ *data_len = mime->data_len;
+
+ data = mime->data;
+
+ mime->data = NULL;
+ mime->data_len = 0;
+
+ return data;
+}
+
+/* Returns TRUE if partial message */
+
SilcBool silc_mime_is_partial(SilcMime mime)
{
const char *type = silc_mime_get_field(mime, "Content-Type");
return TRUE;
}
+/* Set as multipart message */
+
void silc_mime_set_multipart(SilcMime mime, const char *type,
const char *boundary)
{
return;
memset(tmp, 0, sizeof(tmp));
- snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
+ silc_snprintf(tmp, sizeof(tmp) - 1, "multipart/%s; boundary=%s", type, boundary);
silc_mime_add_field(mime, "Content-Type", tmp);
silc_free(mime->boundary);
- mime->boundary = strdup(boundary);
+ mime->boundary = silc_strdup(boundary);
if (mime->multiparts)
return;
mime->multiparts = silc_dlist_init();
}
+/* Add multipart */
+
SilcBool silc_mime_add_multipart(SilcMime mime, SilcMime part)
{
- if (!mime || !mime->multiparts || !part)
+ if (!mime || !mime->multiparts || !part) {
+ silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
return FALSE;
+ }
silc_dlist_add(mime->multiparts, part);
return TRUE;
}
+/* Return TRUE if has multiparts */
+
SilcBool silc_mime_is_multipart(SilcMime mime)
{
- if (!mime)
+ if (!mime) {
+ silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
return FALSE;
+ }
return mime->multiparts != NULL;
}
+/* Returns multiparts */
+
SilcDList silc_mime_get_multiparts(SilcMime mime, const char **type)
{
- if (!mime)
+ if (!mime) {
+ silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
return NULL;
+ }
if (type)
*type = (const char *)mime->multitype;