From 0b33d1a0e8379296856599fedc893d42121c199b Mon Sep 17 00:00:00 2001 From: Stephan Bosch <stephan.bosch@open-xchange.com> Date: Sun, 10 Oct 2021 22:12:23 +0200 Subject: [PATCH] lib-managesieve: Implement Sieve URL API. --- src/lib-managesieve/Makefile.am | 31 +- src/lib-managesieve/managesieve-url.c | 509 +++++++++++++++++++++ src/lib-managesieve/managesieve-url.h | 89 ++++ src/lib-managesieve/test-managesieve-url.c | 345 ++++++++++++++ 4 files changed, 972 insertions(+), 2 deletions(-) create mode 100644 src/lib-managesieve/managesieve-url.c create mode 100644 src/lib-managesieve/managesieve-url.h create mode 100644 src/lib-managesieve/test-managesieve-url.c diff --git a/src/lib-managesieve/Makefile.am b/src/lib-managesieve/Makefile.am index 079df4359..54558ea9b 100644 --- a/src/lib-managesieve/Makefile.am +++ b/src/lib-managesieve/Makefile.am @@ -7,10 +7,37 @@ AM_CPPFLAGS = \ libmanagesieve_la_SOURCES = \ managesieve-arg.c \ managesieve-quote.c \ - managesieve-parser.c + managesieve-parser.c \ + managesieve-url.c noinst_HEADERS = \ managesieve-protocol.h \ managesieve-arg.h \ managesieve-quote.h \ - managesieve-parser.h + managesieve-parser.h \ + managesieve-url.h + +test_programs = \ + test-managesieve-url + +test_nocheck_programs = + +noinst_PROGRAMS = $(test_programs) $(test_nocheck_programs) + +test_libs = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE) \ + $(LIBDOVECOT) +test_deps = \ + $(noinst_LTLIBRARIES) \ + $(LIBDOVECOT_STORAGE_DEPS) \ + $(LIBDOVECOT_DEPS) + +test_managesieve_url_SOURCES = test-managesieve-url.c +test_managesieve_url_LDADD = $(test_libs) +test_managesieve_url_DEPENDENCIES = $(test_deps) + +check-local: + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/lib-managesieve/managesieve-url.c b/src/lib-managesieve/managesieve-url.c new file mode 100644 index 000000000..9d7cdb874 --- /dev/null +++ b/src/lib-managesieve/managesieve-url.c @@ -0,0 +1,509 @@ +/* Copyright (c) 2021 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "str.h" +#include "strfuncs.h" +#include "net.h" +#include "uri-util.h" + +#include "managesieve-url.h" + +/* RFC 5804, Section 3: + + sieveurl = sieveurl-server / sieveurl-list-scripts / + sieveurl-script + + sieveurl-server = "sieve://" authority + + sieveurl-list-scripts = "sieve://" authority ["/"] + + sieveurl-script = "sieve://" authority "/" + [owner "/"] scriptname + + authority = <defined in [URI-GEN]> + + owner = *ochar + ;; %-encoded version of [SASL] authorization + ;; identity (script owner) or "userid". + ;; + ;; Empty owner is used to reference + ;; global scripts. + ;; + ;; Note that ASCII characters such as " ", ";", + ;; "&", "=", "/" and "?" must be %-encoded + ;; as per rule specified in [URI-GEN]. + + scriptname = 1*ochar + ;; %-encoded version of UTF-8 representation + ;; of the script name. + ;; Note that ASCII characters such as " ", ";", + ;; "&", "=", "/" and "?" must be %-encoded + ;; as per rule specified in [URI-GEN]. + + ochar = unreserved / pct-encoded / sub-delims-sh / + ":" / "@" + ;; Same as [URI-GEN] 'pchar', + ;; but without ";", "&" and "=". + + unreserved = <defined in [URI-GEN]> + + pct-encoded = <defined in [URI-GEN]> + + sub-delims-sh = "!" / "$" / "'" / "(" / ")" / + "*" / "+" / "," + ;; Same as [URI-GEN] sub-delims, + ;; but without ";", "&" and "=". + */ + +/* Character lookup table + + unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" [bit0] + sub-delims-sh = "!" / "$" / "'" / "(" / ")" / + "*" / "+" / "," + ;; Same as [URI-GEN] sub-delims, + ;; but without ";", "&" and "=". [bit1] + ochar = unreserved / pct-encoded / sub-delims-sh / + ":" / "@" [bit0|bit1|bit2] + */ + +static const unsigned char managesieve_url_ochar_mask = (1<<0)|(1<<1)|(1<<2); + +static const unsigned char managesieve_url_char_lookup[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 + 2, 0, 0, 0, 2, 0, 0, 2, 2, 2, 2, 2, 2, 1, 1, 0, // 20 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, // 30 + 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70 + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // b0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // c0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // d0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // f0 +}; + +/* + * Sieve URL parser + */ + +struct managesieve_url_parser { + struct uri_parser parser; + + enum managesieve_url_parse_flags flags; + + struct managesieve_url *url; + struct managesieve_url *base; +}; + +static int +managesieve_url_parse_scheme(struct managesieve_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + const char *scheme; + int ret; + + if ((url_parser->flags & MANAGESIEVE_URL_PARSE_SCHEME_EXTERNAL) != 0) + return 1; + + ret = uri_parse_scheme(parser, &scheme); + if (ret < 0) + return -1; + if (ret == 0) { + parser->error = "Relative Sieve URL not allowed"; + return -1; + } + + if (strcasecmp(scheme, "sieve") != 0) { + parser->error = "Not a Sieve URL"; + return -1; + } + return 0; +} + +static int +managesieve_url_parse_userinfo(struct managesieve_url_parser *url_parser, + struct uri_authority *auth, + const char **user_r, const char **password_r) +{ + struct uri_parser *parser = &url_parser->parser; + const char *p; + + *user_r = *password_r = NULL; + + if (auth->enc_userinfo == NULL) + return 0; + if ((url_parser->flags & MANAGESIEVE_URL_ALLOW_USERINFO_PART) == 0) { + parser->error = "Sieve URL does not allow `userinfo@' part"; + return -1; + } + + p = strchr(auth->enc_userinfo, ':'); + if (p == NULL) { + if (!uri_data_decode(parser, auth->enc_userinfo, NULL, user_r)) + return -1; + } else { + if (!uri_data_decode(parser, auth->enc_userinfo, p, user_r)) + return -1; + if (!uri_data_decode(parser, p + 1, NULL, password_r)) + return -1; + } + return 0; +} + +static int +managesieve_url_parse_authority(struct managesieve_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + struct managesieve_url *url = url_parser->url; + struct uri_authority auth; + const char *user = NULL, *password = NULL; + int ret; + + if ((ret = uri_parse_host_authority(parser, &auth)) < 0) + return -1; + if (auth.host.name == NULL || *auth.host.name == '\0') { + parser->error = + "Sieve URL does not allow empty host identifier"; + return -1; + } + if (ret > 0) { + if (managesieve_url_parse_userinfo(url_parser, &auth, + &user, &password) < 0) + return -1; + } + if (url != NULL) { + uri_host_copy(parser->pool, &url->host, &auth.host); + url->port = auth.port; + url->user = p_strdup(parser->pool, user); + url->password = p_strdup(parser->pool, password); + } + return 0; +} + +static int +managesieve_url_parse_path_segment(struct managesieve_url_parser *url_parser, + const char **segment_r) ATTR_NULL(2) +{ + struct uri_parser *parser = &url_parser->parser; + const unsigned char *first, *offset; + string_t *segment = NULL; + int ret; + + first = offset = parser->cur; + if (segment_r != NULL) + segment = t_str_new(128); + while (parser->cur < parser->end) { + if (*parser->cur == '%') { + unsigned char ch = 0; + + if (segment != NULL) { + str_append_data(segment, offset, + parser->cur - offset); + } + + ret = uri_parse_pct_encoded(parser, &ch); + if (ret < 0) + return -1; + i_assert(ret > 0); + + if (segment != NULL) + str_append_c(segment, ch); + offset = parser->cur; + continue; + } + if ((managesieve_url_char_lookup[*parser->cur] & + managesieve_url_ochar_mask) == 0) + break; + parser->cur++; + } + if (segment != NULL) + str_append_data(segment, offset, parser->cur - offset); + + if (parser->cur < parser->end && *parser->cur != '/' && + *parser->cur != '?' && *parser->cur != '#') { + parser->error = p_strdup_printf(parser->pool, + "Path segment contains invalid character %s", + uri_char_sanitize(*parser->cur)); + return -1; + } + + if (first == parser->cur) + return 0; + + if (segment != NULL) + *segment_r = p_strdup(parser->pool, str_c(segment)); + return 1; +} + +static int +managesieve_url_parse_path(struct managesieve_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + struct managesieve_url *url = url_parser->url; + const char *segment1, *segment2; + int ret; + + if (parser->cur >= parser->end || *parser->cur != '/') + return 0; + parser->cur++; + + ret = managesieve_url_parse_path_segment(url_parser, + (url == NULL ? + NULL : &segment1)); + if (ret < 0) + return -1; + if (ret == 0) { + if (url != NULL) + url->scriptname = ""; + return 1; + } + + if (parser->cur >= parser->end || *parser->cur != '/') { + if (url != NULL) + url->scriptname = segment1; + return 1; + } + parser->cur++; + + ret = managesieve_url_parse_path_segment(url_parser, + (url == NULL ? + NULL : &segment2)); + if (ret < 0) + return -1; + if (ret == 0) { + parser->error = "Empty script name"; + return -1; + } + if (*parser->cur == '/') { + parser->error = "Script name contains invalid character '/'"; + return -1; + } + + if (url != NULL) { + url->owner = segment1; + url->scriptname = segment2; + } + return 1; +} + +static int managesieve_url_do_parse(struct managesieve_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + + /* "sieve:" */ + if (managesieve_url_parse_scheme(url_parser) < 0) + return -1; + + /* "//" authority + */ + if (parser->cur >= parser->end || parser->cur[0] != '/' || + (parser->cur + 1) >= parser->end || parser->cur[1] != '/') { + parser->error = "Sieve URL requires `//' after `sieve:'"; + return -1; + } + parser->cur += 2; + + if (managesieve_url_parse_authority(url_parser) < 0) + return -1; + + /* "/" [owner "/"] scriptname */ + if (managesieve_url_parse_path(url_parser) < 0) + return -1; + + /* Sieve URL has no query */ + if (*parser->cur == '?') { + parser->error = "Query component not allowed in Sieve URL"; + return -1; + } + + /* Sieve URL has no fragment */ + if (*parser->cur == '#') { + parser->error = "Fragment component not allowed in Sieve URL"; + return -1; + } + + /* Must be at end of URL now */ + i_assert(parser->cur == parser->end); + + return 0; +} + +/* Public API */ + +int managesieve_url_parse(const char *url, + enum managesieve_url_parse_flags flags, pool_t pool, + struct managesieve_url **url_r, const char **error_r) +{ + struct managesieve_url_parser url_parser; + + i_zero(&url_parser); + uri_parser_init(&url_parser.parser, pool, url); + + if (url_r != NULL) + url_parser.url = p_new(pool, struct managesieve_url, 1); + url_parser.flags = flags; + + if (managesieve_url_do_parse(&url_parser) < 0) { + i_assert(url_parser.parser.error != NULL); + *error_r = url_parser.parser.error; + return -1; + } + + if (url_r != NULL) + *url_r = url_parser.url; + return 0; +} + +/* + * Sieve URL manipulation + */ + +void managesieve_url_init_authority_from(struct managesieve_url *dest, + const struct managesieve_url *src) +{ + i_zero(dest); + dest->host = src->host; + dest->port = src->port; +} + +void managesieve_url_copy_authority(pool_t pool, struct managesieve_url *dest, + const struct managesieve_url *src) +{ + i_zero(dest); + uri_host_copy(pool, &dest->host, &src->host); + dest->port = src->port; +} + +struct managesieve_url * +managesieve_url_clone_authority(pool_t pool, const struct managesieve_url *src) +{ + struct managesieve_url *new_url; + + new_url = p_new(pool, struct managesieve_url, 1); + managesieve_url_copy_authority(pool, new_url, src); + + return new_url; +} + +void managesieve_url_copy(pool_t pool, struct managesieve_url *dest, + const struct managesieve_url *src) +{ + managesieve_url_copy_authority(pool, dest, src); + dest->owner = p_strdup(pool, src->owner); + dest->scriptname = p_strdup(pool, src->scriptname); +} + +void managesieve_url_copy_with_userinfo(pool_t pool, + struct managesieve_url *dest, + const struct managesieve_url *src) +{ + managesieve_url_copy(pool, dest, src); + dest->user = p_strdup(pool, src->user); + dest->password = p_strdup(pool, src->password); +} + +struct managesieve_url * +managesieve_url_clone(pool_t pool, const struct managesieve_url *src) +{ + struct managesieve_url *new_url; + + new_url = p_new(pool, struct managesieve_url, 1); + managesieve_url_copy(pool, new_url, src); + + return new_url; +} + +struct managesieve_url * +managesieve_url_clone_with_userinfo(pool_t pool, + const struct managesieve_url *src) +{ + struct managesieve_url *new_url; + + new_url = p_new(pool, struct managesieve_url, 1); + managesieve_url_copy_with_userinfo(pool, new_url, src); + + return new_url; +} + +/* + * Sieve URL construction + */ + +static void +managesieve_url_add_scheme(string_t *urlstr) +{ + /* scheme */ + uri_append_scheme(urlstr, "sieve"); + str_append(urlstr, "//"); +} + +static void +managesieve_url_add_authority(string_t *urlstr, + const struct managesieve_url *url) +{ + /* userinfo */ + if (url->user != NULL) { + if (url->user != NULL) + uri_append_user_data(urlstr, ";:", url->user); + str_append_c(urlstr, '@'); + } + /* host */ + uri_append_host(urlstr, &url->host); + /* port */ + if (url->port != MANAGESIEVE_DEFAULT_PORT) + uri_append_port(urlstr, url->port); +} + +static void +managesieve_url_add_path(string_t *urlstr, const struct managesieve_url *url) +{ + if (url->owner == NULL) { + if (url->scriptname == NULL) + return; + } else { + i_assert(url->scriptname != NULL && *url->scriptname != '\0'); + + str_append_c(urlstr, '/'); + uri_append_path_segment_data(urlstr, ";&=", url->owner); + } + + str_append_c(urlstr, '/'); + uri_append_path_segment_data(urlstr, ";&=", url->scriptname); +} + +const char *managesieve_url_create(const struct managesieve_url *url) +{ + string_t *urlstr = t_str_new(512); + + managesieve_url_add_scheme(urlstr); + managesieve_url_add_authority(urlstr, url); + managesieve_url_add_path(urlstr, url); + + return str_c(urlstr); +} + +const char *managesieve_url_create_host(const struct managesieve_url *url) +{ + string_t *urlstr = t_str_new(512); + + managesieve_url_add_scheme(urlstr); + managesieve_url_add_authority(urlstr, url); + + return str_c(urlstr); +} + +const char *managesieve_url_create_authority(const struct managesieve_url *url) +{ + string_t *urlstr = t_str_new(256); + + managesieve_url_add_authority(urlstr, url); + + return str_c(urlstr); +} diff --git a/src/lib-managesieve/managesieve-url.h b/src/lib-managesieve/managesieve-url.h new file mode 100644 index 000000000..37b3dce4d --- /dev/null +++ b/src/lib-managesieve/managesieve-url.h @@ -0,0 +1,89 @@ +#ifndef MANAGESIEVE_URL_H +#define MANAGESIEVE_URL_H + +#include "net.h" +#include "uri-util.h" + +#include "managesieve-protocol.h" + +struct managesieve_url { + /* server */ + struct uri_host host; + in_port_t port; + + /* userinfo (not parsed by default) */ + const char *user; + const char *password; + + /* path */ + const char *owner; + const char *scriptname; +}; + +/* + * Sieve URL parsing + */ + +enum managesieve_url_parse_flags { + /* Scheme part 'sieve:' is already parsed externally. This implies that + this is an absolute SIEVE URL. */ + MANAGESIEVE_URL_PARSE_SCHEME_EXTERNAL = 0x01, + /* Allow 'user:password@' part in SIEVE URL */ + MANAGESIEVE_URL_ALLOW_USERINFO_PART = 0x04, +}; + +int managesieve_url_parse(const char *url, + enum managesieve_url_parse_flags flags, pool_t pool, + struct managesieve_url **url_r, const char **error_r) + ATTR_NULL(4); + +/* + * Sieve URL evaluation + */ + +static inline in_port_t +managesieve_url_get_port_default(const struct managesieve_url *url, + in_port_t default_port) +{ + return (url->port != 0 ? url->port : default_port); +} + +static inline in_port_t +managesieve_url_get_port(const struct managesieve_url *url) +{ + return managesieve_url_get_port_default(url, MANAGESIEVE_DEFAULT_PORT); +} + +/* + * Sieve URL manipulation + */ + +void managesieve_url_init_authority_from(struct managesieve_url *dest, + const struct managesieve_url *src); +void managesieve_url_copy_authority(pool_t pool, struct managesieve_url *dest, + const struct managesieve_url *src); +struct managesieve_url * +managesieve_url_clone_authority(pool_t pool, const struct managesieve_url *src); + +void managesieve_url_copy(pool_t pool, struct managesieve_url *dest, + const struct managesieve_url *src); +void managesieve_url_copy_with_userinfo(pool_t pool, + struct managesieve_url *dest, + const struct managesieve_url *src); + +struct managesieve_url * +managesieve_url_clone(pool_t pool,const struct managesieve_url *src); +struct managesieve_url * +managesieve_url_clone_with_userinfo(pool_t pool, + const struct managesieve_url *src); + +/* + * Sieve URL construction + */ + +const char *managesieve_url_create(const struct managesieve_url *url); + +const char *managesieve_url_create_host(const struct managesieve_url *url); +const char *managesieve_url_create_authority(const struct managesieve_url *url); + +#endif diff --git a/src/lib-managesieve/test-managesieve-url.c b/src/lib-managesieve/test-managesieve-url.c new file mode 100644 index 000000000..d1c8a6328 --- /dev/null +++ b/src/lib-managesieve/test-managesieve-url.c @@ -0,0 +1,345 @@ +/* Copyright (c) 2021 Pigeonhole authors, see the included COPYING file + */ + +#include "lib.h" +#include "net.h" +#include "managesieve-url.h" +#include "test-common.h" + +struct valid_managesieve_url_test { + const char *url; + enum managesieve_url_parse_flags flags; + + struct managesieve_url url_parsed; +}; + +/* Valid MANAGESIEVE URL tests */ +static struct valid_managesieve_url_test valid_url_tests[] = { + /* Generic tests */ + { + .url = "sieve://localhost", + .url_parsed = { + .host = { .name = "localhost" }, + }, + }, + { + .url = "sieve://www.%65%78%61%6d%70%6c%65.com", + .url_parsed = { + .host = { .name = "www.example.com" }, + }, + }, + { + .url = "sieve://www.dovecot.org:8080", + .url_parsed = { + .host = { .name = "www.dovecot.org" }, + .port = 8080, + }, + }, + { + .url = "sieve://127.0.0.1", + .url_parsed = { + .host = { + .name = "127.0.0.1", + .ip = { .family = AF_INET }, + }, + }, + }, + { + .url = "sieve://[::1]", + .url_parsed = { + .host = { + .name = "[::1]", + .ip = { .family = AF_INET6 }, + }, + }, + }, + { + .url = "sieve://[::1]:8080", + .url_parsed = { + .host = { + .name = "[::1]", + .ip = { .family = AF_INET6 }, + }, + .port = 8080, + }, + }, + { + .url = "sieve://user@api.dovecot.org", + .flags = MANAGESIEVE_URL_ALLOW_USERINFO_PART, + .url_parsed = { + .host = { .name = "api.dovecot.org" }, + .user = "user", + }, + }, + { + .url = "sieve://userid:secret@api.dovecot.org", + .flags = MANAGESIEVE_URL_ALLOW_USERINFO_PART, + .url_parsed = { + .host = { .name = "api.dovecot.org" }, + .user = "userid", + .password = "secret", + }, + }, + { + .url = "sieve://su%3auserid:secret@api.dovecot.org", + .flags = MANAGESIEVE_URL_ALLOW_USERINFO_PART, + .url_parsed = { + .host = { .name = "api.dovecot.org" }, + .user = "su:userid", + .password = "secret", + }, + }, + { + .url = "sieve://www.example.com/", + .url_parsed = { + .host = { .name = "www.example.com" }, + .scriptname = "", + }, + }, + { + .url = "sieve://www.example.com/frop", + .url_parsed = { + .host = { .name = "www.example.com" }, + .scriptname = "frop", + }, + }, + { + .url = "sieve://www.example.com/user/frop", + .url_parsed = { + .host = { .name = "www.example.com" }, + .owner = "user", + .scriptname = "frop", + }, + }, +}; + +static unsigned int valid_url_test_count = N_ELEMENTS(valid_url_tests); + +static void +test_managesieve_url_equal(struct managesieve_url *urlt, struct managesieve_url *urlp) +{ + if (urlp->host.name == NULL || urlt->host.name == NULL) { + test_assert(urlp->host.name == urlt->host.name); + } else { + test_assert(strcmp(urlp->host.name, urlt->host.name) == 0); + } + test_assert(urlp->port == urlt->port); + test_assert(urlp->host.ip.family == urlt->host.ip.family); + if (urlp->user == NULL || urlt->user == NULL) { + test_assert(urlp->user == urlt->user); + } else { + test_assert(strcmp(urlp->user, urlt->user) == 0); + } + if (urlp->password == NULL || urlt->password == NULL) { + test_assert(urlp->password == urlt->password); + } else { + test_assert(strcmp(urlp->password, urlt->password) == 0); + } + if (urlp->owner == NULL || urlt->owner == NULL) { + test_assert(urlp->owner == urlt->owner); + } else { + test_assert(strcmp(urlp->owner, urlt->owner) == 0); + } + if (urlp->scriptname == NULL || urlt->scriptname == NULL) { + test_assert(urlp->scriptname == urlt->scriptname); + } else { + test_assert(strcmp(urlp->scriptname, urlt->scriptname) == 0); + } +} + +static void test_managesieve_url_valid(void) +{ + unsigned int i; + + for (i = 0; i < valid_url_test_count; i++) T_BEGIN { + const char *url = valid_url_tests[i].url; + enum managesieve_url_parse_flags flags = + valid_url_tests[i].flags; + struct managesieve_url *urlt = &valid_url_tests[i].url_parsed; + struct managesieve_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("managesieve url valid [%d]", i)); + + if (managesieve_url_parse(url, flags, + pool_datastack_create(), + &urlp, &error) < 0) + urlp = NULL; + + test_out_reason(t_strdup_printf("managesieve_url_parse(%s)", + valid_url_tests[i].url), urlp != NULL, error); + if (urlp != NULL) + test_managesieve_url_equal(urlt, urlp); + + test_end(); + } T_END; +} + +struct invalid_managesieve_url_test { + const char *url; + enum managesieve_url_parse_flags flags; + struct managesieve_url url_base; +}; + +static struct invalid_managesieve_url_test invalid_url_tests[] = { + { + .url = "imap://example.com/INBOX" + }, + { + .url = "managesieve:/www.example.com" + }, + { + .url = "" + }, + { + .url = "/frop" + }, + { + .url = "sieve//www.example.com/frop\"" + }, + { + .url = "sieve:///dovecot.org" + }, + { + .url = "sieve://[]/frop" + }, + { + .url = "sieve://[v08.234:232:234:234:2221]/user/frop" + }, + { + .url = "sieve://[1::34a:34:234::6]/frop" + }, + { + .url = "sieve://example%a.com/frop" + }, + { + .url = "sieve://example.com%/user/frop" + }, + { + .url = "sieve://example%00.com/frop" + }, + { + .url = "sieve://example.com:65536/frop" + }, + { + .url = "sieve://example.com:72817/frop" + }, + { + .url = "sieve://example.com/user/%00" + }, + { + .url = "sieve://example.com/user/%0r" + }, + { + .url = "sieve://example.com/user/%" + }, + { + .url = "sieve://example.com/?%00" + }, + { + .url = "sieve://example.com/user/" + }, + { + .url = "sieve://example.com/user/frop/frml" + }, + { + .url = "sieve://www.example.com/user/frop#IMAP_Server" + }, + { + .url = "sieve://example.com/#%00", + }, + { + .url = "sieve://example.com/?query" + }, + { + .url = "sieve://example.com/user?query" + }, + { + .url = "sieve://example.com/?query/user" + }, + { + .url = "sieve://example.com/#fragment" + }, + { + .url = "sieve://example.com/user#fragment" + }, + { + .url = "sieve://example.com/#fragment/user" + }, +}; + +static unsigned int invalid_url_test_count = N_ELEMENTS(invalid_url_tests); + +static void test_managesieve_url_invalid(void) +{ + unsigned int i; + + for (i = 0; i < invalid_url_test_count; i++) T_BEGIN { + const char *url = invalid_url_tests[i].url; + enum managesieve_url_parse_flags flags = invalid_url_tests[i].flags; + struct managesieve_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("managesieve url invalid [%d]", i)); + + if (managesieve_url_parse(url, flags, pool_datastack_create(), + &urlp, &error) < 0) + urlp = NULL; + test_out_reason(t_strdup_printf("parse %s", url), + urlp == NULL, error); + + test_end(); + } T_END; + +} + +static const char *parse_create_url_tests[] = { + "sieve://www.example.com/", + "sieve://10.0.0.1/", + "sieve://[::1]/", + "sieve://www.example.com:993/", + "sieve://www.example.com/frop", + "sieve://www.example.com/user/frop", + "sieve://www.example.com/user/%23shared", +}; + +static unsigned int +parse_create_url_test_count = N_ELEMENTS(parse_create_url_tests); + +static void test_managesieve_url_parse_create(void) +{ + unsigned int i; + + for (i = 0; i < parse_create_url_test_count; i++) T_BEGIN { + const char *url = parse_create_url_tests[i]; + struct managesieve_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("managesieve url parse/create [%d]", i)); + + if (managesieve_url_parse(url, 0, pool_datastack_create(), + &urlp, &error) < 0) + urlp = NULL; + test_out_reason(t_strdup_printf("parse %s", url), + urlp != NULL, error); + if (urlp != NULL) { + const char *urlnew = managesieve_url_create(urlp); + test_out(t_strdup_printf("create %s", urlnew), + strcmp(url, urlnew) == 0); + } + + test_end(); + } T_END; + +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_managesieve_url_valid, + test_managesieve_url_invalid, + test_managesieve_url_parse_create, + NULL + }; + return test_run(test_functions); +} -- GitLab