/* Copyright (c) Pigeonhole authors, see top-level COPYING file */

/* NOTE: much of the functionality implemented here should eventually appear
 * somewhere in Dovecot itself.
 */

#include "lib.h"
#include "str.h"
#include "unichar.h"

#include "rfc2822.h"

#include "message-header-encode.h"

#include <stdio.h>
#include <ctype.h>

bool rfc2822_header_field_name_verify(const char *field_name, unsigned int len)
{
	const char *p = field_name;
	const char *pend = p + len;

	/* field-name   =   1*ftext
	   ftext        =   %d33-57 /               ; Any character except
	                    %d59-126                ;  controls, SP, and
	                                            ;  ":".
	 */

	while (p < pend) {
		if (*p < 33 || *p == ':')
			return FALSE;

		p++;
	}
	return TRUE;
}

bool rfc2822_header_field_body_verify(const char *field_body, unsigned int len,
				      bool allow_crlf, bool allow_utf8)
{
	const unsigned char *p = (const unsigned char *)field_body;
	const unsigned char *pend = p + len;
	bool is8bit = FALSE;

	/* RFC5322:

	   unstructured    =  (*([FWS] VCHAR) *WSP)
	   VCHAR           =  %x21-7E
	   FWS             =  ([*WSP CRLF] 1*WSP) /   ; Folding white space
	   WSP             =  SP / HTAB               ; White space
	 */

	while (p < pend) {
		if (*p < 0x20) {
			if (*p == '\r' || *p == '\n') {
				if (!allow_crlf)
					return FALSE;
			} else if (*p != '\t') {
				return FALSE;
			}
		}
		if (!is8bit && *p > 127) {
			if (!allow_utf8)
				return FALSE;

			is8bit = TRUE;
		}
		p++;
	}

	if (is8bit && !uni_utf8_str_is_valid(field_body))
		return FALSE;
	return TRUE;
}

/*
 *
 */

const char *rfc2822_header_field_name_sanitize(const char *name)
{
	char *result = t_strdup_noconst(name);
	char *p;

	/* Make the whole name lower case ... */
	result = str_lcase(result);

	/* ... except for the first letter and those that follow '-' */
	p = result;
	*p = i_toupper(*p);
	while (*p != '\0') {
		if (*p == '-') {
			p++;

			if (*p != '\0')
				*p = i_toupper(*p);
			continue;
		}
		p++;
	}
	return result;
}

/*
 * Message construction
 */

/* FIXME: This should be collected into a Dovecot API for composing internet
 * mail messages.
 */

unsigned int
rfc2822_header_append(string_t *header, const char *name, const char *body,
		      bool crlf, uoff_t *body_offset_r)
{
	static const unsigned int max_line = 80;

	const char *bp = body; /* Pointer */
	const char *sp = body; /* Start pointer */
	const char *wp = NULL; /* Whitespace pointer */
	const char *nlp = NULL; /* New-line pointer */
	unsigned int line_len = strlen(name);
	unsigned int lines = 0;

	/* Write header field name first */
	str_append(header, name);
	str_append(header, ": ");

	if (body_offset_r != NULL)
		*body_offset_r = str_len(header);

	line_len +=  2;

	/* Add field body; fold it if necessary and account for existing
	   folding.
	 */
	while (*bp != '\0') {
		bool ws_first = TRUE;

		while (*bp != '\0' && nlp == NULL &&
		       (wp == NULL || line_len < max_line)) {
			if (*bp == ' ' || *bp == '\t') {
				if (ws_first)
					wp = bp;
				ws_first = FALSE;
			} else if (*bp == '\r' || *bp == '\n') {
				if (ws_first)
					nlp = bp;
				else
					nlp = wp;
				break;
			} else {
				ws_first = TRUE;
			}

			bp++; line_len++;
		}

		if (*bp == '\0')
			break;

		/* Existing newline ? */
		if (nlp != NULL) {
			/* Replace any consecutive newline and whitespace for
			   consistency. */
			while (*bp == ' ' || *bp == '\t' || *bp == '\r' ||
			       *bp == '\n')
				bp++;

			str_append_data(header, sp, nlp-sp);

			if (crlf)
				str_append(header, "\r\n");
			else
				str_append(header, "\n");

			while (*bp == ' ' || *bp == '\t')
				bp++;
			if (*bp != '\0') {
				/* Continued line; replace leading whitespace
				   with single TAB. */
				str_append_c(header, '\t');
			}

			sp = bp;
		} else {
			/* Insert newline at last whitespace within the max_line
			   limit. */
			i_assert(wp >= sp);
			str_append_data(header, sp, wp-sp);

			/* Force continued line; drop any existing whitespace.
			 */
			while (*wp == ' ' || *wp == '\t')
				wp++;

			if (crlf)
				str_append(header, "\r\n");
			else
				str_append(header, "\n");

			/* Insert single TAB instead of the original
			   whitespace. */
			str_append_c(header, '\t');

			sp = wp;
			if (sp > bp)
				bp = sp;
		}

		lines++;

		line_len = bp - sp;
		wp = NULL;
		nlp = NULL;
	}

	if (bp != sp || lines == 0) {
		str_append_data(header, sp, bp-sp);
		if (crlf)
			str_append(header, "\r\n");
		else
			str_append(header, "\n");
		lines++;
	}

	return lines;
}

void rfc2822_header_printf(string_t *header, const char *name,
			   const char *fmt, ...)
{
	const char *body;
	va_list args;

	va_start(args, fmt);
	body = t_strdup_vprintf(fmt, args);
	va_end(args);

	rfc2822_header_write(header, name, body);
}

void rfc2822_header_utf8_printf(string_t *header, const char *name,
				const char *fmt, ...)
{
	string_t *body = t_str_new(256);
	va_list args;

	va_start(args, fmt);
	message_header_encode(t_strdup_vprintf(fmt, args), body);
	va_end(args);

	rfc2822_header_write(header, name, str_c(body));
}


void rfc2822_header_write_address(string_t *header, const char *name,
				  const char *address)
{
	bool has_8bit = FALSE;
	const char *p;

	for (p = address; *p != '\0'; p++) {
		if ((*p & 0x80) != 0)
			has_8bit = TRUE;
	}

	if (!has_8bit) {
		rfc2822_header_write(header, name, address);
	} else {
		string_t *body = t_str_new(256);
		message_header_encode(address, body);
		rfc2822_header_write(header, name, str_c(body));
	}
}
