/* $Id: attach.c,v 1.29 2007/08/31 14:16:01 nicm Exp $ */

/*
 * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

#include <ctype.h>
#include <string.h>

#include "fdm.h"

char 		*attach_type(struct mail *, char *, const char *, char **);
struct attach	*attach_get(struct mail *, char **, size_t *, const char *,
		    int *);

struct attach *
attach_visit(struct attach *atr, u_int *n)
{
	struct attach	*atn;

	if (atr == NULL)
		return (NULL);

	if (TAILQ_EMPTY(&atr->children)) {
		atn = TAILQ_NEXT(atr, entry);
		if (atn == NULL) {
			if (n != NULL)
				(*n)--;
			atr = atr->parent;
			if (atr != NULL)
				atr = TAILQ_NEXT(atr, entry);
		} else
			atr = atn;
	} else {
		if (n != NULL)
			(*n)++;
		atr = TAILQ_FIRST(&atr->children);
	}

	return (atr);
}

void printflike2
attach_log(struct attach *atr, const char *fmt, ...)
{
	va_list	ap;
	char   *prefix;
	u_int	n;

	va_start(ap, fmt);
	xvasprintf(&prefix, fmt, ap);
	va_end(ap);

	n = 0;
	while (atr != NULL) {
		if (TAILQ_EMPTY(&atr->children)) {
			if (atr->name == NULL) {
				log_debug3("%s:%*s%u, %s: offset %zu, size %zu,"
				    " body %zu", prefix, n + 1, " ", atr->idx,
				    atr->type, atr->data, atr->size, atr->body);
			} else {
				log_debug3("%s:%*s%u, %s: offset %zu, size %zu,"
				    " body %zu: %s", prefix, n + 1, " ",
				    atr->idx, atr->type, atr->data, atr->size,
				    atr->body, atr->name);
			}
		} else {
			log_debug3("%s:%*s%u, %s", prefix, n + 1, " ", atr->idx,
			    atr->type);
		}

		atr = attach_visit(atr, &n);
	}

	xfree(prefix);
}

void
attach_free(struct attach *atr)
{
	struct attach	*at;

	while (!TAILQ_EMPTY(&atr->children)) {
		at = TAILQ_FIRST(&atr->children);
		TAILQ_REMOVE(&atr->children, at, entry);
		attach_free(at);
	}

	if (atr->type != NULL)
		xfree(atr->type);
	if (atr->name != NULL)
		xfree(atr->name);
	xfree(atr);
}

char *
attach_type(struct mail *m, char *hdr, const char *name, char **value)
{
	size_t	 len, llen;
	ssize_t	 namelen;
	char	*ptr, *type = NULL;

	*value = NULL;

	len = m->size - (hdr - m->data);
	if (len < 13 || strncasecmp(hdr, "content-type:", 13) != 0)
		goto error;
	len -= 13;
	hdr += 13;

	/* Skip spaces. */
	while (len > 0 && isspace((u_char) *hdr)) {
		len--;
		hdr++;
	}
	if (len == 0)
		goto error;

	/* Find end of line. */
	ptr = memchr(hdr, '\n', len);
	if (ptr == NULL)
		llen = len;
	else
		llen = ptr - hdr;

	/* Find type. */
	ptr = memchr(hdr, ';', llen);
	if (ptr == NULL)
		ptr = hdr + llen;
	type = xmalloc(ptr - hdr + 1);
	memcpy(type, hdr, ptr - hdr);
	type[ptr - hdr] = '\0';
	len -= ptr - hdr;
	hdr = ptr;

	/* If this is now the end of the line, return the type. */
	if (len == 0 || *ptr == '\n')
		return (type);
	/* Skip the semicolon. */
	len--;
	hdr++;

	/*
	 * Now follows a set of attributes of the form name=value, separated
	 * by semicolons, possibly crossing multiple lines and possibly with
	 * the value enclosed in quotes.
	 */
	namelen = strlen(name);
	for (;;) {
		/* Skip spaces and newlines. */
		while (len > 0 && (isspace((u_char) *hdr) || *hdr == '\n')) {
			hdr++;
			len--;
		}
		if (len == 0)
			goto error;

		/* Find end of line. */
		ptr = memchr(hdr, '\n', len);
		if (ptr == NULL)
			llen = len;
		else
			llen = ptr - hdr;

		/* Find the end of the attribute name. */
		ptr = memchr(hdr, '=', llen);
		if (ptr == NULL)
			break;
		if (ptr - hdr == namelen && strncmp(hdr, name, namelen) == 0) {
 			llen -= (ptr - hdr + 1);
			hdr = ptr + 1;

			ptr = memchr(hdr, ';', llen);
			if (ptr != NULL)
				llen = ptr - hdr;
			if (*hdr == '"') {
				if (llen < 2 || hdr[llen - 1] != '"')
					goto error;
				hdr++;
				llen -= 2;
			}

			*value = xmalloc(llen + 1);
			memcpy(*value, hdr, llen);
			(*value)[llen] = '\0';
			break;
		}

		/* Skip to next semicolon. */
		ptr = memchr(hdr, ';', llen);
		if (ptr == NULL)
			break;
		hdr = ptr + 1;
		len -= (ptr - hdr) + 1;
	}

	return (type);

error:
	if (type != NULL)
		xfree(type);
	if (*value != NULL) {
		xfree(*value);
		*value = NULL;
	}
	return (NULL);
}

struct attach *
attach_build(struct mail *m)
{
	struct attach	*atr = NULL, *at;
	char		*hdr, *ptr, *b = NULL, *type;
	size_t		 len, bl;
	int		 last;
	u_int		 n;

	hdr = find_header(m, "content-type", &len, 0);
	if (hdr == NULL)
		return (NULL);

	type = attach_type(m, hdr, "boundary", &b);
	if (type == NULL || b == NULL) {
		if (type != NULL)
			xfree(type);
		goto error;
	}
	if (strncasecmp(type, "multipart/", 10) != 0) {
		xfree(type);
		goto error;
	}
	bl = strlen(b);

	atr = xmalloc(sizeof *atr);
	memset(atr, 0, sizeof *atr);
	TAILQ_INIT(&atr->children);
	atr->type = type;

	/* Find the first boundary. */
	line_init(m, &ptr, &len);
	while (ptr != NULL) {
		if (ptr[0] == '-' && ptr[1] == '-') {
			if (len - 3 == bl && strncmp(ptr + 2, b, bl) == 0)
				break;
		}
		line_next(m, &ptr, &len);
	}
	if (ptr == NULL)
		goto error;

	/* Now iterate over the rest. */
	last = 0;
	n = 0;
	while (ptr != NULL && !last) {
		if (ptr[0] == '-' && ptr[1] == '-') {
			if (len - 5 == bl && strncmp(ptr + 2, b, bl) == 0)
				break;
		}

		at = attach_get(m, &ptr, &len, b, &last);
		if (at == NULL)
			goto error;
		at->idx = n++;
		at->parent = atr;
		TAILQ_INSERT_TAIL(&atr->children, at, entry);
	}
	if (ptr == NULL)
		goto error;

	xfree(b);
	return (atr);

error:
	if (atr != NULL)
		attach_free(atr);

	if (b != NULL)
		xfree(b);
	return (NULL);
}

struct attach *
attach_get(struct mail *m, char **ptr, size_t *len, const char *b, int *last)
{
	struct attach	*atr, *at;
	char		*name = NULL, *b2 = NULL;
	size_t		 bl, bl2;
	int		 last2;
	u_int		 n;

	bl = strlen(b);

	atr = xmalloc(sizeof *atr);
	memset(atr, 0, sizeof *atr);
	TAILQ_INIT(&atr->children);

	atr->data = *ptr - m->data;

	while (*ptr != NULL) {
		if (*len >= 13 && strncasecmp(*ptr, "content-type:", 13) == 0)
			break;
		line_next(m, ptr, len);
	}
	if (*ptr == NULL)
		goto error;

	atr->type = attach_type(m, *ptr, "name", &name);
	if (atr->type == NULL) {
		if (name != NULL)
			xfree(name);
		goto error;
	}
	atr->name = name;

	if (strncasecmp(atr->type, "multipart/", 10) != 0) {
		/* Skip the remaining headers. */
		while (*ptr != NULL && *len > 1)
			line_next(m, ptr, len);
		if (*ptr == NULL)
			goto error;

		atr->body = *ptr - m->data;
		for (;;) {
			line_next(m, ptr, len);
			if (*ptr == NULL)
				break;
			if (*len < 3 || (*ptr)[0] != '-' || (*ptr)[1] != '-')
				continue;

			if (*len - 5 == bl && strncmp(*ptr + 2, b, bl) == 0 &&
			    strncmp(*ptr + bl + 2, "--", 2) == 0) {
				*last = 1;
				break;
			}
			if (*len - 3 == bl && strncmp(*ptr + 2, b, bl) == 0)
				break;
		}
		if (*ptr == NULL)
			goto error;

		atr->size = *ptr - m->data - atr->data;
	} else {
		/* XXX avoid doing this twice. */
		xfree(atr->type);
		atr->type = attach_type(m, *ptr, "boundary", &b2);
		if (b2 == NULL)
			goto error;
		bl2 = strlen(b2);

		/* Find the first boundary. */
		while (*ptr != NULL) {
			if ((*ptr)[0] == '-' && (*ptr)[1] == '-') {
				if (*len - 3 == bl2 &&
				    strncmp(*ptr + 2, b2, bl2) == 0)
					break;
			}
			line_next(m, ptr, len);
		}
		if (ptr == NULL)
			goto error;

		/* Now iterate over the rest. */
		last2 = 0;
		n = 0;
		while (*ptr != NULL && !last2) {
			at = attach_get(m, ptr, len, b2, &last2);
			if (at == NULL)
				goto error;
			at->idx = n++;
			at->parent = atr;
			TAILQ_INSERT_TAIL(&atr->children, at, entry);
		}

		/* And skip on to the end of the multipart. */
		while (*ptr != NULL) {
			if ((*ptr)[0] == '-' && (*ptr)[1] == '-') {
				if (*len - 5 == bl2 &&
				    strncmp(*ptr + 2, b2, bl2) == 0)
					break;
			}
			line_next(m, ptr, len);
		}
		if (*ptr == NULL)
			goto error;
		line_next(m, ptr, len);

		xfree(b2);
	}

	return (atr);

error:
	if (b2 != NULL)
		xfree(b2);

	attach_free(atr);
	return (NULL);
}
