summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/test-istream-attachment.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-mail/test-istream-attachment.c')
-rw-r--r--src/lib-mail/test-istream-attachment.c486
1 files changed, 486 insertions, 0 deletions
diff --git a/src/lib-mail/test-istream-attachment.c b/src/lib-mail/test-istream-attachment.c
new file mode 100644
index 0000000..ac3096e
--- /dev/null
+++ b/src/lib-mail/test-istream-attachment.c
@@ -0,0 +1,486 @@
+/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "sha1.h"
+#include "hash-format.h"
+#include "safe-mkstemp.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "istream-attachment-extractor.h"
+#include "istream-attachment-connector.h"
+#include "ostream.h"
+#include "test-common.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define BINARY_TEXT_LONG "we have\ra lot \nof \0binary stuff in here\n" \
+"b adjig sadjg jasidgjiaehga3wht8a3w8ghxjc dsgad hasdghsd gasd ds" \
+"jdsoga sjdga0w3tjhawjgsertniq3n5oqerjqw2r89q23h awhrqh835r8a"
+#define BINARY_TEXT_LONG_BASE64 \
+"d2UgaGF2ZQ1hIGxvdCAKb2YgAGJpbmFyeSBzdHVmZiBpbiBoZXJlCmIgYWRqaWcgc2FkamcgamFz\r\n" \
+"aWRnamlhZWhnYTN3aHQ4YTN3OGdoeGpjIGRzZ2FkIGhhc2RnaHNkIGdhc2QgZHNqZHNvZ2Egc2pk\r\n" \
+"Z2EwdzN0amhhd2pnc2VydG5pcTNuNW9xZXJqcXcycjg5cTIzaCBhd2hycWg4MzVyOGE="
+
+#define BINARY_TEXT_SHORT "eh"
+#define BINARY_TEXT_SHORT_BASE64 "ZWg="
+
+static const char mail_input[] =
+"MIME-Version: 1.0\r\n"
+"Content-Type: multipart/alternative;\r\n boundary=\"bound\"\r\n"
+"\r\n"
+"mime header\r\n"
+"\r\n--bound\r\n"
+"Content-Transfer-Encoding: base64\r\n"
+"Content-Type: text/plain\r\n"
+"\r\n"
+BINARY_TEXT_LONG_BASE64
+"\r\n--bound\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Transfer-Encoding: base64\r\n"
+"\r\n"
+BINARY_TEXT_SHORT_BASE64
+"\r\n--bound--\r\n";
+
+static const char mail_output[] =
+"MIME-Version: 1.0\r\n"
+"Content-Type: multipart/alternative;\r\n boundary=\"bound\"\r\n"
+"\r\n"
+"mime header\r\n"
+"\r\n--bound\r\n"
+"Content-Transfer-Encoding: base64\r\n"
+"Content-Type: text/plain\r\n"
+"\r\n"
+"\r\n--bound\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Transfer-Encoding: base64\r\n"
+"\r\n"
+"\r\n--bound--\r\n";
+
+static const char *mail_broken_input_body_prefix =
+"MIME-Version: 1.0\r\n"
+"Content-Type: multipart/alternative;\r\n boundary=\"bound\"\r\n"
+"\r\n"
+"--bound\r\n"
+"Content-Transfer-Encoding: base64\r\n"
+"Content-Type: text/plain\r\n"
+"\r\n";
+
+static const char *mail_broken_input_bodies[] = {
+ /* broken base64 input */
+ "Zm9vCg=\n",
+ "Zm9vCg\n",
+ "Zm9vC\n",
+ /* extra whitespace */
+ "Zm9v\n Zm9v\n",
+ "Zm9v \nZm9v\n",
+ /* mixed LF vs CRLFs */
+ "Zm9vYmFy\r\nZm9vYmFy\n",
+ "Zm9vYmFy\nZm9vYmFy\r\n",
+ /* line length increases */
+ "Zm9v\nZm9vYmFy\n",
+ "Zm9v\nZm9vCg==",
+ "Zm9v\nZm9vYgo="
+};
+
+static const char *mail_nonbroken_input_bodies[] = {
+ /* suffixes with explicit '=' end */
+ "Zm9vCg==",
+ "Zm9vCg==\n",
+ "Zm9vCg==\r\n",
+ "Zm9vCg==\nfoo\n",
+ "Zm9vCg==\r\nfoo\n",
+ "Zm9vCg== \t\t\n\n",
+ /* suffixes with shorter line length */
+ "Zm9vYmFy\nZm9v\n",
+ "Zm9vYmFy\r\nZm9v\r\n",
+ "Zm9vYmFy\nZm9v\nfoo\n",
+ "Zm9vYmFy\r\nZm9v\r\nfoo\n",
+ "Zm9vYmFy\nZm9v\n \t\t\n\n",
+ /* suffixes with empty line */
+ "Zm9v\n\n",
+ "Zm9v\r\n\r\n",
+ "Zm9v\n\nfoo\n"
+ "Zm9v\r\n\nfoo\n"
+ "Zm9v\r\n\r\nfoo\n"
+#if 0
+ /* the whitespace here could be handled as suffixes, but for now
+ they're not: */
+ "Zm9v ",
+ "Zm9v \n"
+#endif
+};
+
+struct attachment {
+ size_t buffer_offset;
+ uoff_t start_offset;
+ uoff_t encoded_size, decoded_size;
+ unsigned int base64_blocks_per_line;
+};
+
+static buffer_t *attachment_data;
+static ARRAY(struct attachment) attachments;
+
+static int test_open_temp_fd(void *context ATTR_UNUSED)
+{
+ string_t *str = t_str_new(128);
+ int fd;
+
+ str_append(str, "/tmp/dovecot-test.");
+ fd = safe_mkstemp(str, 0600, (uid_t)-1, (gid_t)-1);
+ if (fd == -1)
+ i_fatal("safe_mkstemp(%s) failed: %m", str_c(str));
+ i_unlink(str_c(str));
+ return fd;
+}
+
+static int test_open_attachment_ostream(struct istream_attachment_info *info,
+ struct ostream **output_r,
+ const char **error_r ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+ struct attachment *a;
+
+ if (attachment_data == NULL)
+ attachment_data = buffer_create_dynamic(default_pool, 1024);
+ if (!array_is_created(&attachments))
+ i_array_init(&attachments, 8);
+ a = array_append_space(&attachments);
+ a->buffer_offset = attachment_data->used;
+ a->start_offset = info->start_offset;
+ a->encoded_size = info->encoded_size;
+ a->base64_blocks_per_line = info->base64_blocks_per_line;
+ test_assert(strlen(info->hash) == 160/8*2); /* sha1 size */
+
+ *output_r = o_stream_create_buffer(attachment_data);
+ if (o_stream_seek(*output_r, a->buffer_offset) < 0)
+ i_unreached();
+ return 0;
+}
+
+static int
+test_open_attachment_ostream_error(struct istream_attachment_info *info ATTR_UNUSED,
+ struct ostream **output_r ATTR_UNUSED,
+ const char **error_r,
+ void *context ATTR_UNUSED)
+{
+ *error_r = "test open error";
+ return -1;
+}
+
+static int test_close_attachment_ostream(struct ostream *output, bool success,
+ const char **error_r ATTR_UNUSED,
+ void *context ATTR_UNUSED)
+{
+ struct attachment *a;
+
+ i_assert(success);
+
+ a = array_back_modifiable(&attachments);
+ a->decoded_size = output->offset - a->buffer_offset;
+
+ if (o_stream_finish(output) < 0)
+ i_unreached();
+ o_stream_destroy(&output);
+ return 0;
+}
+
+static int
+test_close_attachment_ostream_error(struct ostream *output,
+ bool success, const char **error,
+ void *context ATTR_UNUSED)
+{
+ if (success)
+ *error = "test output error";
+ o_stream_abort(output);
+ o_stream_destroy(&output);
+ return -1;
+}
+
+static struct istream *
+test_build_original_istream(struct istream *base_input, uoff_t msg_size)
+{
+ struct istream_attachment_connector *conn;
+ const unsigned char *data = attachment_data->data;
+ const struct attachment *a;
+ struct istream *input;
+ uoff_t data_size = attachment_data->used;
+ const char *error;
+
+ conn = istream_attachment_connector_begin(base_input, msg_size);
+ array_foreach(&attachments, a) {
+ input = i_stream_create_from_data(data, a->decoded_size);
+ if (istream_attachment_connector_add(conn, input,
+ a->start_offset, a->encoded_size,
+ a->base64_blocks_per_line, TRUE, &error) < 0)
+ i_unreached();
+ i_stream_unref(&input);
+
+ i_assert(a->decoded_size <= data_size);
+ data += a->decoded_size;
+ data_size -= a->decoded_size;
+ }
+ i_assert(data_size == 0);
+ return istream_attachment_connector_finish(&conn);
+}
+
+static void
+get_istream_attachment_settings(struct istream_attachment_settings *set_r)
+{
+ const char *error;
+
+ i_zero(set_r);
+ set_r->min_size = 1;
+ set_r->drain_parent_input = TRUE;
+ set_r->open_temp_fd = test_open_temp_fd;
+ set_r->open_attachment_ostream = test_open_attachment_ostream;
+ set_r->close_attachment_ostream= test_close_attachment_ostream;
+ if (hash_format_init("%{sha1}", &set_r->hash_format, &error) < 0)
+ i_unreached();
+}
+
+static int test_input_stream(struct istream *file_input)
+{
+ struct istream_attachment_settings set;
+ struct istream *input, *input2;
+ const unsigned char *data;
+ size_t size;
+ struct sha1_ctxt hash;
+ uoff_t msg_size, orig_msg_size;
+ buffer_t *base_buf;
+ unsigned char hash_file[SHA1_RESULTLEN], hash_attached[SHA1_RESULTLEN];
+ int ret = 0;
+
+ /* get hash when directly reading input */
+ input = i_stream_create_crlf(file_input);
+ sha1_init(&hash);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ sha1_loop(&hash, data, size);
+ i_stream_skip(input, size);
+ }
+ sha1_result(&hash, hash_file);
+ msg_size = orig_msg_size = input->v_offset;
+ i_stream_unref(&input);
+
+ /* read through attachment extractor */
+ get_istream_attachment_settings(&set);
+
+ i_stream_seek(file_input, 0);
+ input = i_stream_create_crlf(file_input);
+ input2 = i_stream_create_attachment_extractor(input, &set, NULL);
+ i_stream_unref(&input);
+ base_buf = buffer_create_dynamic(default_pool, 1024);
+ while (i_stream_read_more(input2, &data, &size) > 0) {
+ buffer_append(base_buf, data, size);
+ i_stream_skip(input2, size);
+ }
+ i_stream_unref(&input2);
+
+ /* rebuild the original stream and see if the hash matches */
+ for (unsigned int i = 0; i < 2; i++) {
+ input2 = i_stream_create_from_data(base_buf->data, base_buf->used);
+ input = test_build_original_istream(input2, msg_size);
+ i_stream_unref(&input2);
+
+ sha1_init(&hash);
+ while (i_stream_read_more(input, &data, &size) > 0) {
+ sha1_loop(&hash, data, size);
+ i_stream_skip(input, size);
+ }
+ test_assert_idx(input->eof && input->stream_errno == 0, i);
+ sha1_result(&hash, hash_attached);
+ i_stream_unref(&input);
+
+ if (memcmp(hash_file, hash_attached, SHA1_RESULTLEN) != 0)
+ ret = -1;
+
+ /* try again without knowing the message's size */
+ msg_size = UOFF_T_MAX;
+ }
+
+ /* try with a wrong message size */
+ for (int i = 0; i < 2; i++) {
+ input2 = i_stream_create_from_data(base_buf->data, base_buf->used);
+ input = test_build_original_istream(input2,
+ i == 0 ? orig_msg_size + 1 : orig_msg_size - 1);
+ i_stream_unref(&input2);
+ while (i_stream_read_more(input, &data, &size) > 0)
+ i_stream_skip(input, size);
+ test_assert(input->stream_errno == (i == 0 ? EPIPE : EINVAL));
+ i_stream_unref(&input);
+ }
+
+ buffer_free(&base_buf);
+ buffer_free(&attachment_data);
+ if (array_is_created(&attachments))
+ array_free(&attachments);
+ return ret;
+}
+
+static void test_istream_attachment(void)
+{
+ struct istream_attachment_settings set;
+ struct istream *datainput, *input;
+ const unsigned char *data;
+ size_t i, size;
+ int ret;
+
+ test_begin("istream attachment");
+ datainput = test_istream_create_data(mail_input, sizeof(mail_input));
+ test_istream_set_allow_eof(datainput, FALSE);
+
+ get_istream_attachment_settings(&set);
+ input = i_stream_create_attachment_extractor(datainput, &set, NULL);
+
+ for (i = 1; i <= sizeof(mail_input); i++) {
+ test_istream_set_size(datainput, i);
+ while ((ret = i_stream_read(input)) > 0) ;
+ test_assert(ret == 0);
+ }
+ test_istream_set_allow_eof(datainput, TRUE);
+ while ((ret = i_stream_read(input)) > 0) ;
+ test_assert(ret == -1);
+
+ data = i_stream_get_data(input, &size);
+ test_assert(size == sizeof(mail_output) &&
+ memcmp(data, mail_output, size) == 0);
+
+ data = attachment_data->data;
+ test_assert(attachment_data->used ==
+ sizeof(BINARY_TEXT_LONG)-1 + strlen(BINARY_TEXT_SHORT));
+ test_assert(memcmp(data, BINARY_TEXT_LONG, sizeof(BINARY_TEXT_LONG)-1) == 0);
+ test_assert(memcmp(data + sizeof(BINARY_TEXT_LONG)-1,
+ BINARY_TEXT_SHORT, strlen(BINARY_TEXT_SHORT)) == 0);
+ i_stream_unref(&input);
+ i_stream_unref(&datainput);
+
+ buffer_free(&attachment_data);
+ if (array_is_created(&attachments))
+ array_free(&attachments);
+ test_end();
+}
+
+static bool test_istream_attachment_extractor_one(const char *body, int err_type)
+{
+ const size_t prefix_len = strlen(mail_broken_input_body_prefix);
+ struct istream_attachment_settings set;
+ struct istream *datainput, *input;
+ char *mail_text;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+ bool unchanged;
+
+ mail_text = i_strconcat(mail_broken_input_body_prefix, body, NULL);
+ datainput = test_istream_create_data(mail_text, strlen(mail_text));
+
+ get_istream_attachment_settings(&set);
+ if (err_type == 1)
+ set.open_attachment_ostream = test_open_attachment_ostream_error;
+ else if (err_type == 2)
+ set.close_attachment_ostream = test_close_attachment_ostream_error;
+ input = i_stream_create_attachment_extractor(datainput, &set, NULL);
+
+ while ((ret = i_stream_read(input)) > 0) ;
+ if (err_type != 0) {
+ test_assert(ret == -1 && input->stream_errno == EIO);
+ unchanged = FALSE;
+ goto cleanup;
+ }
+ test_assert(ret == -1 && input->stream_errno == 0);
+
+ data = i_stream_get_data(input, &size);
+ i_assert(size >= prefix_len &&
+ memcmp(data, mail_broken_input_body_prefix, prefix_len) == 0);
+ data += prefix_len;
+ size -= prefix_len;
+
+ i_assert(attachment_data != NULL);
+ unchanged = attachment_data->used <= strlen(body) &&
+ memcmp(attachment_data->data, body, attachment_data->used) == 0 &&
+ strlen(body) - attachment_data->used == size &&
+ memcmp(data, body + attachment_data->used, size) == 0;
+
+cleanup:
+ buffer_free(&attachment_data);
+ if (array_is_created(&attachments))
+ array_free(&attachments);
+
+ i_stream_unref(&input);
+ i_stream_unref(&datainput);
+ i_free(mail_text);
+ return unchanged;
+}
+
+static void test_istream_attachment_extractor(void)
+{
+ unsigned int i;
+
+ test_begin("istream attachment extractor");
+ for (i = 0; i < N_ELEMENTS(mail_broken_input_bodies); i++)
+ test_assert(test_istream_attachment_extractor_one(mail_broken_input_bodies[i], 0));
+ for (i = 0; i < N_ELEMENTS(mail_nonbroken_input_bodies); i++)
+ test_assert(!test_istream_attachment_extractor_one(mail_nonbroken_input_bodies[i], 0));
+ test_end();
+}
+
+static void test_istream_attachment_extractor_error(void)
+{
+ unsigned int i;
+
+ test_begin("istream attachment extractor error");
+ for (int err_type = 1; err_type <= 2; err_type++) {
+ for (i = 0; i < N_ELEMENTS(mail_broken_input_bodies); i++)
+ test_istream_attachment_extractor_one(mail_broken_input_bodies[i], err_type);
+ for (i = 0; i < N_ELEMENTS(mail_nonbroken_input_bodies); i++)
+ test_istream_attachment_extractor_one(mail_nonbroken_input_bodies[i], err_type);
+ }
+ test_end();
+}
+
+static void test_istream_attachment_connector(void)
+{
+ struct istream *input;
+
+ test_begin("istream attachment connector");
+ input = i_stream_create_from_data(mail_input, sizeof(mail_input));
+ test_assert(test_input_stream(input) == 0);
+ i_stream_unref(&input);
+ test_end();
+}
+
+static int test_input_file(const char *path)
+{
+ struct istream *file_input;
+ int ret = 0;
+
+ lib_init();
+
+ file_input = i_stream_create_file(path, 64);
+ if (test_input_stream(file_input) < 0) {
+ fprintf(stderr, "istream-attachment-extractor: mismatch on file %s\n",
+ path);
+ ret = -1;
+ }
+ i_stream_unref(&file_input);
+
+ lib_deinit();
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ static void (*const test_functions[])(void) = {
+ test_istream_attachment,
+ test_istream_attachment_extractor,
+ test_istream_attachment_extractor_error,
+ test_istream_attachment_connector,
+ NULL
+ };
+ if (argc > 1)
+ return test_input_file(argv[1]) < 0 ? 1 : 0;
+ else
+ return test_run(test_functions);
+}