From b9a2f18466a0d3377bab3e7a57691bdd75d8507c Mon Sep 17 00:00:00 2001 From: Timo Sirainen 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* + ASTRING-CHAR = ATOM-CHAR / resp-specials + ATOM-CHAR = + + x-command = "X" atom + 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);