605 lines
14 KiB
C
605 lines
14 KiB
C
|
/*
|
||
|
* $Id$
|
||
|
*
|
||
|
* MiniMIME - a library for handling MIME messages
|
||
|
*
|
||
|
* Copyright (C) 2003 Jann Fischer <rezine@mistrust.net>
|
||
|
* 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. Neither the name of the author nor the names of the contributors
|
||
|
* may be used to endorse or promote products derived from this software
|
||
|
* without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY JANN FISCHER AND CONTRIBUTORS ``AS IS'' AND
|
||
|
* ANY EXPRESS 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 JANN FISCHER OR THE VOICES IN HIS HEAD
|
||
|
* 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.
|
||
|
*/
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
|
||
|
#include "mm_internal.h"
|
||
|
|
||
|
/** @file mm_context.c
|
||
|
*
|
||
|
* Modules for manipulating MiniMIME contexts
|
||
|
*/
|
||
|
|
||
|
/** @defgroup context Accessing and manipulating MIME contexts
|
||
|
*
|
||
|
* Each message in MiniMIME is represented by a so called ``context''. A
|
||
|
* context holds all necessary information given about a MIME message, such
|
||
|
* as the envelope, all MIME parts etc.
|
||
|
*/
|
||
|
|
||
|
/** @{
|
||
|
* @name Manipulating MiniMIME contexts
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Creates a new MiniMIME context object.
|
||
|
*
|
||
|
* @return a new MiniMIME context object
|
||
|
* @see mm_context_free
|
||
|
*
|
||
|
* This function creates a new MiniMIME context, which will hold a message.
|
||
|
* The memory needed is allocated dynamically and should later be free'd
|
||
|
* using mm_context_free().
|
||
|
*
|
||
|
* Before a context can be created, the MiniMIME library needs to be
|
||
|
* initialized properly using mm_library_init().
|
||
|
*
|
||
|
*/
|
||
|
MM_CTX *
|
||
|
mm_context_new(void)
|
||
|
{
|
||
|
MM_CTX *ctx;
|
||
|
|
||
|
MM_ISINIT();
|
||
|
|
||
|
ctx = (MM_CTX *)xmalloc(sizeof(MM_CTX));
|
||
|
ctx->messagetype = MM_MSGTYPE_FLAT; /* This is the default */
|
||
|
ctx->boundary = NULL;
|
||
|
ctx->preamble = xstrdup("This is a message in MIME format, generated "
|
||
|
"by MiniMIME 0.1");
|
||
|
|
||
|
TAILQ_INIT(&ctx->parts);
|
||
|
SLIST_INIT(&ctx->warnings);
|
||
|
|
||
|
return ctx;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Releases a MiniMIME context object
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context
|
||
|
* @see mm_context_new
|
||
|
*
|
||
|
* This function releases all memory associated with MiniMIME context object
|
||
|
* that was created using mm_context_new(). It will also release all memory
|
||
|
* used for the MIME parts attached, and their specific properties (such as
|
||
|
* Content-Type information, headers, and the body data).
|
||
|
*/
|
||
|
void
|
||
|
mm_context_free(MM_CTX *ctx)
|
||
|
{
|
||
|
struct mm_mimepart *part;
|
||
|
struct mm_warning *warning, *nxt;
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
TAILQ_FOREACH(part, &ctx->parts, next) {
|
||
|
TAILQ_REMOVE(&ctx->parts, part, next);
|
||
|
mm_mimepart_free(part);
|
||
|
}
|
||
|
|
||
|
if (ctx->boundary != NULL) {
|
||
|
xfree(ctx->boundary);
|
||
|
ctx->boundary = NULL;
|
||
|
}
|
||
|
|
||
|
if (ctx->preamble != NULL) {
|
||
|
xfree(ctx->preamble);
|
||
|
ctx->preamble = NULL;
|
||
|
}
|
||
|
|
||
|
for (warning = SLIST_FIRST(&ctx->warnings);
|
||
|
warning != SLIST_END(&ctx->warnings);
|
||
|
warning = nxt) {
|
||
|
nxt = SLIST_NEXT(warning, next);
|
||
|
SLIST_REMOVE(&ctx->warnings, warning, mm_warning, next);
|
||
|
xfree(warning);
|
||
|
warning = NULL;
|
||
|
}
|
||
|
|
||
|
xfree(ctx);
|
||
|
ctx = NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attaches a MIME part object to a MiniMIME context.
|
||
|
*
|
||
|
* @param ctx the MiniMIME context
|
||
|
* @param part the MIME part object to attach
|
||
|
* @return 0 on success or -1 on failure. Sets mm_errno on failure.
|
||
|
*
|
||
|
* This function attaches a MIME part to a context, appending it to the end
|
||
|
* of the message.
|
||
|
*
|
||
|
* The MIME part should be initialized before attaching it using
|
||
|
* mm_mimepart_new().
|
||
|
*/
|
||
|
int
|
||
|
mm_context_attachpart(MM_CTX *ctx, struct mm_mimepart *part)
|
||
|
{
|
||
|
assert(ctx != NULL);
|
||
|
assert(part != NULL);
|
||
|
|
||
|
if (TAILQ_EMPTY(&ctx->parts)) {
|
||
|
TAILQ_INSERT_HEAD(&ctx->parts, part, next);
|
||
|
} else {
|
||
|
TAILQ_INSERT_TAIL(&ctx->parts, part, next);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attaches a MIME part object to a MiniMIME context at a given position
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context
|
||
|
* @param part The MIME part object to attach
|
||
|
* @param pos After which part to attach the object
|
||
|
* @return 0 on success or -1 if the given position is invalid
|
||
|
* @see mm_context_attachpart
|
||
|
*
|
||
|
* This function attaches a MIME part object after a given position in the
|
||
|
* specified context. If the position is invalid (out of range), the part
|
||
|
* will not get attached to the message and the function returns -1. If
|
||
|
* the index was in range, the MIME part will get attached after the MIME
|
||
|
* part at the given position, moving any possible following MIME parts one
|
||
|
* down the hierarchy.
|
||
|
*/
|
||
|
int
|
||
|
mm_context_attachpart_after(MM_CTX *ctx, struct mm_mimepart *part, int pos)
|
||
|
{
|
||
|
struct mm_mimepart *p;
|
||
|
int where;
|
||
|
|
||
|
where = 0;
|
||
|
p = NULL;
|
||
|
|
||
|
TAILQ_FOREACH(part, &ctx->parts, next) {
|
||
|
if (where == pos) {
|
||
|
p = part;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (p == NULL) {
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
TAILQ_INSERT_AFTER(&ctx->parts, p, part, next);
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deletes a MIME part object from a MiniMIME context
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context object
|
||
|
* @param which The number of the MIME part object to delete
|
||
|
* @param freemem Whether to free the memory associated with the MIME part
|
||
|
* object
|
||
|
* @return 0 on success or -1 on failure. Sets mm_errno on failure.
|
||
|
*
|
||
|
* This function deletes a MIME part from a given context. The MIME part to
|
||
|
* delete is specified as numerical index by the parameter ``which''. If the
|
||
|
* parameter ``freemem'' is set to anything greater than 0, the memory that
|
||
|
* is associated will be free'd by using mm_mimepart_free(), otherwise the
|
||
|
* memory is left untouched (if you still have a pointer to the MIME part
|
||
|
* around).
|
||
|
*/
|
||
|
int
|
||
|
mm_context_deletepart(MM_CTX *ctx, int which, int freemem)
|
||
|
{
|
||
|
struct mm_mimepart *part;
|
||
|
int cur;
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
assert(which >= 0);
|
||
|
|
||
|
cur = 0;
|
||
|
|
||
|
TAILQ_FOREACH(part, &ctx->parts, next) {
|
||
|
if (cur == which) {
|
||
|
TAILQ_REMOVE(&ctx->parts, part, next);
|
||
|
if (freemem)
|
||
|
mm_mimepart_free(part);
|
||
|
return 0;
|
||
|
}
|
||
|
cur++;
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Counts the number of attached MIME part objects in a given MiniMIME context
|
||
|
*
|
||
|
* @param ctx The MiniMIME context
|
||
|
* @returns The number of attached MIME part objects
|
||
|
*/
|
||
|
int
|
||
|
mm_context_countparts(MM_CTX *ctx)
|
||
|
{
|
||
|
int count;
|
||
|
struct mm_mimepart *part;
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
count = 0;
|
||
|
|
||
|
if (TAILQ_EMPTY(&ctx->parts)) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
TAILQ_FOREACH(part, &ctx->parts, next) {
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
assert(count > -1);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets a specified MIME part object from a MimeMIME context
|
||
|
*
|
||
|
* @param ctx The MiniMIME context
|
||
|
* @param which The number of the MIME part object to retrieve
|
||
|
* @returns The requested MIME part object on success or a NULL pointer if
|
||
|
* there is no such part.
|
||
|
*/
|
||
|
struct mm_mimepart *
|
||
|
mm_context_getpart(MM_CTX *ctx, int which)
|
||
|
{
|
||
|
struct mm_mimepart *part;
|
||
|
int cur;
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
cur = 0;
|
||
|
|
||
|
TAILQ_FOREACH(part, &ctx->parts, next) {
|
||
|
if (cur == which) {
|
||
|
return part;
|
||
|
}
|
||
|
cur++;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks whether a given context represents a composite (multipart) message
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context object
|
||
|
* @return 1 if the context is a composite message or 0 if it's flat
|
||
|
*
|
||
|
*/
|
||
|
int
|
||
|
mm_context_iscomposite(MM_CTX *ctx)
|
||
|
{
|
||
|
if (ctx->messagetype == MM_MSGTYPE_MULTIPART) {
|
||
|
return 1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks whether there are any warnings associated with a given context
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context
|
||
|
* @return 1 if there are warnings associated with the context, otherwise 0
|
||
|
*/
|
||
|
int
|
||
|
mm_context_haswarnings(MM_CTX *ctx)
|
||
|
{
|
||
|
if (SLIST_EMPTY(&ctx->warnings)) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates a generic boundary string for a given context
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context
|
||
|
* @return 0 on success or -1 on failure
|
||
|
*
|
||
|
* This function generates a default boundary string for the given context.
|
||
|
* If there is already a boundary for the context, the memory will be free()'d.
|
||
|
*/
|
||
|
int
|
||
|
mm_context_generateboundary(MM_CTX *ctx)
|
||
|
{
|
||
|
char *boundary;
|
||
|
struct mm_mimepart *part;
|
||
|
struct mm_param *param;
|
||
|
|
||
|
if (mm_mimeutil_genboundary("++MiniMIME++", 20, &boundary) == -1) {
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
if (ctx->boundary != NULL) {
|
||
|
xfree(ctx->boundary);
|
||
|
ctx->boundary = NULL;
|
||
|
}
|
||
|
|
||
|
/* If we already have an envelope, make sure that we also justify the
|
||
|
* "boundary" parameter of the envelope.
|
||
|
*/
|
||
|
part = mm_context_getpart(ctx, 0);
|
||
|
if (part == NULL) {
|
||
|
return(0);
|
||
|
}
|
||
|
if (part->type != NULL) {
|
||
|
param = mm_content_gettypeparamobjbyname(part->type, "boundary");
|
||
|
if (param == NULL) {
|
||
|
param = mm_param_new();
|
||
|
param->name = xstrdup("boundary");
|
||
|
param->value = xstrdup(boundary);
|
||
|
mm_content_attachtypeparam(part->type, param);
|
||
|
} else {
|
||
|
if (param->value != NULL) {
|
||
|
xfree(param->value);
|
||
|
param->value = NULL;
|
||
|
}
|
||
|
param->value = xstrdup(boundary);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctx->boundary = boundary;
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets a preamble for the given MiniMIME context
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context
|
||
|
* @param preamble The preamble to set
|
||
|
* @return 0 on success or -1 on failure
|
||
|
*
|
||
|
* This function sets the MIME preamble (the text between the end of envelope
|
||
|
* headers and the beginning of the first MIME part) for a given context
|
||
|
* object. If preamble is a NULL-pointer then the preamble will be deleted,
|
||
|
* and the currently associated memory will be free automagically.
|
||
|
*/
|
||
|
int
|
||
|
mm_context_setpreamble(MM_CTX *ctx, char *preamble)
|
||
|
{
|
||
|
if (ctx == NULL)
|
||
|
return(-1);
|
||
|
|
||
|
if (preamble == NULL) {
|
||
|
if (ctx->preamble != NULL) {
|
||
|
xfree(ctx->preamble);
|
||
|
}
|
||
|
ctx->preamble = NULL;
|
||
|
} else {
|
||
|
ctx->preamble = xstrdup(preamble);
|
||
|
}
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
mm_context_getpreamble(MM_CTX *ctx)
|
||
|
{
|
||
|
if (ctx == NULL)
|
||
|
return(NULL);
|
||
|
|
||
|
return(ctx->preamble);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an ASCII message of the specified context
|
||
|
*
|
||
|
* @param ctx A valid MiniMIME context object
|
||
|
* @param flat Where to store the message
|
||
|
* @param flags Flags that affect the flattening process
|
||
|
*
|
||
|
* This function ``flattens'' a MiniMIME context, that is, it creates an ASCII
|
||
|
* represantation of the message the context contains. The flags can be a
|
||
|
* bitwise combination of the following constants:
|
||
|
*
|
||
|
* - MM_FLATTEN_OPAQUE : use opaque MIME parts when flattening
|
||
|
* - MM_FLATTEN_SKIPENVELOPE : do not flatten the envelope part
|
||
|
*
|
||
|
* Great care is taken to not produce invalid MIME output.
|
||
|
*/
|
||
|
int
|
||
|
mm_context_flatten(MM_CTX *ctx, char **flat, size_t *length, int flags)
|
||
|
{
|
||
|
struct mm_mimepart *part;
|
||
|
char *message;
|
||
|
char *flatpart;
|
||
|
char *buf;
|
||
|
char *envelope_headers;
|
||
|
size_t message_size;
|
||
|
size_t tmp_size;
|
||
|
char envelope;
|
||
|
|
||
|
mm_errno = MM_ERROR_NONE;
|
||
|
envelope = 1;
|
||
|
|
||
|
message = NULL;
|
||
|
message_size = 0;
|
||
|
|
||
|
if (ctx->boundary == NULL) {
|
||
|
if (mm_context_iscomposite(ctx)) {
|
||
|
mm_context_generateboundary(ctx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TAILQ_FOREACH(part, &ctx->parts, next) {
|
||
|
if (envelope) {
|
||
|
if (flags & MM_FLATTEN_SKIPENVELOPE) {
|
||
|
envelope = 0;
|
||
|
if ((message = (char *) malloc(1)) == NULL) {
|
||
|
mm_errno = MM_ERROR_ERRNO;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
*message = '\0';
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (part->type == NULL && mm_context_countparts(ctx) > 1) {
|
||
|
if (mm_mimepart_setdefaultcontenttype(part, 1)
|
||
|
== -1) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
if (mm_context_generateboundary(ctx) == -1) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
ctx->messagetype = MM_MSGTYPE_MULTIPART;
|
||
|
}
|
||
|
|
||
|
if (mm_envelope_getheaders(ctx, &envelope_headers,
|
||
|
&tmp_size) == -1) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
message = envelope_headers;
|
||
|
message_size = tmp_size;
|
||
|
envelope = 0;
|
||
|
|
||
|
if (ctx->preamble != NULL
|
||
|
&& mm_context_iscomposite(ctx)
|
||
|
&& !(flags & MM_FLATTEN_NOPREAMBLE)) {
|
||
|
tmp_size += strlen(ctx->preamble)
|
||
|
+ (strlen("\r\n") * 2);
|
||
|
buf = (char *)xrealloc(message, tmp_size);
|
||
|
if (buf == NULL) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
message_size += tmp_size;
|
||
|
message = buf;
|
||
|
strlcat(message, "\r\n", message_size);
|
||
|
strlcat(message, ctx->preamble, message_size);
|
||
|
strlcat(message, "\r\n", message_size);
|
||
|
}
|
||
|
} else {
|
||
|
/* Enforce Content-Type if none exist */
|
||
|
if (part->type == NULL) {
|
||
|
if (mm_mimepart_setdefaultcontenttype(part, 0)
|
||
|
== -1) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Append a boundary if necessary */
|
||
|
if (ctx->boundary != NULL) {
|
||
|
tmp_size = strlen(ctx->boundary) +
|
||
|
(strlen("\r\n") * 2) + strlen("--");
|
||
|
|
||
|
if (tmp_size < 1) {
|
||
|
return(-1);
|
||
|
}
|
||
|
if (message_size + tmp_size < 1) {
|
||
|
return(-1);
|
||
|
}
|
||
|
|
||
|
buf = (char *)xrealloc(message, message_size
|
||
|
+ tmp_size);
|
||
|
if (buf == NULL) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
message_size += tmp_size;
|
||
|
message = buf;
|
||
|
strlcat(message, "\r\n", message_size);
|
||
|
strlcat(message, "--", message_size);
|
||
|
strlcat(message, ctx->boundary, message_size);
|
||
|
strlcat(message, "\r\n", message_size);
|
||
|
}
|
||
|
|
||
|
if (mm_mimepart_flatten(part, &flatpart, &tmp_size,
|
||
|
(flags & MM_FLATTEN_OPAQUE)) == -1) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
if (tmp_size < 1) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
buf = (char *) xrealloc(message, message_size
|
||
|
+ tmp_size);
|
||
|
if (buf == NULL) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
message_size += tmp_size;
|
||
|
message = buf;
|
||
|
|
||
|
strlcat(message, flatpart, message_size);
|
||
|
xfree(flatpart);
|
||
|
flatpart = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Append end boundary */
|
||
|
if (ctx->boundary != NULL && mm_context_iscomposite(ctx)) {
|
||
|
tmp_size = strlen(ctx->boundary) + (strlen("\r\n") * 2)
|
||
|
+ (strlen("--") * 2);
|
||
|
buf = (char *)xrealloc(message, message_size + tmp_size);
|
||
|
if (buf == NULL) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
message_size += tmp_size;
|
||
|
message = buf;
|
||
|
if (message[strlen(message)-1] != 13)
|
||
|
strlcat(message, "\r", message_size);
|
||
|
strlcat(message, "\n", message_size);
|
||
|
strlcat(message, "--", message_size);
|
||
|
strlcat(message, ctx->boundary, message_size);
|
||
|
strlcat(message, "--", message_size);
|
||
|
strlcat(message, "\r\n", message_size);
|
||
|
}
|
||
|
|
||
|
*flat = message;
|
||
|
*length = message_size;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
cleanup:
|
||
|
if (message != NULL) {
|
||
|
xfree(message);
|
||
|
message = NULL;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/** @} */
|