Blob Blame History Raw
From b9a2f18466a0d3377bab3e7a57691bdd75d8507c Mon Sep 17 00:00:00 2001
From: Timo Sirainen <timo.sirainen@open-xchange.com>
Date: Mon, 17 Aug 2020 17:32:11 +0300
Subject: [PATCH] lib-imap: Add imap_parser_read_tag() and _read_command_name()

---
 src/lib-imap/imap-parser.c      | 67 +++++++++++++++++++++++++++++++++
 src/lib-imap/imap-parser.h      |  7 ++++
 src/lib-imap/test-imap-parser.c | 67 +++++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+)

diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c
index b6c6e63fb1..52d79282fa 100644
--- a/src/lib-imap/imap-parser.c
+++ b/src/lib-imap/imap-parser.c
@@ -947,3 +947,70 @@ const char *imap_parser_read_word(struct imap_parser *parser)
 		return NULL;
 	}
 }
+
+static int
+imap_parser_read_next_atom(struct imap_parser *parser, bool parsing_tag,
+			   const char **atom_r)
+{
+	const unsigned char *data;
+	size_t i, data_size;
+
+	data = i_stream_get_data(parser->input, &data_size);
+
+	/*
+	   tag            = 1*<any ASTRING-CHAR except "+">
+	   ASTRING-CHAR   = ATOM-CHAR / resp-specials
+	   ATOM-CHAR      = <any CHAR except atom-specials>
+
+	   x-command      = "X" atom <experimental command arguments>
+	   atom           = 1*ATOM-CHAR
+	*/
+	for (i = 0; i < data_size; i++) {
+		/* explicitly check for atom-specials, because
+		   IS_ATOM_PARSER_INPUT() allows some atom-specials */
+		switch (data[i]) {
+		case ' ':
+		case '\r':
+		case '\n':
+			data_size = i + (data[i] == ' ' ? 1 : 0);
+			parser->line_size += data_size;
+			i_stream_skip(parser->input, data_size);
+			*atom_r = p_strndup(parser->pool, data, i);
+			/* don't allow empty string */
+			return i == 0 ? -1 : 1;
+		/* atom-specials: */
+		case '(':
+		case ')':
+		case '{':
+		/* list-wildcards: */
+		case '%':
+		case '*':
+		/* quoted-specials: */
+		case '"':
+		case '\\':
+		/* resp-specials: */
+		case ']':
+			return -1;
+		case '+':
+			if (parsing_tag)
+				return -1;
+			break;
+		default:
+			if ((unsigned char)data[i] < ' ' ||
+			    (unsigned char)data[i] >= 0x80)
+				return -1;
+		}
+	}
+	return 0;
+}
+
+int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r)
+{
+	return imap_parser_read_next_atom(parser, TRUE, tag_r);
+}
+
+int imap_parser_read_command_name(struct imap_parser *parser,
+				  const char **name_r)
+{
+	return imap_parser_read_next_atom(parser, FALSE, name_r);
+}
diff --git a/src/lib-imap/imap-parser.h b/src/lib-imap/imap-parser.h
index e5d01c17f2..5e09d61d2b 100644
--- a/src/lib-imap/imap-parser.h
+++ b/src/lib-imap/imap-parser.h
@@ -101,5 +101,12 @@ int imap_parser_finish_line(struct imap_parser *parser, unsigned int count,
 /* Read one word - used for reading tag and command name.
    Returns NULL if more data is needed. */
 const char *imap_parser_read_word(struct imap_parser *parser);
+/* Read command tag. Returns 1 if tag was returned, 0 if more data is needed,
+   -1 if input isn't a valid tag. */
+int imap_parser_read_tag(struct imap_parser *parser, const char **tag_r);
+/* Read command name. Returns 1 if command name was returned, 0 if more data is
+   needed, -1 if input isn't a valid command name string. */
+int imap_parser_read_command_name(struct imap_parser *parser,
+				  const char **name_r);
 
 #endif
diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c
index 93ef8fd59b..3ca4e34858 100644
--- a/src/lib-imap/test-imap-parser.c
+++ b/src/lib-imap/test-imap-parser.c
@@ -79,10 +79,77 @@ static void test_imap_parser_partial_list(void)
 	test_end();
 }
 
+static void test_imap_parser_read_tag_cmd(void)
+{
+	enum read_type {
+		BOTH,
+		TAG,
+		COMMAND
+	};
+	struct {
+		const char *input;
+		const char *tag;
+		int ret;
+		enum read_type type;
+	} tests[] = {
+		{ "tag foo", "tag", 1, BOTH },
+		{ "tag\r", "tag", 1, BOTH },
+		{ "tag\rfoo", "tag", 1, BOTH },
+		{ "tag\nfoo", "tag", 1, BOTH },
+		{ "tag\r\nfoo", "tag", 1, BOTH },
+		{ "\n", NULL, -1, BOTH },
+		{ "tag", NULL, 0, BOTH },
+		{ "tag\t", NULL, -1, BOTH },
+		{ "tag\001", NULL, -1, BOTH },
+		{ "tag\x80", NULL, -1, BOTH },
+		{ "tag(", NULL, -1, BOTH },
+		{ "tag)", NULL, -1, BOTH },
+		{ "tag{", NULL, -1, BOTH },
+		{ "tag/ ", "tag/", 1, BOTH },
+		{ "tag%", NULL, -1, BOTH },
+		{ "tag*", NULL, -1, BOTH },
+		{ "tag\"", NULL, -1, BOTH },
+		{ "tag\\", NULL, -1, BOTH },
+		{ "tag+", NULL, -1, TAG },
+		{ "tag+ ", "tag+", 1, COMMAND },
+	};
+	struct istream *input;
+	struct imap_parser *parser;
+	const char *atom;
+	int ret;
+
+	test_begin("imap_parser_read_tag and imap_parser_read_command_name");
+	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+		if (tests[i].type != COMMAND) {
+			input = test_istream_create(tests[i].input);
+			test_assert(i_stream_read(input) > 0);
+			parser = imap_parser_create(input, NULL, 1024);
+			ret = imap_parser_read_tag(parser, &atom);
+			test_assert_idx(ret == tests[i].ret, i);
+			test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i);
+			imap_parser_unref(&parser);
+			i_stream_destroy(&input);
+		}
+
+		if (tests[i].type != TAG) {
+			input = test_istream_create(tests[i].input);
+			test_assert(i_stream_read(input) > 0);
+			parser = imap_parser_create(input, NULL, 1024);
+			ret = imap_parser_read_command_name(parser, &atom);
+			test_assert_idx(ret == tests[i].ret, i);
+			test_assert_idx(ret <= 0 || strcmp(tests[i].tag, atom) == 0, i);
+			imap_parser_unref(&parser);
+			i_stream_destroy(&input);
+		}
+	}
+	test_end();
+}
+
 int main(void)
 {
 	static void (*const test_functions[])(void) = {
 		test_imap_parser_crlf,
+		test_imap_parser_read_tag_cmd,
 		NULL
 	};
 	return test_run(test_functions);