From: Aki Tuomi Date: Wed, 8 Jul 2020 07:59:48 +0000 (+0300) Subject: lib: istream - Do not attempt read past end in i_stream_next_line_finish X-Git-Tag: 2.3.11.2~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0da6a699521424b8a0a56c12589ce45d20861f6e;p=thirdparty%2Fdovecot%2Fcore.git lib: istream - Do not attempt read past end in i_stream_next_line_finish --- diff --git a/src/lib/istream.c b/src/lib/istream.c index 771079d30c..95cf54d251 100644 --- a/src/lib/istream.c +++ b/src/lib/istream.c @@ -560,7 +560,7 @@ static char *i_stream_next_line_finish(struct istream_private *stream, size_t i) char *ret; size_t end; - if (i > 0 && stream->buffer[i-1] == '\r') { + if (i > stream->skip && stream->buffer[i-1] == '\r') { end = i - 1; stream->line_crlf = TRUE; } else { @@ -568,7 +568,8 @@ static char *i_stream_next_line_finish(struct istream_private *stream, size_t i) stream->line_crlf = FALSE; } - if (stream->buffer == stream->w_buffer) { + if (stream->buffer == stream->w_buffer && + end < stream->buffer_size) { /* modify the buffer directly */ stream->w_buffer[end] = '\0'; ret = (char *)stream->w_buffer + stream->skip; @@ -577,8 +578,9 @@ static char *i_stream_next_line_finish(struct istream_private *stream, size_t i) if (stream->line_str == NULL) stream->line_str = str_new(default_pool, 256); str_truncate(stream->line_str, 0); - str_append_data(stream->line_str, stream->buffer + stream->skip, - end - stream->skip); + if (stream->skip < end) + str_append_data(stream->line_str, stream->buffer + stream->skip, + end - stream->skip); ret = str_c_modifiable(stream->line_str); } diff --git a/src/lib/test-istream.c b/src/lib/test-istream.c index 7a5ec415b5..88e51e285e 100644 --- a/src/lib/test-istream.c +++ b/src/lib/test-istream.c @@ -2,6 +2,7 @@ #include "test-lib.h" #include "istream.h" +#include "istream-crlf.h" static void test_istream_children(void) { @@ -56,7 +57,325 @@ static void test_istream_children(void) test_end(); } +static void test_istream_next_line_expect(struct istream *is, const char *expect, + unsigned int i) +{ + const char *line = i_stream_next_line(is); + return test_assert_strcmp_idx(line, expect, i); +} + +static void test_istream_next_line(void) +{ + /* single line cases */ +#define TEST_CASE(a, s, b) { \ + .input = (const unsigned char*)((a)), .input_len = sizeof((a)), \ + .skip = s, \ + .output = b } + const struct test_case_sl { + const unsigned char *input; + size_t input_len; + size_t skip; + const char *output; + } test_cases_sl[] = { + TEST_CASE("", 0, NULL), + TEST_CASE("a\n", 1, ""), + TEST_CASE("a\r\n", 0, "a"), + TEST_CASE("a\r\n", 1, ""), + TEST_CASE("a\r\n", 2, ""), + TEST_CASE("hello\nworld\n", 6, "world"), + TEST_CASE("hello\nworld", 6, NULL), + TEST_CASE("hello\n\n\n\n", 6, ""), + TEST_CASE("wrong\n\r\n\n", 0, "wrong"), + TEST_CASE("wrong\n\r\r\n", 6, "\r"), + TEST_CASE("wrong\n\r\r\n", 7, ""), + }; + + test_begin("i_stream_next_line"); + + for(unsigned int i = 0; i < N_ELEMENTS(test_cases_sl); i++) { + const struct test_case_sl *test_case = &test_cases_sl[i]; + struct istream *input = + i_stream_create_copy_from_data(test_case->input, test_case->input_len); + test_assert_idx(i_stream_read(input) >= 0 || + (input->stream_errno == 0 && input->eof), i); + i_stream_skip(input, test_case->skip); + test_assert_strcmp_idx(i_stream_next_line(input), test_case->output, i); + test_assert_idx(input->stream_errno == 0, i); + i_stream_unref(&input); + + input = test_istream_create_data(test_case->input, test_case->input_len); + test_assert_idx(i_stream_read(input) >= 0 || + (input->stream_errno == 0 && input->eof), i); + i_stream_skip(input, test_case->skip); + test_assert_strcmp_idx(i_stream_next_line(input), test_case->output, i); + test_assert_idx(input->stream_errno == 0, i); + i_stream_unref(&input); + } + +#undef TEST_CASE +#define TEST_CASE(a) test_istream_create_data((a), sizeof(a)) + /* multiline tests */ + struct istream *is = TEST_CASE("\n\n\n\n\n\n"); + size_t i; + test_assert(i_stream_read(is) >= 0 || (is->stream_errno == 0 && is->eof)); + for(i = 0; i < 6; i++) + test_istream_next_line_expect(is, "", i); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + is = TEST_CASE( + "simple\r\n" + "multiline\n" + "test with\0" + "some exciting\n" + "things\r\n\0"); + test_assert(i_stream_read(is) >= 0 || (is->stream_errno == 0 && is->eof)); + test_istream_next_line_expect(is, "simple", 0); + test_istream_next_line_expect(is, "multiline", 1); + test_istream_next_line_expect(is, "test with", 2); + test_istream_next_line_expect(is, "things", 3); + test_istream_next_line_expect(is, NULL, 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + is = TEST_CASE( + "NUL\0" + "test\n"); + test_assert(i_stream_read(is) >= 0 || (is->stream_errno == 0 && is->eof)); + test_istream_next_line_expect(is, "NUL", 0); + test_istream_next_line_expect(is, NULL, 1); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + const char test_data_1[] = + "this is some data\n" + "written like this\n" + "to attempt and induce\n" + "errors or flaws\n"; + + is = TEST_CASE(test_data_1); + size_t n = 0; + const char *const *lines = t_strsplit(test_data_1, "\n"); + for(i = 0; i < sizeof(test_data_1); i++) { + test_istream_set_size(is, i); + i_stream_read(is); + const char *line = i_stream_next_line(is); + if (line != NULL) { + test_assert_strcmp_idx(lines[n], line, n); + n++; + } + } + test_assert(n == 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + const char test_data_2[] = + "this is some data\n" + "written like this\n" + "to attempt and induce\n" + "errors or flaws"; + + is = TEST_CASE(test_data_2); + lines = t_strsplit(test_data_2, "\n"); + i_stream_set_return_partial_line(is, TRUE); + n = 0; + + /* requires one extra read to get the last line */ + for(i = 0; i < sizeof(test_data_1) + 1; i++) { + test_istream_set_size(is, I_MIN(sizeof(test_data_1), i)); + i_stream_read(is); + const char *line = i_stream_next_line(is); + if (line != NULL) { + test_assert_strcmp_idx(lines[n], line, n); + n++; + } + i_stream_read(is); + } + test_assert(n == 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + const char test_data_3[] = + "this is some data\r\n" + "written like this\r\n" + "to attempt and induce\r\n" + "errors or flaws\r\n"; + + struct istream *is_1 = TEST_CASE(test_data_3); + is = i_stream_create_crlf(is_1); + i_stream_unref(&is_1); + + lines = t_strsplit_spaces(test_data_3, "\r\n"); + n = 0; + + for(i = 0; i < sizeof(test_data_3); i++) { + test_istream_set_size(is, i); + i_stream_read(is); + const char *line = i_stream_next_line(is); + if (line != NULL) { + test_assert_strcmp_idx(lines[n], line, n); + n++; + } + } + test_assert(n == 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + test_end(); +} + +static void test_istream_read_next_line(void) +{ + /* single line cases */ +#undef TEST_CASE +#define TEST_CASE(a, s, b) { \ + .input = (const unsigned char*)((a)), .input_len = sizeof((a)), \ + .skip = s, \ + .output = b } + const struct test_case_sl { + const unsigned char *input; + size_t input_len; + size_t skip; + const char *output; + } test_cases_sl[] = { + TEST_CASE("", 0, NULL), + TEST_CASE("a\n", 1, ""), + TEST_CASE("a\r\n", 0, "a"), + TEST_CASE("a\r\n", 1, ""), + TEST_CASE("a\r\n", 2, ""), + TEST_CASE("hello\nworld\n", 6, "world"), + TEST_CASE("hello\nworld", 6, NULL), + TEST_CASE("hello\n\n\n\n", 6, ""), + TEST_CASE("wrong\n\r\n\n", 0, "wrong"), + TEST_CASE("wrong\n\r\r\n", 6, "\r"), + TEST_CASE("wrong\n\r\r\n", 7, ""), + }; + + test_begin("i_stream_read_next_line"); + for(unsigned int i = 0; i < N_ELEMENTS(test_cases_sl); i++) { + const struct test_case_sl *test_case = &test_cases_sl[i]; + struct istream *input = + i_stream_create_copy_from_data(test_case->input, test_case->input_len); + i_stream_skip(input, test_case->skip); + test_assert_strcmp_idx(i_stream_read_next_line(input), test_case->output, i); + test_assert_idx(input->stream_errno == 0, i); + i_stream_unref(&input); + + input = test_istream_create_data(test_case->input, test_case->input_len); + i_stream_skip(input, test_case->skip); + test_assert_strcmp_idx(i_stream_read_next_line(input), test_case->output, i); + test_assert_idx(input->stream_errno == 0, i); + i_stream_unref(&input); + } + + const char test_data_1[] = + "this is some data\n" + "written like this\n" + "to attempt and induce\n" + "errors or flaws\n"; + +#undef TEST_CASE +#define TEST_CASE(a) test_istream_create_data((a), sizeof(a)) + /* multiline tests */ + struct istream *is = TEST_CASE("\n\n\n\n\n\n"); + size_t i; + for(i = 0; i < 6; i++) + test_assert_strcmp_idx(i_stream_read_next_line(is), "", i); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + is = TEST_CASE( + "simple\r\n" + "multiline\n" + "test with\0" + "some exciting\n" + "things\r\n\0"); + test_assert_strcmp_idx(i_stream_read_next_line(is), "simple", 0); + test_assert_strcmp_idx(i_stream_read_next_line(is), "multiline", 1); + test_assert_strcmp_idx(i_stream_read_next_line(is), "test with", 2); + test_assert_strcmp_idx(i_stream_read_next_line(is), "things", 3); + test_assert_strcmp_idx(i_stream_read_next_line(is), NULL, 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + is = TEST_CASE( + "NUL\0" + "test\n"); + test_assert_strcmp_idx(i_stream_read_next_line(is), "NUL", 0); + test_assert_strcmp_idx(i_stream_read_next_line(is), NULL, 1); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + is = TEST_CASE(test_data_1); + size_t n = 0; + const char *const *lines = t_strsplit(test_data_1, "\n"); + for(i = 0; i < sizeof(test_data_1); i++) { + test_istream_set_size(is, i); + const char *line = i_stream_read_next_line(is); + if (line != NULL) { + test_assert_strcmp_idx(lines[n], line, n); + n++; + } + } + test_assert(n == 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + const char test_data_2[] = + "this is some data\n" + "written like this\n" + "to attempt and induce\n" + "errors or flaws"; + + is = TEST_CASE(test_data_2); + lines = t_strsplit(test_data_2, "\n"); + i_stream_set_return_partial_line(is, TRUE); + n = 0; + + for(i = 0; i < sizeof(test_data_1); i++) { + test_istream_set_size(is, i); + const char *line = i_stream_read_next_line(is); + if (line != NULL) { + test_assert_strcmp_idx(lines[n], line, n); + n++; + } + } + test_assert(n == 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + const char test_data_3[] = + "this is some data\r\n" + "written like this\r\n" + "to attempt and induce\r\n" + "errors or flaws\r\n"; + + + struct istream *is_1 = TEST_CASE(test_data_3); + is = i_stream_create_crlf(is_1); + i_stream_unref(&is_1); + + lines = t_strsplit_spaces(test_data_3, "\r\n"); + n = 0; + + for(i = 0; i < sizeof(test_data_3); i++) { + test_istream_set_size(is, i); + const char *line = i_stream_read_next_line(is); + if (line != NULL) { + test_assert_strcmp_idx(lines[n], line, n); + n++; + } + } + test_assert(n == 4); + test_assert(is->stream_errno == 0); + i_stream_unref(&is); + + test_end(); +} + void test_istream(void) { test_istream_children(); + test_istream_next_line(); + test_istream_read_next_line(); }