#include <sys_defs.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <mymalloc.h>
#include <msg.h>
#include <vstring.h>
#include <rec_type.h>
#include <is_header.h>
#include <header_opts.h>
#include <mail_params.h>
#include <header_token.h>
#include <lex_822.h>
#include <base64_code.h>
#include <mime_state.h>
typedef struct MIME_STACK {
int def_ctype;
int def_stype;
char *boundary;
ssize_t bound_len;
struct MIME_STACK *next;
} MIME_STACK;
#define MIME_MAX_TOKEN 3
struct MIME_STATE {
int curr_state;
int curr_ctype;
int curr_stype;
int curr_encoding;
int curr_domain;
VSTRING *output_buffer;
int prev_rec_type;
int nesting_level;
MIME_STACK *stack;
HEADER_TOKEN token[MIME_MAX_TOKEN];
VSTRING *token_buffer;
int err_flags;
off_t head_offset;
off_t body_offset;
char base64_align_buf[2];
char base64_align_count;
char base64_flags;
int base64_crlf_pending;
int static_flags;
MIME_STATE_HEAD_OUT head_out;
MIME_STATE_ANY_END head_end;
MIME_STATE_BODY_OUT body_out;
MIME_STATE_ANY_END body_end;
MIME_STATE_ERR_PRINT err_print;
void *app_context;
};
#define MIME_CTYPE_OTHER 0
#define MIME_CTYPE_TEXT 1
#define MIME_CTYPE_MESSAGE 2
#define MIME_CTYPE_MULTIPART 3
#define MIME_STYPE_OTHER 0
#define MIME_STYPE_PLAIN 1
#define MIME_STYPE_RFC822 2
#define MIME_STYPE_PARTIAL 3
#define MIME_STYPE_EXTERN_BODY 4
#define MIME_BASE64_STARTED (1<<0)
#define MIME_STATE_PRIMARY MIME_HDR_PRIMARY
#define MIME_STATE_MULTIPART MIME_HDR_MULTIPART
#define MIME_STATE_NESTED MIME_HDR_NESTED
#define MIME_STATE_BODY (MIME_HDR_NESTED + 1)
#define SET_MIME_STATE(ptr, state, ctype, stype, encoding, domain) do { \
(ptr)->curr_state = (state); \
(ptr)->curr_ctype = (ctype); \
(ptr)->curr_stype = (stype); \
(ptr)->curr_encoding = (encoding); \
(ptr)->curr_domain = (domain); \
if ((state) == MIME_STATE_BODY) \
(ptr)->body_offset = 0; \
else \
(ptr)->head_offset = 0; \
} while (0)
#define SET_CURR_STATE(ptr, state) do { \
(ptr)->curr_state = (state); \
if ((state) == MIME_STATE_BODY) \
(ptr)->body_offset = 0; \
else \
(ptr)->head_offset = 0; \
} while (0)
typedef struct MIME_ENCODING {
const char *name;
int encoding;
int domain;
} MIME_ENCODING;
#define MIME_ENC_QP 1
#define MIME_ENC_BASE64 2
#ifndef MIME_ENC_7BIT
#define MIME_ENC_7BIT 7
#define MIME_ENC_8BIT 8
#define MIME_ENC_BINARY 9
#endif
static const MIME_ENCODING mime_encoding_map[] = {
"7bit", MIME_ENC_7BIT, MIME_ENC_7BIT,
"8bit", MIME_ENC_8BIT, MIME_ENC_8BIT,
"binary", MIME_ENC_BINARY, MIME_ENC_BINARY,
"base64", MIME_ENC_BASE64, MIME_ENC_7BIT,
"quoted-printable", MIME_ENC_QP, MIME_ENC_7BIT,
0,
};
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
#define END(x) vstring_end(x)
#define CU_CHAR_PTR(x) ((const unsigned char *) (x))
#define REPORT_ERROR_LEN(state, err_type, text, len) do { \
if ((state->err_flags & err_type) == 0) { \
if (state->err_print != 0) \
state->err_print(state->app_context, err_type, text, len); \
state->err_flags |= err_type; \
} \
} while (0)
#define REPORT_ERROR(state, err_type, text) do { \
const char *_text = text; \
ssize_t _len = strlen(text); \
REPORT_ERROR_LEN(state, err_type, _text, _len); \
} while (0)
#define REPORT_ERROR_BUF(state, err_type, buf) \
REPORT_ERROR_LEN(state, err_type, STR(buf), LEN(buf))
#define HEAD_OUT(ptr, info, len) do { \
if ((ptr)->head_out) { \
(ptr)->head_out((ptr)->app_context, (ptr)->curr_state, \
(info), (ptr)->output_buffer, (ptr)->head_offset); \
(ptr)->head_offset += (len) + 1; \
} \
} while(0)
#ifdef __APPLE_OS_X_SERVER__
#define BODY_OUT(ptr, rec_type, text, len) do { \
if ((ptr)->body_out) { \
if (var_mime_max_body_size > 0 && \
(ptr)->nesting_level > 0 && \
(ptr)->body_offset + (len) + 1 > (off_t) var_mime_max_body_size) \
REPORT_ERROR_LEN(ptr, MIME_ERR_BODY_TOO_LARGE, text, len); \
(ptr)->body_out((ptr)->app_context, (rec_type), \
(text), (len), (ptr)->body_offset); \
(ptr)->body_offset += (len) + 1; \
} \
} while(0)
#else
#define BODY_OUT(ptr, rec_type, text, len) do { \
if ((ptr)->body_out) { \
(ptr)->body_out((ptr)->app_context, (rec_type), \
(text), (len), (ptr)->body_offset); \
(ptr)->body_offset += (len) + 1; \
} \
} while(0)
#endif
static void mime_state_push(MIME_STATE *state, int def_ctype, int def_stype,
const char *boundary)
{
MIME_STACK *stack;
state->nesting_level += 1;
stack = (MIME_STACK *) mymalloc(sizeof(*stack));
stack->def_ctype = def_ctype;
stack->def_stype = def_stype;
if ((stack->bound_len = strlen(boundary)) > var_mime_bound_len)
stack->bound_len = var_mime_bound_len;
stack->boundary = mystrndup(boundary, stack->bound_len);
stack->next = state->stack;
state->stack = stack;
if (msg_verbose)
msg_info("PUSH boundary %s", stack->boundary);
}
static void mime_state_pop(MIME_STATE *state)
{
MIME_STACK *stack;
if ((stack = state->stack) == 0)
msg_panic("mime_state_pop: there is no stack");
if (msg_verbose)
msg_info("POP boundary %s", stack->boundary);
state->nesting_level -= 1;
state->stack = stack->next;
myfree(stack->boundary);
myfree((char *) stack);
}
MIME_STATE *mime_state_alloc(int flags,
MIME_STATE_HEAD_OUT head_out,
MIME_STATE_ANY_END head_end,
MIME_STATE_BODY_OUT body_out,
MIME_STATE_ANY_END body_end,
MIME_STATE_ERR_PRINT err_print,
void *context)
{
MIME_STATE *state;
state = (MIME_STATE *) mymalloc(sizeof(*state));
state->err_flags = 0;
state->body_offset = 0;
SET_MIME_STATE(state, MIME_STATE_PRIMARY,
MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
MIME_ENC_7BIT, MIME_ENC_7BIT);
state->output_buffer = vstring_alloc(100);
state->prev_rec_type = 0;
state->nesting_level = 0;
state->stack = 0;
state->token_buffer = vstring_alloc(1);
state->base64_align_count = 0;
state->base64_flags = 0;
state->base64_crlf_pending = 0;
state->static_flags = flags;
state->head_out = head_out;
state->head_end = head_end;
state->body_out = body_out;
state->body_end = body_end;
state->err_print = err_print;
state->app_context = context;
return (state);
}
MIME_STATE *mime_state_free(MIME_STATE *state)
{
vstring_free(state->output_buffer);
while (state->stack)
mime_state_pop(state);
if (state->token_buffer)
vstring_free(state->token_buffer);
myfree((char *) state);
return (0);
}
static void mime_state_content_type(MIME_STATE *state,
const HEADER_OPTS *header_info)
{
const char *cp;
ssize_t tok_count;
int def_ctype;
int def_stype;
#define TOKEN_MATCH(tok, text) \
((tok).type == HEADER_TOK_TOKEN && strcasecmp((tok).u.value, (text)) == 0)
#define RFC2045_TSPECIALS "()<>@,;:\\\"/[]?="
#define PARSE_CONTENT_TYPE_HEADER(state, ptr) \
header_token(state->token, MIME_MAX_TOKEN, \
state->token_buffer, ptr, RFC2045_TSPECIALS, ';')
cp = STR(state->output_buffer) + strlen(header_info->name) + 1;
if ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) > 0) {
if (TOKEN_MATCH(state->token[0], "text")) {
state->curr_ctype = MIME_CTYPE_TEXT;
if (tok_count >= 3
&& state->token[1].type == '/'
&& TOKEN_MATCH(state->token[2], "plain"))
state->curr_stype = MIME_STYPE_PLAIN;
else
state->curr_stype = MIME_STYPE_OTHER;
return;
}
if (TOKEN_MATCH(state->token[0], "message")) {
state->curr_ctype = MIME_CTYPE_MESSAGE;
state->curr_stype = MIME_STYPE_OTHER;
if (tok_count >= 3
&& state->token[1].type == '/') {
if (TOKEN_MATCH(state->token[2], "rfc822"))
state->curr_stype = MIME_STYPE_RFC822;
else if (TOKEN_MATCH(state->token[2], "partial"))
state->curr_stype = MIME_STYPE_PARTIAL;
else if (TOKEN_MATCH(state->token[2], "external-body"))
state->curr_stype = MIME_STYPE_EXTERN_BODY;
}
return;
}
if (TOKEN_MATCH(state->token[0], "multipart")) {
state->curr_ctype = MIME_CTYPE_MULTIPART;
if (tok_count >= 3
&& state->token[1].type == '/'
&& TOKEN_MATCH(state->token[2], "digest")) {
def_ctype = MIME_CTYPE_MESSAGE;
def_stype = MIME_STYPE_RFC822;
} else {
def_ctype = MIME_CTYPE_TEXT;
def_stype = MIME_STYPE_PLAIN;
}
while ((tok_count = PARSE_CONTENT_TYPE_HEADER(state, &cp)) >= 0) {
if (tok_count >= 3
&& TOKEN_MATCH(state->token[0], "boundary")
&& state->token[1].type == '=') {
if (state->nesting_level > var_mime_maxdepth) {
if (state->static_flags & MIME_OPT_REPORT_NESTING)
REPORT_ERROR_BUF(state, MIME_ERR_NESTING,
state->output_buffer);
} else {
mime_state_push(state, def_ctype, def_stype,
state->token[2].u.value);
}
}
}
}
return;
}
else {
state->curr_ctype = MIME_CTYPE_OTHER;
return;
}
}
static void mime_state_content_encoding(MIME_STATE *state,
const HEADER_OPTS *header_info)
{
const char *cp;
const MIME_ENCODING *cmp;
#define PARSE_CONTENT_ENCODING_HEADER(state, ptr) \
header_token(state->token, 1, state->token_buffer, ptr, (char *) 0, 0)
cp = STR(state->output_buffer) + strlen(header_info->name) + 1;
if (PARSE_CONTENT_ENCODING_HEADER(state, &cp) > 0
&& state->token[0].type == HEADER_TOK_TOKEN) {
for (cmp = mime_encoding_map; cmp->name != 0; cmp++) {
if (strcasecmp(state->token[0].u.value, cmp->name) == 0) {
state->curr_encoding = cmp->encoding;
state->curr_domain = cmp->domain;
break;
}
}
}
}
static const char *mime_state_enc_name(int encoding)
{
const MIME_ENCODING *cmp;
for (cmp = mime_encoding_map; cmp->name != 0; cmp++)
if (encoding == cmp->encoding)
return (cmp->name);
return ("unknown");
}
static void mime_state_downgrade(MIME_STATE *state, int rec_type,
const char *text, ssize_t len)
{
static char hexchars[] = "0123456789ABCDEF";
const unsigned char *cp;
int ch;
#define QP_ENCODE(buffer, ch) { \
VSTRING_ADDCH(buffer, '='); \
VSTRING_ADDCH(buffer, hexchars[(ch >> 4) & 0xff]); \
VSTRING_ADDCH(buffer, hexchars[ch & 0xf]); \
}
for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++) {
if (LEN(state->output_buffer) > 72) {
VSTRING_ADDCH(state->output_buffer, '=');
VSTRING_TERMINATE(state->output_buffer);
BODY_OUT(state, REC_TYPE_NORM,
STR(state->output_buffer),
LEN(state->output_buffer));
VSTRING_RESET(state->output_buffer);
}
ch = *cp;
if ((ch < 32 && ch != '\t') || ch == '=' || ch > 126) {
QP_ENCODE(state->output_buffer, ch);
} else {
VSTRING_ADDCH(state->output_buffer, ch);
}
}
if (rec_type == REC_TYPE_NORM) {
if (LEN(state->output_buffer) > 0
&& ((ch = END(state->output_buffer)[-1]) == ' ' || ch == '\t')) {
vstring_truncate(state->output_buffer,
LEN(state->output_buffer) - 1);
QP_ENCODE(state->output_buffer, ch);
}
VSTRING_TERMINATE(state->output_buffer);
BODY_OUT(state, REC_TYPE_NORM,
STR(state->output_buffer),
LEN(state->output_buffer));
VSTRING_RESET(state->output_buffer);
}
}
static void mime_state_downgrade_base64_real(MIME_STATE *state, int rec_type,
const char *text, ssize_t len)
{
char b64_align[3], *b64_data;
ssize_t b64_len, trim;
enum { BASE64_LINE_LENGTH = 76 };
int flush;
flush = (len == 0 && state->curr_state != MIME_STATE_BODY) ||
rec_type == REC_TYPE_EOF;
if (len > 0) {
if (state->base64_align_count == 1) {
if (len == 1) {
state->base64_align_buf[1] = text[0];
state->base64_align_count = 2;
len = 0;
} else {
b64_align[0] = state->base64_align_buf[0];
state->base64_align_count = 0;
b64_align[1] = text[0];
b64_align[2] = text[1];
text += 2;
len -= 2;
base64_encode_append(state->output_buffer, b64_align, 3);
}
} else if (state->base64_align_count == 2) {
b64_align[0] = state->base64_align_buf[0];
b64_align[1] = state->base64_align_buf[1];
state->base64_align_count = 0;
b64_align[2] = text[0];
text += 1;
len -= 1;
base64_encode_append(state->output_buffer, b64_align, 3);
}
if (len > 0) {
trim = len % 3;
if (trim > 0)
state->base64_align_buf[0] = text[len - trim];
if (trim > 1)
state->base64_align_buf[1] = text[len - 1];
state->base64_align_count = trim;
len -= trim;
base64_encode_append(state->output_buffer, text, len);
}
}
if (flush) {
if (state->base64_align_count > 0) {
base64_encode_append(state->output_buffer, state->base64_align_buf,
state->base64_align_count);
state->base64_align_count = 0;
}
}
b64_data = STR(state->output_buffer);
b64_len = LEN(state->output_buffer);
while (b64_len > BASE64_LINE_LENGTH) {
BODY_OUT(state, REC_TYPE_NORM, b64_data, BASE64_LINE_LENGTH);
b64_data += BASE64_LINE_LENGTH;
b64_len -= BASE64_LINE_LENGTH;
}
if (flush) {
BODY_OUT(state, REC_TYPE_NORM, b64_data, b64_len);
VSTRING_RESET(state->output_buffer);
} else if (b64_data != STR(state->output_buffer)) {
memmove(STR(state->output_buffer), b64_data, b64_len);
VSTRING_AT_OFFSET(state->output_buffer, b64_len);
}
VSTRING_TERMINATE(state->output_buffer);
}
static void mime_state_downgrade_base64(MIME_STATE *state, int rec_type,
const char *text, ssize_t len)
{
if (rec_type == REC_TYPE_NORM && len == 0) {
if (state->base64_flags == 0) {
state->base64_flags |= MIME_BASE64_STARTED;
BODY_OUT(state, rec_type, text, len);
} else
++state->base64_crlf_pending;
} else {
while (state->base64_crlf_pending > 0) {
mime_state_downgrade_base64_real(state, REC_TYPE_NORM, "\r\n", 2);
--state->base64_crlf_pending;
}
mime_state_downgrade_base64_real(state, rec_type, text, len);
if (rec_type == REC_TYPE_NORM)
++state->base64_crlf_pending;
}
}
static void mime_state_undo_crlf(MIME_STATE *state)
{
if ((state->static_flags & MIME_OPT_DOWNGRADE_BASE64) &&
state->curr_domain == MIME_ENC_BINARY) {
if (state->base64_crlf_pending > 0)
--state->base64_crlf_pending;
}
}
int mime_state_update(MIME_STATE *state, int rec_type,
const char *text, ssize_t len)
{
int input_is_text = (rec_type == REC_TYPE_NORM
|| rec_type == REC_TYPE_CONT);
MIME_STACK *sp;
const HEADER_OPTS *header_info;
const unsigned char *cp;
#define SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type) do { \
state->prev_rec_type = rec_type; \
return (state->err_flags); \
} while (0)
if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT)
mime_state_update(state, REC_TYPE_NORM, "", 0);
switch (state->curr_state) {
case MIME_STATE_PRIMARY:
case MIME_STATE_MULTIPART:
case MIME_STATE_NESTED:
if (LEN(state->output_buffer) > 0) {
if (input_is_text) {
if (state->prev_rec_type == REC_TYPE_CONT) {
if (LEN(state->output_buffer) < var_header_limit) {
vstring_strncat(state->output_buffer, text, len);
} else {
if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER,
state->output_buffer);
}
SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
}
if (IS_SPACE_TAB(*text)) {
if (LEN(state->output_buffer) < var_header_limit) {
vstring_strcat(state->output_buffer, "\n");
vstring_strncat(state->output_buffer, text, len);
} else {
if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER)
REPORT_ERROR_BUF(state, MIME_ERR_TRUNC_HEADER,
state->output_buffer);
}
SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
}
}
if (LEN(state->output_buffer) > 0) {
header_info = header_opts_find(STR(state->output_buffer));
if (!(state->static_flags & MIME_OPT_DISABLE_MIME)
&& header_info != 0) {
if (header_info->type == HDR_CONTENT_TYPE)
mime_state_content_type(state, header_info);
if (header_info->type == HDR_CONTENT_TRANSFER_ENCODING)
mime_state_content_encoding(state, header_info);
}
if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_HEADER) != 0
&& (state->err_flags & MIME_ERR_8BIT_IN_HEADER) == 0) {
for (cp = CU_CHAR_PTR(STR(state->output_buffer));
cp < CU_CHAR_PTR(END(state->output_buffer)); cp++)
if (*cp & 0200) {
REPORT_ERROR_BUF(state, MIME_ERR_8BIT_IN_HEADER,
state->output_buffer);
break;
}
}
if (header_info == 0
|| header_info->type != HDR_CONTENT_TRANSFER_ENCODING
|| (state->static_flags & (MIME_OPT_DOWNGRADE |
MIME_OPT_DOWNGRADE_BASE64)) == 0
|| ((state->static_flags & MIME_OPT_DOWNGRADE) &&
state->curr_domain == MIME_ENC_7BIT)
|| ((state->static_flags & MIME_OPT_DOWNGRADE_BASE64) &&
state->curr_domain != MIME_ENC_BINARY))
HEAD_OUT(state, header_info, len);
state->prev_rec_type = 0;
VSTRING_RESET(state->output_buffer);
}
}
if (input_is_text) {
ssize_t header_len;
if ((header_len = is_header_buf(text, len)) > 0) {
vstring_strncpy(state->output_buffer, text, header_len);
for (text += header_len, len -= header_len;
len > 0 && IS_SPACE_TAB(*text);
text++, len--)
;
vstring_strncat(state->output_buffer, text, len);
SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
}
}
if (((state->static_flags & MIME_OPT_DOWNGRADE) &&
state->curr_domain != MIME_ENC_7BIT) ||
((state->static_flags & MIME_OPT_DOWNGRADE_BASE64) &&
state->curr_domain == MIME_ENC_BINARY)) {
if (state->curr_ctype == MIME_CTYPE_MESSAGE
|| state->curr_ctype == MIME_CTYPE_MULTIPART)
cp = CU_CHAR_PTR("7bit");
else if (state->static_flags & MIME_OPT_DOWNGRADE_BASE64)
cp = CU_CHAR_PTR("base64");
else
cp = CU_CHAR_PTR("quoted-printable");
vstring_sprintf(state->output_buffer,
"Content-Transfer-Encoding: %s", cp);
HEAD_OUT(state, (HEADER_OPTS *) 0, len);
VSTRING_RESET(state->output_buffer);
}
if (state->curr_state == MIME_STATE_PRIMARY && state->head_end)
state->head_end(state->app_context);
if (state->static_flags & MIME_OPT_REPORT_ENCODING_DOMAIN) {
if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
if (state->curr_stype == MIME_STYPE_PARTIAL
|| state->curr_stype == MIME_STYPE_EXTERN_BODY) {
if (state->curr_domain != MIME_ENC_7BIT)
REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
mime_state_enc_name(state->curr_encoding));
}
else if (state->curr_stype == MIME_STYPE_RFC822) {
if (state->curr_encoding != state->curr_domain)
REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
mime_state_enc_name(state->curr_encoding));
}
} else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
if (state->curr_encoding != state->curr_domain)
REPORT_ERROR(state, MIME_ERR_ENCODING_DOMAIN,
mime_state_enc_name(state->curr_encoding));
}
}
if (input_is_text) {
if (len == 0) {
state->body_offset = 0;
if (state->curr_ctype == MIME_CTYPE_MESSAGE) {
if (state->curr_stype == MIME_STYPE_RFC822
|| (state->static_flags & MIME_OPT_RECURSE_ALL_MESSAGE))
SET_MIME_STATE(state, MIME_STATE_NESTED,
MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
MIME_ENC_7BIT, MIME_ENC_7BIT);
else
SET_CURR_STATE(state, MIME_STATE_BODY);
} else if (state->curr_ctype == MIME_CTYPE_MULTIPART) {
SET_MIME_STATE(state, MIME_STATE_BODY,
MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
MIME_ENC_7BIT, MIME_ENC_7BIT);
} else {
SET_CURR_STATE(state, MIME_STATE_BODY);
}
}
else {
if (msg_verbose)
msg_info("garbage in %s header",
state->curr_state == MIME_STATE_MULTIPART ? "multipart" :
state->curr_state == MIME_STATE_PRIMARY ? "primary" :
state->curr_state == MIME_STATE_NESTED ? "nested" :
"other");
switch (state->curr_state) {
case MIME_STATE_PRIMARY:
BODY_OUT(state, REC_TYPE_NORM, "", 0);
SET_CURR_STATE(state, MIME_STATE_BODY);
break;
#if 0
case MIME_STATE_NESTED:
if (state->body_offset <= 1
&& rec_type == REC_TYPE_NORM
&& len > 7
&& (strncmp(text + (*text == '>'), "From ", 5) == 0
|| strncmp(text, "=46rom ", 7) == 0))
break;
#endif
default:
SET_CURR_STATE(state, MIME_STATE_BODY);
break;
}
}
}
else {
SET_CURR_STATE(state, MIME_STATE_BODY);
}
case MIME_STATE_BODY:
if (input_is_text) {
if ((state->static_flags & MIME_OPT_REPORT_8BIT_IN_7BIT_BODY) != 0
&& state->curr_encoding == MIME_ENC_7BIT
&& (state->err_flags & MIME_ERR_8BIT_IN_7BIT_BODY) == 0) {
for (cp = CU_CHAR_PTR(text); cp < CU_CHAR_PTR(text + len); cp++)
if (*cp & 0200) {
REPORT_ERROR_LEN(state, MIME_ERR_8BIT_IN_7BIT_BODY,
text, len);
break;
}
}
if (state->stack && state->prev_rec_type != REC_TYPE_CONT
&& len > 2 && text[0] == '-' && text[1] == '-') {
for (sp = state->stack; sp != 0; sp = sp->next) {
if (len >= 2 + sp->bound_len &&
strncmp(text + 2, sp->boundary, sp->bound_len) == 0) {
mime_state_undo_crlf(state);
(void) mime_state_flush(state);
while (sp != state->stack)
mime_state_pop(state);
if (len >= 4 + sp->bound_len &&
strncmp(text + 2 + sp->bound_len, "--", 2) == 0) {
mime_state_pop(state);
SET_MIME_STATE(state, MIME_STATE_BODY,
MIME_CTYPE_OTHER, MIME_STYPE_OTHER,
MIME_ENC_7BIT, MIME_ENC_7BIT);
} else {
SET_MIME_STATE(state, MIME_STATE_MULTIPART,
sp->def_ctype, sp->def_stype,
MIME_ENC_7BIT, MIME_ENC_7BIT);
}
break;
}
}
}
if ((state->static_flags & MIME_OPT_DOWNGRADE_BASE64)
&& state->curr_domain == MIME_ENC_BINARY)
mime_state_downgrade_base64(state, rec_type, text, len);
else
if ((state->static_flags & MIME_OPT_DOWNGRADE)
&& state->curr_domain != MIME_ENC_7BIT)
mime_state_downgrade(state, rec_type, text, len);
else
BODY_OUT(state, rec_type, text, len);
}
else {
if ((state->static_flags & MIME_OPT_DOWNGRADE_BASE64)
&& state->curr_domain == MIME_ENC_BINARY)
mime_state_downgrade_base64(state, rec_type, "", 0);
if (state->body_end)
state->body_end(state->app_context);
}
SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type);
default:
msg_panic("mime_state_update: unknown state: %d", state->curr_state);
}
}
int mime_state_flush(MIME_STATE *state)
{
if ((state->static_flags & MIME_OPT_DOWNGRADE_BASE64) &&
state->curr_domain == MIME_ENC_BINARY) {
mime_state_downgrade_base64(state, REC_TYPE_EOF, "", 0);
state->base64_flags &= ~MIME_BASE64_STARTED;
if (state->base64_crlf_pending != 0)
msg_panic("mime_state_flush: base64_crlf_pending = %d", state->base64_crlf_pending);
}
return state->err_flags;
}
static const MIME_STATE_DETAIL mime_err_detail[] = {
MIME_ERR_NESTING, "5.6.0", "MIME nesting exceeds safety limit",
MIME_ERR_TRUNC_HEADER, "5.6.0", "message header length exceeds safety limit",
MIME_ERR_8BIT_IN_HEADER, "5.6.0", "improper use of 8-bit data in message header",
MIME_ERR_8BIT_IN_7BIT_BODY, "5.6.0", "improper use of 8-bit data in message body",
MIME_ERR_ENCODING_DOMAIN, "5.6.0", "invalid message/* or multipart/* encoding domain",
#ifdef __APPLE_OS_X_SERVER__
MIME_ERR_BODY_TOO_LARGE, "5.6.0", "MIME body part too large",
#endif
0,
};
const char *mime_state_error(int error_code)
{
const MIME_STATE_DETAIL *mp;
if (error_code == 0)
msg_panic("mime_state_error: there is no error");
for (mp = mime_err_detail; mp->code; mp++)
if (mp->code & error_code)
return (mp->text);
msg_panic("mime_state_error: unknown error code %d", error_code);
}
const MIME_STATE_DETAIL *mime_state_detail(int error_code)
{
const MIME_STATE_DETAIL *mp;
if (error_code == 0)
msg_panic("mime_state_detail: there is no error");
for (mp = mime_err_detail; mp->code; mp++)
if (mp->code & error_code)
return (mp);
msg_panic("mime_state_detail: unknown error code %d", error_code);
}
#ifdef TEST
#include <stdlib.h>
#include <stringops.h>
#include <vstream.h>
#include <msg_vstream.h>
#include <rec_streamlf.h>
#define REC_LEN 1024
static void head_out(void *context, int class, const HEADER_OPTS *unused_info,
VSTRING *buf, off_t offset)
{
VSTREAM *stream = (VSTREAM *) context;
vstream_fprintf(stream, "%s %ld\t|%s\n",
class == MIME_HDR_PRIMARY ? "MAIN" :
class == MIME_HDR_MULTIPART ? "MULT" :
class == MIME_HDR_NESTED ? "NEST" :
"ERROR", (long) offset, STR(buf));
}
static void head_end(void *context)
{
VSTREAM *stream = (VSTREAM *) context;
vstream_fprintf(stream, "HEADER END\n");
}
static void body_out(void *context, int rec_type, const char *buf, ssize_t len,
off_t offset)
{
VSTREAM *stream = (VSTREAM *) context;
vstream_fprintf(stream, "BODY %c %ld\t|", rec_type, (long) offset);
vstream_fwrite(stream, buf, len);
if (rec_type == REC_TYPE_NORM)
VSTREAM_PUTC('\n', stream);
}
static void body_end(void *context)
{
VSTREAM *stream = (VSTREAM *) context;
vstream_fprintf(stream, "BODY END\n");
}
static void err_print(void *unused_context, int err_flag,
const char *text, ssize_t len)
{
msg_warn("%s: %.*s", mime_state_error(err_flag),
len < 100 ? (int) len : 100, text);
}
int var_header_limit = 2000;
int var_mime_maxdepth = 20;
int var_mime_bound_len = 2000;
int main(int unused_argc, char **argv)
{
int rec_type;
int last = 0;
VSTRING *buf;
MIME_STATE *state;
int err;
#define MIME_OPTIONS \
(MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
| MIME_OPT_REPORT_8BIT_IN_HEADER \
| MIME_OPT_REPORT_ENCODING_DOMAIN \
| MIME_OPT_REPORT_TRUNC_HEADER \
| MIME_OPT_REPORT_NESTING \
| MIME_OPT_DOWNGRADE)
msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
msg_verbose = 1;
buf = vstring_alloc(10);
state = mime_state_alloc(MIME_OPTIONS,
head_out, head_end,
body_out, body_end,
err_print,
(void *) VSTREAM_OUT);
do {
rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
VSTRING_TERMINATE(buf);
err = mime_state_update(state, last = rec_type, STR(buf), LEN(buf));
vstream_fflush(VSTREAM_OUT);
} while (rec_type > 0);
if (err & MIME_ERR_TRUNC_HEADER)
msg_warn("message header length exceeds safety limit");
if (err & MIME_ERR_NESTING)
msg_warn("MIME nesting exceeds safety limit");
if (err & MIME_ERR_8BIT_IN_HEADER)
msg_warn("improper use of 8-bit data in message header");
if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
msg_warn("improper use of 8-bit data in message body");
if (err & MIME_ERR_ENCODING_DOMAIN)
msg_warn("improper message/* or multipart/* encoding domain");
#ifdef __APPLE_OS_X_SERVER__
if (err & MIME_ERR_BODY_TOO_LARGE)
msg_warn("MIME body part too large");
#endif
mime_state_free(state);
vstring_free(buf);
exit(0);
}
#endif