void mbox_sync_headers_add_space(struct mbox_sync_mail_context *ctx,
				 size_t size)
{
	size_t data_size, pos, start_pos;
	const unsigned char *data;
	void *p;

	i_assert(size < SSIZE_T_MAX);

	if (ctx->mail.pseudo)
		start_pos = ctx->hdr_pos[MBOX_HDR_X_IMAPBASE];
	else if (ctx->mail.space > 0) {
		/* update the header using the existing offset.
		   otherwise we might chose wrong header and just decrease
		   the available space */
		start_pos = ctx->mail.offset - ctx->hdr_offset;
	} else {
		/* Append at the end of X-Keywords header,
		   or X-UID if it doesn't exist */
		start_pos = ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] != (size_t)-1 ?
			ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] :
			ctx->hdr_pos[MBOX_HDR_X_UID];
	}

	data = str_data(ctx->header);
	data_size = str_len(ctx->header);
	i_assert(start_pos < data_size);

	for (pos = start_pos; pos < data_size; pos++) {
		if (data[pos] == '\n') {
			/* possibly continues in next line */
			if (pos+1 == data_size || !IS_LWSP(data[pos+1]))
				break;
			start_pos = pos+1;
		} else if (!IS_LWSP(data[pos]) && data[pos] != '\r') {
			start_pos = pos+1;
		}
	}

	/* pos points to end of header now, and start_pos to beginning
	   of whitespace. */
	mbox_sync_move_buffer(ctx, pos, size, 0);

	p = buffer_get_space_unsafe(ctx->header, pos, size);
	memset(p, ' ', size);

	if (ctx->header_first_change > pos)
		ctx->header_first_change = pos;
	ctx->header_last_change = (size_t)-1;

	ctx->mail.space = (pos - start_pos) + size;
	ctx->mail.offset = ctx->hdr_offset;
	if (ctx->mail.space > 0)
		ctx->mail.offset += start_pos;
}
Exemple #2
0
static void parse_trailing_whitespace(struct mbox_sync_mail_context *ctx,
				      struct message_header_line *hdr)
{
	size_t i, space = 0;

	/* the value may contain newlines. we can't count whitespace before
	   and after it as a single contiguous whitespace block, as that may
	   get us into situation where removing whitespace goes eg.
	   " \n \n" -> " \n\n" which would then be treated as end of headers.

	   that could probably be avoided by being careful, but as newlines
	   should never be there (we don't generate them), it's not worth the
	   trouble. */

	for (i = hdr->full_value_len; i > 0; i--) {
		if (!IS_LWSP(hdr->full_value[i-1]))
			break;
		space++;
	}

	if ((ssize_t)space > ctx->mail.space) {
		i_assert(space != 0);
		ctx->mail.offset = ctx->hdr_offset + str_len(ctx->header) + i;
		ctx->mail.space = space;
	}
}
Exemple #3
0
static void compress_lwsp(string_t *dest, const unsigned char *src,
			  unsigned int src_len)
{
	unsigned int i;
	bool prev_lwsp = TRUE;

	for (i = 0; i < src_len; i++) {
		if (IS_LWSP(src[i])) {
			if (!prev_lwsp) {
				prev_lwsp = TRUE;
				str_append_c(dest, ' ');
			}
		} else {
			prev_lwsp = FALSE;
			str_append_c(dest, src[i]);
		}
	}
}
int message_parse_header_next(struct message_header_parser_ctx *ctx,
			      struct message_header_line **hdr_r)
{
        struct message_header_line *line = &ctx->line;
	const unsigned char *msg;
	size_t i, size, startpos, colon_pos, parse_size;
	int ret;
	bool continued, continues, last_no_newline, last_crlf;
	bool no_newline, crlf_newline;

	*hdr_r = NULL;
	if (line->eoh)
		return -1;

	if (ctx->skip > 0) {
		i_stream_skip(ctx->input, ctx->skip);
		ctx->skip = 0;
	}

	if (line->continues)
		colon_pos = 0;
	else {
		/* new header line */
		line->name_offset = ctx->input->v_offset;
		colon_pos = UINT_MAX;
		buffer_set_used_size(ctx->value_buf, 0);
	}

	no_newline = FALSE;
	crlf_newline = FALSE;
	continued = line->continues;
	continues = FALSE;

	for (startpos = 0;;) {
		ret = i_stream_read_data(ctx->input, &msg, &size, startpos+1);
		if (ret >= 0) {
			/* we want to know one byte in advance to find out
			   if it's multiline header */
			parse_size = size == 0 ? 0 : size-1;
		} else {
			parse_size = size;
		}

		if (ret <= 0 && startpos == parse_size) {
			if (ret == -1) {
				if (startpos > 0) {
					/* header ended unexpectedly. */
					no_newline = TRUE;
					ctx->skip = startpos;
					break;
				}
				/* error / EOF with no bytes */
				return -1;
			}

			if (size > 0 && !ctx->skip_line && !continued &&
			    (msg[0] == '\n' ||
			     (msg[0] == '\r' && size > 1 && msg[1] == '\n'))) {
				/* end of headers - this mostly happens just
				   with mbox where headers are read separately
				   from body */
				size = 0;
				if (ctx->hdr_size != NULL)
					ctx->hdr_size->lines++;
				if (msg[0] == '\r') {
					ctx->skip = 2;
					crlf_newline = TRUE;
				} else {
					ctx->skip = 1;
					if (ctx->hdr_size != NULL)
						ctx->hdr_size->virtual_size++;
				}
				break;
			}
			if (ret == 0 && !ctx->input->eof) {
				/* stream is nonblocking - need more data */
				return 0;
			}
			i_assert(size > 0);

			/* a) line is larger than input buffer
			   b) header ended unexpectedly */
			if (ret == -2) {
				/* go back to last LWSP if found. */
				size_t min_pos = !continued ? colon_pos : 0;
				for (i = size-1; i > min_pos; i--) {
					if (IS_LWSP(msg[i])) {
						size = i;
						break;
					}
				}
				if (i == min_pos && (msg[size-1] == '\r' ||
						     msg[size-1] == '\n')) {
					/* we may or may not have a full header,
					   but we don't know until we get the
					   next character. leave out the
					   linefeed and finish the header on
					   the next run. */
					size--;
					if (size > 0 && msg[size-1] == '\r')
						size--;
				}
				/* the buffer really has to be more than 2 to
				   avoid CRLF looping forever */
				i_assert(size > 0);

				continues = TRUE;
			}
			no_newline = TRUE;
			ctx->skip = size;
			break;
		}

		/* find ':' */
		if (colon_pos == UINT_MAX) {
			for (i = startpos; i < parse_size; i++) {
				if (msg[i] > ':')
					continue;

				if (msg[i] == ':' && !ctx->skip_line) {
					colon_pos = i;
					line->full_value_offset =
						ctx->input->v_offset + i + 1;
					break;
				}
				if (msg[i] == '\n') {
					/* end of headers, or error */
					break;
				}

				if (msg[i] == '\0')
					ctx->has_nuls = TRUE;
			}
		} else {
			i = startpos;
		}

		/* find '\n' */
		for (; i < parse_size; i++) {
			if (msg[i] <= '\n') {
				if (msg[i] == '\n')
					break;
				if (msg[i] == '\0')
					ctx->has_nuls = TRUE;
			}
		}

		if (i < parse_size && i+1 == size && ret == -2) {
			/* we don't know if the line continues. */
			i++;
		} else if (i < parse_size) {
			/* got a line */
			if (ctx->skip_line) {
				/* skipping a line with a huge header name */
				if (ctx->hdr_size != NULL) {
					ctx->hdr_size->lines++;
					ctx->hdr_size->physical_size += i + 1;
					ctx->hdr_size->virtual_size += i + 1;
				}
				if (i == 0 || msg[i-1] != '\r') {
					/* missing CR */
					if (ctx->hdr_size != NULL)
						ctx->hdr_size->virtual_size++;
				}

				i_stream_skip(ctx->input, i + 1);
				startpos = 0;
				ctx->skip_line = FALSE;
				continue;
			}
			continues = i+1 < size && IS_LWSP(msg[i+1]);

			if (ctx->hdr_size != NULL)
				ctx->hdr_size->lines++;
			if (i == 0 || msg[i-1] != '\r') {
				/* missing CR */
				if (ctx->hdr_size != NULL)
					ctx->hdr_size->virtual_size++;
				size = i;
			} else {
				size = i-1;
				crlf_newline = TRUE;
			}

			ctx->skip = i+1;
			break;
		}

		startpos = i;
	}

	last_crlf = line->crlf_newline &&
		(ctx->flags & MESSAGE_HEADER_PARSER_FLAG_DROP_CR) == 0;
	last_no_newline = line->no_newline ||
		(ctx->flags & MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE) != 0;

	line->continues = continues;
	line->continued = continued;
	line->crlf_newline = crlf_newline;
	line->no_newline = no_newline;
	if (size == 0 && !continued) {
		/* end of headers */
		line->eoh = TRUE;
		line->name_len = line->value_len = line->full_value_len = 0;
		line->name = ""; line->value = line->full_value = NULL;
		line->middle = NULL; line->middle_len = 0;
		line->full_value_offset = line->name_offset;
		line->continues = FALSE;
	} else if (line->continued) {
		line->value = msg;
		line->value_len = size;
	} else if (colon_pos == UINT_MAX) {
		/* missing ':', assume the whole line is name */
		line->value = NULL;
		line->value_len = 0;

		str_truncate(ctx->name, 0);
		buffer_append(ctx->name, msg, size);
		line->name = str_c(ctx->name);
		line->name_len = str_len(ctx->name);

		line->middle = NULL;
		line->middle_len = 0;
	} else {
		size_t pos;

		line->value = msg + colon_pos+1;
		line->value_len = size - colon_pos - 1;
		if (ctx->flags & MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP) {
			/* get value. skip all LWSP after ':'. Note that
			   RFC2822 doesn't say we should, but history behind
			   it..

			   Exception to this is if the value consists only of
			   LWSP, then skip only the one LWSP after ':'. */
			for (pos = 0; pos < line->value_len; pos++) {
				if (!IS_LWSP(line->value[pos]))
					break;
			}

			if (pos == line->value_len) {
				/* everything was LWSP */
				if (line->value_len > 0 &&
				    IS_LWSP(line->value[0]))
					pos = 1;
			}
		} else {
			pos = line->value_len > 0 &&
				IS_LWSP(line->value[0]) ? 1 : 0;
		}

		line->value += pos;
		line->value_len -= pos;
		line->full_value_offset += pos;

		/* get name, skip LWSP before ':' */
		while (colon_pos > 0 && IS_LWSP(msg[colon_pos-1]))
			colon_pos--;

		str_truncate(ctx->name, 0);
		/* use buffer_append() so the name won't be truncated if there
		   are NULs. */
		buffer_append(ctx->name, msg, colon_pos);
		str_append_c(ctx->name, '\0');

		/* keep middle stored also in ctx->name so it's available
		   with use_full_value */
		line->middle = msg + colon_pos;
		line->middle_len = (size_t)(line->value - line->middle);
		str_append_n(ctx->name, line->middle, line->middle_len);

		line->name = str_c(ctx->name);
		line->name_len = colon_pos;
		line->middle = str_data(ctx->name) + line->name_len + 1;
	}

	if (!line->continued) {
		/* first header line. make a copy of the line since we can't
		   really trust input stream not to lose it. */
		buffer_append(ctx->value_buf, line->value, line->value_len);
		line->value = line->full_value = ctx->value_buf->data;
		line->full_value_len = line->value_len;
	} else if (line->use_full_value) {
		/* continue saving the full value. */
		if (last_no_newline) {
			/* line is longer than fit into our buffer, so we
			   were forced to break it into multiple
			   message_header_lines */
		} else {
			if (last_crlf)
				buffer_append_c(ctx->value_buf, '\r');
			buffer_append_c(ctx->value_buf, '\n');
		}
		if ((ctx->flags & MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE) &&
		    line->value_len > 0 && line->value[0] != ' ' &&
		    IS_LWSP(line->value[0])) {
			buffer_append_c(ctx->value_buf, ' ');
			buffer_append(ctx->value_buf,
				      line->value + 1, line->value_len - 1);
		} else {
			buffer_append(ctx->value_buf,
				      line->value, line->value_len);
		}
		line->full_value = buffer_get_data(ctx->value_buf,
						   &line->full_value_len);
	} else {
		/* we didn't want full_value, and this is a continued line. */
		line->full_value = NULL;
		line->full_value_len = 0;
	}

	/* always reset it */
	line->use_full_value = FALSE;

	if (ctx->hdr_size != NULL) {
		ctx->hdr_size->physical_size += ctx->skip;
		ctx->hdr_size->virtual_size += ctx->skip;
	}

	*hdr_r = line;
	return 1;
}