/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2005 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see .
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
/*
* mime.c - Implement MIME multipart/related handling
*
* References:
* RFC 2387 (The MIME Multipart/Related Content-type)
* RFC 2025 (Multipurpose Internet Mail Extensions [MIME])
*
* See gwlib/mime.h for more details on the implementation.
*
* Stipe Tolj
*/
#include
#include
#include
#include "gwlib/gwlib.h"
#include "gwlib/mime.h"
struct MIMEEntity {
List *headers;
List *multiparts;
Octstr *body;
struct MIMEEntity *start; /* in case multipart/related */
};
/********************************************************************
* Creation and destruction routines.
*/
MIMEEntity *mime_entity_create(void)
{
MIMEEntity *e;
e = gw_malloc(sizeof(MIMEEntity));
e->headers = http_create_empty_headers();
e->multiparts = gwlist_create();
e->body = NULL;
e->start = NULL;
return e;
}
void static mime_entity_destroy_item(void *e)
{
mime_entity_destroy(e);
}
void mime_entity_destroy(MIMEEntity *e)
{
gw_assert(e != NULL);
if (e->headers != NULL)
gwlist_destroy(e->headers, octstr_destroy_item);
if (e->multiparts != NULL)
gwlist_destroy(e->multiparts, mime_entity_destroy_item);
octstr_destroy(e->body);
e->start = NULL; /* will be destroyed on it's own via gwlist_destroy */
gw_free(e);
}
/********************************************************************
* Helper routines. Some are derived from gwlib/http.[ch]
*/
/*
* Read some headers, i.e., until the first empty line (read and discard
* the empty line as well). Return -1 for error, 0 for all headers read.
*/
static int read_mime_headers(ParseContext *context, List *headers)
{
Octstr *line, *prev;
if (gwlist_len(headers) == 0)
prev = NULL;
else
prev = gwlist_get(headers, gwlist_len(headers) - 1);
for (;;) {
line = parse_get_line(context);
if (line == NULL) {
return -1;
}
if (octstr_len(line) == 0) {
octstr_destroy(line);
break;
}
if (isspace(octstr_get_char(line, 0)) && prev != NULL) {
octstr_append(prev, line);
octstr_destroy(line);
} else {
gwlist_append(headers, line);
prev = line;
}
}
return 0;
}
/* This function checks that there is a boundary parameter in the headers
* for a multipart MIME object. If not, it is inserted and passed back to caller
* in the variable boundary_elem.
*/
static void fix_boundary_element(List *headers, Octstr **boundary_elem)
{
/*
* Check if we have an boundary parameter already in the
* Content-Type header. If no, add one, otherwise parse which one
* we should use.
* XXX this can be abstracted as function in gwlib/http.[ch].
*/
Octstr *value = http_header_value(headers, octstr_imm("Content-Type"));
Octstr *boundary = value ? http_get_header_parameter(value, octstr_imm("boundary")) :
NULL;
if (boundary == NULL) {
boundary = octstr_format("_boundary_%d_%ld_%c_%c_bd%d",
random(), (long)time(NULL), 'A' + (random()%26),
'a'+(random() % 26), random());
octstr_format_append(value, "; boundary=%S", boundary);
http_header_remove_all(headers, "Content-Type");
http_header_add(headers, "Content-Type", octstr_get_cstr(value));
http_header_add(headers, "MIME-Version", "1.0");
}
if (value)
octstr_destroy(value);
if (boundary_elem)
*boundary_elem = boundary;
else if (boundary)
octstr_destroy(boundary);
}
/********************************************************************
* Mapping function from other data types, mainly Octstr and HTTP.
*/
static Octstr *mime_entity_to_octstr_real(MIMEEntity *m, unsigned int level)
{
Octstr *mime, *boundary = NULL;
List *headers;
long i;
gw_assert(m != NULL && m->headers != NULL);
mime = octstr_create("");
/*
* First of all check if we have further MIME entity dependencies,
* which means we have further MIMEEntities in our m->multiparts
* list. If no, then add headers and body and return. This is the
* easy case. Otherwise we have to loop inside our entities.
*/
if (gwlist_len(m->multiparts) == 0) {
for (i = 0; i < gwlist_len(m->headers); i++) {
octstr_append(mime, gwlist_get(m->headers, i));
octstr_append(mime, octstr_imm("\r\n"));
}
octstr_append(mime, octstr_imm("\r\n"));
if (m->body != NULL)
octstr_append(mime, m->body);
goto finished;
}
/* This call ensures boundary exists, and returns it */
fix_boundary_element(m->headers, &boundary);
headers = http_header_duplicate(m->headers);
/* headers */
for (i = 0; i < gwlist_len(headers); i++) {
octstr_append(mime, gwlist_get(headers, i));
octstr_append(mime, octstr_imm("\r\n"));
}
http_destroy_headers(headers);
/* loop through all MIME multipart entities of this entity */
for (i = 0; i < gwlist_len(m->multiparts); i++) {
MIMEEntity *e = gwlist_get(m->multiparts, i);
Octstr *body;
if (i != 0)
octstr_append(mime, octstr_imm("\r\n"));
octstr_append(mime, octstr_imm("\r\n--"));
octstr_append(mime, boundary);
octstr_append(mime, octstr_imm("\r\n"));
/* call ourself to produce the MIME entity body */
body = mime_entity_to_octstr_real(e, level + 1);
octstr_append(mime, body);
octstr_destroy(body);
}
/* add the last boundary statement, but hive an EOL
* if we are on the top of the recursion stack. */
/* if (level > 0) */
octstr_append(mime, octstr_imm("\r\n"));
octstr_append(mime, octstr_imm("\r\n--"));
octstr_append(mime, boundary);
octstr_append(mime, octstr_imm("--\r\n"));
octstr_destroy(boundary);
finished:
return mime;
}
Octstr *mime_entity_to_octstr(MIMEEntity *m)
{
Octstr *mime;
/* mapping function required to pass recurssion level */
mime = mime_entity_to_octstr_real(m, 0);
return mime;
}
static Octstr *get_start_param(Octstr *content_type)
{
Octstr *start;
int len;
if (!content_type)
return NULL;
start = http_get_header_parameter(content_type, octstr_imm("start"));
if (start && (len = octstr_len(start)) > 0 &&
octstr_get_char(start, 0) == '"' && octstr_get_char(start, len-1) == '"') {
octstr_delete(start, 0, 1);
octstr_delete(start, len-2, 1);
}
return start;
}
static int cid_matches(List *headers, Octstr *start)
{
Octstr *cid = http_header_value(headers, octstr_imm("Content-ID"));
int ret;
if (start != NULL && cid != NULL &&
octstr_compare(start, cid) == 0)
ret = 1;
else
ret = 0;
if (cid)
octstr_destroy(cid);
return ret;
}
/*
* This routine is used for mime_[http|octstr]_to_entity() in order to
* reduce code duplication. Basically the only difference is how the headers
* are parsed or passed to the resulting MIMEEntity representation.
*/
static MIMEEntity *mime_something_to_entity(Octstr *mime, List *headers)
{
MIMEEntity *e;
ParseContext *context;
Octstr *value, *boundary, *start;
int len = 0;
gw_assert(mime != NULL);
value = boundary = start = NULL;
context = parse_context_create(mime);
e = mime_entity_create();
/* parse the headers up to the body. If we have headers already passed
* from our caller, then duplicate them and continue */
if (headers != NULL) {
/* duplicate existing headers */
e->headers = http_header_duplicate(headers);
} else {
/* parse the headers out of the mime block */
if ((read_mime_headers(context, e->headers) != 0) || e->headers == NULL) {
debug("mime.parse",0,"Failed to read MIME headers in Octstr block:");
octstr_dump(mime, 0);
mime_entity_destroy(e);
parse_context_destroy(context);
return NULL;
}
}
/*
* Now check if the body is a multipart. This is indicated by an 'boundary'
* parameter in the 'Content-Type' value. If yes, call ourself for the
* multipart entities after parsing them.
*/
value = http_header_value(e->headers, octstr_imm("Content-Type"));
boundary = http_get_header_parameter(value, octstr_imm("boundary"));
start = get_start_param(value);
/* Beware that we need *unquoted* strings to compare against in the
* following parsing sections. */
if (boundary && (len = octstr_len(boundary)) > 0 &&
octstr_get_char(boundary, 0) == '"' && octstr_get_char(boundary, len-1) == '"') {
octstr_delete(boundary, 0, 1);
octstr_delete(boundary, len-2, 1);
}
if (boundary != NULL) {
/* we have a multipart block as body, parse the boundary blocks */
Octstr *entity, *seperator, *os;
/* loop by all boundary blocks we have in the body */
seperator = octstr_create("--");
octstr_append(seperator, boundary);
while ((entity = parse_get_seperated_block(context, seperator)) != NULL) {
MIMEEntity *m;
/* we have still two linefeeds at the beginning and end that we
* need to remove, these are from the separator.
* We check if it is \n or \r\n?! */
if (octstr_get_char(entity, 0) == '\r')
octstr_delete(entity, 0, 2);
else
octstr_delete(entity, 0, 1);
if (octstr_get_char(entity, octstr_len(entity) - 2) == '\r' &&
octstr_get_char(entity, octstr_len(entity) - 4) == '\r')
octstr_delete(entity, octstr_len(entity) - 4, 4);
else if (octstr_get_char(entity, octstr_len(entity) - 2) == '\r')
octstr_delete(entity, octstr_len(entity) - 2, 2);
else
octstr_delete(entity, octstr_len(entity) - 1, 1);
debug("mime.parse",0,"MIME multipart: Parsing entity:");
octstr_dump(entity, 0);
/* call ourself for this MIME entity and inject to list */
m = mime_octstr_to_entity(entity);
gwlist_append(e->multiparts, m);
/* check if this entity is our start entity (in terms of related)
* and set our start pointer to it */
if (cid_matches(m->headers, start)) {
/* set only if none has been set before */
e->start = (e->start == NULL) ? m : e->start;
}
octstr_destroy(entity);
}
/* ok, we parsed all blocks, we expect to see now the end boundary */
octstr_append_cstr(seperator, "--");
os = parse_get_line(context);
if (os != NULL && octstr_compare(os, seperator) != 0) {
debug("mime.parse",0,"Failed to see end boundary, parsed line is '%s'.",
octstr_get_cstr(os));
}
octstr_destroy(seperator);
octstr_destroy(os);
}
else {
/* we don't have boundaries, so this is no multipart block,
* pass the body to the MIME entity. */
e->body = parse_get_rest(context);
}
parse_context_destroy(context);
octstr_destroy(value);
octstr_destroy(boundary);
octstr_destroy(start);
return e;
}
MIMEEntity *mime_octstr_to_entity(Octstr *mime)
{
gw_assert(mime != NULL);
return mime_something_to_entity(mime, NULL);
}
MIMEEntity *mime_http_to_entity(List *headers, Octstr *body)
{
gw_assert(headers != NULL && body != NULL);
return mime_something_to_entity(body, headers);
}
List *mime_entity_headers(MIMEEntity *m)
{
List *headers;
gw_assert(m != NULL && m->headers != NULL);
/* Need a fixup before hand over. */
fix_boundary_element(m->headers,NULL);
headers = http_header_duplicate(m->headers);
return headers;
}
Octstr *mime_entity_body(MIMEEntity *m)
{
Octstr *os, *body;
ParseContext *context;
MIMEEntity *e;
gw_assert(m != NULL && m->headers != NULL);
/* For non-multipart, return body directly. */
if (mime_entity_num_parts(m) == 0)
return octstr_duplicate(m->body);
os = mime_entity_to_octstr(m);
context = parse_context_create(os);
e = mime_entity_create();
/* parse the headers up to the body */
if ((read_mime_headers(context, e->headers) != 0) || e->headers == NULL) {
debug("mime.parse",0,"Failed to read MIME headers in Octstr block:");
octstr_dump(os, 0);
mime_entity_destroy(e);
parse_context_destroy(context);
return NULL;
}
/* the rest is the body */
body = parse_get_rest(context);
octstr_destroy(os);
mime_entity_destroy(e);
parse_context_destroy(context);
return body;
}
/* Make a copy of a mime object. recursively. */
MIMEEntity *mime_entity_duplicate(MIMEEntity *e)
{
MIMEEntity *copy = mime_entity_create();
int i, n;
mime_replace_headers(copy, e->headers);
copy->body = e->body ? octstr_duplicate(e->body) : NULL;
for (i = 0, n = gwlist_len(e->multiparts); i < n; i++)
gwlist_append(copy->multiparts,
mime_entity_duplicate(gwlist_get(e->multiparts, i)));
return copy;
}
/* Replace top-level MIME headers: Old ones removed completetly */
void mime_replace_headers(MIMEEntity *e, List *headers)
{
gw_assert(e != NULL);
gw_assert(headers != NULL);
http_destroy_headers(e->headers);
e->headers = http_header_duplicate(headers);
e->start = NULL; /* clear it, since header change means it could have changed.*/
}
/* Get number of body parts. Returns 0 if this is not
* a multipart object.
*/
int mime_entity_num_parts(MIMEEntity *e)
{
gw_assert(e != NULL);
return e->multiparts ? gwlist_len(e->multiparts) : 0;
}
/* Append a new part to list of body parts. Copy is made
* Note that if it was not multipart, this action makes it so!
*/
void mime_entity_add_part(MIMEEntity *e, MIMEEntity *part)
{
gw_assert(e != NULL);
gw_assert(part != NULL);
gwlist_append(e->multiparts, mime_entity_duplicate(part));
}
/* Get part i in list of body parts. Copy is made*/
MIMEEntity *mime_entity_get_part(MIMEEntity *e, int i)
{
MIMEEntity *m;
gw_assert(e != NULL);
gw_assert(i >= 0);
gw_assert(i < gwlist_len(e->multiparts));
m = gwlist_get(e->multiparts, i);
gw_assert(m);
return mime_entity_duplicate(m);
}
/* Remove part i in list of body parts. */
void mime_entity_remove_part(MIMEEntity *e, int i)
{
MIMEEntity *m;
gw_assert(e != NULL);
gw_assert(i >= 0);
gw_assert(i < gwlist_len(e->multiparts));
m = gwlist_get(e->multiparts, i);
gwlist_delete(e->multiparts, i, 1);
if (m == e->start) e->start = NULL;
mime_entity_destroy(m);
}
/* Replace part i in list of body parts. Old one will be deleted */
void mime_entity_replace_part(MIMEEntity *e, int i, MIMEEntity *newpart)
{
MIMEEntity *m;
gw_assert(e != NULL);
gw_assert(i >= 0);
gw_assert(i < gwlist_len(e->multiparts));
m = gwlist_get(e->multiparts, i);
gwlist_delete(e->multiparts, i, 1);
gwlist_insert(e->multiparts, i, mime_entity_duplicate(newpart));
if (m == e->start) e->start = NULL;
mime_entity_destroy(m);
}
/* Change body element of non-multipart entity.
* We don't check that object is multi part. Result is just that
* body will be ignored.
*/
void mime_entity_set_body(MIMEEntity *e, Octstr *body)
{
gw_assert(e != NULL);
gw_assert(body != NULL);
if (e->body)
octstr_destroy(e->body);
e->body = octstr_duplicate(body);
}
/* Returns (copy of) the 'start' element of a multi-part entity. */
MIMEEntity *mime_multipart_start_elem(MIMEEntity *e)
{
gw_assert(e != NULL);
/* If e->start element is not yet set, set it as follows:
* - if content type is not set, then set it to NULL
* - if the start element is not set but this is a multipart object, set
* it to first multipart element, else set it to null
* - if the start element of the content type is set, find a matching object
* and set e->start accordingly.
* Finally, return a copy of it.
*/
if (!e->start) {
Octstr *ctype = http_header_value(e->headers, octstr_imm("Content-Type"));
Octstr *start = get_start_param(ctype);
int i;
if (!ctype)
e->start = NULL;
else if (!start) {
if (gwlist_len(e->multiparts) > 0)
e->start = gwlist_get(e->multiparts, 0);
else
e->start = NULL;
} else
for (i = 0; i < gwlist_len(e->multiparts); i++) {
MIMEEntity *x = gwlist_get(e->multiparts, i);
if (cid_matches(x->headers, start)) {
e->start = x;
break;
}
}
if (ctype)
octstr_destroy(ctype);
if (start)
octstr_destroy(start);
}
return (e->start) ? mime_entity_duplicate(e->start) : NULL;
}
/********************************************************************
* Routines for debugging purposes.
*/
static void mime_entity_dump_real(MIMEEntity *m, unsigned int level)
{
long i, items;
Octstr *prefix, *type, *charset;
unsigned int j;
gw_assert(m != NULL && m->headers != NULL);
prefix = octstr_create("");
for (j = 0; j < level * 2; j++)
octstr_append_cstr(prefix, " ");
http_header_get_content_type(m->headers, &type, &charset);
debug("mime.dump",0,"%sContent-Type `%s'", octstr_get_cstr(prefix),
octstr_get_cstr(type));
if (m->start != NULL) {
Octstr *cid = http_header_value(m->start->headers, octstr_imm("Content-ID"));
debug("mime.dump",0,"%sRelated to Content-ID <%s> MIMEEntity at address `%p'",
octstr_get_cstr(prefix), octstr_get_cstr(cid), m->start);
octstr_destroy(cid);
}
items = gwlist_len(m->multiparts);
debug("mime.dump",0,"%sBody contains %ld MIME entities, size %ld", octstr_get_cstr(prefix),
items, (items == 0 && m->body) ? octstr_len(m->body) : -1);
octstr_destroy(prefix);
octstr_destroy(type);
octstr_destroy(charset);
for (i = 0; i < items; i++) {
MIMEEntity *e = gwlist_get(m->multiparts, i);
mime_entity_dump_real(e, level + 1);
}
}
void mime_entity_dump(MIMEEntity *m)
{
gw_assert(m != NULL && m->headers != NULL);
debug("mms",0,"Dumping MIMEEntity at address %p", m);
mime_entity_dump_real(m, 0);
}