// 分析邮件头及 multipart 各部分的头 static int mime_state_head(MIME_STATE *state, const char *s, int n) { MIME_NODE *node = state->curr_node; if (n <= 0) return (n); /* 如果还未找到换行符,则继续 */ if (node->last_lf != '\n') { while (n > 0) { node->last_ch = *s; ADDCH(node->buffer, node->last_ch); n--; state->curr_off++; if (node->last_ch == '\n') { node->last_lf = '\n'; break; } s++; } return (n); } /* 如果数据以换行开始, 说明当前的邮件头结束 */ if (*s == '\n') { /* 上次数据为: \n\r 或 \n */ state->curr_off++; node->header_end = state->curr_off; if (LEN(node->buffer) > 0) { /* 处理头部的最后一行数据 */ mime_header_line(node); node->valid_line++; } /* 略过开头无用的空行 */ if (node->valid_line == 0) return (0); /* 如果当前结点为 multipart 格式, 则重置 state->curr_bound */ if (node->boundary != NULL) state->curr_bound = STR(node->boundary); state->curr_status = MIME_S_BODY; node->body_begin = state->curr_off; return (n - 1); } if (*s == '\r') { state->curr_off++; if (node->last_ch == '\r') { /* XXX: 出现了 \n\r\r 现象 */ node->last_ch = '\r'; node->last_lf = 0; return (n - 1); } node->last_ch = '\r'; /* 返回, 以期待下一个字符为 '\n' */ return (n - 1); } /* 清除 '\n' */ node->last_lf = 0; /* 如果数据以空格或TAB开始, 说明数据附属于上一行 */ if (IS_SPACE_TAB(*s)) { /* 说明本行数据附属于上一行数据 */ while (n > 0) { node->last_ch = *s; ADDCH(node->buffer, node->last_ch); n--; state->curr_off++; if (node->last_ch == '\n') { /* 处理完本完整行数据 */ node->last_lf = '\n'; break; } s++; } return (n); } /* 处理头部的上一行数据 */ if (LEN(node->buffer) > 0) { mime_header_line(node); node->valid_line++; } return (n); }
static void postcat(VSTREAM *fp, VSTRING *buffer, int flags) { int prev_type = 0; int rec_type; struct timeval tv; time_t time; int ch; off_t offset; const char *error_text; char *attr_name; char *attr_value; int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT); int state; /* state machine, input type */ int do_print; /* state machine, output control */ long data_offset; /* state machine, read optimization */ long data_size; /* state machine, read optimization */ #define TEXT_RECORD(rec_type) \ (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) /* * See if this is a plausible file. */ if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { if (!strchr(REC_TYPE_ENVELOPE, ch)) { msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); return; } vstream_ungetc(fp, ch); } /* * Other preliminaries. */ if (flags & PC_FLAG_PRINT_ENV) vstream_printf("*** ENVELOPE RECORDS %s ***\n", VSTREAM_PATH(fp)); state = PC_STATE_ENV; do_print = (flags & PC_FLAG_PRINT_ENV); data_offset = data_size = -1; /* * Now look at the rest. */ for (;;) { if (flags & PC_FLAG_PRINT_OFFSET) offset = vstream_ftell(fp); rec_type = rec_get_raw(fp, buffer, 0, rec_flags); if (rec_type == REC_TYPE_ERROR) msg_fatal("record read error"); if (rec_type == REC_TYPE_EOF) break; /* * First inspect records that have side effects on the (envelope, * header, body) state machine or on the record reading order. * * XXX Comments marked "Optimization:" identify subtle code that will * likely need to be revised when the queue file organization is * changed. */ #define PRINT_MARKER(flags, fp, offset, type, text) do { \ if ((flags) & PC_FLAG_PRINT_OFFSET) \ vstream_printf("%9lu ", (unsigned long) (offset)); \ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ vstream_printf("%3d ", (type)); \ vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \ vstream_fflush(VSTREAM_OUT); \ } while (0) #define PRINT_RECORD(flags, offset, type, value) do { \ if ((flags) & PC_FLAG_PRINT_OFFSET) \ vstream_printf("%9lu ", (unsigned long) (offset)); \ if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ vstream_printf("%3d ", (type)); \ vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \ vstream_fflush(VSTREAM_OUT); \ } while (0) if (TEXT_RECORD(rec_type)) { /* This is wrong when the message starts with whitespace. */ if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT)) && prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type) && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) { /* Update the state machine. */ state = PC_STATE_BODY; do_print = (flags & PC_FLAG_PRINT_BODY); /* Optimization: terminate if nothing left to print. */ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0) break; /* Optimization: skip to extracted segment marker. */ if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) && data_offset >= 0 && data_size >= 0 && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) msg_fatal("seek error: %m"); } /* Optional output happens further down below. */ } else if (rec_type == REC_TYPE_MESG) { /* Sanity check. */ if (state != PC_STATE_ENV) msg_warn("%s: out-of-order message content marker", VSTREAM_PATH(fp)); /* Optional output. */ if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS"); /* Optimization: skip to extracted segment marker. */ if ((flags & PC_MASK_PRINT_TEXT) == 0 && data_offset >= 0 && data_size >= 0 && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) msg_fatal("seek error: %m"); /* Update the state machine, even when skipping. */ state = PC_STATE_HEADER; do_print = (flags & PC_FLAG_PRINT_HEADER); continue; } else if (rec_type == REC_TYPE_XTRA) { /* Sanity check. */ if (state != PC_STATE_HEADER && state != PC_STATE_BODY) msg_warn("%s: out-of-order extracted segment marker", VSTREAM_PATH(fp)); /* Optional output (terminate preceding header/body line). */ if (do_print && prev_type == REC_TYPE_CONT) VSTREAM_PUTCHAR('\n'); if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED"); /* Update the state machine. */ state = PC_STATE_ENV; do_print = (flags & PC_FLAG_PRINT_ENV); /* Optimization: terminate if nothing left to print. */ if (do_print == 0) break; continue; } else if (rec_type == REC_TYPE_END) { /* Sanity check. */ if (state != PC_STATE_ENV) msg_warn("%s: out-of-order message end marker", VSTREAM_PATH(fp)); /* Optional output. */ if (flags & PC_FLAG_PRINT_ENV) PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END"); /* Terminate the state machine. */ break; } else if (rec_type == REC_TYPE_PTR) { /* Optional output. */ /* This record type is exposed only with '-v'. */ if (do_print) PRINT_RECORD(flags, offset, rec_type, STR(buffer)); /* Skip to the pointer's target record. */ if (rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR) msg_fatal("bad pointer record, or input is not seekable"); continue; } else if (rec_type == REC_TYPE_SIZE) { /* Optional output (here before we update the state machine). */ if (do_print) PRINT_RECORD(flags, offset, rec_type, STR(buffer)); /* Read the message size/offset for the state machine optimizer. */ if (data_size >= 0 || data_offset >= 0) { msg_warn("file contains multiple size records"); } else { if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2 || data_offset <= 0 || data_size <= 0) msg_fatal("invalid size record: %.100s", STR(buffer)); /* Optimization: skip to the message header. */ if ((flags & PC_FLAG_PRINT_ENV) == 0) { if (vstream_fseek(fp, data_offset, SEEK_SET) < 0) msg_fatal("seek error: %m"); /* Update the state machine. */ state = PC_STATE_HEADER; do_print = (flags & PC_FLAG_PRINT_HEADER); } } continue; } /* * Don't inspect side-effect-free records that aren't printed. */ if (do_print == 0) continue; if (flags & PC_FLAG_PRINT_OFFSET) vstream_printf("%9lu ", (unsigned long) offset); if (flags & PC_FLAG_PRINT_RTYPE_DEC) vstream_printf("%3d ", rec_type); switch (rec_type) { case REC_TYPE_TIME: REC_TYPE_TIME_SCAN(STR(buffer), tv); time = tv.tv_sec; vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_WARN: REC_TYPE_WARN_SCAN(STR(buffer), time); vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ if (state == PC_STATE_ENV) vstream_printf("%s: ", rec_type_name(rec_type)); else if (msg_verbose) vstream_printf("unterminated_text: "); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); if (state == PC_STATE_ENV || msg_verbose || (flags & PC_FLAG_PRINT_OFFSET) != 0) { rec_type = 0; VSTREAM_PUTCHAR('\n'); } break; case REC_TYPE_NORM: if (msg_verbose) vstream_printf("%s: ", rec_type_name(rec_type)); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); VSTREAM_PUTCHAR('\n'); break; case REC_TYPE_DTXT: /* This record type is exposed only with '-v'. */ vstream_printf("%s: ", rec_type_name(rec_type)); vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); VSTREAM_PUTCHAR('\n'); break; case REC_TYPE_ATTR: error_text = split_nameval(STR(buffer), &attr_name, &attr_value); if (error_text != 0) { msg_warn("%s: malformed attribute: %s: %.100s", VSTREAM_PATH(fp), error_text, STR(buffer)); break; } if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) { time = atol(attr_value); vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME, asctime(localtime(&time))); } else { vstream_printf("%s: %s=%s\n", rec_type_name(rec_type), attr_name, attr_value); } break; default: vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer)); break; } prev_type = rec_type; /* * In case the next record is broken. */ vstream_fflush(VSTREAM_OUT); } }
static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info, VSTRING *buf, char *expected) { int type; int check_first = (*expected == REC_TYPE_CONTENT[0]); int time_seen = 0; char *attr_name; char *attr_value; char *saved_attr; int skip_attr; /* * Limit the input record size. All front-end programs should protect the * mail system against unreasonable inputs. This also requires that we * limit the size of envelope records written by the local posting agent. * * Records with named attributes are filtered by postdrop(1). * * We must allow PTR records here because of "postsuper -r". */ for (;;) { if ((type = rec_get(qfile, buf, var_line_limit)) < 0 || strchr(expected, type) == 0) return (file_read_error(info, type)); if (msg_verbose) msg_info("%s: read %c %s", info->id, type, vstring_str(buf)); if (type == *expected) break; if (type == REC_TYPE_FROM) { if (info->sender == 0) info->sender = mystrdup(vstring_str(buf)); /* Compatibility with Postfix < 2.3. */ if (time_seen == 0) rec_fprintf(cleanup, REC_TYPE_TIME, "%ld", (long) info->st.st_mtime); } if (type == REC_TYPE_TIME) time_seen = 1; /* * XXX Workaround: REC_TYPE_FILT (used in envelopes) == REC_TYPE_CONT * (used in message content). * * As documented in postsuper(1), ignore content filter record. */ if (*expected != REC_TYPE_CONTENT[0]) { if (type == REC_TYPE_FILT) /* Discard FILTER record after "postsuper -r". */ continue; if (type == REC_TYPE_RDR) /* Discard REDIRECT record after "postsuper -r". */ continue; } if (*expected == REC_TYPE_EXTRACT[0]) { if (type == REC_TYPE_RRTO) /* Discard return-receipt record after "postsuper -r". */ continue; if (type == REC_TYPE_ERTO) /* Discard errors-to record after "postsuper -r". */ continue; if (type == REC_TYPE_ATTR) { saved_attr = mystrdup(vstring_str(buf)); skip_attr = (split_nameval(saved_attr, &attr_name, &attr_value) == 0 && rec_attr_map(attr_name) == 0); myfree(saved_attr); /* Discard other/header/body action after "postsuper -r". */ if (skip_attr) continue; } } /* * XXX Force an empty record when the queue file content begins with * whitespace, so that it won't be considered as being part of our * own Received: header. What an ugly Kluge. */ if (check_first && (type == REC_TYPE_NORM || type == REC_TYPE_CONT)) { check_first = 0; if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0])) rec_put(cleanup, REC_TYPE_NORM, "", 0); } if ((REC_PUT_BUF(cleanup, type, buf)) < 0) return (cleanup_service_error(info, CLEANUP_STAT_WRITE)); } return (0); }
static void qmqpd_write_content(QMQPD_STATE *state) { char *start; char *next; int len; int rec_type; int first = 1; int ch; /* * Start the message content segment. Prepend our own Received: header to * the message content. List the recipient only when a message has one * recipient. Otherwise, don't list the recipient to avoid revealing Bcc: * recipients that are supposed to be invisible. */ rec_fputs(state->cleanup, REC_TYPE_MESG, ""); rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])", state->name, state->name, state->rfc_addr); if (state->rcpt_count == 1 && state->recipient) { rec_fprintf(state->cleanup, REC_TYPE_NORM, "\tby %s (%s) with %s id %s", var_myhostname, var_mail_name, state->protocol, state->queue_id); quote_822_local(state->buf, state->recipient); rec_fprintf(state->cleanup, REC_TYPE_NORM, "\tfor <%s>; %s", STR(state->buf), mail_date(state->arrival_time.tv_sec)); } else { rec_fprintf(state->cleanup, REC_TYPE_NORM, "\tby %s (%s) with %s", var_myhostname, var_mail_name, state->protocol); rec_fprintf(state->cleanup, REC_TYPE_NORM, "\tid %s; %s", state->queue_id, mail_date(state->arrival_time.tv_sec)); } #ifdef RECEIVED_ENVELOPE_FROM quote_822_local(state->buf, state->sender); rec_fprintf(state->cleanup, REC_TYPE_NORM, "\t(envelope-from <%s>)", STR(state->buf)); #endif /* * Write the message content. * * XXX Force an empty record when the queue file content begins with * whitespace, so that it won't be considered as being part of our own * Received: header. What an ugly Kluge. * * XXX Deal with UNIX-style From_ lines at the start of message content just * in case. */ for (next = STR(state->message); /* void */ ; /* void */ ) { if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0) break; if (ch == '\n') rec_type = REC_TYPE_NORM; else rec_type = REC_TYPE_CONT; if (first) { if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { rec_fprintf(state->cleanup, rec_type, "X-Mailbox-Line: %.*s", len, start); continue; } first = 0; if (len > 0 && IS_SPACE_TAB(start[0])) rec_put(state->cleanup, REC_TYPE_NORM, "", 0); } if (rec_put(state->cleanup, rec_type, start, len) < 0) { state->err = CLEANUP_STAT_WRITE; return; } } }
int mime_state_update(MIME_STATE *state, int rec_type, const char *text, int len) { int input_is_text = (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT); MIME_STACK *sp; 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) /* * Be sure to flush any partial output line that might still be buffered * up before taking any other "end of input" actions. */ if (!input_is_text && state->prev_rec_type == REC_TYPE_CONT) mime_state_update(state, REC_TYPE_NORM, "", 0); /* * This message state machine is kept simple for the sake of robustness. * Standards evolve over time, and we want to be able to correctly * process messages that are not yet defined. This state machine knows * about headers and bodies, understands that multipart/whatever has * multiple body parts with a header and body, and that message/whatever * has message headers at the start of a body part. */ switch (state->curr_state) { /* * First, deal with header information that we have accumulated from * previous input records. Discard text that does not fit in a header * buffer. Our limit is quite generous; Sendmail will refuse mail * with only 32kbyte in all the message headers combined. */ 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_strcat(state->output_buffer, text); } else { if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER, STR(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_strcat(state->output_buffer, text); } else { if (state->static_flags & MIME_OPT_REPORT_TRUNC_HEADER) REPORT_ERROR(state, MIME_ERR_TRUNC_HEADER, STR(state->output_buffer)); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } } /* * The input is (the beginning of) another message header, or is * not a message header, or is not even a text record. With no * more input to append to this saved header, do output * processing and reset the saved header buffer. Hold on to the * content transfer encoding header if we have to do a 8->7 * transformation, because the proper information depends on the * content type header: message and multipart require a domain, * leaf entities have either a transformation or a domain. */ 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(state, MIME_ERR_8BIT_IN_HEADER, STR(state->output_buffer)); break; } } /* Output routine is explicitly allowed to change the data. */ if (header_info == 0 || header_info->type != HDR_CONTENT_TRANSFER_ENCODING || (state->static_flags & MIME_OPT_DOWNGRADE) == 0 || state->curr_domain == MIME_ENC_7BIT) HEAD_OUT(state, header_info, len); state->prev_rec_type = 0; VSTRING_RESET(state->output_buffer); } } /* * With past header information moved out of the way, proceed with a * clean slate. */ if (input_is_text) { int header_len; /* * See if this input is (the beginning of) a message header. * Normalize obsolete "name space colon" syntax to "name colon". * Things would be too confusing otherwise. */ if ((header_len = is_header(text)) > 0) { vstring_strncpy(state->output_buffer, text, header_len); for (text += header_len; IS_SPACE_TAB(*text); text++) /* void */ ; vstring_strcat(state->output_buffer, text); SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); } } /* * This input terminates a block of message headers. When converting * 8-bit to 7-bit mail, this is the right place to emit the correct * content-transfer-encoding header. With message or multipart we * specify 7bit, with leaf entities we specify quoted-printable. * * We're not going to convert non-text data into base 64. If they send * arbitrary binary data as 8-bit text, then the data is already * broken beyond recovery, because the Postfix SMTP server sanitizes * record boundaries, treating broken record boundaries as CRLF. * * Clear the output buffer, we will need it for storage of the * conversion result. */ if ((state->static_flags & MIME_OPT_DOWNGRADE) && state->curr_domain != MIME_ENC_7BIT) { if (state->curr_ctype == MIME_CTYPE_MESSAGE || state->curr_ctype == MIME_CTYPE_MULTIPART) cp = CU_CHAR_PTR("7bit"); 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); } /* * This input terminates a block of message headers. Call the * optional header end routine at the end of the first header block. */ if (state->curr_state == MIME_STATE_PRIMARY && state->head_end) state->head_end(state->app_context); /* * This is the right place to check if the sender specified an * appropriate identity encoding (7bit, 8bit, binary) for multipart * and for message. */ 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_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)); } } /* * Find out if the next body starts with its own message headers. In * agressive mode, examine headers of partial and external-body * messages. Otherwise, treat such headers as part of the "body". Set * the proper encoding information for the multipart prolog. * * XXX This changes state to MIME_STATE_NESTED and then outputs a body * line, so that the body offset is not properly reset. */ if (input_is_text) { if (*text == 0) { state->body_offset = 0; /* XXX */ 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); } } /* * Invalid input. Force output of one blank line and jump to the * body state, leaving all other state alone. */ else { SET_CURR_STATE(state, MIME_STATE_BODY); BODY_OUT(state, REC_TYPE_NORM, "", 0); } } /* * This input is not text. Go to body state, unconditionally. */ else { SET_CURR_STATE(state, MIME_STATE_BODY); } /* FALLTHROUGH */ /* * Body text. Look for message boundaries, and recover from missing * boundary strings. Missing boundaries can happen in agressive mode * with text/rfc822-headers or with message/partial. Ignore non-space * cruft after --boundary or --boundary--, because some MUAs do, and * because only perverse software would take advantage of this to * escape detection. We have to ignore trailing cruft anyway, because * our saved copy of the boundary string may have been truncated for * safety reasons. * * Optionally look for 8-bit data in content that was announced as, or * that defaults to, 7-bit. Unfortunately, we cannot turn this on by * default. Majordomo sends requests for approval that do not * propagate the MIME information from the enclosed message to the * message headers of the approval request. * * Set the proper state information after processing a message boundary * string. * * Don't look for boundary strings at the start of a continued record. */ 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(state, MIME_ERR_8BIT_IN_7BIT_BODY, text); break; } } if (state->stack && state->prev_rec_type != REC_TYPE_CONT && text[0] == '-' && text[1] == '-') { for (sp = state->stack; sp != 0; sp = sp->next) { if (strncmp(text + 2, sp->boundary, sp->bound_len) == 0) { while (sp != state->stack) mime_state_pop(state); if (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; } } } /* Put last for consistency with header output routine. */ 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); } /* * The input is not a text record. Inform the application that this * is the last opportunity to send any pending output. */ else { if (state->body_end) state->body_end(state->app_context); } SAVE_PREV_REC_TYPE_AND_RETURN_ERR_FLAGS(state, rec_type); /* * Oops. This can't happen. */ default: msg_panic("mime_state_update: unknown state: %d", state->curr_state); } }